배포 직후 파드는 Running인데 실제 요청은 503으로 실패합니다. 앱이 준비되기 전에 Service가 트래픽을 보내거나, 죽은 프로세스를 kubelet이 감지하지 못하면 장애가 길어집니다. Liveness, Readiness, Startup Probe는 Kubernetes가 애플리케이션 상태를 올바르게 판단하게 해줍니다.
신규 버전을 배포했습니다. kubectl rollout status가 완료됐고 파드는 Running입니다. 그런데 배포 직후 5분 동안 고객들이 503 에러를 받았습니다. 무슨 일이 벌어진 걸까요? 앱이 시작되는 데 30초가 필요한데, Kubernetes는 컨테이너가 Running 상태가 되는 즉시 트래픽을 보냈습니다. 아직 준비가 안 된 파드가 요청을 받아 에러를 반환한 겁니다.
이 문제의 해답이 Probe입니다. Kubernetes는 세 가지 Probe로 컨테이너의 상태를 주기적으로 확인합니다. Liveness Probe는 "이 컨테이너가 살아있는가? 아니면 재시작해야 하는가"를 판단하고, Readiness Probe는 "이 컨테이너가 트래픽을 받을 준비가 됐는가"를 판단합니다. Startup Probe는 초기화가 오래 걸리는 앱을 위한 특별한 유예 기간입니다. 세 Probe를 올바르게 설정하면 배포 중에도 무중단 서비스가 가능합니다.
- 1Liveness Probe: 앱 장애 자동 감지 및 재시작
- 2Readiness Probe: 준비 전 트래픽 차단
- 3Startup Probe: 느린 초기화 대응
- 4HTTP, TCP, Exec 세 가지 Probe 유형
- 5Probe 파라미터 튜닝 (initialDelaySeconds, periodSeconds 등)
- 6배포 중 무중단을 위한 Probe 설계 패턴
kubectl create namespace probe-demokubectl run probe-test --image=nginx:1.25 -n probe-demo --dry-run=clientkubectl get endpoints -n probe-demo 2>/dev/null || echo 'ready'kubectl get events --sort-by=.lastTimestamp -n probe-demo 2>/dev/null | head -5 || echo 'ready'Liveness Probe: 데드락과 무한루프를 감지해 재시작
앱이 Running 상태지만 실제로는 응답을 못하는 상황이 있습니다. 데드락, 메모리 누수로 인한 GC 멈춤, 무한 루프 등이 대표적입니다. Liveness Probe는 이를 감지해 컨테이너를 재시작합니다.

# liveness-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
namespace: probe-demo
spec:
containers:
- name: app
image: registry.k8s.io/e2e-test-images/agnhost:2.40
args:
- liveness
livenessProbe:
httpGet:
path: /healthz # 이 엔드포인트가 200-399 반환하면 정상
port: 8080
httpHeaders: # 필요 시 헤더 추가
- name: Custom-Header
value: Awesome
initialDelaySeconds: 5 # 컨테이너 시작 후 5초 대기 후 첫 체크
periodSeconds: 10 # 10초마다 체크
timeoutSeconds: 3 # 3초 내 응답 없으면 실패
failureThreshold: 3 # 3번 연속 실패 시 재시작
successThreshold: 1 # 1번 성공하면 정상으로 간주
TCP 소켓 방식 (DB처럼 HTTP 엔드포인트가 없는 경우):
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 15
periodSeconds: 20
Exec 방식 (커스텀 스크립트 실행):
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
# 배포 후 Probe 상태 확인
kubectl apply -f liveness-demo.yaml
kubectl describe pod liveness-http -n probe-demo | grep -A 10 "Liveness"
# Liveness: http-get http://:8080/healthz delay=5s timeout=3s period=10s #success=1 #failure=3
# Probe 실패 이벤트 확인
kubectl get events -n probe-demo --sort-by=.lastTimestamp | grep -i "liveness\|unhealthy"
# Warning Unhealthy 5s pod/liveness-http Liveness probe failed: HTTP probe failed with statuscode: 500
# Normal Killing 5s pod/liveness-http Container app failed liveness probe, will be restarted
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
Readiness Probe: 준비된 파드만 트래픽 받기
롤링 업데이트 중 새 파드가 Running 상태가 되는 순간, 아직 DB 연결도 못 맺고 캐시도 워밍업 안 된 상태에서 실제 요청이 들어오면 503이 발생합니다. Liveness Probe가 실패했다고 해서 바로 재시작하면 서비스가 더 불안정해질 수 있습니다. Readiness Probe는 이 두 가지를 명확히 분리합니다. 파드가 살아있더라도 아직 준비가 안 됐으면 Service 엔드포인트에서 제외해 트래픽을 차단하고, 준비가 완료되면 자동으로 복귀시킵니다.
Readiness Probe는 Service가 트래픽을 보낼 파드를 결정합니다. 실패하면 파드가 Service 엔드포인트에서 제거되어 트래픽이 차단되고, 다시 성공하면 복귀합니다. 파드는 종료되지 않습니다.

