LabHub
배우기 러닝패스 코스

Kubernetes運用実務

誰も drain していないのに Pod が消えた

LabHub 에서 이어서 보기

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

목표

NoSchedule 과 NoExecute 가 이미 떠 있는 파드에 각각 무엇을 하는지 실제 축출로 확인하고, tolerationSeconds 가 정하는 축출 시각을 파드마다 계산해 표로 만든 뒤, 상태를 가진 워크로드에 줄 값을 근거와 함께 정한다.

왜 중요한가

운영자가 불려 나가는 사고의 절반은 아무도 명령을 치지 않았는데 파드가 움직인 경우다. 노드가 하트비트를 멈추면 노드 컨트롤러가 node.kubernetes.io/not-ready 또는 node.kubernetes.io/unreachable 테인트를 NoExecute 로 붙이고, 그 순간부터 각 파드의 tolerationSeconds 가 시계를 돌린다. 문제는 이 값을 적은 적이 없는 팀이 대부분이라는 것이다. 적지 않으면 어드미션이 300초를 넣어 준다 — 즉 모든 워크로드가 5분 뒤에 옮긴다는 같은 정책을 쓰고 있다. 스테이트리스 프런트엔드에는 너무 길고, 로컬 상태가 큰 데이터 워크로드에는 너무 짧다. 이 값을 이해하고 워크로드마다 고르는 것이 노드 사고 대응의 절반이다.

단계

  1. 네임스페이스 ops-evict 를 만들고, /root/ops-eviction/web.yaml 에 Deployment web 을 쓰세요 — 레플리카 3, 라벨 app: web, 이미지 nginx:1.27.3, 그리고 nodeSelectorkubernetes.io/hostname: lab-node-0 에만 앉게 합니다. 적용하고 파드 3개가 모두 Running 이 될 때까지 기다리세요.
  2. web 파드에는 톨러레이션을 한 줄도 적지 않았습니다. 그런데 실제 오브젝트에는 붙어 있습니다. web 파드 아무거나 하나를 골라 -o json 으로 확인하고, 그 톨러레이션들을 /root/ops-eviction/default-tolerations.tsv<키><효과><tolerationSeconds> 한 줄씩, 키 이름 오름차순으로 저장하세요. 다른 줄은 넣지 마세요.
  3. /root/ops-eviction/brittle.yaml/root/ops-eviction/patient.yaml 에 파드 두 개를 쓰세요. 둘 다 네임스페이스 ops-evict, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-1 입니다. 두 파드는 톨러레이션의 효과만 다릅니다 — brittle 은 라벨 role: brittle 이고 키 maintoperator: Exists·effect: NoSchedule 로 견디는 톨러레이션 하나(tolerationSeconds 없음), patient 은 라벨 role: patient 이고 키 maintoperator: Exists·effect: NoExecute·tolerationSeconds: 3600 으로 견디는 톨러레이션 하나입니다. 둘 다 적용해 lab-node-1 에서 Running 이 되게 하세요.
  4. lab-node-1 에 테인트 maint=planned:NoSchedule 을 거세요. 그다음 /root/ops-eviction/noschedule.tsvbrittlepatient 의 지금 상태를 <파드이름><노드이름><phase> 한 줄씩, 파드 이름 오름차순으로 저장하세요.
  5. 같은 노드 lab-node-1 에 테인트 maint=planned:NoExecute 를 더 거세요. brittle 이 사라질 때까지 고정 sleep 이 아니라 조건을 기다리는 반복문으로 기다린 뒤, 결과를 /root/ops-eviction/noexecute.tsv 에 두 줄로 저장하세요 — brittlegone, patientRunning 입니다.
  6. /root/ops-eviction/shortwait.yaml/root/ops-eviction/longwait.yaml 을 쓰세요. 둘 다 네임스페이스 ops-evict, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-2 입니다. 두 파드 모두 키 linkdownoperator: Exists·effect: NoExecute 로 견디되, shortwaittolerationSeconds: 20, longwaittolerationSeconds: 3600 입니다. 라벨은 각각 role: shortwait, role: longwait 입니다. 둘 다 lab-node-2 에서 Running 이 되게 하세요.
  7. 링크가 끊긴 상황을 흉내 냅니다 — lab-node-2 에 테인트 linkdown=yes:NoExecute 를 거세요. shortwait 이 사라질 때까지 조건 반복문으로 기다린 뒤 /root/ops-eviction/partition.tsv 에 두 줄로 저장하세요 — shortwaitgone, longwaitRunning 입니다.
  8. 먼저 /root/ops-eviction/forever.yaml 에 파드 forever 를 만드세요 — 네임스페이스 ops-evict, 라벨 role: forever, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-0, 톨러레이션은 키를 적지 않고 operator: Exists·effect: NoExecute 하나뿐입니다(tolerationSeconds 없음). 그다음 /root/ops-eviction/evict-plan.sh 를 만드세요 — ops-evict 의 모든 파드를 읽어 파드마다 linkdownNoExecute 를 몇 초 견디는지 <파드이름><값> 으로 표준출력에만 찍습니다. 값은 견디는 톨러레이션이 없으면 0, 있지만 tolerationSeconds 가 없으면 never, 있으면 그 초입니다. 출력은 파드 이름 오름차순이어야 합니다. 그 출력을 /root/ops-eviction/evict-plan.tsv 에 저장하세요.
  9. 네임스페이스 ops-ledger 를 만들고 /root/ops-eviction/ledger.yaml 에 Deployment ledger 를 쓰세요 — 네임스페이스 ops-ledger, 레플리카 2, 라벨 app: ledger, 이미지 nginx:1.27.3. 이 워크로드는 노드 로컬 상태가 커서 짧은 단절에는 버티는 편이 낫습니다. node.kubernetes.io/not-readynode.kubernetes.io/unreachable 두 키를 operator: Exists·effect: NoExecute 로 견디되, 두 톨러레이션의 tolerationSeconds 를 같은 값으로, 900 이상 3600 이하로 정하세요. 적용해 파드 2개가 Running 이 되게 한 뒤 /root/ops-eviction/decision.tsv 에 두 줄을 쓰세요 — tolerationSeconds<고른 값>, reason<40자 이상의 근거 한 문장>.

