LabHub
배우기 러닝패스 코스

내가 부순다 — 가설을 먼저 쓰는 카오스 실험실 · 하나를 죽이면 몇 명이 아픈가 · 이론

하나를 죽이면 몇 명이 아픈가

LabHub 에서 이어서 보기

한 줄 요약

Pod 하나를 죽였을 때 몇 명이 아픈지는 복제본 수보다 종료 절차가 더 크게 좌우한다.

왜 이게 필요했나

"복제본을 늘렸으니 무중단입니다" 라는 문장은 절반만 맞다. 복제본이 셋이어도
배포할 때마다 실패가 몇 건씩 새어 나가는 서비스가 흔하다. 늘 몇 건뿐이라
아무도 신고하지 않고, 그래서 몇 년째 그대로다. 이 새는 요청이 어디에서
나오는지 알려면 Pod 가 죽는 과정을 한 단계씩 봐야 한다.

어떻게 동작하나

Pod 삭제 요청이 들어오면 두 가지 일이 동시에 시작된다. 하나는 컨트롤
플레인이 그 Pod 를 Service 의 EndpointSlice 에서 빼는 일이고, 다른 하나는
kubelet 이 컨테이너를 멈추는 일이다.
[Pod 생명주기 문서](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/)는
이 둘이 순서가 아니라 병렬이라고 분명히 적고 있다. 종료 중인 엔드포인트의
ready 상태는 항상 false 가 되어 부하 분산 대상에서 빠지지만, 그 사실이 노드의
iptables 규칙까지 퍼지는 데는 시간이 걸린다.

문제는 여기서 생긴다. 규칙이 아직 안 퍼진 그 짧은 동안 새 연결이 죽어 가는
Pod 로 계속 들어오는데, 그 Pod 의 프로세스는 이미 TERM 을 받고 소켓을 닫아
버렸다. 요청은 연결 거부로 실패한다. 애플리케이션은 정상이고 쿠버네티스도
정상인데 사용자만 실패한다.

해결의 열쇠는 프로세스가 조금 더 버티게 하는 것이다.
[컨테이너 생명주기 훅](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/)의
preStop 은 컨테이너에 TERM 신호를 보내기 전에 실행되고, 이 훅이 끝나야
신호가 나간다. 훅으로 몇 초만 잡아 두면 그 사이에 엔드포인트 제거가 퍼지고,
새 연결은 살아 있는 Pod 로만 간다. 훅이 도는 동안에도 애플리케이션은 계속
서비스하고 있다는 점이 핵심이다.

잘못된 기대                     실제 순서  1. 엔드포인트에서 뺀다          엔드포인트 제거 ─┐ 동시에 시작  2. 그다음 TERM 을 보낸다        TERM 전송 ──────┘                                  preStop 이 있으면 TERM 만 뒤로 밀린다

시간 예산은 terminationGracePeriodSeconds 가 정한다. 기본값은 30초이고,
이 시간이 지나도 컨테이너가 살아 있으면 런타임이 KILL 로 끝낸다. 그리고
유예 시간의 카운트다운은 preStop 훅이 시작되기 전에 이미 시작된다. 훅에
5초를 쓰기로 했다면 유예 시간은 그보다 넉넉해야 한다.

또 하나 구분해 둘 것이 있다.
[중단(disruption) 문서](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/)는
중단을 자발적인 것과 비자발적인 것으로 나눈다. PodDisruptionBudget 은
자발적 중단(노드 비우기, 클러스터 업그레이드)에만 개입한다. 우리가 손으로
kubectl delete pod 를 하거나 프로세스가 죽는 것은 PDB 가 막지 못한다.
"PDB 를 걸었으니 안전합니다" 가 위험한 이유다.

연결을 재사용하는 클라이언트라면 이야기가 한 겹 더 복잡해진다. 엔드포인트
목록은 새 연결이 어디로 갈지를 정할 뿐, 이미 열려 있는 연결을 옮겨 주지
않는다. keep-alive 로 연결을 붙들고 있는 클라이언트는 엔드포인트에서 빠진
Pod 와 계속 대화하다가 그 Pod 가 죽는 순간 실패한다. 그래서 종료 처리를
제대로 하려면 preStop 으로 시간을 버는 것에 더해, 애플리케이션이 TERM 을 받고
열린 연결을 정리하며 내려가는 코드까지 있어야 한다. 이 실습의 부하
생성기가 요청마다 새 연결을 여는 것은 이 변수를 일부러 빼 두기 위해서다.

현장에서 만나는 모습

노드 한 대를 비우는 작업에서 이 셋이 한꺼번에 드러난다. PDB 가 없으면 같은
서비스의 Pod 가 동시에 다 빠져 완전한 중단이 되고, PDB 가 있어도 종료 처리가
없으면 Pod 가 빠질 때마다 실패가 새어 나간다. 복제본 수는 얼마나 크게
아픈지를 정하고, 종료 절차는 아예 아픈지 아닌지를 정한다.

배포 전략도 같은 축에서 읽는다. 롤링 업데이트는 한 번에 몇 개까지 빠져도
되는지(maxUnavailable)와 잠깐 몇 개까지 더 띄워도 되는지(maxSurge)로
폭발 반경을 조절하는 장치다. 반대로 Recreate 는 옛 Pod 를 모두 내린 뒤에
새 Pod 를 올리므로 중단이 반드시 생긴다. 실험 환경에서 Recreate 를 쓰는 것은
원인을 하나만 남기기 위한 통제 조건이지 운영 권장값이 아니다. 옛 Pod 가
남아 있으면 새 설정의 효과가 옛 Pod 의 응답에 가려 숫자가 뭉개진다.

다음 퀴즈에서 확인할 것

엔드포인트 제거와 TERM 전송의 순서, preStop 훅이 무엇을 뒤로 미루는지,
PodDisruptionBudget 이 어떤 중단에만 적용되는지 확인한다.