Certified Kubernetes Administrator (CKA) #6 클러스터 업그레이드: kubeadm upgrade plan/apply, 노드별 drain

#5 HA 클러스터에서 여러 control plane과 외부 etcd로 가용성을 높였다면, 이번 글은 그 클러스터를 한 마이너 버전 올리는 표준 절차입니다. CKA 실기에서 거의 빠지지 않고 나오는 단골 작업입니다. 절차 자체는 정해져 있어서, 순서를 외우고 손에 익히면 확실하게 점수를 가져가는 영역입니다.

업그레이드가 까다로운 이유는 명령이 어려워서가 아니라 순서를 한 칸이라도 어기면 클러스터가 흔들리기 때문입니다. control plane을 먼저, 그다음 워커를. 한 번에 한 마이너 버전만. 그리고 kubeadm을 먼저 올리고 kubelet과 kubectl을 뒤에. 이 세 원칙을 몸에 새기는 것이 이번 글의 목표입니다.

업그레이드의 세 가지 원칙 #

명령을 외우기 전에 원칙부터 잡겠습니다. 이 세 가지를 어기면 어떤 명령을 써도 어긋납니다.

1) control plane을 먼저, 워커를 나중에. control plane의 컴포넌트(apiserver, controller-manager, scheduler)가 워커의 kubelet보다 같거나 높은 버전이어야 합니다. 그래서 항상 control plane 노드를 먼저 올리고, 그게 끝난 뒤에 워커 노드를 하나씩 올립니다.

2) 한 번에 한 마이너 버전만. 쿠버네티스는 1.30에서 1.32로 두 단계를 건너뛰는 업그레이드를 지원하지 않습니다. 1.30 → 1.31 → 1.32처럼 반드시 마이너 버전을 하나씩 거쳐야 합니다. 패치 버전(예: 1.31.0 → 1.31.4)은 같은 마이너 안이라 자유롭게 올릴 수 있습니다.

3) 한 노드 안에서는 kubeadm → kubelet/kubectl 순. 노드 하나를 올릴 때도 순서가 있습니다. 먼저 kubeadm 패키지를 새 버전으로 교체하고, kubeadm upgrade로 컴포넌트를 올린 뒤, 마지막에 kubeletkubectl 패키지를 올리고 kubelet을 재시작합니다.

원칙내용
노드 순서control plane 먼저 → 워커 노드
버전 점프한 번에 한 마이너 버전만 (건너뛰기 금지)
패키지 순서kubeadm → (upgrade apply/node) → kubelet/kubectl

control plane 업그레이드 #

control plane 노드부터 시작하겠습니다. 예시로 현재 1.31에서 1.32로 올린다고 하겠습니다. 먼저 control plane 노드에 접속합니다.

ssh controlplane

1) 업그레이드 계획 확인 #

kubeadm upgrade plan이 현재 버전과 올릴 수 있는 버전을 보여줍니다. 어떤 컴포넌트가 어떤 버전으로 가는지 표로 출력하므로, 본격 작업 전에 반드시 확인하겠습니다.

kubeadm upgrade plan

2) kubeadm 패키지 교체 #

먼저 패키지 저장소에서 사용할 마이너 버전을 활성화해야 합니다. 쿠버네티스 1.28부터는 마이너 버전별로 저장소가 분리되어 있어, /etc/apt/sources.list.d/kubernetes.list의 버전 부분을 새 마이너로 바꿔야 합니다.

# 저장소를 새 마이너 버전으로 교체 (1.31 → 1.32)
sed -i 's/v1.31/v1.32/' /etc/apt/sources.list.d/kubernetes.list

# 패키지 목록 갱신
apt update

# 설치 가능한 정확한 버전 확인
apt-cache madison kubeadm

# kubeadm을 목표 버전으로 교체
apt-mark unhold kubeadm
apt install -y kubeadm=1.32.0-1.1
apt-mark hold kubeadm

apt-mark hold는 패키지가 의도치 않게 자동 업그레이드되는 것을 막는 잠금입니다. 교체 직전에 unhold로 풀고, 교체 직후 다시 hold로 잠그는 습관을 들이겠습니다.

3) kubeadm으로 control plane 컴포넌트 업그레이드 #

이제 교체한 kubeadm으로 control plane 컴포넌트를 실제로 올립니다.

# 교체된 kubeadm 버전 확인
kubeadm version

# control plane 컴포넌트 업그레이드 실행
kubeadm upgrade apply v1.32.0

kubeadm upgrade apply는 apiserver, controller-manager, scheduler의 static Pod 매니페스트를 새 버전 이미지로 교체하고, 필요한 경우 etcd도 함께 올립니다. 이 단계가 control plane 업그레이드의 핵심입니다. control plane 노드가 여러 대인 HA 클러스터라면 나머지 control plane 노드에서는 kubeadm upgrade node를 실행합니다(apply는 첫 노드에서만).