참고

건드리지 않을 노드에 비교군을 세운다

네임스페이스 ops-evict 를 만들고, /root/ops-eviction/web.yaml 에 Deployment web 을 쓰세요 — 레플리카 3, 라벨 app: web, 이미지 nginx:1.27.3, 그리고 nodeSelectorkubernetes.io/hostname: lab-node-0 에만 앉게 합니다. 적용하고 파드 3개가 모두 Running 이 될 때까지 기다리세요.

이 실습은 뒤에서 lab-node-1 과 lab-node-2 를 일부러 망가뜨립니다. 비교군이 없으면 파드가 사라진 이유가 테인트인지 다른 것인지 구분할 수 없습니다. 새 네임스페이스는 default 서비스 어카운트가 생기기까지 잠깐 걸리니 실패하면 몇 초 뒤 다시 적용하세요.

아무도 적지 않은 톨러레이션이 이미 붙어 있다

web 파드에는 톨러레이션을 한 줄도 적지 않았습니다. 그런데 실제 오브젝트에는 붙어 있습니다. web 파드 아무거나 하나를 골라 -o json 으로 확인하고, 그 톨러레이션들을 /root/ops-eviction/default-tolerations.tsv<키><효과><tolerationSeconds> 한 줄씩, 키 이름 오름차순으로 저장하세요. 다른 줄은 넣지 마세요.

어드미션 컨트롤러가 붙입니다. 파드를 만들 때 이 두 키를 직접 적지 않으면 자동으로 채워지고, 직접 적으면 채워지지 않습니다. 어느 web 파드를 골라도 답은 같습니다. 칸 구분은 탭이니 jq 의 @tsv 가 편합니다.

같은 노드에, 견디는 시간만 다른 파드 둘

/root/ops-eviction/brittle.yaml/root/ops-eviction/patient.yaml 에 파드 두 개를 쓰세요. 둘 다 네임스페이스 ops-evict, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-1 입니다. 두 파드는 톨러레이션의 효과만 다릅니다 — brittle 은 라벨 role: brittle 이고 키 maintoperator: Exists·effect: NoSchedule 로 견디는 톨러레이션 하나(tolerationSeconds 없음), patient 은 라벨 role: patient 이고 키 maintoperator: Exists·effect: NoExecute·tolerationSeconds: 3600 으로 견디는 톨러레이션 하나입니다. 둘 다 적용해 lab-node-1 에서 Running 이 되게 하세요.

톨러레이션은 이 테인트가 붙어도 나는 버틴다는 선언입니다. operator 가 Exists 면 값은 보지 않지만 효과는 반드시 맞아야 합니다 — 키가 같아도 효과가 다르면 견디지 못합니다. 두 파드의 차이가 정확히 그것이고, 뒤 단계에서 그 차이가 무엇을 만드는지 봅니다.

NoSchedule 은 이미 떠 있는 파드를 건드리지 않는다

lab-node-1 에 테인트 maint=planned:NoSchedule 을 거세요. 그다음 /root/ops-eviction/noschedule.tsvbrittlepatient 의 지금 상태를 <파드이름><노드이름><phase> 한 줄씩, 파드 이름 오름차순으로 저장하세요.

이름이 스케줄하지 마라인 이유가 있습니다. 이 효과는 새로 앉히는 것만 막습니다. patient 는 이 효과를 견디지 못하는데도 그대로 있어야 정상입니다 — 아무 일도 일어나지 않는 것을 확인하는 것이 이 단계의 목적입니다.

NoExecute 를 걸면 정말로 사라진다

같은 노드 lab-node-1 에 테인트 maint=planned:NoExecute 를 더 거세요. brittle 이 사라질 때까지 고정 sleep 이 아니라 조건을 기다리는 반복문으로 기다린 뒤, 결과를 /root/ops-eviction/noexecute.tsv 에 두 줄로 저장하세요 — brittlegone, patientRunning 입니다.

