ARM 서버 Kubernetes Part 7: 클러스터 무중단 업그레이드

Part 6에서는 애플리케이션 Pod을 무중단으로 업그레이드했다.
하지만 클러스터 자체(kubelet, kubeadm)를 업그레이드할 때는 어떻게 해야 할까?

Part 5에서 v1.29 → v1.35 직접 점프를 시도했을 때 NotReady 상태가 발생했다.
이번 글에서는 v1.29 → v1.35까지 순차적으로 무중단 업그레이드하는 방법을 다룬다.


목표

클러스터 무중단 업그레이드 성공 조건:

  1. v1.29.15 → v1.35.1까지 순차적 업그레이드
  2. PDB로 서비스 최소 Pod 수 유지
  3. 서비스 다운타임 0초 (API 서버 짧은 중단 제외)
  4. 올바른 순서: 항상 master 먼저, worker 나중에

환경

K8s 클러스터: v1.29.15 (3노드)
  - k8s-master  (192.168.122.10)
  - k8s-worker1 (192.168.122.11)
  - k8s-worker2 (192.168.122.12)

업그레이드 경로: v1.29.15 → v1.30.14 → v1.31.14 → v1.32.12 → v1.33.8 → v1.34.4 → v1.35.1

배포된 서비스:
  - nginx-demo (3 replicas, PDB: minAvailable 2)
  - apache-demo (2 replicas, PDB: minAvailable 1)

⚠️ 중요: 실행 위치 구분

이 문서의 모든 명령어는 실행 위치가 명시되어 있습니다:

  • 📍 master 노드 (192.168.122.10): kubectl 명령어 실행
  • 📍 worker 노드 (192.168.122.11, 192.168.122.12): apt, systemctl 등 노드 자체 작업

⚠️ 중요: 업그레이드 순서

반드시 지켜야 할 규칙:

❌ 잘못된 순서: worker 먼저 → master 나중
✅ 올바른 순서: master 먼저 → worker 나중

이유:

  • K8s는 워커 노드가 마스터보다 높은 버전이면 호환성 문제 발생
  • worker가 더 높으면 kubelet이 API 서버와 통신 불가 → NotReady 상태

교훈: 처음에 worker를 먼저 업그레이드했다가 클러스터 전체가 NotReady 상태가 되었다.
반드시 master 먼저, worker 나중에 업그레이드해야 한다!


1단계: 현재 상태 확인

📍 실행 위치: master 노드 (192.168.122.10)

1
kubectl get nodes

결과:

NAME          STATUS   ROLES           AGE   VERSION
k8s-master    Ready    control-plane   76m   v1.29.15
k8s-worker1   Ready    <none>          76m   v1.29.15
k8s-worker2   Ready    <none>          76m   v1.29.15

2단계: v1.30 업그레이드

2.1. Master 업그레이드 (먼저!)

📍 실행 위치: master 노드 (192.168.122.10)

Step 1: kubeadm 업그레이드

1
2
3
4
sudo apt-mark unhold kubelet kubeadm kubectl
sudo sed -i "s|v1\.[0-9]*|v1.30|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubeadm

Step 2: 컨트롤 플레인 업그레이드

📍 실행 위치: master 노드 (192.168.122.10)

1
sudo kubeadm upgrade apply v1.30.14 -y

결과:

[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.30.14". Enjoy!

Step 3: kubelet & kubectl 업그레이드

📍 실행 위치: master 노드 (192.168.122.10)

1
2
3
sudo apt-get install -y --allow-change-held-packages kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet

2.2. Worker1 업그레이드

Step 1: drain (Pod 이동)

📍 실행 위치: master 노드 (192.168.122.10)

1
kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data

PDB 작동 확인:

error when evicting pods/"nginx-demo-xxx" -n "default" (will retry after 5s): 
Cannot evict pod as it would violate the pod's disruption budget.

✅ PDB가 순차적으로 Pod 제거!

Step 2: worker1 노드 업그레이드

📍 실행 위치: worker1 노드 (192.168.122.11)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# worker1에 SSH 접속
ssh ubuntu@192.168.122.11

# 패키지 업그레이드
sudo apt-mark unhold kubelet kubeadm kubectl
sudo sed -i "s|v1\.[0-9]*|v1.30|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubelet kubeadm kubectl
sudo systemctl restart kubelet

# SSH 세션 종료
exit

Step 3: uncordon (스케줄링 재개)

📍 실행 위치: master 노드 (192.168.122.10)

1
kubectl uncordon k8s-worker1

상태 확인:

1
kubectl get nodes

결과:

NAME          STATUS   ROLES           AGE   VERSION
k8s-master    Ready    control-plane   78m   v1.30.14
k8s-worker1   Ready    <none>          78m   v1.30.14
k8s-worker2   Ready    <none>          78m   v1.29.15

2.3. Worker2 업그레이드

Step 1: drain

📍 실행 위치: master 노드 (192.168.122.10)

1
kubectl drain k8s-worker2 --ignore-daemonsets --delete-emptydir-data

Step 2: worker2 노드 업그레이드

📍 실행 위치: worker2 노드 (192.168.122.12)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# worker2에 SSH 접속
ssh ubuntu@192.168.122.12

# 패키지 업그레이드
sudo apt-mark unhold kubelet kubeadm kubectl
sudo sed -i "s|v1\.[0-9]*|v1.30|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubelet kubeadm kubectl
sudo systemctl restart kubelet

# SSH 세션 종료
exit

Step 3: uncordon

📍 실행 위치: master 노드 (192.168.122.10)

1
2
kubectl uncordon k8s-worker2
kubectl get nodes

최종 상태:

NAME          STATUS   ROLES           AGE   VERSION
k8s-master    Ready    control-plane   83m   v1.30.14
k8s-worker1   Ready    <none>          82m   v1.30.14
k8s-worker2   Ready    <none>          82m   v1.30.14

3단계: v1.31 업그레이드

v1.30과 동일한 순서로 진행:

3.1. Master v1.31 업그레이드

📍 실행 위치: master 노드 (192.168.122.10)

1
2
3
4
5
6
7
8
sudo apt-mark unhold kubeadm
sudo sed -i "s|v1\.[0-9]*|v1.31|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubeadm
sudo kubeadm upgrade apply v1.31.14 -y
sudo apt-get install -y --allow-change-held-packages kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet

ConfigMap 버전 이슈 해결:

v1.31 업그레이드 중 ConfigMap의 kubernetesVersion이 v1.30으로 남아있어서 kubeadm이 거부하는 문제가 발생할 수 있다.

📍 실행 위치: master 노드 (192.168.122.10)

1
2
3
4
# ConfigMap 수동 업데이트 (필요 시)
kubectl -n kube-system get cm kubeadm-config -o yaml | \
  sed "s/kubernetesVersion: v1.30.14/kubernetesVersion: v1.31.14/" | \
  kubectl apply -f -

3.2. Worker1 v1.31 업그레이드

📍 Step 1: master에서 drain

1
kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data

📍 Step 2: worker1 노드에서 업그레이드

1
2
3
4
5
6
7
8
9
ssh ubuntu@192.168.122.11

sudo apt-mark unhold kubelet kubeadm kubectl
sudo sed -i "s|v1\.[0-9]*|v1.31|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubelet kubeadm kubectl
sudo systemctl restart kubelet

exit

📍 Step 3: master에서 uncordon

1
kubectl uncordon k8s-worker1

3.3. Worker2 v1.31 업그레이드

동일한 방식으로 진행.

최종 상태:

NAME          STATUS   ROLES           AGE    VERSION
k8s-master    Ready    control-plane   137m   v1.31.14
k8s-worker1   Ready    <none>          137m   v1.31.14
k8s-worker2   Ready    <none>          137m   v1.31.14

4단계: v1.32 → v1.33 → v1.34 순차 업그레이드

각 버전마다 동일한 순서 반복:

각 버전 업그레이드 순서

1. 📍 master 노드: kubeadm 업그레이드
2. 📍 master 노드: kubeadm upgrade apply v1.XX.YY
3. 📍 master 노드: kubelet/kubectl 업그레이드
4. 📍 master 노드: kubectl drain k8s-worker1
5. 📍 worker1 노드: kubelet/kubeadm/kubectl 업그레이드
6. 📍 master 노드: kubectl uncordon k8s-worker1
7. 📍 master 노드: kubectl drain k8s-worker2
8. 📍 worker2 노드: kubelet/kubeadm/kubectl 업그레이드
9. 📍 master 노드: kubectl uncordon k8s-worker2

v1.32 예시:

📍 master 노드:

1
2
3
4
5
6
7
sudo sed -i "s|v1\.[0-9]*|v1.32|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubeadm=1.32.12-1.1
sudo kubeadm upgrade apply v1.32.12 -y
sudo apt-get install -y --allow-change-held-packages kubelet=1.32.12-1.1 kubectl=1.32.12-1.1
sudo systemctl daemon-reload
sudo systemctl restart kubelet

📍 worker 노드들 (각각):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# master에서: kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data

ssh ubuntu@192.168.122.11
sudo apt-mark unhold kubelet kubeadm kubectl
sudo sed -i "s|v1\.[0-9]*|v1.32|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubelet=1.32.12-1.1 kubeadm=1.32.12-1.1 kubectl=1.32.12-1.1
sudo systemctl restart kubelet
exit

# master에서: kubectl uncordon k8s-worker1

v1.33, v1.34도 동일한 방식으로 진행.


5단계: v1.35 업그레이드 (중요!)

5.1. Master v1.35 업그레이드

📍 실행 위치: master 노드 (192.168.122.10)

1
2
3
4
5
6
7
sudo sed -i "s|v1\.[0-9]*|v1.35|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubeadm=1.35.1-1.1
sudo kubeadm upgrade apply v1.35.1 -y
sudo apt-get install -y --allow-change-held-packages kubelet=1.35.1-1.1 kubectl=1.35.1-1.1
sudo systemctl daemon-reload
sudo systemctl restart kubelet

5.2. ⚠️ v1.35 주요 변경사항

kubelet 플래그 제거:

v1.35부터 --pod-infra-container-image 플래그가 완전히 제거되었다.
업그레이드 후 kubelet이 계속 재시작되면서 아래 에러 발생:

E0220 07:16:28.686442   92191 run.go:72] "command failed" 
err="failed to parse kubelet flag: unknown flag: --pod-infra-container-image"

