쿠버네티스 운영 실무 · 클러스터 업그레이드와 다운그레이드 전략 · 이론
버전 스큐와 무중단 업그레이드의 순서
한 줄 요약
업그레이드는 "새 버전을 깐다"가 아니라 "여러 컴포넌트의 버전 차이를 허용 범위 안에 두면서 한 칸씩 옮긴다"이다.
왜 이게 필요했나
쿠버네티스는 하나의 프로그램이 아니라 apiserver, controller-manager, scheduler, kubelet, kube-proxy, kubectl 이 각자 다른 프로세스로 도는 분산 시스템이다. 이들을 동시에 교체하는 것은 불가능하다. 노드가 일곱 대면 kubelet 도 일곱 개이고, 그중 하나가 재부팅에 실패하면 그 노드만 옛 버전으로 남는다. 그래서 쿠버네티스는 "버전이 달라도 되는 범위"를 공식적으로 정의한다. 이것이 버전 스큐 정책이다.
저자의 홈랩 확장 기록에도 이 문제가 등장한다. 새로 붙인 미니 PC 두 대에 예전 클러스터의 잔재인 v1.32 kubelet 이 남아 있었고, 그 상태로 조인했다면 컨트롤 플레인과 노드의 버전이 어긋난 채로 굴러갔을 것이다. 결국 teardown 부터 다시 하는 스크립트로 묶어 정리했다. 버전은 "대충 비슷하면 되는 값"이 아니라 지원 범위가 문서로 못 박힌 계약이다.
어떻게 동작하나
기준점은 언제나 kube-apiserver 다. 나머지는 apiserver 를 중심으로 허용 범위가 정해진다.
| 컴포넌트 | apiserver 대비 허용 범위 |
| --- | --- |
| kubelet | 최대 3마이너까지 낮아도 됨. 절대 높으면 안 됨 |
| kube-proxy | kubelet 과 같은 규칙 |
| controller-manager / scheduler | 최대 1마이너까지 낮아도 됨. 높으면 안 됨 |
| kubectl | 위아래로 1마이너까지 (±1) |
apiserver 가 1.34 라고 하자. 이 표를 그대로 적용하면 kubelet 은 1.31 부터 1.34 까지, controller-manager 는 최대 1.34, kubectl 은 1.33 부터 1.35 까지가 된다. kubectl 만 유일하게 apiserver 보다 높을 수 있다는 점이 자주 헷갈리는 대목이다.
여기에 두 가지 규칙이 더 붙는다.
1. 마이너는 한 번에 하나씩. 1.32 에서 1.34 로 바로 갈 수 없다. 1.33 을 거쳐야 한다. 건너뛰면 저장된 API 오브젝트의 변환 경로가 보장되지 않는다.
2. 컨트롤 플레인 다운그레이드는 지원되지 않는다. 업그레이드 과정에서 etcd 안의 데이터가 새 스키마로 쓰이기 때문에, 되돌리는 길은 "이전 버전으로 내리기"가 아니라 업그레이드 직전에 떠 둔 etcd 스냅샷으로 복구하기다. 이 한 문장이 앞 모듈과 이 모듈을 잇는다.
kubeadm 클러스터의 실제 순서는 이렇다.
etcd 스냅샷 백업 → kubeadm upgrade plan (무엇이 가능한지 확인) → 첫 컨트롤 플레인에서 kubeadm upgrade apply v1.x.y → 나머지 컨트롤 플레인에서 kubeadm upgrade node → 노드마다: drain → kubelet/kubeadm 패키지 교체 → uncordondrain 과 cordon 의 차이도 여기서 갈린다. cordon 은 앞으로 스케줄되지 않게 막을 뿐 이미 떠 있는 파드는 그대로 둔다. drain 은 거기에 더해 기존 파드를 쫓아낸다(evict). 그래서 노드 작업의 표준 순서는 cordon 으로 유입을 끊고, drain 으로 비우고, 작업하고, uncordon 으로 되돌리는 것이다. uncordon 을 잊으면 그 노드는 조용히 놀게 된다.
파드를 쫓아낼 때 서비스가 죽지 않도록 지켜 주는 장치가 PodDisruptionBudget 이다. minAvailable: 2 를 걸면 evict 요청이 그 선을 넘지 않는 한에서만 승인된다. 단 minAvailable 과 maxUnavailable 은 둘 중 하나만 쓸 수 있다.
현장에서 만나는 모습
첫째, 카나리 없는 일괄 업그레이드. 노드를 한 번에 다 올리면 문제가 생겼을 때 되돌릴 대조군이 없다. 노드 한 대를 먼저 올리고 워크로드가 정상인지 확인한 뒤 나머지를 배치로 나누는 것이 기본이다. 노드에 단계 라벨을 붙여 두면 계획이 문서가 아니라 클러스터 상태가 된다.
둘째, PDB 가 drain 을 영원히 막는 경우. 복제본이 1개인데 minAvailable: 1 을 걸면 그 파드는 절대 evict 되지 못한다. drain 이 몇 분째 멈춰 있다면 PDB 부터 본다.
셋째, "상태가 Ready"와 "실제로 동작한다"는 다른 명제. 저자가 KubeVirt 를 올리면서 모든 컴포넌트가 AllComponentsReady 인데 VM 은 뜨지 않았던 일을 기록해 두었다. 업그레이드 후에도 마찬가지다. 노드가 Ready 라는 것과 워크로드가 정상이라는 것은 따로 확인해야 한다.
다음 실습에서 할 것
실제 노드를 cordon 하고 PDB 를 건 워크로드를 drain 으로 옮긴 뒤 uncordon 으로 되돌린다. apiserver 1.34 를 기준으로 스큐 표의 빈칸을 직접 채우고, 백업부터 uncordon 까지의 업그레이드 런북을 쓰고, 노드에 단계 라벨을 붙여 카나리 롤아웃 계획을 JSON 으로 만든다.