1. Probe란 무엇인가
Kubernetes는 기본적으로 컨테이너의 프로세스가 살아있는지만 감시한다. 컨테이너 안의 메인 프로세스가 죽지 않고 떠 있기만 하면, 그 애플리케이션이 실제로 정상 동작하는지 응답이 가능한 상태인지는 Kubernetes가 알 방법이 없다.
Probe는 이 간극을 메우기 위한 기능이다. Kubernetes가 주기적으로 컨테이너에 직접 "지금 괜찮아?"라고 물어보고(HTTP 요청, TCP 접속, 명령어 실행 등), 그 응답을 기준으로 컨테이너의 상태를 판단하게 해준다. Probe를 어떻게 설정하느냐에 따라 장애가 발생했을 때 그 여파가 사용자에게까지 전달되는지, 아니면 Kubernetes 선에서 조용히 처리되는지가 갈린다.
Kubernetes는 목적이 다른 3가지 Probe를 제공한다.

2. livenessProbe — "이 컨테이너, 아직 살아있나?"
livenessProbe는 컨테이너가 정상적으로 동작 중인지 확인한다. 이 확인이 계속 실패하면 Kubernetes는 해당 컨테이너를 재시작한다.
여기서 핵심은, livenessProbe는 "프로세스가 죽었는지"가 아니라 **"프로세스는 떠 있지만 사실상 죽은 것과 다름없는 상태(hang, 데드락)에 빠졌는지"**를 감지하기 위한 것이다. 프로세스 자체가 죽는 경우는 컨테이너 런타임이 알아서 감지하고 재시작하지만, 프로세스는 살아있는데 무한루프나 데드락에 걸려 응답을 전혀 못 하는 상황은 오직 애플리케이션 레벨의 확인(livenessProbe)으로만 잡아낼 수 있다.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
3. readinessProbe — "이 컨테이너, 지금 트래픽 받아도 되나?"
readinessProbe는 컨테이너가 트래픽을 받을 준비가 됐는지를 확인한다. 이 확인이 실패하면 Kubernetes는 컨테이너를 재시작하지 않고, 대신 해당 Pod를 Service의 트래픽 대상(Endpoints)에서 조용히 제외한다. 이후 다시 확인이 성공하면 자동으로 Endpoints에 복귀한다.
애플리케이션은 시작 직후라도 DB 커넥션 풀 초기화, 캐시 예열, 외부 API 연결 확인 등 "떠 있지만 아직 일할 준비는 안 된" 시간이 있는 경우가 많다. 이런 순간에 Service가 트래픽을 계속 보내면 사용자가 에러를 그대로 받게 된다. readinessProbe는 바로 이 시간 동안 해당 Pod로 트래픽이 가지 않도록 막아준다.
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe와 readinessProbe를 혼동하면 안 된다. 둘 다 "실패하면 큰일 나는 것" 정도로 뭉뚱그려 이해하기 쉬운데, 실제로는 대응 방식이 완전히 다르다. readinessProbe 실패는 "잠깐 트래픽만 빼줘"이고, livenessProbe 실패는 "이건 재시작해야 해"다. 만약 DB 연결이 일시적으로 끊긴 상황에 livenessProbe가 실패하도록 설정해뒀다면, 컨테이너를 계속 재시작해봐야 DB가 안 돌아오는 이상 문제는 해결되지 않고 오히려 재시작 루프만 반복하게 된다. 이런 경우는 readinessProbe로 처리하는 것이 맞다.
4. startupProbe — "이 컨테이너, 아직 켜지는 중인가?"
startupProbe는 느리게 초기화되는 애플리케이션을 위한 Probe다. startupProbe가 설정되어 있으면, 이 Probe가 성공할 때까지는 livenessProbe와 readinessProbe가 아예 동작하지 않는다.
이게 왜 필요한지는 startupProbe가 없던 시절의 문제를 생각해보면 이해하기 쉽다. 초기 구동에 90초가 걸리는 레거시 애플리케이션이 있다고 하자. livenessProbe의 initialDelaySeconds를 90초보다 길게 잡아두면 정상 구동 시에는 문제가 없지만, 이 값을 기준으로 삼으면 정작 애플리케이션이 실제로 멈췄을 때 탐지가 90초만큼 항상 늦어진다. 반대로 값을 짧게 잡으면, 아직 초기화 중인 애플리케이션을 livenessProbe가 "죽었다"고 오판해서 계속 재시작시키는 재시작 루프에 빠질 수 있다.
startupProbe는 이 딜레마를 해결한다. 초기 구동 시간이 아무리 길어도 startupProbe가 성공할 때까지는 다른 Probe가 기다려주고, 일단 한 번 startupProbe가 성공하고 나면 그 이후부터는 livenessProbe가 짧은 주기로 빠르게 이상 상태를 감지할 수 있다.
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10 # 최대 30 × 10 = 300초까지 초기 구동 시간을 허용
5. Pod 생명주기 속에서 Probe가 동작하는 순서
세 Probe가 실제로 어느 시점에 어떻게 맞물려 동작하는지 타임라인으로 보면 다음과 같다.

