정상 응답을 뱉는 파드가 트래픽을 죽이는 동안
롤링·블루그린 배포에서 헬스체크 통과와 실제 트래픽 수용 준비는 전혀 다른 문제다. liveness·readiness를 DB, 캐시, 외부 API 같은 의존성 상태에 연결하지 않으면 정상처럼 보이는 파드가 장애를 조용히 확산시킨다. 배포 게이트를 무엇으로 삼고 언제 롤백을 선언할지 판단 기준을 정리한다.
DevInsight에서 발행해요
readiness 프로브가 200을 돌려주는 파드가 트래픽을 받은 뒤 500을 쏟아내는 장면은 생각보다 흔하다. 프로브는 통과했고 파드는 Running이고 레플리카 수도 정상이다. 대시보드에는 아무 문제가 없는데 사용자만 에러를 본다. 이 간극은 헬스체크를 "프로세스가 살아 있는가" 수준으로 정의했을 때 벌어진다.
쿠버네티스의 readiness와 liveness는 이름은 비슷해도 하는 일이 전혀 다르다. readiness는 서비스 엔드포인트에 이 파드를 넣을지 말지를 결정하고, liveness는 컨테이너를 죽여서 다시 띄울지를 결정한다. 여기서 첫 번째 함정이 나온다. readiness가 실패하면 트래픽에서 빠질 뿐 재시작은 일어나지 않는다. 반대로 liveness가 실패하면 재시작은 일어나지만 트래픽에서 빠지는 시점은 보장되지 않는다. 두 프로브를 같은 핸들러에 연결하는 순간, 이 구분은 사라진다.
살아 있다와 준비됐다는 다른 질문이다
가장 흔한 안티패턴은 /health 하나를 만들어 liveness와 readiness 양쪽에 붙이는 것이다. 이 핸들러는 보통 "HTTP 서버가 응답한다"만 확인한다. 프로세스가 요청을 받을 수 있으면 200을 준다. 의존성이 전부 죽어 있어도 200이다. 파드는 준비된 것으로 취급되고, 서비스는 죽은 의존성을 향해 트래픽을 밀어 넣는다.
여기서 "그러면 readiness에 DB 핑을 넣으면 되겠네"라는 반사적 결론이 나온다. 절반만 맞다. readiness에 의존성 확인을 넣으면 준비되지 않은 파드는 트래픽에서 빠진다. 문제는 그 다음이다. DB가 잠깐 느려지면 모든 파드의 readiness가 동시에 실패하고, 엔드포인트가 전부 비어서 서비스 전체가 죽는다. 재시작보다 나쁜 결과다. 파드 하나가 잘못된 게 아니라 전체가 동시에 빠지기 때문이다.
의존성 확인을 넣을 거라면 두 가지를 함께 설계해야 한다. 확인에 타임아웃을 짧게 걸고(1~2초), 실패를 즉시 반영하지 말고 연속 실패 횟수를 요구한다. failureThreshold를 3으로 두면 일시적 지연은 흡수된다. 그리고 readiness가 실패해도 liveness는 절대 같은 조건을 보면 안 된다. liveness는 프로세스 자체가 교착에 빠졌는지를 보는 용도로 남겨두는 편이 안전하다.
liveness는 문제를 고치지 않는다
liveness 실패는 재시작을 유발한다. 재시작은 상태를 초기화할 뿐 원인을 제거하지 않는다. 외부 API가 죽어서 헬스체크가 실패하는 상황에 liveness를 연결하면, 파드는 죽었다 살아나기를 반복한다. 백오프가 붙어 재시작 간격이 벌어지고, CrashLoopBackOff로 넘어가면서 배포는 영원히 끝나지 않는다. 롤링 업데이트에서 새 파드가 Ready가 되지 못하면 maxUnavailable에 따라 기존 파드가 남아 있기를 기대하지만, 잘못 설정하면 가용 용량이 0이 되는 순간도 온다.
liveness에 넣을 만한 것은 프로세스 내부의 교착, 이벤트 루프 정지, 예상 못 한 런타임 상태 정도다. 자신의 프로세스만으로 판단할 수 있는 신호에 한정한다. DB, 캐시, 외부 API는 여기서 제외하는 게 맞다. 판단이 애매하면 이렇게 기준을 잡으면 된다. "이 조건이 실패했을 때 재시작이 실제로 문제를 해결하는가?" 해결하지 못한다면 liveness가 아니다.
배포 게이트를 무엇으로 삼을 것인가
롤링 배포든 블루그린이든 핵심은 새 버전에 트래픽을 넘기기 전 무엇을 확인할 것인가다. readiness 통과는 최소 조건일 뿐 게이트가 아니다. 다음 항목을 후보로 두고 팀 상황에 맞게 고른다.
- 새 파드가 실제 요청을 처리한 뒤의 에러율 (5xx 비율, 5분 윈도)
- 지연 시간 분포의 p95, p99 (평균은 숨긴다)
- 핵심 의존성에 대한 커넥션 성공률
- 새 버전에만 있는 기능의 스모크 테스트 결과
이 중에서 나는 에러율과 p99 지연을 기본 게이트로 두는 쪽을 선호한다. 이유는 단순하다. 이 둘은 사용자 경험과 직결되고, 자동 판정이 쉽다. 스모크 테스트는 커버리지가 좁아서 통과해도 안심할 수 없다.
블루그린은 이 게이트를 걸기에 구조적으로 유리하다. 구 버전 환경이 그대로 살아 있으니 게이트가 실패하면 트래픽을 되돌리기만 하면 된다. 대신 두 환경을 동시에 유지하는 비용과, DB 스키마처럼 공유 자원을 바꿨을 때의 되돌림 가능성을 미리 따져야 한다. 스키마를 확장만 했다면 롤백이 쉽지만, 컬럼을 지우거나 이름을 바꿨다면 구 버전이 깨진다. 이 경우 롤백은 사실상 불가능하고, 전진 수정만 남는다.
롤링 배포는 반대로 되돌림이 느리다. 새 파드가 이미 일부 트래픽을 받았고, 이전 ReplicaSet으로 되돌려도 그 사이 쌓인 데이터나 부작용은 남는다. 그래서 롤링에서는 게이트를 더 보수적으로 잡는다. 새 파드 비율을 낮게 시작하고, 관찰 시간을 길게 가져간다.
롤백을 선언하는 시점
가장 어려운 결정은 "언제 되돌릴 것인가"다. 여기서 흔한 실수는 원인을 파악한 뒤에 롤백하겠다고 버티는 것이다. 원인 분석은 롤백 이후에 해도 된다. 판단 기준을 배포 전에 숫자로 정해두고, 그 선을 넘으면 논의 없이 되돌린다. 예를 들어 "5xx가 1%를 넘고 2분 이상 지속되면 롤백"처럼 조건을 명문화한다.
되돌릴 수 없는 변경을 함께 배포했다면 사정이 달라진다. 이 경우 판단 기준을 롤백이 아니라 전진 수정으로 잡고, 영향 범위를 좁히는 쪽에 자원을 쓴다. 어느 쪽이든 배포 중에 기준을 새로 만드는 것은 늦다. 배포 전에 정해둔 숫자만 신뢰할 수 있다.
검증은 배포 파이프라인 밖에서도 가능하다. 새 버전 파드를 하나만 띄워 놓고 실제 트래픽의 일부를 흘려보내며 readiness가 아닌 실제 응답 품질을 측정하는 방식이다. 이 방법은 의존성이 느릴 때 readiness가 어떻게 반응하는지, 그리고 그것이 전체 가용성에 어떤 영향을 주는지 관찰할 수 있다는 점에서 유용하다. 프로브 설정을 바꿀 때는 이렇게 실제 트래픽 조건에서 먼저 확인하고 적용하는 편이 안전하다.
결국 헬스체크 설계에서 먼저 정할 것은 프로브의 경로가 아니라 실패의 책임 소재다. readiness 실패는 트래픽에서 빼는 것으로 끝나야 하고, liveness 실패는 재시작이 실제로 해결하는 문제에만 걸어야 한다. 다음 배포부터라도 두 프로브를 같은 핸들러에서 분리하고, 롤백 조건을 배포 전에 숫자로 적어두는 것부터 시작하면 된다.
댓글
댓글을 읽어오는 중입니다.
같이 읽으면 좋은 글
방금 읽은 주제와 이어지는 글을 골랐습니다.
200 OK는 무중단 배포를 지켜주지 않는다
무중단 배포가 실패하는 지점은 서버가 다운된 순간이 아니라, 헬스체크가 형식적인 200을 돌려주는 순간이다. 준비가 덜 된 신버전으로 트래픽이 몰리고 장애는 배포가 끝난 뒤에야 폭발한다. readiness와 liveness를 DB·Redis·메시지 큐 같은 실제 의존성 상태와 연결하는 방법, 탐지 지연을 줄이는 체크 주기, 그리고 어떤 지표에서 롤백을 눌러야 하는지 기준을 함께 정리한다.
캐시 적중률 98%인데 배포판은 왜 자꾸 구버전일까
의존성 캐시와 빌드 캐시, 매트릭스 전략은 CI 실행 시간을 반으로 줄이지만 무효화 기준을 잘못 잡으면 오래된 패키지가 그대로 빌드를 통과시키는 예상 밖의 사고로 돌아온다. 이 글은 캐시가 실제로 빌드를 깨뜨리는 구체적인 상황들을 짚고, 잠금 파일·해시 입력·매트릭스 키 설계에서 무효화 시점을 정확히 읽는 원칙을 정리해 캐시를 안전하게 쓰는 기준을 제시한다.
이미지를 얇게 만들수록 빌드가 비대해지는 역설
Next.js/Node.js 컨테이너를 multi-stage 빌드와 standalone 출력으로 최적화하다 보면 용량은 80% 줄었는데 빌드 시간은 되레 늘고, 캐시 무효화로 매 배포마다 의존성을 새로 내려받는 경우가 생긴다. 이 글은 무엇을 줄여야 하고 무엇은 절대 건드리면 안 되는지를 가르는 기준을 결정 메모 형식으로 정리하고, 최적화가 역효과를 내는 지점을 짚는다.
이전 글
eslintrc를 버리고 flat config로 갈아탈 때 조용히 무너지는 지점들
DevInsight Digest
새 글이 쌓이면, 피드에서 바로 이어 읽으세요.
과장된 알림 대신 발행한 글 전체를 RSS로 제공합니다.