이 동작이 Liveness Probe와의 핵심 차이입니다:
- Liveness 실패 → 컨테이너 재시작
- Readiness 실패 → 트래픽 차단 (파드는 유지)
# readiness-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: probe-demo
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /ready # /ready가 200 반환할 때만 트래픽 수신
port: 80
initialDelaySeconds: 10 # 앱 초기화 대기
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
kubectl apply -f readiness-deployment.yaml
# 엔드포인트 확인 (Readiness 통과한 파드만 목록에 있음)
kubectl get endpoints web-app-svc -n probe-demo
# NAME ENDPOINTS AGE
# web-app-svc 10.244.1.5:80,10.244.1.6:80,10.244.2.3:80 30s
# 특정 파드의 Readiness 상태
kubectl get pod -n probe-demo -o wide
# READY 컬럼: 3/3이면 모든 컨테이너가 Readiness 통과
# READY 컬럼: 0/1이면 Readiness 실패 중
Startup Probe: 느린 앱을 위한 유예 기간
Spring Boot, JVM 기반 서비스, 대규모 딥러닝 모델 로딩처럼 초기화에 수십 초가 필요한 앱에서 Liveness Probe의 initialDelaySeconds만으로는 대응이 어렵습니다. 시작 시간에 편차가 있기 때문입니다.
Startup Probe가 성공하기 전까지 Liveness Probe는 실행되지 않습니다. 즉, "초기화 완료 신호"를 보내기 전까지 Liveness가 파드를 재시작하지 않도록 보호합니다.
containers:
- name: spring-app
image: myapp/spring-api:v2.0
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
failureThreshold: 30 # 최대 30번 × 10초 = 300초(5분) 대기
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 0 # Startup이 성공하면 즉시 시작
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3
Spring Boot Actuator의 /actuator/health/liveness와 /actuator/health/readiness는 K8s Probe에 최적화된 전용 엔드포인트입니다. 각각 livenessState와 readinessState를 독립적으로 노출합니다.
# Startup Probe 동작 확인
kubectl describe pod spring-app-xxx -n probe-demo | grep -A 5 "Startup"
# Startup: http-get http://:8080/actuator/health/liveness
# delay=0s timeout=1s period=10s #success=1 #failure=30
# 시작 중인 파드 이벤트 확인
kubectl get events -n probe-demo --sort-by=.lastTimestamp | head -10
# Normal Starting 15s pod/spring-app Startup probe succeeded
# Normal Ready 15s pod/spring-app Container is ready
실습: 완전한 Probe 설정을 가진 Deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: full-probe-app
namespace: probe-demo
spec:
replicas: 2
selector:
matchLabels:
app: full-probe-app
template:
metadata:
labels:
app: full-probe-app
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
startupProbe:
httpGet:
path: /
port: 80
failureThreshold: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 5
failureThreshold: 2
successThreshold: 2 # 2번 연속 성공해야 Ready
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "300m"
memory: "256Mi"
EOF
# 배포 상태 확인
kubectl rollout status deployment/full-probe-app -n probe-demo
# 모든 Probe 설정 확인
kubectl describe deployment full-probe-app -n probe-demo | grep -A 20 "Containers:"
새 버전을 배포했습니다. kubectl rollout status가 완료됐는데 고객 모니터링에 503 에러 스파이크가 잡혔습니다. 5분 후 자연히 사라졌지만, 배포할 때마다 이 패턴이 반복됩니다.
운영 리소스 직접 패치
안전한 실행 조건: 변경 내용을 코드에 반영할 계획이 있고 영향 범위를 검토했을 때만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl patch deployment api -n production --type='json' -p='[위 항목을 모두 확인한 후 복사할 수 있습니다
# 1단계: 배포 시점 이벤트 확인
kubectl get events -n production --sort-by=.lastTimestamp | tail -20
# Normal Scheduled 2m pod/api-v2-xxx Successfully assigned
# Normal Pulled 2m pod/api-v2-xxx Container image already present
# Normal Started 2m pod/api-v2-xxx Started container api
# (Readiness 관련 이벤트가 없음 — Probe 미설정)
# 2단계: 현재 Deployment에 Probe 설정 여부 확인
kubectl get deployment api -n production \
-o jsonpath='{.spec.template.spec.containers[0].readinessProbe}'
# null ← Readiness Probe 없음!
# 3단계: 배포 직후 파드 Ready 전환 시간 파악
# 앱 로그에서 "Application started" 메시지 타임스탬프 확인
kubectl logs -l app=api -n production --since=10m | grep -i "started\|ready\|listening"
# 2026-05-16 03:15:22 INFO Application started in 23.4 seconds
# 4단계: Readiness Probe 추가 (앱의 헬스 엔드포인트 사용)
kubectl patch deployment api -n production --type='json' -p='[
{
"op": "add",
"path": "/spec/template/spec/containers/0/readinessProbe",
"value": {
"httpGet": {"path": "/health", "port": 3000},
"initialDelaySeconds": 30,
"periodSeconds": 5,
"failureThreshold": 3,
"successThreshold": 1
}
}
]'
# 5단계: 롤아웃하며 엔드포인트 변화 관찰
kubectl rollout restart deployment/api -n production
# 터미널 1: 엔드포인트 모니터링
watch kubectl get endpoints api-svc -n production
# 배포 중에는 기존 파드 IP만 있다가
# Readiness 통과한 신규 파드 IP가 추가됨
# 그 후 구 파드 IP 제거
# 터미널 2: 서비스 응답 모니터링
while true; do curl -s -o /dev/null -w "%{http_code}\n" http://api-svc/health; sleep 1; done
# 200 200 200 200 200 ... ← 배포 중에도 503 없음
근본 원인: Readiness Probe 없이 Rolling Update를 하면, 신규 파드가 Running 상태가 되는 즉시 트래픽을 받습니다. 앱 초기화가 완료되기 전이므로 503이 발생합니다. Readiness Probe를 추가하면 실제로 요청을 처리할 수 있는 상태가 됐을 때만 엔드포인트에 추가됩니다.
추가 팁: minReadySeconds를 Deployment에 설정하면 Readiness Probe 통과 후 추가로 N초를 기다린 후 다음 파드를 교체합니다. 프로세스 재시작 후 웜업이 필요한 캐시 기반 서비스에 유용합니다.
시나리오: 레거시 Node.js 앱에 Probe 설계 추가
팀이 운영 중인 Node.js Express 앱에 Probe가 하나도 없습니다. 팀 리드가 "이번 분기 안에 Probe 추가해"라고 요청했습니다. 앱 코드를 수정하는 것부터 K8s 설정까지 전 과정을 진행합니다.
// app.js에 헬스 엔드포인트 추가
const express = require('express');
const app = express();
// 상태 변수
let isReady = false;
// 앱 초기화 (DB 연결, 캐시 워밍 등)
async function initialize() {
await connectDatabase(); // DB 연결
await warmupCache(); // 캐시 워밍
isReady = true; // 준비 완료
console.log('Application ready');
}
// Liveness: 앱 프로세스가 살아있는지
app.get('/healthz', (req, res) => {
res.status(200).json({ status: 'alive' });
});
// Readiness: DB 연결, 캐시 등 실제 서비스 가능 여부
app.get('/ready', async (req, res) => {
if (!isReady) {
return res.status(503).json({ status: 'not ready' });
}
try {
await db.query('SELECT 1'); // DB 연결 확인
res.status(200).json({ status: 'ready' });
} catch (err) {
res.status(503).json({ status: 'db error', error: err.message });
}
});
initialize();
app.listen(3000);
# Kubernetes Probe 설정
containers:
- name: node-api
image: myapp/node-api:v1.2.0
ports:
- containerPort: 3000
startupProbe:
httpGet:
path: /ready
port: 3000
failureThreshold: 12 # 최대 60초 대기 (5초 × 12)
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 3000
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
periodSeconds: 5
failureThreshold: 2 # 10초 연속 실패 시 트래픽 차단
successThreshold: 1
# 배포 후 Probe 동작 검증
kubectl apply -f node-api-deployment.yaml
kubectl rollout status deployment/node-api -n production
# Probe 통과 이벤트 확인
kubectl describe pod -l app=node-api -n production | grep -E "Started|Ready|Startup|Liveness|Readiness"
# 의도적으로 DB를 내려서 Readiness Probe 실패 동작 확인
kubectl exec -n production $(kubectl get pod -l app=postgres -o name) -- pg_ctl stop
watch kubectl get endpoints node-api-svc -n production
# 파드가 엔드포인트에서 빠지는 것 확인
실무 포인트: /healthz(Liveness)와 /ready(Readiness)를 반드시 분리하세요. Liveness는 최소한의 프로세스 생존만 확인하고, Readiness는 의존 서비스(DB, 캐시, 외부 API)까지 포함한 실제 서비스 가능 여부를 확인해야 합니다. Liveness에 DB 체크를 넣으면 DB 장애 시 멀쩡한 앱이 계속 재시작되는 최악의 상황이 발생합니다.
핵심 요약
| Probe | 실패 시 동작 | 주요 용도 |
|---|---|---|
| Liveness | 컨테이너 재시작 | 데드락, 무한루프 감지 |
| Readiness | Service 엔드포인트 제거 | 배포 중 트래픽 차단, 의존성 장애 대응 |
| Startup | Liveness 실행 유예 | 느린 초기화 보호 |
| 파라미터 | 의미 |
|---|---|
| initialDelaySeconds | 컨테이너 시작 후 첫 체크까지 대기 |
| periodSeconds | 체크 주기 |
| timeoutSeconds | 응답 대기 시간 |
| failureThreshold | 연속 실패 허용 횟수 |
| successThreshold | 연속 성공 필요 횟수 (Readiness만 1 이상 가능) |