Skip to content
Published on

Kubernetes v1.33 운영 실전 플레이북: 업그레이드와 보안 자동화

Authors
Kubernetes v1.33 운영 실전 플레이북: 업그레이드와 보안 자동화

v1.33 "Octarine" 릴리스 핵심 변경 사항

Kubernetes v1.33은 2025년 4월 23일에 릴리스되었으며, 코드명은 "Octarine: The Color of Magic"이다. 64개의 개선 사항이 포함되어 있고, 그 중 18개가 Stable로 졸업, 20개가 Beta 진입, 24개가 새로운 Alpha 기능이다.

이 플레이북은 v1.33으로 업그레이드하는 운영팀이 알아야 할 것, 해야 할 것, 주의해야 할 것을 실행 가능한 단위로 정리한 문서다.

Stable로 졸업한 핵심 기능

기능KEP운영 영향도필요 조치
Sidecar ContainersKEP-753높음initContainers에 restartPolicy: Always 사용 가능. 기존 sidecar injection 워크어라운드 정리
User NamespacesKEP-127높음Pod에서 hostUsers: false 기본 활성화. 보안 격리 강화
nftables kube-proxyKEP-3866중간iptables에서 nftables로 전환 가능. 대규모 Service 환경에서 성능 향상
CRD Validation RatchetingKEP-4008중간기존 CRD 필드값이 새 스키마에 안 맞아도 변경 안 하면 통과

Beta로 진입한 주요 기능

기능KEP운영 영향도설명
In-Place Pod ResizeKEP-1287높음Pod 재시작 없이 CPU/Memory 조정 가능. 기본 활성화
DRA Driver-owned StatusKEP-4817중간DRA 드라이버가 ResourceClaim 상태를 직접 업데이트
Pod-level Resource LimitsKEP-2837중간컨테이너 단위가 아닌 Pod 단위로 리소스 상한 설정

Deprecated/Removed

항목상태대응
Endpoints APIDeprecatedEndpointSlice API로 마이그레이션
flowcontrol.apiserver.k8s.io/v1beta3Removedv1으로 업데이트
Node Status conditions 필드 중 일부변경모니터링 쿼리 수정 확인

플레이 1: 업그레이드 사전 준비

Step 1. 현재 클러스터 상태 점검

#!/bin/bash
# pre-upgrade-check.sh - 업그레이드 전 클러스터 상태 점검

echo "=== 1. 현재 버전 확인 ==="
kubectl version --short 2>/dev/null || kubectl version

echo ""
echo "=== 2. 노드 상태 확인 ==="
kubectl get nodes -o wide
NOT_READY=$(kubectl get nodes --no-headers | grep -v " Ready " | wc -l)
if [ "$NOT_READY" -gt 0 ]; then
  echo "WARNING: $NOT_READY 개 노드가 NotReady 상태입니다. 업그레이드 전 해결 필요."
fi

echo ""
echo "=== 3. Deprecated API 사용 확인 ==="
# v1.33에서 제거된 API를 사용하는 리소스 탐색
kubectl get --raw /metrics | grep -E 'apiserver_requested_deprecated_apis'

echo ""
echo "=== 4. PodDisruptionBudget 확인 ==="
kubectl get pdb --all-namespaces -o wide
echo "PDB가 없는 critical 워크로드가 있으면 업그레이드 중 downtime 발생 가능"

echo ""
echo "=== 5. etcd 상태 확인 ==="
kubectl -n kube-system exec -it etcd-$(hostname) -- \
  etcdctl endpoint health --cluster \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

echo ""
echo "=== 6. 버전 skew 확인 ==="
echo "Control Plane:"
kubectl get nodes -l node-role.kubernetes.io/control-plane -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}'
echo ""
echo "Workers:"
kubectl get nodes -l '!node-role.kubernetes.io/control-plane' -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}'

Step 2. Deprecated API 마이그레이션

v1.33에서 가장 중요한 API 변경은 Endpoints API의 deprecation이다. 기존에 Endpoints를 직접 읽고 있는 컨트롤러나 스크립트가 있다면 EndpointSlice로 전환해야 한다.

# Endpoints API를 사용하는 리소스 찾기
kubectl get endpoints --all-namespaces -o name | wc -l

# EndpointSlice로 읽는 방법
kubectl get endpointslices --all-namespaces -o wide

코드에서 Endpoints를 직접 참조하는 경우 변경 예시:

# Before: Endpoints API
from kubernetes import client

v1 = client.CoreV1Api()
endpoints = v1.read_namespaced_endpoints("my-service", "default")
for subset in endpoints.subsets:
    for address in subset.addresses:
        print(address.ip)