해결:

📍 실행 위치: master 노드 (192.168.122.10)

1
2
sudo sed -i "s/ --pod-infra-container-image=[^ ]*//g" /var/lib/kubelet/kubeadm-flags.env
sudo systemctl restart kubelet

상태 확인:

1
systemctl status kubelet

정상이면 active (running) 상태여야 함.

5.3. Worker 노드 v1.35 업그레이드

Worker1 업그레이드

📍 Step 1: master에서 drain

1
kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data

📍 Step 2: worker1 노드에서 업그레이드

1
2
3
4
5
6
7
ssh ubuntu@192.168.122.11

sudo apt-mark unhold kubelet kubeadm kubectl
sudo sed -i "s|v1\.[0-9]*|v1.35|g" /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubelet=1.35.1-1.1 kubeadm=1.35.1-1.1 kubectl=1.35.1-1.1
sudo systemctl restart kubelet

📍 Step 3: 플래그 제거 (v1.35 전용)

1
2
3
4
5
6
# worker1 노드에서 계속
sudo sed -i "s/ --pod-infra-container-image=[^ ]*//g" /var/lib/kubelet/kubeadm-flags.env
sudo systemctl restart kubelet
systemctl status kubelet

exit

📍 Step 4: master에서 uncordon

1
kubectl uncordon k8s-worker1

Worker2 업그레이드

동일한 방식으로 진행.


6단계: 최종 상태 확인

📍 실행 위치: master 노드 (192.168.122.10)

1
kubectl get nodes

결과:

NAME          STATUS   ROLES           AGE     VERSION
k8s-master    Ready    control-plane   4h      v1.35.1
k8s-worker1   Ready    <none>          4h      v1.35.1
k8s-worker2   Ready    <none>          3h59m   v1.35.1

Pod 상태:

1
kubectl get pods -l "app in (nginx-demo,apache-demo)"

결과:

NAME                           READY   STATUS    RESTARTS   AGE
apache-demo-569d746c79-bwj9r   1/1     Running   0          53m
apache-demo-569d746c79-tg6ft   1/1     Running   0          65m
nginx-demo-775cb84cd7-fc7mn    1/1     Running   0          53m
nginx-demo-775cb84cd7-k4l8q    1/1     Running   0          65m
nginx-demo-775cb84cd7-zm45l    1/1     Running   0          53m

서비스 가용성:

1
2
3
for i in {1..20}; do 
  curl -s -o /dev/null -w "%{http_code} " http://192.168.255.100/
done

결과:

200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200

20/20 요청 모두 200 OK!