NoExecute 는 이미 떠 있는 파드에도 효과가 있습니다. brittle 에도 maint 톨러레이션이 있지만 효과가 NoSchedule 이라 이 테인트는 견디지 못합니다. 기다리는 반복문은 이런 모양입니다 — for i in $(seq 1 60); do kubectl -n ops-evict get pod brittle >/dev/null 2>&1 || break; sleep 2; done

네트워크가 끊겼을 때를 대비한 파드 둘

/root/ops-eviction/shortwait.yaml/root/ops-eviction/longwait.yaml 을 쓰세요. 둘 다 네임스페이스 ops-evict, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-2 입니다. 두 파드 모두 키 linkdownoperator: Exists·effect: NoExecute 로 견디되, shortwaittolerationSeconds: 20, longwaittolerationSeconds: 3600 입니다. 라벨은 각각 role: shortwait, role: longwait 입니다. 둘 다 lab-node-2 에서 Running 이 되게 하세요.

linkdown 은 우리가 정한 사용자 정의 키입니다. 내장 키(not-ready·unreachable)를 쓰지 않는 이유가 있습니다 — 그 두 키의 테인트는 노드 컨트롤러가 노드 컨디션을 보고 직접 관리해서, Ready 인 노드에 손으로 붙이면 곧바로 걷어 갑니다(직접 확인했습니다). 축출 규칙 자체는 키와 무관하게 같습니다.

노드가 응답을 멈춘 것처럼 만들고 시각을 잰다

링크가 끊긴 상황을 흉내 냅니다 — lab-node-2 에 테인트 linkdown=yes:NoExecute 를 거세요. shortwait 이 사라질 때까지 조건 반복문으로 기다린 뒤 /root/ops-eviction/partition.tsv 에 두 줄로 저장하세요 — shortwaitgone, longwaitRunning 입니다.

진짜 클러스터라면 노드가 하트비트를 멈춘 뒤 노드 컨트롤러가 node.kubernetes.io/unreachable 을 붙이고 그때부터 같은 규칙이 돕니다. 여기 노드는 kwok 이 만든 가짜라 하트비트를 끊을 수 없고, 내장 키를 손으로 붙이면 노드 컨트롤러가 곧바로 걷어 가므로 사용자 정의 키로 같은 일을 합니다. 20초가 지나야 사라지니 조건 반복문으로 기다리세요.

축출 시각표를 기계가 계산하게 한다

먼저 /root/ops-eviction/forever.yaml 에 파드 forever 를 만드세요 — 네임스페이스 ops-evict, 라벨 role: forever, 이미지 nginx:1.27.3, nodeSelectorkubernetes.io/hostname: lab-node-0, 톨러레이션은 키를 적지 않고 operator: Exists·effect: NoExecute 하나뿐입니다(tolerationSeconds 없음). 그다음 /root/ops-eviction/evict-plan.sh 를 만드세요 — ops-evict 의 모든 파드를 읽어 파드마다 linkdownNoExecute 를 몇 초 견디는지 <파드이름><값> 으로 표준출력에만 찍습니다. 값은 견디는 톨러레이션이 없으면 0, 있지만 tolerationSeconds 가 없으면 never, 있으면 그 초입니다. 출력은 파드 이름 오름차순이어야 합니다. 그 출력을 /root/ops-eviction/evict-plan.tsv 에 저장하세요.

키가 비어 있는 톨러레이션은 모든 키를 견딘다는 뜻이고 effect 가 비어 있으면 모든 효과를 견딘다는 뜻입니다 — 그 두 경우를 빠뜨리면 forever 가 never 로 나오지 않습니다. 스크립트가 파일을 직접 쓰면 채점기가 다시 돌릴 때 학생 산출물을 덮어쓰므로 표준출력으로만 내보내세요.

상태를 가진 워크로드에 줄 값을 정하고 근거를 남긴다

네임스페이스 ops-ledger 를 만들고 /root/ops-eviction/ledger.yaml 에 Deployment ledger 를 쓰세요 — 네임스페이스 ops-ledger, 레플리카 2, 라벨 app: ledger, 이미지 nginx:1.27.3. 이 워크로드는 노드 로컬 상태가 커서 짧은 단절에는 버티는 편이 낫습니다. node.kubernetes.io/not-readynode.kubernetes.io/unreachable 두 키를 operator: Exists·effect: NoExecute 로 견디되, 두 톨러레이션의 tolerationSeconds 를 같은 값으로, 900 이상 3600 이하로 정하세요. 적용해 파드 2개가 Running 이 되게 한 뒤 /root/ops-eviction/decision.tsv 에 두 줄을 쓰세요 — tolerationSeconds<고른 값>, reason<40자 이상의 근거 한 문장>.

값을 늘리면 잠깐 끊긴 노드에서 헛되이 옮겨 다니는 일이 줄지만, 진짜로 죽은 노드에서도 그만큼 오래 서비스가 비어 있게 됩니다. 근거에는 무엇을 얻고 무엇을 잃는지가 들어가야 합니다. 파일에 적은 숫자와 클러스터에 실제로 올라간 숫자가 같아야 통과합니다.