ARM 서버 Kubernetes Part 6: 무중단 업그레이드

Part 5에서 K8s 클러스터를 v1.29.15 → v1.35.1로 업그레이드했지만, kubelet 플래그 호환성 문제로 일시적으로 NotReady 상태가 발생했다.
결과적으로 무중단이 아니었다.

이번 글에서는 진짜 무중단 업그레이드를 구현한다.
애플리케이션 Pod을 업그레이드하면서도 서비스 다운타임 0초를 달성하는 방법을 다룬다.


목표

무중단 업그레이드 성공 조건:

  1. 모든 HTTP 요청이 200 OK (503/502 없음)
  2. Pod가 순차적으로 교체됨 (한 번에 1개씩)
  3. PDB가 항상 최소 Pod 수 유지
  4. 서비스 다운타임 0초

환경

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

배포된 서비스:
  - nginx-demo (nginx:alpine, 1 replica → 3 replica로 확장)
  - apache-demo (httpd:alpine, 2 replicas)

외부 접근:
  - Ingress Controller: 192.168.255.100 (MetalLB)
  - / → nginx-demo
  - /apache → apache-demo

1단계: 현재 상태 확인

1
kubectl get deployments -o wide

결과:

NAME          READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
apache-demo   2/2     2            2           20m   apache       httpd:alpine   app=apache-demo
nginx-demo    1/1     1            1           17m   nginx        nginx:alpine   app=nginx-demo

nginx-demo가 1개만 있으므로, 무중단 테스트를 위해 3개로 확장한다.


2단계: nginx-demo replicas 증가

1
2
3
kubectl scale deployment nginx-demo --replicas=3

kubectl get pods -l app=nginx-demo

결과:

NAME                          READY   STATUS    RESTARTS   AGE
nginx-demo-75b947f799-5srr9   1/1     Running   0          18m
nginx-demo-75b947f799-87zch   1/1     Running   0          5s
nginx-demo-75b947f799-v8qcj   1/1     Running   0          5s

3단계: Pod Disruption Budget (PDB) 설정

PDB는 업그레이드 중에도 최소한의 Pod 수를 보장한다.
이를 통해 서비스 가용성을 유지할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat << 'EOF' | kubectl apply -f -
# nginx-demo PDB (최소 2개 유지)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: nginx-demo-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: nginx-demo
---
# apache-demo PDB (최소 1개 유지)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: apache-demo-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: apache-demo
EOF

확인:

1
kubectl get pdb

결과:

NAME              MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
apache-demo-pdb   1               N/A               1                     7s
nginx-demo-pdb    2               N/A               1                     7s
  • nginx-demo: 총 3개 중 최소 2개 유지 (동시 중단 가능: 1개)
  • apache-demo: 총 2개 중 최소 1개 유지 (동시 중단 가능: 1개)

4단계: Rolling Update 전략 설정

무중단을 보장하려면 maxUnavailable=0으로 설정해야 한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 동시에 생성 가능한 추가 Pod 수
      maxUnavailable: 0    # 동시에 불가용할 수 있는 Pod 수 (0 = 무중단)
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 2
EOF

확인:

1
kubectl get deployment nginx-demo -o yaml | grep -A 5 "strategy:"

결과:

1
2
3
4
5
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate

5단계: 무중단 업그레이드 실행

이제 nginx 이미지를 nginx:alpinenginx:1.25-alpine으로 업그레이드한다.

1
2
3
kubectl set image deployment/nginx-demo nginx=nginx:1.25-alpine

kubectl rollout status deployment/nginx-demo

롤아웃 진행 과정:

deployment.apps/nginx-demo image updated
Waiting for deployment "nginx-demo" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-demo" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-demo" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-demo" successfully rolled out

핵심 관찰:

  • 한 번에 1개씩 Pod가 업데이트됨
  • 새 Pod이 Ready 상태가 된 후에만 이전 Pod 종료
  • 항상 2개 이상의 Pod이 Running 상태 유지 (PDB 덕분)

6단계: 업그레이드 결과 확인

1
kubectl get pods -l app=nginx-demo

결과:

NAME                          READY   STATUS    RESTARTS   AGE
nginx-demo-775cb84cd7-8zfmn   1/1     Running   0          22s
nginx-demo-775cb84cd7-lm8hc   1/1     Running   0          12s
nginx-demo-775cb84cd7-qmgjd   1/1     Running   0          32s

이미지 확인:

1
kubectl get pods -l app=nginx-demo -o jsonpath="{.items[*].spec.containers[*].image}"

결과:

nginx:1.25-alpine nginx:1.25-alpine nginx:1.25-alpine

✅ 모든 Pod이 새 이미지로 업그레이드 완료!


7단계: 서비스 가용성 확인

업그레이드 중에도 서비스가 정상적으로 응답했는지 확인한다.

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

결과:

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

20/20 요청 모두 200 OK! (다운타임 0초)


8단계: apache-demo도 동일하게 테스트

