배포할 때마다 502 가 조금씩 난다
무중단 배포를 설정했는데 배포할 때마다 502 가 몇십 건씩 나요. 설정은 다 맞는데도요. 서버가 내려가는 순간과 트래픽이 끊기는 순간 사이의 빈틈을 메우는 법을 정리했어요.
무중단 배포를 설정해 뒀어요. 새 버전을 띄우고 헬스체크가 통과하면 옛 버전을 내리는 순서요.
그런데 배포할 때마다 502 가 몇십 건씩 나요. 많지는 않아서 오래 방치했는데, 배포 빈도가 늘면서 무시할 수 없는 수가 됐어요.
로그를 보면 옛 버전이 내려가는 시점에 몰려 있어요. 이미 종료된 프로세스로 요청이 갔다는 뜻이에요.
"무중단"이라고 설정했는데 왜 끊기는지가 이 편의 질문이에요. 답은 누가 언제 무엇을 알게 되는가의 타이밍에 있어요.
설정이 틀린 게 아니에요. 설정들 사이에 생기는 시간차를 아무도 메우지 않은 겁니다.
왜 그런가
프로세스가 종료 신호를 받는 시점과, 로드 밸런서가 그 서버를 목록에서 빼는 시점이 다르기 때문이에요.
보통 이런 순서로 일어나요. 오케스트레이터가 파드에 종료 신호를 보내요. 동시에 그 파드를 서비스 목록에서 빼라는 지시가 로드 밸런서 쪽으로 전파돼요. 그런데 이 전파는 즉시가 아니에요. 여러 노드의 규칙을 갱신해야 하고, 그 사이 짧은 시간이 걸려요.
문제는 애플리케이션이 신호를 받자마자 종료하는 경우예요. 로드 밸런서는 아직 그 서버가 살아 있다고 알고 트래픽을 보내는데, 받을 프로세스가 없어요. 그게 502 예요.
여기서 핵심은 삭제 지시와 실제 반영 사이의 시간차예요. 이 시간차 동안에도 요청이 오니까, 애플리케이션은 신호를 받고도 잠시 더 살아서 받아 줘야 해요.
또 하나가 진행 중인 요청이에요. 종료 신호를 받은 시점에 처리 중이던 요청이 있어요. 그것들이 끝날 때까지 기다려야 하는데, 기다리지 않고 바로 죽으면 그 요청들도 502 가 돼요.
그래서 필요한 게 둘이에요. 신호를 받은 뒤 잠시 대기하는 것, 그리고 진행 중인 요청을 끝까지 처리하는 것. 둘 다 없으면 배포마다 조금씩 끊깁니다.
코드로 보면
신호를 받고도 잠시 더 산다:
SIGTERM 수신 → 즉시 종료
로드 밸런서는 아직 이 서버를 살아 있다고 안다 → 502
· 1) 종료 훅으로 전파 시간을 벌어 준다
preStop: sleep 5~15초
그동안에도 새 요청을 정상 처리한다
· 2) 그다음 우아한 종료 — 진행 중 요청을 끝낸다
SIGTERM → 새 연결 거부, 진행 중 요청 완료 대기 → 종료
terminationGracePeriodSeconds > preStop + 최대 요청 시간
· 3) 준비됨 검사를 먼저 실패시키는 방법도 있다
SIGTERM 수신 → /healthz/ready 를 즉시 실패로 전환
→ 로드 밸런서가 빼는 동안 살아 있는 요청은 계속 처리
유예 시간이 preStop 보다 짧으면 강제 종료된다✕ 문제 지점 · ✓ 해법 · 그대로 돌려 보기 전에 자기 환경에 맞게 고쳐 주세요.
실전에서는
순서를 정확히 잡는 게 핵심이에요.
가장 흔한 실수가 유예 시간 설정을 서로 안 맞추는 거예요. 종료 훅에서 15초를 자는데 전체 유예가 10초면, 자다가 강제 종료돼요. 진행 중 요청은 그대로 끊기고요. 전체 유예 시간은 훅 대기 시간과 최대 요청 처리 시간을 합친 것보다 커야 해요.
두 번째는 애플리케이션이 종료 신호를 아예 안 다루는 경우예요. 프레임워크가 기본으로 우아한 종료를 지원하는지 확인해야 해요. 지원하더라도 진행 중 요청 대기 시간이 기본값으로 짧게 잡혀 있을 수 있어요.
세 번째는 신호가 애플리케이션까지 안 가는 경우예요. 셸 스크립트로 감싸서 실행하면 셸이 신호를 받고 자식 프로세스에 전달하지 않을 수 있어요. 컨테이너 안에서 프로세스가 1번이 아니면 이런 문제가 생깁니다.
새 버전 쪽에도 대칭적인 문제가 있어요. 준비됨 판정이 너무 빨리 나면, 아직 캐시도 안 채워지고 커넥션 풀도 준비 안 된 서버로 트래픽이 가요. 그러면 느리고, 앞 편에서 본 대로 다시 제외될 수도 있어요. 기동 유예를 충분히 주는 게 필요합니다.
그리고 장수 연결은 별도 문제예요. 웹소켓이나 서버 전송 이벤트처럼 오래 유지되는 연결은 "진행 중 요청 완료"를 기다릴 수 없어요. 클라이언트가 재연결하도록 설계해야 하고, 한꺼번에 끊기지 않게 흩어야 합니다.
배포 속도도 봐야 해요. 한 번에 너무 많은 파드를 교체하면 남은 용량이 부족해져요. 앞 편의 헬스체크 연쇄 제외와 만나면 배포 자체가 장애가 됩니다.
처방
- 종료 훅으로 대기 시간을 두세요. 로드 밸런서 전파가 끝날 때까지 살아 있어야 합니다. 5~15초가 흔한 범위예요.
- 우아한 종료를 켜세요. 새 연결은 거부하되 진행 중 요청은 끝내고 나가야 해요.
- 유예 시간 계산을 맞추세요. 전체 유예 > 훅 대기 + 최대 요청 처리 시간. 안 맞으면 강제 종료됩니다.
- 종료 신호가 애플리케이션까지 가는지 확인하세요. 셸로 감싸면 전달이 끊길 수 있어요.
- 새 버전의 기동 유예를 충분히 주세요. 준비되기 전에 트래픽을 받으면 그 서버가 바로 나빠집니다.
- 배포 속도를 조절하세요. 한 번에 너무 많이 교체하면 남은 용량이 부족해져요.
- 장수 연결은 재연결 전략을 따로 설계하세요. 끊길 때 한꺼번에 몰리지 않게 흩어야 합니다.
- 배포 중 5xx 를 지표로 보세요. "조금씩 난다"를 숫자로 만들지 않으면 고쳐졌는지도 모릅니다.
지금 확인해 보세요
배포 중 무엇이 끊기는가:
· 배포 시각과 5xx 를 겹쳐 본다
sum(rate(http_responses_total{code=~'5..'}[1m]))
배포 시각에 봉우리가 생기면 이 문제다
· 상태 코드별로 나눈다 — 원인이 다르다
502 → 뒤쪽이 없거나 끊겼다 (종료 타이밍)
504 → 응답이 늦다 (기동 직후 느림)
· 설정 정합성 — 이 부등식이 성립해야 한다
terminationGracePeriod > preStop + 최대 요청 처리 시간
· 애플리케이션이 종료 신호를 실제로 받는가
종료 로그가 찍히는지 확인한다
안 찍히면 신호가 프로세스까지 안 간 것이다
· 워커·배치도 같이 확인한다
웹 서버만 설정하고 워커는 그냥 죽이는 경우가 흔하다핵심 한 줄무중단은 설정이 아니라 타이밍이다. 신호를 받고도 잠시 더 살아라.