Certified Kubernetes Application Developer (CKAD) #11 Probes: liveness, readiness, startup (exec/HTTP/TCP)

컨테이너가 떠 있다고 해서 그 안의 애플리케이션이 정상으로 동작하는 것은 아닙니다. 프로세스는 살아 있지만 데드락에 빠져 응답을 못 할 수도 있고, 프로세스는 떴지만 아직 초기화가 끝나지 않아 요청을 받으면 안 되는 상태일 수도 있습니다. 쿠버네티스가 이 두 가지를 구별해서 다루는 장치가 probe입니다.

이번 글에서는 컨테이너가 살아있는지, 그리고 트래픽을 받을 준비가 됐는지를 쿠버네티스가 어떻게 확인하는지 다루겠습니다. probe는 CKAD도메인 가운데 Observability and Maintenance(15%)에 속하며, 매니페스트를 직접 작성하는 유형으로 자주 출제됩니다. 개념의 깊이보다 정확한 YAML 형식과 파라미터 의미가 점수를 가르므로, 예제를 따라 치며 손에 익히겠습니다.

probe란 무엇인가 #

probe는 kubelet이 컨테이너의 건강 상태를 주기적으로 검사하는 진단입니다. kubelet은 정해진 주기마다 컨테이너에 대해 검사를 실행하고, 그 결과(성공,실패)에 따라 컨테이너를 재시작하거나 Service 엔드포인트에서 빼는 식으로 대응합니다.

probe가 없으면 쿠버네티스는 컨테이너의 메인 프로세스가 살아 있는지만 봅니다. 프로세스가 죽으면 restartPolicy에 따라 재시작하지만, 프로세스는 살아 있는데 응답을 못 하는 좀비 상태는 감지하지 못합니다. probe는 이 빈틈을 메웁니다.

K8s 실무 트랙 #5 글에서 Pod와 컨테이너의 기본 동작을 다뤘다면, 이번 글은 그 컨테이너의 건강을 어떻게 판정하는지 한 단계 깊이 다룹니다.

세 종류의 probe #

쿠버네티스에는 목적이 다른 세 종류의 probe가 있습니다. 셋의 차이를 정확히 구별하는 것이 이 글의 핵심입니다.

probe무엇을 묻는가실패 시 동작
livenessProbe컨테이너가 살아 있는가컨테이너를 재시작
readinessProbe트래픽을 받을 준비가 됐는가Service 엔드포인트에서 제외(재시작 안 함)
startupProbe느린 애플리케이션의 초기화가 끝났는가통과 전까지 다른 probe를 비활성

livenessProbe #

livenessProbe는 컨테이너가 살아 있는지를 묻습니다. 검사가 실패하면 kubelet은 컨테이너를 죽이고 restartPolicy에 따라 재시작합니다. 프로세스는 떠 있지만 데드락이나 무한 루프로 응답을 못 하는 컨테이너를 자동 복구하는 용도입니다.

주의할 점은 너무 공격적인 liveness 설정이 오히려 장애를 만든다는 것입니다. 초기화가 느린 애플리케이션에 짧은 liveness만 걸면, 정상이지만 아직 준비 중인 컨테이너를 반복해서 죽이고 재시작하는 CrashLoop이 발생합니다.

readinessProbe #

readinessProbe는 트래픽을 받을 준비가 됐는지를 묻습니다. 검사가 실패하면 kubelet은 컨테이너를 죽이지 않고, 대신 그 Pod의 IP를 Service 엔드포인트 목록에서 뺍니다. 즉 트래픽이 그 Pod로 가지 않게 합니다. 검사가 다시 성공하면 엔드포인트에 자동으로 복귀합니다.

캐시 워밍업, DB 연결 확립, 의존 서비스 대기처럼 일시적으로 요청을 받으면 안 되는 상태를 다룰 때 씁니다. 컨테이너를 재시작하지 않는다는 점이 liveness와의 결정적 차이입니다.

startupProbe #

startupProbe는 느린 애플리케이션의 초기화 보호를 위한 probe 입니다. 시작이 오래 걸리는 레거시 애플리케이션에서, startupProbe가 통과하기 전까지는 liveness와 readiness probe가 비활성됩니다. 덕분에 긴 초기화 동안 liveness가 컨테이너를 죽이는 일을 막습니다.

startupProbe가 성공하면 그 뒤로는 liveness와 readiness가 정상 동작합니다. 즉 startupProbe는 시작 구간 전용 안전장치이며, 한 번 통과하면 더는 실행되지 않습니다.

세 종류의 핸들러 #

각 probe는 검사를 수행하는 방식을 핸들러로 지정합니다. 핸들러는 세 가지가 있고, 어떤 probe에도 붙일 수 있습니다.

핸들러검사 방법성공 판정
exec컨테이너 안에서 command 실행종료 코드 0
httpGet지정 path,port로 HTTP GET응답 코드 200〜399
tcpSocket지정 port로 TCP 연결 시도연결 성립

exec #