# After: EndpointSlice API
discovery_v1 = client.DiscoveryV1Api()
slices = discovery_v1.list_namespaced_endpoint_slice(
    "default",
    label_selector="kubernetes.io/service-name=my-service"
)
for ep_slice in slices.items:
    for endpoint in ep_slice.endpoints:
        for address in endpoint.addresses:
            print(address)

Step 3. etcd 백업

업그레이드 전 반드시 etcd 스냅샷을 생성한다. 이것이 최후의 롤백 수단이다.

# etcd 스냅샷 생성
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-pre-v133-$(date +%Y%m%d%H%M).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# 스냅샷 검증
ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-pre-v133-*.db --write-out=table

플레이 2: Control Plane 업그레이드

kubeadm 기반 클러스터에서의 업그레이드 절차다. 관리형 서비스(EKS, GKE, AKS)는 콘솔이나 CLI에서 버전을 지정하면 자동 처리된다.

Control Plane 노드 업그레이드

# 1. kubeadm 업그레이드
sudo apt-get update
sudo apt-get install -y kubeadm=1.33.0-1.1

# 2. 업그레이드 계획 확인
sudo kubeadm upgrade plan v1.33.0

# 3. 업그레이드 실행 (첫 번째 control plane 노드)
sudo kubeadm upgrade apply v1.33.0

# 4. kubelet과 kubectl 업그레이드
sudo apt-get install -y kubelet=1.33.0-1.1 kubectl=1.33.0-1.1
sudo systemctl daemon-reload
sudo systemctl restart kubelet

# 5. 추가 control plane 노드 (두 번째, 세 번째)
sudo kubeadm upgrade node

업그레이드 후 Control Plane 검증

# API Server 버전 확인
kubectl version

# 컴포넌트 상태 확인
kubectl get componentstatuses 2>/dev/null || \
  kubectl get --raw /readyz?verbose

# etcd 클러스터 상태
kubectl -n kube-system exec -it etcd-cp-01 -- \
  etcdctl member list --write-out=table \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

플레이 3: Worker 노드 롤링 업그레이드

Worker 노드는 한 번에 하나씩 업그레이드하여 서비스 가용성을 유지한다.

#!/bin/bash
# worker-upgrade.sh <node-name>
NODE=$1

echo "=== $NODE 업그레이드 시작 ==="

# 1. 노드 cordon (새 Pod 스케줄링 차단)
kubectl cordon "$NODE"

# 2. 노드 drain (기존 Pod 퇴거)
kubectl drain "$NODE" \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --timeout=300s \
  --grace-period=120

# 3. 노드에서 패키지 업그레이드 (SSH)
ssh "$NODE" "sudo apt-get update && \
  sudo apt-get install -y kubeadm=1.33.0-1.1 && \
  sudo kubeadm upgrade node && \
  sudo apt-get install -y kubelet=1.33.0-1.1 && \
  sudo systemctl daemon-reload && \
  sudo systemctl restart kubelet"

# 4. 노드 uncordon
kubectl uncordon "$NODE"

# 5. 노드 상태 확인
kubectl get node "$NODE" -o wide
echo "=== $NODE 업그레이드 완료 ==="

롤링 업그레이드 병렬도 결정

클러스터 규모동시 업그레이드 노드 수근거
3-10 노드1여유 노드가 적으므로 보수적 진행
10-50 노드2-3PDB 준수 확인 후 병렬화
50-200 노드5-10노드 그룹별 순차 진행
200+ 노드노드 풀 단위Blue-green 노드 풀 교체 권장

플레이 4: In-Place Pod Resize 활용

v1.33에서 Beta로 승격된 In-Place Pod Resize는 운영팀에게 가장 실용적인 기능이다. Pod를 재시작하지 않고 CPU/Memory를 조정할 수 있다.

리사이즈 실행

# 현재 리소스 확인
kubectl get pod my-app-xyz -o jsonpath='{.spec.containers[0].resources}'

# 리사이즈 요청 (resize subresource 사용)
kubectl patch pod my-app-xyz --subresource=resize --type=merge -p '{
  "spec": {
    "containers": [{
      "name": "app",
      "resources": {
        "requests": {"cpu": "500m", "memory": "512Mi"},
        "limits": {"cpu": "1000m", "memory": "1Gi"}
      }
    }]
  }
}'

# 리사이즈 상태 확인
kubectl get pod my-app-xyz -o jsonpath='{.status.resize}'
# "Proposed" → "InProgress" → "Completed" 순서로 전환

Resize Policy 설정

Container spec에서 리사이즈 정책을 지정할 수 있다:

apiVersion: v1
kind: Pod
metadata:
  name: resizable-app
spec:
  containers:
    - name: app
      image: nginx:1.27
      resources:
        requests:
          cpu: '250m'
          memory: '256Mi'
        limits:
          cpu: '500m'
          memory: '512Mi'
      resizePolicy:
        - resourceName: cpu
          restartPolicy: NotRequired # CPU는 재시작 없이 조정
        - resourceName: memory
          restartPolicy: RestartContainer # Memory는 컨테이너 재시작 필요