4) kubelet과 kubectl 교체 #

control plane 컴포넌트를 올렸으면, 같은 노드의 kubelet과 kubectl도 올립니다. 이때 control plane 노드의 워크로드를 비워 주는 drain(k drain controlplane --ignore-daemonsets)을 먼저 하는 것이 안전합니다.

# kubelet과 kubectl 교체
apt-mark unhold kubelet kubectl
apt install -y kubelet=1.32.0-1.1 kubectl=1.32.0-1.1
apt-mark hold kubelet kubectl

# kubelet 설정 리로드 후 재시작
systemctl daemon-reload
systemctl restart kubelet

# 스케줄링 다시 허용
k uncordon controlplane

systemctl daemon-reloadsystemctl restart kubelet을 해야 새 버전의 kubelet이 실제로 뜹니다. 재시작을 빠뜨리면 패키지는 올라갔는데 동작 중인 kubelet은 그대로라 버전이 반영되지 않습니다. drain한 노드는 마지막에 k uncordon으로 되돌립니다.

워커 노드 업그레이드 #

control plane이 끝났으면 워커 노드를 하나씩 올립니다. 한 번에 한 노드씩 비우고 올리고 되돌리는 것이 원칙입니다. 여러 워커를 동시에 비우면 워크로드를 받아 줄 노드가 부족해집니다.

1) 노드 drain #

업그레이드할 노드의 Pod를 다른 노드로 옮기고, 새 Pod가 들어오지 못하게 막습니다. 이 명령은 보통 control plane 노드 또는 kubectl이 설정된 곳에서 실행합니다.

k drain node01 --ignore-daemonsets --delete-emptydir-data

옵션 두 가지를 반드시 기억하겠습니다.

  • --ignore-daemonsets: DaemonSet이 띄운 Pod는 노드마다 하나씩 떠야 하므로 drain으로 옮길 수 없습니다. 이 옵션이 없으면 drain이 거부되며 멈춥니다.
  • --delete-emptydir-data: emptyDir 볼륨을 쓰는 Pod가 있으면 그 로컬 데이터가 사라진다는 경고와 함께 drain이 거부됩니다. 이 옵션으로 “데이터가 사라져도 괜찮다"는 동의를 명시해야 진행됩니다.

2) 노드에서 kubeadm과 kubelet 업그레이드 #

이제 그 워커 노드에 접속해 control plane과 같은 방식으로 패키지를 올립니다.

ssh node01

# 저장소를 새 마이너 버전으로 교체
sed -i 's/v1.31/v1.32/' /etc/apt/sources.list.d/kubernetes.list
apt update

# kubeadm 교체
apt-mark unhold kubeadm
apt install -y kubeadm=1.32.0-1.1
apt-mark hold kubeadm

# 워커 노드의 kubelet 설정 업그레이드
kubeadm upgrade node

워커 노드에서는 kubeadm upgrade apply가 아니라 **kubeadm upgrade node**를 씁니다. 이 명령은 control plane 컴포넌트를 건드리지 않고, 그 노드의 kubelet 설정만 새 버전에 맞게 갱신합니다. apply와 node를 헷갈리는 것이 단골 실수입니다.

# kubelet과 kubectl 교체
apt-mark unhold kubelet kubectl
apt install -y kubelet=1.32.0-1.1 kubectl=1.32.0-1.1
apt-mark hold kubelet kubectl

# kubelet 재시작
systemctl daemon-reload
systemctl restart kubelet

3) 노드 uncordon #

업그레이드가 끝났으면 노드를 다시 스케줄링 대상으로 되돌립니다.

k uncordon node01

이 명령을 빠뜨리면 노드는 올라갔는데 SchedulingDisabled 상태로 남아 새 Pod를 받지 못합니다. drain한 노드는 반드시 uncordon으로 마무리하는 짝을 외우겠습니다. 워커가 여러 대라면 node02, node03에 대해 1〜3단계를 똑같이 반복합니다.

cordon, drain, uncordon의 차이 #

세 명령이 헷갈리기 쉬워 한 표로 정리하겠습니다.

명령새 Pod 스케줄링기존 Pod쓰임
k cordon <node>차단그대로 둠노드에 새 Pod만 못 들어오게 표시
k drain <node>차단다른 노드로 evict업그레이드/점검 전 노드 비우기
k uncordon <node>허용영향 없음작업 끝난 노드를 다시 사용

핵심은 drain은 cordon을 포함한다는 것입니다. drain은 내부적으로 노드를 cordon해 새 Pod를 막은 뒤, 기존 Pod를 다른 노드로 옮깁니다. 그래서 drain 뒤에는 cordon이 따로 필요 없고, 작업이 끝나면 uncordon만 해 주면 됩니다.