컨테이너 안에서 명령을 실행하고 종료 코드가 0 이면 성공으로 봅니다. 파일 존재 확인이나 자체 헬스 스크립트처럼 HTTP가 없는 워크로드에 적합합니다.

livenessProbe:
  exec:
    command:
      - cat
      - /tmp/healthy
  initialDelaySeconds: 5
  periodSeconds: 5

위 예제는 컨테이너 안에 /tmp/healthy 파일이 있으면 cat의 종료 코드가 0이라 성공, 파일이 없으면 실패로 판정합니다.

httpGet #

지정한 path와 port로 HTTP GET 요청을 보내고, 응답 코드가 200〜399 면 성공으로 봅니다. 웹 애플리케이션의 가장 일반적인 probe 형식입니다.

readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
    httpHeaders:
      - name: X-Probe
        value: readiness
  initialDelaySeconds: 10
  periodSeconds: 5

port는 숫자뿐 아니라 컨테이너 포트의 이름으로도 지정할 수 있고, httpHeaders로 커스텀 헤더를 붙일 수도 있습니다. HTTPS 검사가 필요하면 scheme: HTTPS를 추가합니다.

tcpSocket #

지정한 port로 TCP 연결이 성립하면 성공으로 봅니다. HTTP 엔드포인트가 없는 데이터베이스나 메시지 브로커처럼 포트가 열렸는지만 확인하면 되는 경우에 씁니다.

livenessProbe:
  tcpSocket:
    port: 6379
  initialDelaySeconds: 15
  periodSeconds: 10

grpc #

gRPC 서버라면 grpc 핸들러로 표준 gRPC health check 프로토콜을 사용할 수도 있습니다. 최신 버전에서 기본 지원하며 형식은 grpc: { port: 50051 } 입니다.

probe 파라미터 #

핸들러와 별개로, 모든 probe는 검사 타이밍을 조절하는 공통 파라미터를 가집니다. 이 값들의 의미와 계산을 정확히 아는 것이 시험에서 자주 요구됩니다.

파라미터기본값의미
initialDelaySeconds0컨테이너 시작 후 첫 검사까지 대기 시간
periodSeconds10검사 주기
timeoutSeconds1한 번의 검사가 응답을 기다리는 제한 시간
successThreshold1실패 후 성공으로 복귀하는 데 필요한 연속 성공 횟수
failureThreshold3실패로 확정하는 데 필요한 연속 실패 횟수
  • initialDelaySeconds. 컨테이너가 막 떴을 때는 아직 준비가 안 됐을 수 있으므로, 첫 검사를 이만큼 미룹니다. liveness에 이 값이 너무 작으면 초기화 중에 컨테이너를 죽일 수 있습니다.
  • periodSeconds. 검사를 반복하는 간격입니다. 짧을수록 빠르게 감지하지만 부하가 늘어납니다.
  • timeoutSeconds. 검사가 이 시간 안에 응답하지 않으면 그 검사는 실패로 칩니다. 기본 1초는 무거운 핸들러에는 짧을 수 있습니다.
  • successThreshold. readiness에서 자주 의미가 있으며, liveness와 startup은 반드시 1이어야 합니다.
  • failureThreshold. 한 번 실패했다고 바로 조치하지 않고, 이만큼 연속 실패해야 실패로 확정합니다.

실패까지 걸리는 시간 계산 #

liveness가 컨테이너를 실제로 재시작하기까지 걸리는 최대 시간은 다음으로 가늠합니다.

첫 검사 시작 = initialDelaySeconds
실패 확정    = initialDelaySeconds + periodSeconds × failureThreshold

예를 들어 initialDelaySeconds: 10, periodSeconds: 5, failureThreshold: 3이면, 컨테이너 시작 후 약 10 + 5 × 3 = 25초 시점에 재시작이 일어날 수 있습니다. startupProbe의 failureThreshold × periodSeconds애플리케이션이 시작에 쓸 수 있는 최대 시간과 같습니다.

liveness vs readiness: 시험 단골 혼동 #

이 둘의 차이는 CKAD에서 가장 자주 헷갈리는 지점이라 한 번 더 분명히 정리하겠습니다.

구분livenessProbereadinessProbe
묻는 것살아 있는가트래픽을 받을 준비가 됐는가
실패 시컨테이너 재시작엔드포인트에서 제외
컨테이너를 죽이는가죽인다죽이지 않는다
회복 방식재시작 후 다시 검사검사 성공 시 엔드포인트 복귀

핵심만 기억하면 됩니다. liveness 실패는 재시작, readiness 실패는 엔드포인트 제외입니다. “트래픽 라우팅"이라는 단어가 문제에 보이면 readiness, “재시작"이나 “복구"가 보이면 liveness로 판단합니다. 둘을 함께 쓰는 것이 일반적이며, 보통 readiness가 liveness보다 먼저 통과해야 합니다.

종합 YAML 예제 #

세 probe를 한 컨테이너에 함께 붙인 형태입니다. 실무와 시험에서 가장 흔한 조합입니다.

apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: app
      image: nginx
      ports:
        - containerPort: 80
      startupProbe:
        httpGet:
          path: /healthz
          port: 80
        failureThreshold: 30
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /ready
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 5
      livenessProbe:
        httpGet:
          path: /healthz
          port: 80
        initialDelaySeconds: 15
        periodSeconds: 10
        failureThreshold: 3

이 매니페스트에서 startupProbe는 최대 30 × 10 = 300초 동안 시작을 기다립니다. 그 안에 /healthz가 한 번 200을 돌려주면 startup이 통과하고, 그 뒤부터 readiness와 liveness가 동작합니다.

시험에서 빠르게 만드는 법 #

probe 전용 generator는 없으므로, #1 글에서 익힌 dry-run으로 Pod 뼈대를 만든 뒤 probe 블록만 추가하는 흐름이 빠릅니다.

k run web --image=nginx --port=80 $do > web.yaml
# 이후 web.yaml의 containers 아래에 probe 블록 추가
# 필드 경로가 헷갈리면 explain으로 확인
k explain pod.spec.containers.livenessProbe --recursive

트러블슈팅 #

probe 설정 오류는 멀쩡한 애플리케이션을 망가뜨립니다. 시험에서도 “이 Pod가 왜 이러는가” 유형으로 나옵니다.

CrashLoopBackOff: liveness가 과격할 때 #

liveness의 initialDelaySeconds가 너무 짧거나 path,port가 틀리면, 정상 컨테이너를 반복해서 죽여 CrashLoopBackOff가 됩니다. 먼저 이벤트와 상태를 봅니다.

k describe pod web
# Events에서 "Liveness probe failed"와 재시작 이력 확인
k get pod web -o jsonpath='{.status.containerStatuses[0].restartCount}'

Liveness probe failed이벤트가 보이면 initialDelaySeconds를 늘리거나, 초기화가 느린 경우 startupProbe를 추가해 시작 구간을 보호합니다.

엔드포인트 누락: readiness가 통과 못 할 때 #

Service로 트래픽이 안 가면 readiness가 의심됩니다. readiness가 실패하면 그 Pod는 엔드포인트 목록에 오르지 못합니다.

k get endpoints my-svc
# ADDRESSES가 비어 있으면 어떤 Pod도 ready가 아님
k describe pod web | grep -A3 Readiness

엔드포인트가 비어 있는데 Pod는 Running이라면, readiness의 path,port가 애플리케이션의 실제 헬스 경로와 맞는지부터 확인합니다.

자주 하는 실수 #

  • liveness와 readiness의 path를 동일하게 두고 의존 서비스 상태까지 liveness가 검사하게 만드는 경우. 의존 서비스가 잠깐 죽으면 멀쩡한 컨테이너까지 재시작됩니다. liveness는 자기 자신만, readiness는 의존성까지 검사하는 것이 원칙입니다.
  • port에 컨테이너가 실제로 열지 않은 포트를 적는 경우. tcpSocket과 httpGet 모두 흔한 실수입니다.
  • startupProbe 없이 느린 애플리케이션에 짧은 liveness만 거는 경우.

시험 포인트 #

  • 세 probe의 실패 동작. liveness=재시작, readiness=엔드포인트 제외, startup=통과 전 다른 probe 비활성. 이 한 줄이 핵심입니다.
  • 세 핸들러의 성공 판정. exec=종료 코드 0, httpGet=200〜399, tcpSocket=연결 성립.
  • 파라미터 5종의 의미와 기본값, 그리고 initialDelaySeconds + periodSeconds × failureThreshold로 실패 확정 시간을 계산하는 법.
  • liveness와 readiness를 묻는 문장으로 구별하기. “재시작"이면 liveness, “트래픽"이면 readiness.
  • k explain pod.spec.containers.livenessProbe --recursive로 필드 경로를 즉시 확인하기.

정리 #

이번 글에서 잡은 것:

  • probe는 kubelet이 컨테이너 건강을 주기적으로 검사하는 장치. 프로세스 생존만으로는 못 잡는 좀비 상태를 메웁니다.
  • liveness(재시작) , readiness(엔드포인트 제외) , startup(시작 보호) 세 종류의 목적과 실패 동작.
  • exec , httpGet , tcpSocket 세 핸들러의 형식과 성공 판정, 그리고 grpc 한 줄.
  • initialDelaySeconds , periodSeconds , timeoutSeconds , successThreshold , failureThreshold 의 의미와 실패 확정 시간 계산.
  • 트러블슈팅. liveness과격 설정으로 인한 CrashLoop, readiness 미통과로 인한 엔드포인트 누락.

다음: Observability #

probe로 컨테이너의 건강을 판정하는 법을 익혔습니다. probe가 실패했을 때 왜 실패했는지 들여다보는 도구가 다음 주제입니다.

#12 Observability: logging, kubectl debug, port-forward, ephemeral container에서는 kubectl logs의 다양한 옵션, 죽은 컨테이너를 진단하는 kubectl debug와 ephemeral container, 로컬에서 Pod에 직접 붙는 kubectl port-forward 까지, 실기에서 트러블슈팅 점수를 챙기는 도구를 손에 익히겠습니다.

X