자동 리사이즈 연동 (VPA + In-Place Resize)

VerticalPodAutoscaler가 In-Place Resize를 활용하도록 설정:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: my-app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: 'InPlaceOrRecreate' # v1.33에서 추가된 모드
  resourcePolicy:
    containerPolicies:
      - containerName: app
        minAllowed:
          cpu: '100m'
          memory: '128Mi'
        maxAllowed:
          cpu: '2000m'
          memory: '4Gi'

플레이 5: User Namespaces 보안 강화

v1.33에서 User Namespaces가 기본 활성화되었다. 컨테이너 내부에서 root로 실행되더라도 호스트에서는 비권한(unprivileged) UID로 매핑되어 보안이 강화된다.

User Namespaces 적용

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  hostUsers: false # User Namespaces 활성화
  containers:
    - name: app
      image: myapp:v2.0
      securityContext:
        runAsUser: 0 # 컨테이너 내부에서는 root
        # 하지만 hostUsers: false이므로 호스트에서는 고유 UID로 매핑

보안 정책과 결합

User Namespaces와 Pod Security Standards를 함께 사용:

# Namespace에 Pod Security 레벨 적용
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Kyverno로 User Namespaces 강제화:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-user-namespaces
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-hostusers-false
      match:
        any:
          - resources:
              kinds: ['Pod']
              namespaces: ['production', 'staging']
      validate:
        message: '프로덕션/스테이징 Pod는 hostUsers: false를 설정해야 합니다.'
        pattern:
          spec:
            hostUsers: false

플레이 6: nftables kube-proxy 전환

v1.33에서 nftables 기반 kube-proxy가 Stable이 되었다. iptables보다 성능이 좋고, 특히 Service 수가 많은 클러스터에서 큰 차이가 난다.

전환 전 확인

# 현재 kube-proxy 모드 확인
kubectl -n kube-system get configmap kube-proxy -o yaml | grep mode

# nftables 지원 확인 (노드에서)
nft --version
# nftables v1.0.6 이상 필요

kube-proxy ConfigMap 수정

apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-proxy
  namespace: kube-system
data:
  config.conf: |
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    kind: KubeProxyConfiguration
    mode: nftables     # iptables → nftables
    nftables:
      masqueradeAll: false
      syncPeriod: 30s
      minSyncPeriod: 1s

전환 후 kube-proxy를 롤링 재시작:

kubectl -n kube-system rollout restart daemonset kube-proxy
kubectl -n kube-system rollout status daemonset kube-proxy

플레이 7: 업그레이드 후 검증

업그레이드가 완료되었다고 끝이 아니다. 아래 검증 단계를 반드시 거친다.

자동화된 검증 스크립트

#!/bin/bash
# post-upgrade-verify.sh

ERRORS=0

echo "=== Post-Upgrade Verification ==="

# 1. 모든 노드 Ready 확인
echo "[1/7] Node Status..."
NOT_READY=$(kubectl get nodes --no-headers | grep -v " Ready " | wc -l)
if [ "$NOT_READY" -gt 0 ]; then
  echo "FAIL: $NOT_READY nodes not ready"
  ERRORS=$((ERRORS + 1))
else
  echo "PASS: All nodes ready"
fi

# 2. 모든 시스템 Pod Running 확인
echo "[2/7] System Pods..."
FAILING=$(kubectl -n kube-system get pods --no-headers | grep -v "Running\|Completed" | wc -l)
if [ "$FAILING" -gt 0 ]; then
  echo "FAIL: $FAILING system pods not running"
  kubectl -n kube-system get pods | grep -v "Running\|Completed"
  ERRORS=$((ERRORS + 1))
else
  echo "PASS: All system pods healthy"
fi

# 3. CoreDNS 동작 확인
echo "[3/7] DNS Resolution..."
kubectl run dns-test --image=busybox:1.36 --restart=Never --rm -it -- \
  nslookup kubernetes.default.svc.cluster.local 2>/dev/null
if [ $? -eq 0 ]; then
  echo "PASS: DNS working"
else
  echo "FAIL: DNS resolution failed"
  ERRORS=$((ERRORS + 1))
fi

# 4. API Server 버전 확인
echo "[4/7] API Server Version..."
SERVER_VERSION=$(kubectl version -o json | jq -r '.serverVersion.gitVersion')
echo "Server version: $SERVER_VERSION"
if [[ "$SERVER_VERSION" == *"1.33"* ]]; then
  echo "PASS: Correct version"
else
  echo "FAIL: Unexpected version"
  ERRORS=$((ERRORS + 1))
fi

