관리 메뉴

근묵자흑

Kubernetes Pattern: Health Probe Pattern(정상상태 점검 패턴) 본문

k8s/kubernetes-pattern

Kubernetes Pattern: Health Probe Pattern(정상상태 점검 패턴)

Luuuuu 2025. 8. 16. 20:17

 

운영 환경에서 Kubernetes 애플리케이션을 실행할 때 가장 중요한 것 중 하나는 애플리케이션의 건강 상태를 정확히 파악하는 것입니다. 단순히 컨테이너가 실행 중이라고 해서 애플리케이션이 정상적으로 작동한다고 볼 수 없죠. 메모리 누수, 데드락, 무한 루프 등 다양한 문제가 발생할 수 있기 때문입니다.

📚 Health Probe가 필요한 이유

실제 장애 시나리오

제가 경험한 실제 사례를 하나 공유하겠습니다. Java 애플리케이션이 OutOfMemoryError를 발생시켰지만, JVM 프로세스는 여전히 실행 중이었습니다. Kubernetes는 프로세스가 살아있다고 판단해 재시작하지 않았고, 결과적으로 서비스는 완전히 중단되었죠.

이런 문제를 해결하기 위해 Kubernetes는 세 가지 Health Probe를 제공합니다:

  1. Liveness Probe: 애플리케이션이 살아있는가?
  2. Readiness Probe: 트래픽을 받을 준비가 되었는가?
  3. Startup Probe: 애플리케이션 시작이 완료되었는가?

실습을 통한 Health Probe 분석 

  • 이번장의 실습 코드는 GitHub 저장소에서 확인할 수 있습니다.

Liveness Probe 깊이 이해하기

1. HTTP Liveness Probe

가장 일반적인 형태의 liveness 체크입니다. Spring Boot Actuator와 같은 health 엔드포인트를 활용합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-http-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: liveness-http-test
  template:
    metadata:
      labels:
        app: liveness-http-test
    spec:
      containers:
      - name: random-generator
        image: k8spatterns/random-generator:1.0
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 15  # 컨테이너 시작 후 대기 시간
          periodSeconds: 10         # 체크 주기
          timeoutSeconds: 5         # 응답 대기 시간
          failureThreshold: 3       # 실패 허용 횟수

주요 파라미터 설명:

  • initialDelaySeconds: 애플리케이션 초기화 시간 고려
  • failureThreshold: 너무 낮으면 불필요한 재시작, 너무 높으면 장애 감지 지연

2. Exec Liveness Probe - 실패 시뮬레이션

이 예제는 의도적으로 실패를 발생시켜 재시작 메커니즘을 관찰합니다:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-exec-test
spec:
  template:
    spec:
      containers:
      - name: busybox
        image: busybox:latest
        command: ["/bin/sh"]
        args:
        - -c
        - |
          touch /tmp/healthy  # 건강 상태 파일 생성
          sleep 30
          rm -f /tmp/healthy  # 30초 후 파일 삭제 (실패 유발)
          sleep 600
        livenessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 5
          periodSeconds: 5
          failureThreshold: 3

실제 테스트 결과:

# 테스트 실행
kubectl apply -f manifests/03-liveness-exec.yaml -n health-probe-test

# 50초 후 확인
kubectl get pods -n health-probe-test
NAME                                  READY   STATUS    RESTARTS      AGE
liveness-exec-test-58b9c4969f-8tnkv   1/1     Running   2 (117s ago)   4m27s

30초 후 health 파일이 삭제되고, 3번의 실패 후(15초) 컨테이너가 재시작됩니다!

Readiness Probe 실전 활용

Readiness vs Liveness의 차이

  • Liveness 실패: 컨테이너 재시작
  • Readiness 실패: Service 엔드포인트에서 제거 (재시작 없음)

점진적 배포 시나리오

apiVersion: apps/v1
kind: Deployment
metadata:
  name: readiness-exec-test
spec:
  replicas: 3  # 3개 레플리카
  template:
    spec:
      containers:
      - name: random-generator
        image: k8spatterns/random-generator:1.0
        env:
        - name: DELAY_STARTUP
          value: "20"  # 20초 지연 시작
        readinessProbe:
          exec:
            command:
            - stat
            - /tmp/random-generator-ready
          periodSeconds: 5
          failureThreshold: 1

엔드포인트 모니터링:

# 실행 후 엔드포인트 관찰
for i in {1..6}; do
  kubectl get endpoints readiness-exec-test -n health-probe-test
  sleep 5
done

# 결과: Pod가 하나씩 Ready 상태가 되면서 엔드포인트에 추가됨
# 0/3 → 1/3 → 2/3 → 3/3

이는 무중단 배포에서 매우 중요합니다. 새 버전의 Pod가 완전히 준비되기 전까지 트래픽을 받지 않습니다.

 

Startup Probe로 느린 시작 해결하기

Spring Boot 같은 무거운 애플리케이션의 문제