핵심 교훈

✅ 성공 요인

요소내용
올바른 순서항상 master 먼저, worker 나중에
실행 위치kubectl은 master, apt/systemctl은 각 노드
1버전씩v1.29 → v1.30 → v1.31 → … → v1.35 순차 진행
PDBdrain 시 최소 Pod 수 보장
버전별 확인각 버전마다 서비스 가용성 확인

❌ 실수와 해결

문제원인해결
worker NotReadyworker를 master보다 먼저 업그레이드master 먼저 업그레이드
kubectl 실패worker 노드에서 kubectl 실행master 노드에서만 실행
ConfigMap 불일치kubeadm upgrade apply 미완료ConfigMap 수동 업데이트
kubelet 재시작 반복v1.35에서 플래그 제거kubeadm-flags.env 수정

📊 최종 결과

  • 업그레이드 경로: v1.29.15 → v1.30.14 → v1.31.14 → v1.32.12 → v1.33.8 → v1.34.4 → v1.35.1
  • 소요 시간: 약 3시간 (6개 버전, 수동 진행)
  • 다운타임: 0초 (API 서버 짧은 중단 외)
  • 서비스 가용성: 100%

업그레이드 체크리스트

각 버전마다 반복:

#작업위치명령어
1kubeadm 업그레이드📍 mastersudo apt-get install kubeadm
2컨트롤 플레인 업그레이드📍 mastersudo kubeadm upgrade apply
3kubelet/kubectl 업그레이드📍 mastersudo apt-get install kubelet kubectl
4master kubelet 재시작📍 mastersudo systemctl restart kubelet
5노드 상태 확인📍 masterkubectl get nodes
6worker1 drain📍 masterkubectl drain k8s-worker1
7worker1 업그레이드📍 worker1ssh → apt-get install
8worker1 uncordon📍 masterkubectl uncordon k8s-worker1
9worker2 drain📍 masterkubectl drain k8s-worker2
10worker2 업그레이드📍 worker2ssh → apt-get install
11worker2 uncordon📍 masterkubectl uncordon k8s-worker2
12전체 노드 상태 확인📍 masterkubectl get nodes
13Pod 상태 확인📍 masterkubectl get pods
14서비스 가용성 테스트📍 mastercurl 192.168.255.100

v1.35 전용 추가 작업

#작업위치명령어
15master 플래그 제거📍 mastersed -i ... kubeadm-flags.env
16worker1 플래그 제거📍 worker1sed -i ... kubeadm-flags.env
17worker2 플래그 제거📍 worker2sed -i ... kubeadm-flags.env
18모든 노드 kubelet 재시작📍 각 노드systemctl restart kubelet
19kubelet 상태 확인📍 각 노드systemctl status kubelet

주의사항

1. 절대 건너뛰지 말 것

❌ v1.29 → v1.35 직접 점프
✅ v1.29 → v1.30 → v1.31 → ... → v1.35 순차 진행

2. 순서 엄수

❌ worker1 → worker2 → master
✅ master → worker1 → worker2

3. 실행 위치 확인

kubectl 명령어 → 📍 master 노드에서만
apt/systemctl → 📍 각 노드에서 직접

4. 버전별 변경사항 확인

각 버전마다 deprecated/removed 기능이 있을 수 있다.

  • v1.35: --pod-infra-container-image 플래그 제거
  • 공식 릴리스 노트 확인 필수

5. PDB 필수

PDB 없이 drain하면 모든 Pod이 동시 제거 → 서비스 중단

📍 master 노드:

1
2
kubectl get pdb
# minAvailable이 설정되어 있는지 확인

다음 단계

v1.35까지 업그레이드했으니, 이제 최신 기능을 활용할 수 있다!

Part 8에서는:

  • Auto-scaling (HPA): 부하에 따라 자동으로 Pod 수 조절
  • Cluster Autoscaler: 노드 수 자동 조절
  • Vertical Pod Autoscaler (VPA): Pod 리소스 자동 조절

실제 운영 환경에서 필요한 자동화 기능을 다룰 예정이다.


참고 자료


작성일: 2026-02-20
테스트 환경: ARM64 KVM (3 노드, Ubuntu 24.04)
업그레이드 경로: v1.29.15 → v1.35.1 (6단계 순차)
최종 상태: ✅ 클러스터 무중단 업그레이드 성공 (다운타임 0초)