쿠버네티스 운영 실무 · 드레인이 멈췄다 — 축출 API 와 유지보수 창 · 실습
드레인이 30분째 끝나지 않는다
목표
드레인이 막혔을 때 축출 API 를 직접 불러 무엇이 막고 있는지 읽고, 백분율 예산의 올림 규칙과 셀렉터가 겹친 예산의 교착, 준비되지 않은 파드의 축출 정책을 클러스터가 계산한 숫자로 확인한 뒤, 예산이 없는 워크로드를 클러스터 전체에서 찾아 목록으로 만든다.
왜 중요한가
kubectl drain 은 파드를 지우지 않는다. 파드마다 축출(Eviction) 서브리소스를 호출하고, 그 요청을 PodDisruptionBudget 이 심사한다. 예산이 모자라면 삭제가 아니라 HTTP 429 가 돌아오고 drain 은 될 때까지 다시 시도한다. 그래서 막힌 드레인은 실패로 끝나지 않고 영원히 계속된다. 이 구조를 모르면 화면의 error when evicting pods 만 보고 원인을 찾을 수 없다. 예산은 숫자 하나가 아니라 currentHealthy·desiredHealthy·disruptionsAllowed 세 숫자로 설명되고, 그 숫자를 읽는 법을 알면 막힌 이유가 대개 셋 중 하나로 좁혀진다 — 예산이 0 이거나, 셀렉터가 겹쳤거나, 준비되지 않은 파드가 예산을 이미 깨 놓았거나.
단계
1. 네임스페이스 ops-pdb 를 만들고 /root/ops-disruption/api.yaml 에 Deployment api 를 쓰세요 — 레플리카 4, 라벨 app: api, 이미지 nginx:1.27.3, nodeSelector 는 kubernetes.io/hostname: lab-node-0 입니다. 적용하고 파드 4개가 모두 lab-node-0 에서 Running 이 될 때까지 기다리세요.
2. /root/ops-disruption/api-pdb.yaml 에 PodDisruptionBudget api-pdb 를 쓰세요 — 네임스페이스 ops-pdb, minAvailable: 4, 셀렉터는 app: api 입니다. 적용한 뒤 컨트롤러가 상태를 계산할 때까지 기다렸다가 /root/ops-disruption/pdb-status.tsv 에 한 줄로 저장하세요 — api-pdb 탭 <currentHealthy> 탭 <desiredHealthy> 탭 <disruptionsAllowed> 탭 <expectedPods>.
3. api 파드 하나를 골라 /root/ops-disruption/eviction.json 에 Eviction 오브젝트를 쓰세요 — apiVersion 은 policy/v1, kind 는 Eviction, metadata.name 은 그 파드 이름, metadata.namespace 는 ops-pdb 입니다. 그 파일로 kubectl create --raw /api/v1/namespaces/ops-pdb/pods/<파드이름>/eviction -f /root/ops-disruption/eviction.json 을 실행해 오류 출력을 /root/ops-disruption/eviction-denied.txt 에 저장하세요(표준오류도 함께 담아야 합니다).
4. kubectl drain lab-node-0 --ignore-daemonsets --delete-emptydir-data --force --timeout=20s 를 실행해 출력을 /root/ops-disruption/drain-blocked.txt 에 저장하세요(표준오류 포함). 드레인은 먼저 노드를 스케줄 차단 상태로 바꾸므로, 명령이 끝난 뒤 반드시 kubectl uncordon lab-node-0 으로 되돌려 놓으세요.
5. /root/ops-disruption/api-pdb-fixed.yaml 에 같은 이름의 PDB api-pdb 를 다시 쓰되 minAvailable 대신 maxUnavailable: 1 을 쓰고 적용하세요(두 필드는 함께 쓸 수 없으므로 새 파일에는 minAvailable 이 없어야 합니다). 허용 중단이 1 이 될 때까지 기다린 뒤 /root/ops-disruption/pdb-after.tsv 에 2단계와 같은 다섯 칸으로 저장하고, 3단계의 요청을 ?dryRun=All 을 붙여 다시 보내 그 출력을 /root/ops-disruption/eviction-allowed.txt 에 저장하세요.
6. /root/ops-disruption/batch.yaml 에 Deployment batch 를 쓰세요 — 네임스페이스 ops-pdb, 레플리카 7, 라벨 app: batch, 이미지 nginx:1.27.3. 그리고 /root/ops-disruption/batch-pdb.yaml 에 PDB batch-pdb 를 쓰세요 — minAvailable: 50% 이고 셀렉터는 app: batch 입니다. 둘 다 적용하고 상태가 계산되면 /root/ops-disruption/rounding.tsv 에 한 줄로 저장하세요 — batch-pdb 탭 7 탭 50% 탭 <desiredHealthy> 탭 <disruptionsAllowed>.
7. /root/ops-disruption/batch-extra.yaml 에 PDB batch-extra 를 하나 더 쓰세요 — 네임스페이스 ops-pdb, maxUnavailable: 1, 셀렉터는 batch-pdb 와 똑같이 app: batch 입니다. 적용한 뒤 batch 파드 하나에 대해 ?dryRun=All 을 붙인 축출 요청을 보내 그 출력을 /root/ops-disruption/overlap.txt 에 저장하세요(요청 본문은 /root/ops-disruption/overlap-eviction.json 에 둡니다).
8. /root/ops-disruption/flaky.yaml 에 Deployment flaky(레플리카 3, 라벨 app: flaky, 이미지 nginx:1.27.3)와 /root/ops-disruption/flaky-pdb.yaml 에 PDB flaky-pdb(minAvailable: 3, 셀렉터 app: flaky)를 쓰고 적용하세요. 그다음 flaky 파드 하나의 status.conditions 를 패치해 Ready 를 False 로 만드세요(kubectl -n ops-pdb patch pod <이름> --subresource=status --type=merge -p ...). 그 파드에 대해 ?dryRun=All 축출을 보내 거절 메시지를 /root/ops-disruption/unhealthy-denied.txt 에 저장하고, 그다음 flaky-pdb 의 spec.unhealthyPodEvictionPolicy 를 AlwaysAllow 로 바꾼 뒤 같은 요청을 다시 보내 /root/ops-disruption/unhealthy-allowed.txt 에 저장하세요.
9. /root/ops-disruption/orphan.yaml 에 Deployment orphan 을 쓰세요 — 네임스페이스 ops-pdb, 레플리카 3, 라벨 app: orphan, 이미지 nginx:1.27.3, PDB 는 만들지 않습니다. 그다음 /root/ops-disruption/pdb-audit.sh 를 만드세요 — 모든 네임스페이스의 Deployment 중 spec.replicas 가 2 이상인데 같은 네임스페이스의 어떤 PDB 에도 덮이지 않은 것을 <네임스페이스> 탭 <이름> 으로 표준출력에만 찍고 이름 순으로 정렬합니다. PDB 의 spec.selector.matchLabels 가 Deployment 의 spec.template.metadata.labels 의 부분집합이면 덮인 것으로 봅니다. 그 출력을 /root/ops-disruption/pdb-audit.txt 에 저장하세요.
참고
- 축출 서브리소스는
kubectl create --raw /api/v1/namespaces/<ns>/pods/<pod>/eviction -f <파일>로 직접 부를 수 있습니다. ?dryRun=All을 붙이면 파드를 지우지 않고 판정만 받아 볼 수 있습니다.- PDB 의 상태는
kubectl get pdb <이름> -o json의.status에 다 들어 있습니다. - 흔한 실수: 출력을 저장할 때
2>&1을 앞에 써서 표준오류가 파일에 담기지 않는다. - 흔한 실수: drain 이 막힌 뒤 노드를 uncordon 하지 않아 용량이 조용히 줄어든 채로 남는다.
- 참고: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/
- 참고: https://kubernetes.io/docs/tasks/run-application/configure-pdb/
단계 9개
- 한 노드에 모인 워크로드
- 허용 중단 0 이라는 숫자를 만든다
- 축출 API 에 직접 물어본다
- 드레인이 끝나지 않는 것을 직접 본다
- 예산을 고치면 같은 요청이 통과한다
- 백분율은 어느 쪽으로 올림되나
- 셀렉터가 겹친 예산 두 개는 교착을 만든다
- 준비되지 않은 파드가 드레인을 붙잡는다
- 예산 없는 워크로드를 클러스터 전체에서 찾는다