일부 엔터프라이즈 애플리케이션은 시작에 수 분이 걸릴 수 있습니다. Liveness Probe만 사용하면:

  • initialDelaySeconds를 매우 크게 설정 → 실제 장애 감지 지연
  • 작게 설정 → 시작 중 불필요한 재시작

Startup Probe 솔루션

apiVersion: apps/v1
kind: Deployment
metadata:
  name: startup-probe-test
spec:
  template:
    spec:
      containers:
      - name: random-generator
        image: k8spatterns/random-generator:2.0
        env:
        - name: DELAY_STARTUP
          value: "90"  # 90초 시작 지연
        startupProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
          failureThreshold: 12  # 최대 120초 대기
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          periodSeconds: 10
          failureThreshold: 3  # 시작 후에는 빠른 장애 감지

실제 테스트 결과:

# 시작 진행 상황 모니터링
Time: 0s | Status: Pending | Ready: false
Time: 10s | Status: Running | Ready: false
Time: 20s | Status: Running | Ready: false
...
Time: 101s | Status: Running | Ready: true
✅ Container ready after 101 seconds

Startup Probe가 성공할 때까지 Liveness/Readiness Probe는 실행되지 않습니다!

모든 프로브 조합하기

실제 프로덕션 환경에서는 세 가지 프로브를 적절히 조합해 사용합니다:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: combined-probes-test
spec:
  template:
    spec:
      containers:
      - name: random-generator
        image: k8spatterns/random-generator:2.0
        startupProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          failureThreshold: 10  # 시작용
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          periodSeconds: 10
          failureThreshold: 3  # 런타임용
        readinessProbe:
          exec:
            command:
            - stat
            - /tmp/random-generator-ready
          periodSeconds: 5
          failureThreshold: 2  # 트래픽 제어용

각 프로브의 역할:

  • Startup: 초기 시작 완료 확인
  • Liveness: 지속적인 생존 상태 확인
  • Readiness: 트래픽 수신 가능 여부 확인

💡 실전 팁과 베스트 프랙티스

1. 적절한 타이밍 설정

# 나쁜 예: 너무 공격적인 설정
livenessProbe:
  periodSeconds: 1
  failureThreshold: 1
  timeoutSeconds: 1

# 좋은 예: 여유있는 설정
livenessProbe:
  periodSeconds: 10
  failureThreshold: 3
  timeoutSeconds: 5

2. 의존성 체크 주의사항

# 나쁜 예: 외부 의존성을 liveness에 포함
livenessProbe:
  exec:
    command: ["check-database-connection.sh"]

# 좋은 예: 외부 의존성은 readiness에만
readinessProbe:
  exec:
    command: ["check-database-connection.sh"]
livenessProbe:
  httpGet:
    path: /health/ping  # 애플리케이션 자체만 체크

3. 헬스체크 엔드포인트 구현

// Go 예제
func healthHandler(w http.ResponseWriter, r *http.Request) {
    // 간단한 self-check만 수행
    if isApplicationHealthy() {
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(map[string]string{
            "status": "UP",
            "timestamp": time.Now().Format(time.RFC3339),
        })
    } else {
        w.WriteHeader(http.StatusServiceUnavailable)
        json.NewEncoder(w).Encode(map[string]string{
            "status": "DOWN",
        })
    }
}

직접 테스트해보기

전체 테스트 실행

# 프로젝트 클론
git clone <repository-url>
cd health-probe

# Minikube 시작
minikube start

# 전체 테스트 실행
./run-test.sh

 

실시간 모니터링

# Pod 상태 관찰
kubectl get pods -n health-probe-test -w

# 이벤트 모니터링
kubectl get events -n health-probe-test --watch | grep probe

# 엔드포인트 변화 관찰
kubectl get endpoints -n health-probe-test -w

테스트 결과 분석

실제 Minikube에서 실행한 결과:

프로브 타입 테스트 결과 재시작 횟수 소요 시간
HTTP Liveness 정상 작동 0 -
TCP Liveness 정상 작동 0 -
Exec Liveness 예상대로 실패 2 45초
Exec Readiness 점진적 Ready 0 30초
HTTP Readiness 정상 작동 0 -
Startup Probe 느린 시작 성공 0 101초
  1. 프로브는 보험이다: 설정하지 않으면 언젠가 후회합니다
  2. 각 프로브의 목적을 명확히: Liveness는 재시작, Readiness는 트래픽 제어
  3. 타이밍이 중요: 너무 공격적이면 불안정, 너무 느슨하면 무용지물
  4. 실패를 계획하라: 프로브는 실패를 가정하고 설계
  5. 모니터링과 함께: 프로브 메트릭을 Prometheus로 수집하여 분석

다음으로 필요한 설정들

  1. 프로덕션 적용: 실제 애플리케이션에 적절한 프로브 설정
  2. 메트릭 수집: Prometheus + Grafana로 프로브 메트릭 시각화
  3. CI/CD 통합: 배포 파이프라인에 프로브 검증 추가
  4. Service Mesh 연동: Istio/Linkerd와 함께 고급 헬스체크 구현