LabHub
배우기 러닝패스 코스

Kubernetes運用実務

drain が30分たっても終わらない

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

드레인이 막혔을 때 축출 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, nodeSelectorkubernetes.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 오브젝트를 쓰세요 — apiVersionpolicy/v1, kindEviction, metadata.name 은 그 파드 이름, metadata.namespaceops-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-pdb750%<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 를 패치해 ReadyFalse 로 만드세요(kubectl -n ops-pdb patch pod <이름> --subresource=status --type=merge -p ...). 그 파드에 대해 ?dryRun=All 축출을 보내 거절 메시지를 /root/ops-disruption/unhealthy-denied.txt 에 저장하고, 그다음 flaky-pdbspec.unhealthyPodEvictionPolicyAlwaysAllow 로 바꾼 뒤 같은 요청을 다시 보내 /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 에 저장하세요.

참고

한 노드에 모인 워크로드

네임스페이스 ops-pdb 를 만들고 /root/ops-disruption/api.yaml 에 Deployment api 를 쓰세요 — 레플리카 4, 라벨 app: api, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-0 입니다. 적용하고 파드 4개가 모두 lab-node-0 에서 Running 이 될 때까지 기다리세요.

드레인은 노드 단위 작업이라 대상 노드가 정해져 있어야 재현이 됩니다. 한 노드에 모아 두면 뒤에서 그 노드를 비우려 할 때 무엇이 막는지가 분명해집니다. 새 네임스페이스는 default 서비스 어카운트가 생기기까지 잠깐 걸립니다.

허용 중단 0 이라는 숫자를 만든다

/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>.

PDB 를 만들자마자 status 가 비어 있을 수 있습니다. 중단 컨트롤러가 셀렉터에 걸리는 파드를 세고 status 를 채울 때까지 기다리세요 — expectedPods 가 0 이 아니게 되면 계산이 끝난 것입니다. 네 개를 모두 지키라고 했으니 허용 중단이 몇이 될지 먼저 예상해 보세요.

축출 API 에 직접 물어본다

api 파드 하나를 골라 /root/ops-disruption/eviction.json 에 Eviction 오브젝트를 쓰세요 — apiVersionpolicy/v1, kindEviction, metadata.name 은 그 파드 이름, metadata.namespaceops-pdb 입니다. 그 파일로 kubectl create --raw /api/v1/namespaces/ops-pdb/pods/<파드이름>/eviction -f /root/ops-disruption/eviction.json 을 실행해 오류 출력을 /root/ops-disruption/eviction-denied.txt 에 저장하세요(표준오류도 함께 담아야 합니다).

드레인은 파드를 지우는 것이 아니라 축출 서브리소스를 호출합니다. 그래서 PDB 가 막으면 삭제가 아니라 HTTP 429 가 돌아옵니다 — 응답 본문에 왜 막혔는지가 적혀 있습니다. 출력을 파일로 남기려면 2>&1 > 파일 이 아니라 > 파일 2>&1 순서여야 합니다.

드레인이 끝나지 않는 것을 직접 본다

kubectl drain lab-node-0 --ignore-daemonsets --delete-emptydir-data --force --timeout=20s 를 실행해 출력을 /root/ops-disruption/drain-blocked.txt 에 저장하세요(표준오류 포함). 드레인은 먼저 노드를 스케줄 차단 상태로 바꾸므로, 명령이 끝난 뒤 반드시 kubectl uncordon lab-node-0 으로 되돌려 놓으세요.

드레인 명령은 두 가지 일을 합니다 — 노드에 예약 차단 표시를 남기고, 그 위의 파드를 축출 API 로 하나씩 내보냅니다. 앞의 일은 성공하고 뒤의 일이 막히면 노드는 차단된 채로 남습니다. 이것이 현장에서 용량이 조용히 줄어드는 흔한 경로입니다.

예산을 고치면 같은 요청이 통과한다

/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 에 저장하세요.

축출 서브리소스는 dryRun 을 지원합니다 — 실제로 파드를 지우지 않고 지금 이 파드를 내보낼 수 있는지만 물어봅니다. 운영에서 유지보수 창을 잡기 전에 확인하는 방법이고, 여기서는 뒤 단계가 쓸 파드를 지우지 않기 위해 씁니다. 성공하면 code 201 이 담긴 Status 가 돌아옵니다.

백분율은 어느 쪽으로 올림되나

/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-pdb750%<desiredHealthy><disruptionsAllowed>.

7의 50%는 3.5 입니다. 쿠버네티스가 어느 쪽으로 보내는지 먼저 예상한 다음 클러스터가 계산한 숫자와 맞춰 보세요. 예상이 틀렸다면 그 방향이 왜 안전한 쪽인지 생각해 보세요. YAML 에서 백분율은 따옴표로 감싸는 편이 안전합니다.

셀렉터가 겹친 예산 두 개는 교착을 만든다

/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 에 둡니다).

두 예산이 각각은 여유가 있는데도 요청이 막힙니다. 축출 서브리소스는 한 파드가 예산 둘에 걸린 상황 자체를 지원하지 않기 때문입니다. 응답 문장을 그대로 읽어 보세요 — 이 메시지를 한 번 본 사람은 다음부터 겹친 셀렉터를 바로 의심하게 됩니다.

준비되지 않은 파드가 드레인을 붙잡는다

/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 를 패치해 ReadyFalse 로 만드세요(kubectl -n ops-pdb patch pod <이름> --subresource=status --type=merge -p ...). 그 파드에 대해 ?dryRun=All 축출을 보내 거절 메시지를 /root/ops-disruption/unhealthy-denied.txt 에 저장하고, 그다음 flaky-pdbspec.unhealthyPodEvictionPolicyAlwaysAllow 로 바꾼 뒤 같은 요청을 다시 보내 /root/ops-disruption/unhealthy-allowed.txt 에 저장하세요.

PDB 가 세는 건강한 파드는 Ready 컨디션이 True 인 파드입니다. 세 개를 지키라고 했는데 하나가 Ready 가 아니면 예산은 이미 깨진 상태이고, 기본 정책에서는 그 망가진 파드조차 내보낼 수 없습니다 — 드레인이 영원히 끝나지 않는 흔한 이유입니다. 정책을 바꾸면 같은 요청이 통과합니다.

예산 없는 워크로드를 클러스터 전체에서 찾는다

/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 에 저장하세요.

이 목록이 유지보수 창을 설계할 때 가장 먼저 필요한 자료입니다 — 예산이 없는 워크로드는 드레인에 아무 저항 없이 통째로 내려갑니다. 부분집합 판정은 jq 로 PDB 셀렉터의 각 항목이 파드 라벨에 같은 값으로 있는지 세면 됩니다. 스크립트가 파일을 직접 쓰면 채점기가 다시 돌릴 때 학생 산출물을 덮어쓰므로 표준출력으로만 내보내세요.