# 5. 핵심 워크로드 상태 확인
echo "[5/7] Critical Workloads..."
for ns in production staging; do
  UNAVAIL=$(kubectl -n "$ns" get deployments --no-headers 2>/dev/null | \
    awk '{if ($2 != $4) print $1}')
  if [ -n "$UNAVAIL" ]; then
    echo "FAIL: Unavailable deployments in $ns: $UNAVAIL"
    ERRORS=$((ERRORS + 1))
  fi
done
echo "PASS: Critical workloads healthy"

# 6. Ingress/Service 동작 확인
echo "[6/7] Service Connectivity..."
kubectl get svc -A --no-headers | awk '{print $1"/"$2}' | head -5 | while read svc; do
  echo "  Checking $svc..."
done
echo "PASS: Services enumerated"

# 7. 메트릭 파이프라인 확인
echo "[7/7] Metrics Pipeline..."
kubectl top nodes 2>/dev/null
if [ $? -eq 0 ]; then
  echo "PASS: Metrics working"
else
  echo "WARN: Metrics pipeline may need time to recover"
fi

echo ""
echo "=== Verification Complete: $ERRORS errors ==="
exit $ERRORS

Conformance Test 실행

업그레이드 후 Kubernetes Conformance Test를 실행하여 클러스터가 스펙을 준수하는지 확인:

# Sonobuoy로 Conformance Test 실행
sonobuoy run --mode=certified-conformance --wait

# 결과 확인
sonobuoy status
sonobuoy results $(sonobuoy retrieve)

업그레이드 롤백 절차

문제가 발견되면 즉시 롤백할 수 있어야 한다.

Worker 노드 롤백

# Worker 노드를 이전 버전으로 다운그레이드
ssh worker-node-01 "sudo apt-get install -y \
  kubeadm=1.32.x-1.1 kubelet=1.32.x-1.1 && \
  sudo kubeadm upgrade node && \
  sudo systemctl daemon-reload && \
  sudo systemctl restart kubelet"

Control Plane 롤백 (최후의 수단)

Control Plane 롤백은 etcd 복원을 동반하므로 위험하다. 반드시 사전에 백업을 확인한 상태에서만 진행한다.

# etcd 스냅샷에서 복원
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-pre-v133-*.db \
  --data-dir=/var/lib/etcd-restored

# etcd 데이터 디렉토리 교체 (모든 control plane 노드에서)
sudo systemctl stop etcd
sudo mv /var/lib/etcd /var/lib/etcd-broken
sudo mv /var/lib/etcd-restored /var/lib/etcd
sudo systemctl start etcd
퀴즈

Q1. v1.33에서 Endpoints API가 deprecated된 후 대체해야 할 API는? 정답: ||EndpointSlice API. v1.21부터 사용 가능했으며, dual-stack 네트워킹 등 새로운 기능을 지원한다.||

Q2. In-Place Pod Resize에서 CPU와 Memory의 리사이즈 동작 차이는? 정답: ||CPU는 컨테이너 재시작 없이(NotRequired) 조정 가능하지만, Memory는 경우에 따라 컨테이너 재시작(RestartContainer)이 필요하다.||

Q3. User Namespaces에서 hostUsers: false를 설정하면 보안상 어떤 이점이 있는가? 정답: ||컨테이너 내부에서 root(UID 0)로 실행되더라도 호스트에서는 비권한 UID로 매핑되어, 컨테이너 탈출(container escape) 시에도 호스트 권한을 얻지 못한다.||

Q4. 업그레이드 전 etcd 백업이 "최후의 롤백 수단"인 이유는? 정답: ||Control Plane 롤백은 API Server 바이너리만 되돌리면 안 되고, etcd에 저장된 클러스터 상태도 함께 복원해야 한다. etcd 스냅샷이 없으면 이전 상태로 돌아갈 방법이 없다.||

Q5. nftables kube-proxy가 iptables보다 대규모 클러스터에서 유리한 이유는? 정답: ||iptables는 규칙이 선형 체인으로 평가되어 Service 수에 비례하여 지연이 증가하지만, nftables는 맵/셋 기반으로 O(1)에 가까운 룩업이 가능하다.||

Q6. Worker 노드 업그레이드 시 drain 전에 cordon을 먼저 하는 이유는? 정답: ||cordon 없이 drain만 하면, drain 중에 새로운 Pod가 해당 노드에 스케줄링될 수 있다. cordon으로 먼저 새 Pod 배치를 차단한 후 drain으로 기존 Pod를 퇴거해야 한다.||

Q7. CRD Validation Ratcheting이 업그레이드에서 중요한 이유는? 정답: ||CRD 스키마를 강화해도, 기존에 저장된 리소스가 변경되지 않는 한 새 스키마 규칙에 걸리지 않는다. 이것은 기존 리소스를 깨뜨리지 않으면서 점진적으로 스키마를 개선할 수 있게 해준다.||

참고 자료