헬스체크와 무중단 배포, 프로브 제대로 쓰기

2026. 10. 19. 13:00·IT
728x90
반응형

서비스를 배포할 때 진행 중이던 요청이 끊기거나, 새로 뜬 인스턴스에 트래픽이 너무 일찍 들어와 오류가 나는 경우가 있다. 둘 다 헬스체크와 종료 처리를 제대로 하지 않아서 생긴다. 이 글은 무중단 배포를 실제로 무중단으로 만드는 두 가지 장치를 다룬다.

살아 있는 것과 받을 준비가 된 것은 다르다

쿠버네티스가 프로브를 셋으로 나눈 이유가 여기 있다.

Liveness(활성)는 "이 프로세스가 살아 있나"를 본다. 실패하면 컨테이너를 재시작한다. 데드락에 빠져 응답하지 못하는 상태를 복구하기 위한 것이다.

Readiness(준비)는 "지금 트래픽을 받아도 되나"를 본다. 실패하면 재시작하지 않고 서비스 엔드포인트에서 뺀다. 의존하는 데이터베이스가 잠시 끊겼을 때, 재시작하는 대신 트래픽만 빼는 것이 맞다.

Startup(시작)은 "기동이 끝났나"를 본다. 기동이 오래 걸리는 애플리케이션에서 liveness가 성급하게 재시작시키는 것을 막는다.

여기서 가장 흔한 실수는 liveness와 readiness를 같은 엔드포인트로 두는 것이다. 그러면 데이터베이스가 잠깐 느려졌을 때 모든 파드가 동시에 재시작된다. 장애가 훨씬 커진다.

각 프로브가 무엇을 확인해야 하는가

기준을 나누면 이렇다.

Liveness는 외부 의존성을 확인하지 않는다. 프로세스 자체가 응답할 수 있는지만 본다. 단순히 200을 돌려주는 엔드포인트면 충분하다. 데이터베이스를 확인하면 안 된다. 데이터베이스 장애가 애플리케이션 재시작으로 번진다.

@app.get("/healthz")   # liveness
def healthz():
    return {"status": "ok"}

Readiness는 요청을 처리할 수 있는지 본다. 여기서는 필수 의존성을 확인해도 된다. 다만 확인 대상을 최소로 하고 타임아웃을 짧게 준다.

@app.get("/readyz")    # readiness
def readyz():
    if not db.is_connected(timeout=1):
        raise HTTPException(503)
    return {"status": "ready"}

확인하는 의존성이 많을수록 준비 상태가 불안정해진다. 없으면 요청을 처리할 수 없는 것만 넣는다. 부가 기능용 외부 API까지 확인하면, 그 API가 흔들릴 때 서비스 전체가 트래픽에서 빠진다.

종료할 때 순서가 중요하다

배포로 파드가 내려갈 때 무슨 일이 일어나는지 보자. 쿠버네티스는 두 가지를 동시에 한다. 엔드포인트에서 파드를 빼고, 컨테이너에 SIGTERM을 보낸다.

문제는 엔드포인트 제거가 즉시 반영되지 않는다는 것이다. 각 노드의 프록시 규칙과 로드 밸런서에 전파되는 데 시간이 걸린다. 그 사이에 이미 종료를 시작한 파드로 요청이 들어온다.

그래서 SIGTERM을 받자마자 종료하면 안 된다. 올바른 순서는 이렇다.

preStop 훅으로 대기를 넣는 방법이 흔하다.

lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 30

terminationGracePeriodSeconds는 SIGTERM 이후 SIGKILL까지의 시간이다. 진행 중인 요청 중 가장 긴 것보다 넉넉해야 한다. 이 값이 짧으면 처리 중이던 요청이 강제로 끊긴다.

애플리케이션 코드에서 할 일

프레임워크가 자동으로 해주지 않는 부분이 있다.

시그널 핸들러를 등록한다. 컨테이너에서 프로세스가 PID 1로 뜨면 기본 시그널 처리가 없어서 SIGTERM이 무시될 수 있다. 셸 스크립트를 거쳐 실행하면 시그널이 자식에게 전달되지 않는 문제도 흔하다. exec 형식으로 실행하거나 init 프로세스를 쓴다.

백그라운드 작업을 정리한다. 큐 컨슈머는 새 메시지 수신을 멈추고 처리 중인 것만 끝낸다. 스케줄러는 다음 실행을 취소한다. 이걸 안 하면 종료 중에 새 작업을 집어 들었다가 중간에 죽는다.

커넥션 풀을 닫는다. 데이터베이스 커넥션을 정리하지 않으면 서버 쪽에 유휴 커넥션이 쌓인다. 배포를 반복하면 커넥션 한도에 걸린다.

헬스체크가 거짓말을 하는 경우

마지막으로 흔한 함정 하나를 짚고 싶다. 헬스체크가 항상 200을 돌려주도록 만들어 두면 장애를 감지하지 못한다.

/healthz가 무조건 {"status":"ok"}를 반환하면, 애플리케이션이 실제로는 모든 요청에 500을 내고 있어도 프로브는 통과한다. 로드 밸런서는 정상으로 보고 트래픽을 계속 보낸다.