- 컨테이너가 시작되면 먼저 startupProbe가 확인을 시작한다 (이 구간에서는 readiness/liveness가 동작하지 않는다)
- startupProbe가 성공하면 그 순간부터 readinessProbe와 livenessProbe가 각자의 주기로 반복 확인을 시작한다
- readinessProbe가 처음으로 성공한 시점부터 해당 Pod가 Service의 Endpoints에 등록되어 트래픽을 받기 시작한다
- 이후 서비스가 살아있는 내내 readiness와 liveness는 각자의 주기(periodSeconds)로 계속 반복된다
startupProbe를 설정하지 않으면 이 순서에서 1번 구간이 생략되고, 컨테이너가 뜨자마자 바로 readiness/liveness가 시작된다.
6. Probe를 설정하지 않으면 생기는 문제
"일단 Probe 없이 배포하고 나중에 설정하지"라고 미루는 경우가 실무에서 은근히 많다. 하지만 Probe 미설정은 단순히 "덜 세련된 설정"이 아니라, 장애가 실제보다 훨씬 크게 번지는 직접적인 원인이 된다.

6.1 readinessProbe가 없을 때
Pod가 새로 뜨는 순간(배포, 오토스케일링, 재시작 등) Kubernetes는 컨테이너 프로세스가 시작되면 바로 그 Pod를 정상으로 간주하고 Service의 Endpoints에 등록한다. 하지만 애플리케이션이 DB 연결이나 캐시 예열을 아직 마치지 못한 상태라면, 이 시점에 들어오는 요청은 그대로 에러나 타임아웃으로 사용자에게 전달된다. 배포할 때마다 짧은 순간 에러율이 튀는 경우 대부분 readinessProbe 미설정이 원인이다.
6.2 livenessProbe가 없을 때
더 심각한 쪽은 이쪽이다. 애플리케이션이 데드락에 걸려 완전히 응답 불가 상태가 되어도, 프로세스 자체는 여전히 떠 있기 때문에 Kubernetes는 이를 정상으로 판단한다. livenessProbe가 없으면 이 상태를 자동으로 감지해서 재시작할 방법이 없으므로, 결국 사람이 모니터링 알람이나 사용자 문의를 통해 뒤늦게 알아채고 수동으로 Pod를 재시작해야 한다. 그 사이의 장애 시간은 고스란히 서비스 다운타임으로 누적된다.
7. 실전 설정 팁
7.1 파라미터 의미
| 필드 | 의미 |
| initialDelaySeconds | 컨테이너 시작 후 첫 확인까지 대기하는 시간 |
| periodSeconds | 확인 주기 (기본 10초) |
| timeoutSeconds | 한 번의 확인에서 응답을 기다리는 최대 시간 |
| successThreshold | 실패 상태에서 성공으로 판단하기 위해 연속으로 필요한 성공 횟수 |
| failureThreshold | 성공 상태에서 실패로 판단하기 위해 연속으로 필요한 실패 횟수 |
7.2 liveness와 readiness의 엔드포인트를 분리하라
가능하다면 /healthz(liveness용)와 /ready(readiness용)를 서로 다른 엔드포인트로 분리하는 것이 좋다. liveness 엔드포인트는 프로세스 자체의 생존만 아주 가볍게 확인하고, readiness 엔드포인트는 DB, 캐시, 외부 의존성까지 포함해서 "실제로 일할 준비가 됐는지"를 확인하는 식으로 역할을 나누면, 앞서 설명한 "DB 문제인데 컨테이너가 재시작되는" 혼선을 방지할 수 있다.
7.3 livenessProbe에 무거운 검사를 넣지 않는다
livenessProbe 안에서 DB 쿼리처럼 무거운 의존성 검사를 하면, DB가 잠깐 느려지기만 해도 애플리케이션 자체는 멀쩡한데 livenessProbe가 타임아웃되어 불필요한 재시작이 계속 발생할 수 있다. 그런 검사는 readinessProbe의 몫이다.
7.4 세 Probe를 함께 쓰는 예시
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myregistry/my-app:1.0.0
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3
8. Probe가 애플리케이션 코드에 미치는 영향
Probe를 YAML에 선언하는 것과 별개로, 애플리케이션 쪽에 그 요청을 받아줄 무언가가 있어야 하는지는 Probe의 확인 방식(httpGet / tcpSocket / exec)에 따라 다르다.
8.1 httpGet — 코드(엔드포인트)가 필요하다
가장 많이 쓰는 httpGet 방식은 애플리케이션이 /healthz, /ready 같은 경로에 응답하는 핸들러를 직접 갖고 있어야 동작한다. Kubernetes가 이 경로로 HTTP 요청을 보내고 응답 코드가 200~399면 성공, 그 외면 실패로 판단하는 구조이기 때문에, 그 요청을 받아줄 코드가 없으면 애초에 성립하지 않는다.
// Express 예시
app.get('/healthz', (req, res) => res.status(200).send('ok'));
app.get('/ready', async (req, res) => {
const dbOk = await checkDbConnection();
res.status(dbOk ? 200 : 503).send(dbOk ? 'ready' : 'not ready');
});
다만 대부분의 경우 몇 줄 안 되는 간단한 라우트로 충분하고, Spring Boot의 Actuator(/actuator/health), Django/FastAPI의 헬스체크 미들웨어처럼 프레임워크가 기본 제공하는 경우도 많아 그런 경우엔 별도 코드 없이 설정만으로 끝나기도 한다.
8.2 tcpSocket — 코드가 필요 없다
애플리케이션이 이미 특정 포트로 리스닝하고 있다면, 그 포트에 TCP 연결이 되는지만 확인하는 방식이다. 앱이 원래 하던 동작(포트 열기) 외에 추가 코드가 필요 없다.
readinessProbe:
tcpSocket:
port: 8080
다만 "포트가 열려있다"만 확인하므로 DB 연결처럼 더 깊은 상태까지는 확인하지 못한다.
8.3 exec — 상황에 따라 다르다
컨테이너 안에서 명령어를 실행해서 종료 코드(exit code)로 판단하는 방식이다.
livenessProbe:
exec:
command: ["cat", "/tmp/healthy"]
단순히 파일 존재 여부 같은 건 코드 없이 스크립트만으로도 가능하지만, 애플리케이션 내부 상태를 반영하고 싶다면 그 앱이 스스로 상태 파일을 갱신하는 로직 정도는 필요하다.
8.4 정리
| 방식 | 앱 코드 필요 여부 |
| httpGet | 필요 (단, 프레임워크가 기본 제공하면 최소화) |
| tcpSocket | 불필요 (포트만 열려있으면 됨) |
| exec | 상황에 따라 (단순 체크는 불필요, 상태 반영하려면 필요) |
실무에서는 HTTP 서버를 이미 띄우는 애플리케이션이라면 httpGet + 간단한 헬스체크 라우트 추가가 가장 흔한 조합이고, 워커/배치처럼 HTTP 서버가 없는 애플리케이션이라면 tcpSocket이나 exec를 주로 쓴다.
9. 정리
| Probe | 확인 내용 | 실패 시 동작 |
| startupProbe | 초기 구동이 끝났는가 | 컨테이너 재시작 (성공 전까지 다른 Probe 대기) |
| readinessProbe | 지금 트래픽을 받을 수 있는가 | Service Endpoints에서 제외 (재시작 없음) |
| livenessProbe | 계속 정상 동작 중인가 | 컨테이너 재시작 |
Probe는 설정하지 않아도 당장 애플리케이션이 못 뜨는 것은 아니라서 우선순위가 밀리기 쉽지만, 실제 장애 상황에서 사용자가 체감하는 다운타임의 크기를 결정짓는 것은 대부분 이 Probe 설정의 유무다. "일단 배포하고 나중에"가 아니라, 배포 매니페스트를 처음 작성하는 시점부터 최소한 readinessProbe와 livenessProbe는 기본값으로 넣어두는 습관을 들이는 것이 좋다.
'프로그래밍 > k8s' 카테고리의 다른 글
| Kubernetes YAML 설정 파일 완벽 정리 — 구조와 주요 리소스 예제 (0) | 2026.09.10 |
|---|---|
| Kubernetes RWX(ReadWriteMany) 완벽 정리 — 개념부터 실전 예제까지 (0) | 2026.09.09 |
| kubectl 사용법 정리 — 자주 쓰는 명령어와 옵션 총정리 (0) | 2026.09.08 |
| Kubernetes 기본 개념 정리 — 그림으로 이해하는 K8s 아키텍처 (0) | 2026.09.07 |
| Kubernetes 멀티 Pod 환경에서 Alert 중복 전송 문제 해결하기 (client-go Leader Election) (0) | 2026.09.04 |