Helm Deployment and Rollback Scenarios
A Release Is One Secret
한국어 원문으로 표시합니다.
한 줄 요약
Helm 릴리스는 마법이 아니라 네임스페이스 안의 시크릿 한 줄이다. 그 사실을 알면 롤백이 무엇을 되돌리는지, 왜 어떤 것은 못 되돌리는지가 전부 설명된다.
왜 이게 필요했나
helm rollback 을 눌렀는데 데이터베이스 마이그레이션이 되돌아오지 않아 서비스가 깨진 경험은 흔하다. 반대로 "롤백했는데 아무것도 안 바뀌었다" 는 신고도 흔하다. 둘 다 같은 오해에서 나온다 — 롤백이 시간을 되감는다고 생각하는 것.
Helm 이 하는 일은 훨씬 단순하다. helm install 을 하면 렌더링된 매니페스트 전체를 gzip 으로 눌러 시크릿에 저장한다. 이름은 sh.helm.release.v1.<릴리스>.v<리비전> 이다. helm upgrade 를 하면 새 리비전 시크릿을 하나 더 만든다. helm rollback 3 은 리비전 3의 시크릿을 꺼내 그 매니페스트를 다시 apply 하는 것이다.
그래서 규칙이 따라 나온다. 매니페스트에 적혀 있던 것은 되돌아온다. 매니페스트 바깥에서 벌어진 일 — 마이그레이션이 바꾼 데이터, Job 이 만든 파일, 외부 API 에 보낸 요청 — 은 되돌아오지 않는다.
어떻게 동작하나
직접 확인할 수 있다.
kubectl get secret -l owner=helm
kubectl get secret sh.helm.release.v1.demo.v1 -o jsonpath='{.data.release}' \
| base64 -d | base64 -d | gzip -d | head -40
두 번 base64 를 푸는 것이 이상해 보이지만 맞다. 쿠버네티스가 시크릿 값을 한 번 인코딩하고, Helm 이 그 안에서 또 한 번 인코딩해 두었다.
helm history 는 이 시크릿들의 목록이다. 리비전마다 상태가 있다.
| 상태 | 뜻 |
|---|---|
deployed |
지금 살아 있는 리비전. 항상 하나뿐이다 |
superseded |
예전에 배포됐다가 다음 리비전에 자리를 내준 것 |
failed |
적용에 실패한 리비전 |
pending-upgrade |
업그레이드를 시작했는데 끝나지 않은 것. 여기 갇히면 다음 배포가 막힌다 |
흔한 착각
롤백은 리비전 번호를 되돌리지 않는다. helm rollback demo 1 을 하면 리비전 1로 돌아가는 게 아니라, 리비전 1의 내용으로 새 리비전 3 을 만든다. history 를 보면 3번 줄에 Rollback to 1 이라고 적혀 있다. 이건 감사 기록을 지우지 않기 위한 설계다 — 무슨 일이 있었는지가 히스토리에 남아야 한다.
보관 개수에 제한이 있다. 기본값은 10개다(--history-max). 오래된 리비전은 지워지므로 "6개월 전으로 롤백" 은 대개 불가능하다. 되돌릴 수 있는 창은 생각보다 좁다.
릴리스 시크릿을 직접 들여다보기
Helm 3 은 릴리스마다 시크릿을 하나 만듭니다. 이 구조를 알면 사고 때 손으로 복구할 수 있습니다.
kubectl -n labhub-prod get secret -l owner=helm,name=labhub
NAME TYPE DATA AGE
sh.helm.release.v1.labhub.v247 helm.sh/release.v1 1 2d
sh.helm.release.v1.labhub.v248 helm.sh/release.v1 1 1d
sh.helm.release.v1.labhub.v249 helm.sh/release.v1 1 3h
# 안을 풀어 본다 — base64 → gzip → JSON
kubectl get secret sh.helm.release.v1.labhub.v249 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gzip -d | jq '.info, .chart.metadata.version'
base64 가 두 번 인 것이 함정입니다. 쿠버네티스 시크릿 자체가 한 겹, Helm 이 저장할 때 또 한 겹입니다.
여기서 나오는 결론 둘입니다.
- 릴리스 이력이 네임스페이스의 시크릿을 먹습니다.
--history-max를 안 정하면 기본 10개가 쌓이고, 차트가 크면 각각 수백 KB 입니다. - 네임스페이스를 지우면 릴리스도 사라집니다. 백업 대상이 아니라 재현 가능해야 하는 것이므로, 값 파일과 차트 버전을 git 에 두는 것이 실제 백업입니다.
세 상태를 구분하기
helm list → deployed 만 보인다
helm list --all → failed, pending-upgrade, superseded 까지
helm history <릴리스> → 리비전별 상태와 설명
superseded 는 정상입니다 — 새 리비전이 나와 물러난 것입니다. 문제는
pending-* 입니다. 이 상태로 남아 있으면 다음 배포가 거절되고, 프로세스는 이미
죽었는데 표시만 남은 것입니다.
failed 도 그대로 두면 안 됩니다. 다음 배포는 되지만 --atomic 롤백의 기준점이
흐려집니다. 원인을 고치고 성공한 배포를 한 번 만들어 두는 것이 정리입니다.
릴리스 이름과 리소스 이름
차트의 리소스 이름은 대개 {{ .Release.Name }}-{{ .Chart.Name }} 로 만들어집니다.
그래서 릴리스 이름을 바꾸면 리소스가 전부 새로 만들어집니다 — 옛것은 남고
새것이 생겨 둘이 공존합니다.
같은 이유로 릴리스 이름은 처음에 신중히 정하고, 바꿔야 한다면 옛 릴리스를 지우고 새로 설치하는 절차를 계획합니다. PVC 를 쓰는 워크로드라면 데이터를 옮기는 단계가 그 사이에 들어갑니다.
실무에서 진짜 중요한 것
릴리스가 시크릿이라는 건 크기 제한이 있다는 뜻이기도 하다. etcd 의 객체 한도는 1MiB 다. 차트가 커지면(특히 CRD 를 잔뜩 담으면) 릴리스 시크릿이 그 한도에 부딪혀 업그레이드가 실패한다. 그때 나오는 오류 메시지는 원인을 전혀 알려 주지 않으므로, 이 구조를 아는 것이 곧 해결 속도다.