LabHub
배우기 러닝패스 코스

CKAD — 쿠버네티스 애플리케이션 개발자 · 파드 퇴근 실험실 — 기존 요청과 종료 유예 · 이론

영업 종료와 주문 취소는 다르다

LabHub 에서 이어서 보기

한 줄 요약

새 손님을 받지 않는 것과 주방이 이미 받은 주문을 끝내는 것은 별개입니다. Kubernetes의 트래픽 제외와 애플리케이션의 종료 처리도 나누어 확인해야 합니다.

왜 이게 필요했나

배포 후 새 버전은 잘 열리는데, 배포 순간 결제나 파일 생성 요청 하나만 실패하는 일이 있습니다. Deployment의 교체가 끝났다는 표시에는 그 요청의 운명이 담겨 있지 않습니다. 새 Pod가 준비됐는지, 옛 Pod가 새 요청 대상에서 빠졌는지, 이미 처리하던 요청을 끝냈는지는 서로 다른 질문입니다. 세 질문을 하나의 초록불로 대신하면 짧은 상태 조회는 통과해도 긴 작업은 끊길 수 있습니다.

이번 단원은 여러 복제본을 둔 서비스 전체의 무중단을 증명하지 않습니다. Service 뒤에 Pod 하나를 두고, 그 Pod가 실제로 수락한 요청 하나의 생애를 끝까지 따라갑니다. 실험 범위를 작게 정한 이유는 숫자를 좋게 보이게 하려는 것이 아니라, 어느 연결과 어느 프로세스가 결과를 만들었는지 분명하게 식별하기 위해서입니다. HTTP 응답에는 Downward API로 주입한 Pod UID를 넣습니다. 이름은 다시 쓸 수 있지만 UID는 생성마다 바뀝니다.

어떻게 동작하나

readinessProbe는 서비스 대상으로 삼을 준비가 됐는지를 표현합니다. 그 결과가 EndpointSlice와 트래픽 처리 경로에 반영되는 과정은 별도로 진행됩니다. Pod에 삭제 요청이 들어오면 종료 중인 endpoint의 ready는 false가 됩니다. 이것을 기존 TCP 연결을 즉시 끊으라는 명령으로 읽으면 안 됩니다. 이미 서버가 받아 처리하는 요청을 언제 끝낼지는 서버의 신호 처리와 연결 동작도 결정합니다.

EndpointSlice의 conditions에는 ready, serving, terminating이 있습니다. terminating은 종료 중임을 알리고, serving은 종료 상태와 별도로 실제 준비 상태를 표현할 수 있습니다. 따라서 종료 중이라 ready가 false여도 serving이 true인 관측이 가능하지만, 항상 그 조합을 봐야 하는 것은 아닙니다. 이번 서버는 drain이 시작되면 healthz도 503을 반환하므로 뒤따르는 준비 검사에 의해 serving이 바뀔 수 있습니다. 첫 종료 endpoint와 나중 endpoint를 섞어서 하나의 고정 상태처럼 쓰지 마세요.

여기서는 publishNotReadyAddresses를 켜지 않은 일반 Service를 씁니다. 이 설정을 켜면 ready 해석에 예외가 생깁니다. 또한 모든 가용 endpoint가 종료 중일 때 serving과 terminating이 함께 true인 대상으로 라우팅할 수 있는 프록시 동작도 있습니다. 따라서 ready=false만으로 새 요청이 단 한 건도 도착하지 않는다고 보장하지 않습니다. 앱의 drain 처리와 트래픽 경로의 실제 동작을 함께 확인해야 합니다.

실험 도우미는 먼저 Service의 healthz 응답을 확인합니다. Pod Ready만 보고 곧바로 작업 요청을 보내면 Service 경로가 아직 준비되지 않은 연결 오류를 종료 효과로 오인할 수 있기 때문입니다. 이어 work 요청을 보내고 서버의 active가 1임을 확인한 다음 삭제합니다. 관측 순서는 연결 준비, 서버 수락, 삭제 요청, 응답 또는 단절입니다. 이 중 수락 증거가 없다면 '처리하던 요청이 끊겼다'는 결론도 낼 수 없습니다.

현장에서 만나는 모습

API 게이트웨이, 서비스 메시, 외부 로드밸런서가 있으면 트래픽을 고르는 계층이 더 많습니다. 단일 k3s Service에서 얻은 결과를 다른 경로의 보증으로 옮길 수 없습니다. 실제 운영에서는 어떤 계층이 새 연결을 멈추고, 어떤 계층이 기존 연결을 유지하는지 따로 관측해야 합니다. 성공률뿐 아니라 요청 지연과 응답 본문, 재시도가 만든 부작용도 함께 봅니다.

실패를 세는 방법도 중요합니다. curl의 전송 성공과 HTTP 200은 같은 조건이 아닙니다. HTTP 500을 받았는데 연결은 정상이라 성공으로 셀 수 있습니다. 반대로 연결 준비 시간 초과를 앱의 강제 종료라고 단정할 수도 없습니다. 이번 실습은 정상 완료 본문, 연결 단절 예외, Pod 종료 코드를 각각 남겨 서로 다른 실패를 구분합니다.

다음 실습에서 할 것

즉시 종료하는 서버와 기존 요청을 기다리는 서버를 비교하고, 첫 terminating endpoint의 conditions를 기록합니다. ready=false라는 값 하나가 이미 수락한 요청의 결과를 대신하지 않는다는 것을 응답과 UID로 설명해 보세요.

공식 문서: [Pod와 endpoint의 종료 관측](https://kubernetes.io/docs/tutorials/services/pods-and-endpoint-termination-flow/), [EndpointSlice](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/).