Rolling Update 전략 설정

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: apache-demo
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: apache-demo
  template:
    metadata:
      labels:
        app: apache-demo
    spec:
      containers:
      - name: apache
        image: httpd:alpine
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 2
EOF

이미지 업그레이드

1
2
3
kubectl set image deployment/apache-demo apache=httpd:2.4-alpine

kubectl rollout status deployment/apache-demo

결과:

deployment.apps/apache-demo image updated
Waiting for deployment "apache-demo" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "apache-demo" rollout to finish: 1 old replicas are pending termination...
deployment "apache-demo" successfully rolled out

서비스 가용성 확인

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

결과:

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

20/20 요청 모두 200 OK! (다운타임 0초)


9단계: 최종 상태 확인

1
2
3
kubectl get deployments
kubectl get pdb
kubectl get pods -l "app in (nginx-demo,apache-demo)"

결과:

=== Deployment 상태 ===
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
apache-demo   2/2     2            2           22m
nginx-demo    3/3     3            3           20m

=== PDB 상태 ===
NAME              MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
apache-demo-pdb   1               N/A               1                     2m18s
nginx-demo-pdb    2               N/A               1                     2m18s

=== Pod 상태 ===
NAME                           READY   STATUS    RESTARTS   AGE
apache-demo-569d746c79-2m574   1/1     Running   0          26s
apache-demo-569d746c79-t9lwf   1/1     Running   0          22s
nginx-demo-775cb84cd7-8zfmn    1/1     Running   0          74s
nginx-demo-775cb84cd7-lm8hc    1/1     Running   0          64s
nginx-demo-775cb84cd7-qmgjd    1/1     Running   0          84s

핵심 개념 정리

1. Pod Disruption Budget (PDB)

역할: 업그레이드/유지보수 중에도 최소 Pod 수 보장

1
2
3
4
5
spec:
  minAvailable: 2  # 또는 maxUnavailable: 1
  selector:
    matchLabels:
      app: nginx-demo

효과:

  • K8s가 한 번에 너무 많은 Pod을 종료하지 못하게 제한
  • 서비스 가용성 유지

2. Rolling Update 전략

1
2
3
4
5
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1          # 추가 Pod 최대 1개
    maxUnavailable: 0    # 동시 불가용 Pod 0개 (무중단)

동작 방식:

  1. 새 Pod 1개 생성 (maxSurge=1)
  2. 새 Pod이 Ready 상태 대기
  3. 이전 Pod 1개 종료
  4. 2-3 반복 (모든 Pod 교체 완료까지)

3. Readiness Probe

1
2
3
4
5
6
readinessProbe:
  httpGet:
    path: /
    port: 80
  initialDelaySeconds: 2
  periodSeconds: 2

역할: Pod이 트래픽을 받을 준비가 되었는지 확인

효과:

  • Ready 상태가 아닌 Pod은 Service 엔드포인트에서 제외
  • 롤아웃 중에도 정상 Pod만 트래픽 수신

정리

✅ 무중단 업그레이드 성공 요인

요소설정효과
PDBminAvailable: 2 (nginx), 1 (apache)최소 Pod 수 보장
Rolling UpdatemaxUnavailable: 0동시 불가용 Pod 없음
Readiness ProbehttpGet / (2초 간격)준비된 Pod만 트래픽 수신
Replica 수nginx 3개, apache 2개여유 있는 Pod 수

📊 실제 결과

  • nginx-demo: 40/40 요청 → 200 OK (100%)
  • apache-demo: 20/20 요청 → 200 OK (100%)
  • 다운타임: 0초
  • 롤아웃 시간: 약 30~40초 (3개 Pod 순차 교체)

🆚 Part 5 vs Part 6

항목Part 5 (클러스터 업그레이드)Part 6 (애플리케이션 업그레이드)
대상K8s 노드 (kubelet, kubeadm)Pod (컨테이너 이미지)
PDB설정 안 함✅ 설정 (최소 Pod 수 보장)
Rolling Update기본 설정✅ maxUnavailable=0
Readiness Probe없음✅ 설정
결과NotReady 발생 (일시 중단)✅ 무중단 (0초)

다음 단계

Part 6에서는 애플리케이션 Pod의 무중단 업그레이드를 다뤘다.
하지만 Part 5에서 본 것처럼, 클러스터 자체를 업그레이드할 때는 NotReady 상태가 발생했다.

Part 7에서는:

  • 클러스터 무중단 업그레이드 (Zero-Downtime Cluster Upgrade)
  • 워커 노드 순차 업그레이드 (drain → upgrade → uncordon)
  • PDB + Pod 분산으로 서비스 가용성 유지
  • 마스터 노드 업그레이드 (API 서버 최소 중단)

핵심 차이:

  • Part 6: Pod 이미지 교체 (애플리케이션 레벨)
  • Part 7: kubelet/kubeadm 업그레이드 (인프라 레벨)

실제 프로덕션 환경에서 K8s 버전을 올릴 때 서비스 다운타임 없이 진행하는 방법을 다룰 예정이다.


참고 자료


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