| 프로브 | 실패했을 때 | 목적 |
|---|---|---|
livenessProbe |
컨테이너를 재시작한다 | 멎은 프로세스 복구 |
readinessProbe |
서비스 엔드포인트에서 뺀다 | 준비되지 않은 파드로 트래픽이 가지 않게 |
startupProbe |
컨테이너를 재시작한다. 성공할 때까지 나머지 둘을 보류한다 | 기동이 느린 애플리케이션 보호 |
흐름은 이렇다. 파드가 뜨면 readiness 가 성공할 때까지 서비스에 붙지 않는다. 운영 중 readiness 가 실패하면 트래픽만 끊기고 프로세스는 그대로 남는다. liveness 가 실패하면 컨테이너가 재시작된다.
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
두 프로브가 같은 엔드포인트를 보게 두지 않는다. readiness 에는 데이터베이스나 메시지 브로커 같은 의존성 확인을 넣고, liveness 에는 프로세스가 응답하는지만 본다. 둘을 같게 두면 데이터베이스가 잠깐 끊겼을 때 트래픽이 빠지는 데서 끝나지 않고 컨테이너가 계속 재시작되는 상태로 간다.
liveness 는 느슨하게 잡는다. 주기를 짧게 하고 실패 임계값을 낮게 두면 일시적인 지연에도 재시작이 걸린다. 부하가 몰릴 때 재시작이 연쇄되면 장애가 커진다.
기동이 오래 걸리면 startupProbe 를 쓴다. liveness 의 initialDelaySeconds 를 크게 잡는 것으로 대신하면, 기동 후 정상 운영 중에도 그 지연이 적용되어 진짜 장애 감지가 늦어진다.
tcpSocket 은 포트에 TCP 연결이 되는지만 본다. HTTP 엔드포인트가 없는 제품(데이터베이스·캐시·브로커)에 쓸 수 있어 편하지만, 스레드가 교착 상태에 빠져도 포트는 열려 있으므로 liveness 로는 약하다. readiness 에 쓰고 liveness 는 exec 나 httpGet 으로 두는 편이 낫다.
readinessProbe:
tcpSocket:
port: 3306
periodSeconds: 5
포트는 이름으로도 지정할 수 있다. 컨테이너 포트에 name: http 를 주고 프로브에서 port: http 로 쓴다.
파드를 지우면 두 가지가 동시에 일어난다. 엔드포인트에서 파드가 빠지는 것과, 컨테이너에 종료 신호가 가는 것이다. 엔드포인트 제거가 각 노드의 kube-proxy 까지 퍼지는 데 시간이 걸리므로, 이미 종료를 시작한 파드로 요청이 잠시 더 들어온다. 배포할 때마다 몇 건씩 오류가 나는 원인이 대개 이것이다.
파드 삭제 요청
├─ 엔드포인트에서 제거 → 각 노드로 전파(시차 있음)
└─ preStop 훅 실행 → SIGTERM → terminationGracePeriodSeconds 대기 → SIGKILL
preStop 에 짧은 대기를 넣어 전파 시간을 벌어 준다.
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 45
preStop 이 도는 시간도 유예 시간에 포함되므로, 유예 시간은 대기 시간에 애플리케이션이 실제로 종료하는 데 걸리는 시간을 더해 잡는다.
애플리케이션이 SIGTERM 을 받아 스스로 정리하는 것이 가장 좋다. 그러려면 그 프로세스가 PID 1 이어야 한다. 셸 형식으로 실행해 /bin/sh 가 PID 1 이 되면 신호가 전달되지 않아 유예 시간을 다 쓰고 강제 종료된다.
postStart 는 컨테이너 진입점과 동시에 실행되므로 순서가 보장되지 않는다. 초기화 작업은 init 컨테이너에 두고, postStart 에는 가벼운 것만 둔다. 이 훅이 실패하면 컨테이너가 재시작된다.