LabHub

CKAD — 쿠버네티스 애플리케이션 개발자 · 애플리케이션 배포 · 이론

배포는 '바꾸기'가 아니라 '되돌릴 수 있게 바꾸기'다

LabHub 에서 이어서 보기

한 줄 요약

Deployment·Helm·Kustomize 는 전부 같은 문제를 푼다. 바꾸는 동안 서비스를 죽이지 않고, 잘못됐을 때 이전 상태로 돌아갈 수 있게 만드는 것. 그래서 세 도구 모두 "리비전"이라는 개념을 갖는다.

왜 이게 필요했나

파드를 직접 만들어 운영하면 이미지를 바꾸는 순간 다운타임이 생긴다. 지우고 다시 만드는 사이에 아무도 없기 때문이다. ReplicaSet 이 개수를 유지해 주지만 "이미지를 바꾼다"는 개념이 없다.

Deployment 는 그 위에 한 층을 더 얹었다. Deployment 는 ReplicaSet 을 여러 개 관리한다. 이미지를 바꾸면 새 ReplicaSet 을 만들고, 새 것을 늘리면서 옛 것을 줄인다. 옛 ReplicaSet 은 레플리카 0 으로 남아 있어서, 되돌리라는 명령이 오면 그 방향을 반대로 돌리기만 하면 된다. 이것이 rollout undo 가 빠른 이유다 — 이미지를 다시 받을 필요가 없다.

속도는 두 숫자로 조절한다. maxSurge 는 desired 를 얼마나 초과해도 되는지, maxUnavailable 은 desired 에서 얼마나 모자라도 되는지다. maxUnavailable: 0 으로 두면 항상 원래 개수만큼 살아 있고, 대신 새 파드가 Ready 가 될 때까지 기다리므로 배포가 느려진다. 여기서 Readiness Probe 가 없으면 이 안전장치가 전부 무력화된다. 프로브가 없으면 컨테이너가 뜬 순간 Ready 로 간주되어, 아직 초기화 중인 파드에 트래픽이 간다.

어떻게 동작하나

배포 전략을 표로 정리하면 이렇다.

| 전략 | 구현 | 되돌리는 속도 | 자원 |
| --- | --- | --- | --- |
| 롤링 업데이트 | Deployment 하나, maxSurge/maxUnavailable | 중간 (다시 롤링) | +maxSurge |
| 블루-그린 | Deployment 둘 + Service 셀렉터 전환 | 즉시 (셀렉터 되돌리기) | 2배 |
| 카나리 | Deployment 둘 + 같은 Service 셀렉터, 레플리카 비율 | 즉시 (카나리 0) | 조금 더 |

블루-그린은 라벨 게임이다. checkout-blueversion: blue, checkout-greenversion: green 라벨을 달고, Service 의 셀렉터에 version: green 을 넣으면 그 순간 트래픽이 통째로 넘어간다. 카나리는 반대로 셀렉터를 공통 라벨만으로 좁게 잡아 두 Deployment 의 파드가 모두 엔드포인트에 들어오게 하고, 레플리카 개수 비율로 트래픽 비율을 만든다. 9:1 이면 대략 10% 다.

kubectl rollout history 에 보이는 CHANGE-CAUSE 는 마법이 아니라 kubernetes.io/change-cause 애너테이션이다. 옛날 --record 플래그는 폐기됐으므로 kubectl annotate deployment web kubernetes.io/change-cause="..." 로 직접 붙인다. 이게 없으면 rollout history 는 리비전 번호만 나열하고 무엇이 바뀌었는지 알 수 없다.

Helm 은 같은 아이디어를 패키지 수준으로 올린 것이다. 릴리스의 각 리비전이 네임스페이스 안의 Secret(sh.helm.release.v1.<이름>.v<번호>)으로 저장되고, 거기에 렌더링된 매니페스트와 values 가 통째로 들어 있다. helm rollback 은 그 리비전의 차트와 values 로 새 리비전을 만든다 — 시간을 되감는 게 아니라 앞으로 나아가면서 옛 내용을 재적용하는 것이다. helm upgrade 는 3-way 병합을 한다. 이전 리비전 매니페스트, 새로 렌더링한 매니페스트, 그리고 클러스터의 현재 실제 상태 셋을 비교해서 필요한 변경만 적용한다. 그래서 다른 도구가 손댄 부분을 함부로 되돌리지 않는다.

Kustomize 는 템플릿 없이 같은 일을 한다. base 에 공통 매니페스트를 두고 overlays/<환경> 에서 namePrefix, 라벨, 패치를 얹는다. 문자열 치환이 아니라 YAML 구조를 이해하고 병합하기 때문에, 결과가 항상 유효한 YAML 이라는 장점이 있다. kubectl kustomize <경로> 로 렌더 결과만 보고, kubectl apply -k <경로> 로 적용한다.

현장에서 만나는 모습

홈랩 클러스터에 GPU Operator 를 올리다 겪은 일이다. 처음에 toolkit.enabled=false 로 설치했는데 그게 오판이었고, 고치려면 그 값 하나만 바꿔야 했다.

helm upgrade gpu-operator nvidia/gpu-operator --version v26.3.3 \  -n gpu-operator --reuse-values --set toolkit.enabled=true

--reuse-values 가 없었다면 이전 리비전의 값이 전부 버려지고 차트 기본값으로 초기화됐을 것이다. 그러면 driver.enabled=false 가 기본값 true 로 돌아가 Operator 가 드라이버를 다시 설치하려 들었을 것이고, 이미 570.195.03 이 깔린 호스트와 충돌해 노드가 GPU 를 잃었을 것이다. 플래그 하나가 사고를 막았다.

업그레이드 후 히스토리는 이렇게 남았다.

REVISION  STATUS      CHART                 DESCRIPTION1         superseded  gpu-operator-v26.3.3  Install complete2         deployed    gpu-operator-v26.3.3  Upgrade complete

리비전 1이 지워지지 않고 superseded 로 남아 있다는 점이 핵심이다. helm rollback gpu-operator 1 로 되돌릴 수 있는 상태였다는 뜻이다.

또 하나 눈여겨볼 것은 Helm 이 DaemonSet 을 직접 만들지 않았다는 점이다. Helm 이 한 일은 ClusterPolicy 커스텀 리소스의 toolkit.enabled 를 false 에서 true 로 바꾼 것뿐이고, 그 변경을 감지한 Operator 컨트롤러가 DaemonSet 을 만들었다. 선언은 Helm 이, 조립은 컨트롤러가 한다. 배포 도구의 책임 경계가 어디까지인지를 보여주는 장면이다.

다음 실습에서 할 것

ckad-deploy 네임스페이스에서 롤링 업데이트 파라미터를 조절하고, 이미지를 두 번 올린 뒤 --to-revision 으로 특정 리비전에 되돌리고, 블루-그린과 카나리를 라벨과 레플리카 비율로 직접 구성한다. 이어서 helm create 로 로컬 차트를 만들어 values 를 고치고 helm template/install/upgrade 를 돌린 뒤, Kustomize base 와 overlay 를 만들어 kubectl kustomizeapply -k 로 적용한다. 이 환경에는 인터넷이 없으므로 helm repo add 는 쓸 수 없고, 반드시 로컬 경로만 사용한다.