검증 #

업그레이드가 제대로 됐는지는 k get nodes의 VERSION 컬럼으로 확인합니다.

k get nodes
NAME           STATUS   ROLES           AGE   VERSION
controlplane   Ready    control-plane   90d   v1.32.0
node01         Ready    <none>          90d   v1.32.0
node02         Ready    <none>          90d   v1.32.0

모든 노드의 STATUS가 Ready이고 VERSION이 목표 버전(v1.32.0)으로 통일되어 있으면 성공입니다. 어느 노드가 SchedulingDisabled로 남아 있다면 uncordon을 빠뜨린 것이고, VERSION이 갱신되지 않았다면 kubelet 재시작을 빠뜨린 것입니다.

control plane 컴포넌트 자체의 버전은 static Pod로도 확인할 수 있습니다.

# apiserver Pod 이미지 태그 확인
k get pod -n kube-system kube-apiserver-controlplane \
  -o jsonpath='{.spec.containers[0].image}'

자주 틀리는 함정 #

시험에서 점수를 깎이는 패턴을 모아 두겠습니다.

  • drain 옵션 누락. --ignore-daemonsets 없이 drain하면 DaemonSet Pod 때문에 거부됩니다. --delete-emptydir-data 없이 drain하면 로컬 데이터 경고로 거부됩니다. 두 옵션을 함께 붙이는 것을 기본형으로 외우겠습니다.
  • 버전 점프 시도. 1.30에서 1.32로 한 번에 가려 하면 kubeadm upgrade가 거부합니다. 반드시 한 마이너씩 거쳐야 합니다.
  • apply와 node 혼동. 첫 control plane은 kubeadm upgrade apply <ver>, 나머지 control plane과 워커는 kubeadm upgrade node입니다.
  • kubelet 재시작 누락. 패키지만 올리고 systemctl restart kubelet을 빠뜨리면 VERSION이 갱신되지 않습니다.
  • uncordon 누락. drain한 노드를 되돌리지 않으면 새 Pod를 받지 못합니다.
  • 순서 역전. 워커를 control plane보다 먼저 올리면 kubelet이 control plane보다 높아져 비호환 상태가 됩니다.

시험 포인트 #

  • 업그레이드 순서는 control plane → 워커, 한 번에 한 마이너 버전, 노드 안에서는 kubeadm → kubelet/kubectl 순입니다.
  • control plane은 kubeadm upgrade plan → 패키지 교체 → kubeadm upgrade apply <ver> → kubelet/kubectl 교체 → systemctl restart kubelet 순입니다.
  • 워커는 k drain <node> --ignore-daemonsets --delete-emptydir-data → 패키지 교체 → kubeadm upgrade node → kubelet/kubectl 교체 → 재시작 → k uncordon <node> 순입니다.
  • HA에서 추가 control plane 노드는 apply가 아니라 kubeadm upgrade node로 올립니다.
  • drain은 cordon을 포함하므로, 끝나면 uncordon만 짝으로 해 주면 됩니다.
  • 검증은 k get nodes의 STATUS와 VERSION 컬럼으로 합니다.

정리 #

이번 글에서 잡은 것:

  • 세 원칙. control plane 먼저, 한 번에 한 마이너 버전, 노드 안에서는 kubeadm 먼저.
  • control plane. kubeadm upgrade plan으로 확인하고, kubeadm 교체 후 kubeadm upgrade apply <ver>로 컴포넌트를 올린 뒤 kubelet/kubectl을 교체하고 재시작합니다.
  • 워커. drain으로 비우고 패키지 교체와 kubeadm upgrade node로 올린 뒤, 재시작하고 uncordon으로 되돌립니다.
  • cordon/drain/uncordon. drain은 cordon을 포함하며, 끝나면 uncordon으로 마무리합니다.
  • 함정. drain 옵션 두 개, 버전 점프 금지, apply와 node 구분, 재시작과 uncordon 누락 주의.

다음: etcd 백업과 복구 #

업그레이드까지 클러스터의 수명 주기를 다뤘습니다. 다음은 클러스터의 모든 상태가 담긴 etcd를 지키는 일입니다.

#7 etcd 백업과 복구에서는 etcdctl snapshot save로 스냅샷을 뜨고, 장애 상황을 가정해 etcdctl snapshot restore로 클러스터 상태를 되돌리는 절차를 다루겠습니다. 인증서 경로와 endpoint 지정, 복구 후 static Pod 매니페스트를 바꿔 새 데이터 디렉터리를 가리키게 하는 과정까지 손으로 따라가며 정리하겠습니다.

X