Helm 차트 제작과 배포 · 업그레이드의 병합 규칙 · 이론
3방향 병합 — 무엇이 되돌아가고 무엇이 살아남는가
한 줄 요약
헬름 3 의 업그레이드는 옛 매니페스트·새 매니페스트·클러스터 실물 셋으로 패치를 만들고, 그래서 차트가 선언한 필드는 되돌아가고 차트가 모르는 필드는 살아남는다.
왜 이게 필요했나
장애 대응은 대개 이렇게 끝난다. 트래픽이 몰려 kubectl scale deploy/api --replicas=20 으로 급히 올리고, 나중에 원인을 찾으려고 kubectl label deploy/api owner=ops 같은 표시를 붙여 둔다. 상황이 정리되고 며칠 뒤, 전혀 관련 없는 기능 하나가 배포된다. 그리고 다음 날 아침 복제 수는 다시 1 이고, 라벨은 그대로 붙어 있다.
이 비대칭이 이해되지 않으면 헬름은 '가끔 내 변경을 지우는 도구' 가 된다. 실제로는 아주 명확한 규칙 하나다.
세 개의 상태로 패치를 만든다
헬름 2 는 옛 매니페스트와 새 매니페스트 둘만 비교했다. 그래서 클러스터에서 사람이 바꾼 값은 비교 대상이 아니었고, 둘 사이에 차이가 없으면 아무 패치도 나가지 않아 사람이 바꾼 값이 남았다. 헬름 3 은 여기에 클러스터의 실물을 하나 더 넣었다.
| 상황 | 옛 매니페스트 | 새 매니페스트 | 실물 | 결과 |
| --- | --- | --- | --- | --- |
| 손으로 복제 수 변경 | 1 | 1 | 5 | 1 로 되돌아간다 |
| 손으로 라벨 추가 | 없음 | 없음 | 있음 | 그대로 남는다 |
| 차트에서 라벨 제거 | 있음 | 없음 | 있음 | 지워진다 |
첫 줄이 헬름 2 와 달라진 지점이다. 차트가 replicas 를 선언하고 있다면 그 필드의 주인은 차트이고, 실물이 달라져 있으면 선언 쪽으로 맞춘다. 두 번째 줄은 차트가 그 필드를 모르므로 패치에 포함되지 않는다. 세 번째 줄은 옛 매니페스트에는 있고 새 매니페스트에는 없으니 '지우라' 는 뜻으로 읽힌다.
여기서 실무적인 결론이 하나 나온다 — 오토스케일러가 관리하는 replicas 는 차트에서 선언하지 말아야 한다. 선언해 두면 배포마다 오토스케일러가 정한 값을 헬름이 되돌리고, 오토스케일러가 다시 올리는 줄다리기가 생긴다. 차트에서 replicas 를 빼고 HPA 에 맡기는 것이 정석이다.
값은 왜 이어지지 않는가
두 번째 함정은 오브젝트가 아니라 값 쪽에 있다.
helm upgrade api ./api --set replicas=4 --set logLevel=debug # 리비전 2helm upgrade api ./api --set replicas=6 # 리비전 3리비전 3 의 사용자 값은 {replicas: 6} 뿐이다. logLevel 은 사라지고 차트 기본값으로 돌아간다. 이것은 버그가 아니라 기본 동작이다 — 업그레이드는 "이번에 준 값" 으로 릴리스를 다시 계산한다. helm get values api 로 확인할 수 있다.
예외가 하나 있고, 이것이 사람을 가장 많이 헷갈리게 한다. 값을 하나도 주지 않으면(--set 도 -f 도 없으면) 헬름은 직전 릴리스의 사용자 값을 그대로 쓴다. 그래서 helm upgrade api ./api 만 돌리면 지난번 값이 유지되고, --set 을 하나라도 주는 순간 나머지가 전부 떨어져 나간다. "값을 빼면 기본값으로 돌아가겠지" 라는 직관이 정확히 반대로 동작하는 자리다. 지난번 값을 확실히 버리려면 --reset-values 를 명시한다.
이어받기 옵션은 세 가지이고 이름이 비슷해 헷갈린다.
| 옵션 | 하는 일 |
| --- | --- |
| --reuse-values | 지난번 릴리스의 값을 그대로 이어받고 이번 --set 만 얹는다 |
| --reset-values | 지난번 값을 버리고 차트 기본값 위에 이번 것만 얹는다(기본 동작과 같다) |
| --reset-then-reuse-values | 차트 기본값으로 되돌린 뒤 지난번 사용자 값을 다시 얹고 이번 것을 얹는다 |
--reuse-values 와 --reset-then-reuse-values 의 차이는 차트 기본값이 바뀌었을 때 드러난다. 전자는 지난번 릴리스가 계산해 둔 값을 통째로 이어받으므로 차트의 새 기본값이 묻히고, 후자는 기본값을 먼저 새로 깐 뒤 사용자가 명시한 것만 다시 얹으므로 새 기본값이 반영된다. 차트를 올리면서 기본값도 함께 올린 경우 이 차이가 배포 결과를 가른다.
--force 는 패치가 아니라 교체다
--force 는 패치 대신 오브젝트를 교체한다. Service 의 clusterIP 처럼 패치로는 바꿀 수 없는 불변 필드에 부딪혔을 때 쓰는 탈출구지만, 교체이므로 해당 오브젝트가 잠깐 사라졌다 다시 생긴다. Deployment 라면 파드가 전부 다시 만들어지고, Service 라면 잠시 라우팅이 끊긴다.
교체에는 한 가지가 더 딸려 온다. 새 매니페스트로 통째로 갈아 끼우는 것이라, 평소 업그레이드에서는 살아남던 '차트가 모르는 필드' 까지 사라진다. 손으로 붙여 둔 owner 라벨은 보통의 업그레이드에서는 그대로 남지만 --force 뒤에는 없다. "업그레이드가 안 먹으니 일단 force" 는 운영에서 가장 비싼 습관 중 하나다.
현장에서 만나는 모습
가장 흔한 사고는 배포 스크립트에서 --set 하나가 빠지는 것이다. 값이 여덟 개쯤 되면 누군가 한 줄을 지우고, 그 값만 조용히 기본값으로 돌아간다. 오류는 나지 않고 배포는 성공으로 표시된다. 이 문제의 정석적인 해법은 이어받기 옵션이 아니라 값을 파일로 저장소에 두는 것이다. -f values-prod.yaml 한 줄이면 무엇으로 배포했는지가 커밋 기록에 남고, 코드 검토를 거치고, 다음 사람이 읽을 수 있다. --reuse-values 는 반대 방향으로 간다 — 편하지만 값이 릴리스 안에만 남아 저장소 어디에도 보이지 않는다.
배포 전에 무엇이 바뀌는지 보고 싶다면 helm upgrade --dry-run=server 가 있다. 렌더 결과를 실제 API 서버에 보내 검증까지 하고 릴리스는 만들지 않는다. 다만 이것은 '검증' 이지 '차이 보기' 는 아니다. 무엇이 달라지는지 줄 단위로 보려면 helm-diff 같은 플러그인이 필요하고, 이 실습 환경에는 없다.
다음 실습에서 할 것
Deployment 를 배포해 두고 클러스터에서 복제 수와 라벨을 손으로 바꾼 뒤, 차트를 하나도 건드리지 않은 업그레이드를 돌려 무엇이 되돌아가고 무엇이 남는지 확인한다. 차트에서 라벨을 뺐을 때 클러스터에서도 지워지는 것을 보고, 이어서 값 쪽으로 넘어가 기본 업그레이드가 이전 사용자 값을 버리는 것과 세 이어받기 옵션의 차이를 릴리스 기록으로 비교한다. 마지막에 서버 측 미리 보기와 --force 를 한 번씩 써 보고 규칙을 보고서로 정리한다.