반대로 너무 엄격해도 문제다. 모든 의존성을 확인하는 헬스체크는 연쇄 장애를 만든다. 하위 서비스 하나가 흔들리면 상위 서비스가 전부 준비 실패가 되고, 그 위가 또 실패한다.

균형점은 이렇다. liveness는 느슨하게, readiness는 필수 의존성만, 실제 기능 확인은 별도의 합성 모니터링으로. 주기적으로 실제 API를 호출해 보는 외부 모니터링이 헬스체크보다 장애를 잘 잡는다. 헬스체크는 트래픽 라우팅을 위한 장치이지 서비스 상태 감시 도구가 아니다. 이 둘을 하나로 만들려다 양쪽 다 못 하게 되는 경우가 많다.

참고 자료

  • Configure Liveness, Readiness and Startup Probes — Kubernetes — 세 프로브의 차이와 설정 항목
  • Pod Lifecycle — Kubernetes — 종료 절차와 terminationGracePeriodSeconds
  • Container Lifecycle Hooks — Kubernetes — preStop 훅의 동작
728x90
반응형

'IT' 카테고리의 다른 글

API 버저닝, 언제 올리고 언제 안 올리나  (0) 2026.10.21
타임아웃과 재시도, 서킷 브레이커까지  (0) 2026.10.20
페이지네이션, 오프셋 대신 커서를 쓸 때  (0) 2026.10.18
CORS 오류가 나는 진짜 이유와 해결법  (0) 2026.10.17
JWT와 세션, 무엇을 골라야 하나  (0) 2026.10.16
'IT' 카테고리의 다른 글
  • API 버저닝, 언제 올리고 언제 안 올리나
  • 타임아웃과 재시도, 서킷 브레이커까지
  • 페이지네이션, 오프셋 대신 커서를 쓸 때
  • CORS 오류가 나는 진짜 이유와 해결법
밍글링글링
밍글링글링
mingling - 밍글링, 밍글밍글링. 코드와 어우러지다. IT/ 프로그래밍/소스
    반응형
    250x250
  • 밍글링글링
    mingling
    밍글링글링
  • 전체
    오늘
    어제
    • 밍글링글링 (463)
      • Flutter (2)
      • 일상생활 (8)
        • 리뷰 (1)
        • 생활정보 (4)
        • 맛집 (0)
        • 여행 (0)
        • 모든정보 (3)
      • JAVA (126)
        • 개념 (6)
        • 예제 (115)
        • Exception (2)
      • C (1)
        • C (1)
        • C++ (0)
        • C# (0)
      • JS (29)
        • JavaScript (18)
        • JQuery (5)
        • AJax (0)
        • NODE.JS (6)
        • Angular.JS 2.0 (0)
      • WEB (87)
        • HTML (6)
        • CSS (61)
        • JSP (20)
        • JSTL (0)
      • FrameWork (8)
        • Spring (8)
        • BootStrap (0)
        • MyBATIS (0)
        • JUnit (0)
      • 외부 라이브러리 (5)
      • 공유 소스 관리 (5)
        • Git (5)
        • SVN (0)
      • 빅데이터 프로그래밍 (37)
        • Python (37)
        • R Programming (0)
      • DB (7)
        • ORACLE (0)
        • MySql (6)
      • Development Tools (7)
        • StarUML (0)
        • eXERD (0)
        • Eclipse (4)
      • SKILL (6)
        • Migration (0)
        • Security (6)
      • MicroSoft (0)
        • Excel (0)
        • Word (0)
      • Android (0)
      • Server (21)
        • Ubuntu (5)
        • Linux (15)
      • IOS (0)
      • XML (0)
      • 미디어 (0)
      • 공지사항 (3)
      • NETWORK (1)
      • 게임 (4)
        • 피파 (1)
        • 리니지M (0)
        • 배틀그라운드 (1)
        • 듀랑고 (2)
      • 세상 이슈 (6)
      • 일렉트론 (0)
      • 대회 소식 (4)
      • 업무 (2)
      • Express, Vue (6)
      • docker (11)
      • svelte (3)
      • 블록체인 (1)
      • IT (66)
      • Rust (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    VS Code 팁
    React Compiler
    프런트엔드
    css table
    nginx
    AI 반도체
    nginx ssl 설정
    servlet class
    자바 배열
    Node
    클론코딩
    ubuntu
    css perspective
    ssl 인증서 발급
    자바 클래스
    gitlab 설치
    SSL 인증서
    티스토리 자동화
    자바 exception
    Extension Bisect
    vscode
    css float
    자바 for문
    자바 객체 지향
    svelte
    AI 코딩 에이전트
    docker
    proxy pass
    Java Array
    리눅스 설치
    jsp parameter
    Rust lang
    자바 생성자
    css transition
    vue 설치
    브라우저 자동화
    mysql db
    lang rust
    java casting
    오류
    nginx ssl 적용
    spring java
    러스트
    jsp include
    vue cli
    제미나이
    API 설계
    rust linux
    css tb
    에디터 팁
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
밍글링글링
헬스체크와 무중단 배포, 프로브 제대로 쓰기
상단으로

티스토리툴바