K8s Pod Probe 헬스체크 전략

Pod Probe란

Kubernetes는 3종류의 Pod Probe로 컨테이너의 상태를 감시합니다. Probe가 없으면 kubelet은 컨테이너 프로세스가 실행 중이기만 하면 정상으로 판단하므로, 실제로는 DB 연결 실패나 데드락 상태인 Pod에 트래픽이 계속 전달됩니다. Probe를 올바르게 설정하면 장애 Pod 자동 제거·재시작이 가능해집니다.

Probe 종류 역할 실패 시 동작
Startup Probe 앱 초기화 완료 확인 컨테이너 재시작
Liveness Probe 앱이 살아있는지 확인 컨테이너 재시작
Readiness Probe 트래픽 수신 가능 여부 Service 엔드포인트에서 제거 (재시작 아님)

Startup Probe: 느린 초기화 보호

Spring Boot나 JVM 기반 앱은 시작에 30초 이상 걸릴 수 있습니다. Startup Probe가 없으면 Liveness Probe가 초기화 중에 실패로 판단하여 컨테이너를 무한 재시작합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-api
spec:
  template:
    spec:
      containers:
        - name: app
          image: spring-api:latest
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            failureThreshold: 30    # 5초 × 30회 = 최대 150초 대기
            timeoutSeconds: 3

Startup Probe가 성공하기 전까지 Liveness/Readiness Probe는 비활성화됩니다. failureThreshold × periodSeconds가 앱의 최대 시작 시간보다 충분히 커야 합니다.

Liveness Probe: 교착 상태 감지

앱이 실행 중이지만 요청을 처리할 수 없는 상태(데드락, 무한 루프, 메모리 누수)를 감지합니다:

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10          # 10초마다 체크
  failureThreshold: 3        # 3회 연속 실패 시 재시작
  timeoutSeconds: 5          # 5초 내 응답 없으면 실패
  successThreshold: 1        # 1회 성공으로 복구

중요: Liveness Probe에 외부 의존성(DB, Redis)을 넣으면 안 됩니다. DB가 일시적으로 다운되었을 때 모든 Pod가 재시작되어 연쇄 장애가 발생합니다.

// ✅ 올바른 Liveness: 앱 프로세스 자체만 확인
@RestController
public class HealthController {

    @GetMapping("/health/live")
    public ResponseEntity<String> liveness() {
        // 앱이 응답 가능한지만 확인
        return ResponseEntity.ok("OK");
    }
}

// ❌ 잘못된 Liveness: 외부 의존성 포함
@GetMapping("/health/live")
public ResponseEntity<String> badLiveness() {
    // DB 연결 실패 시 Pod 재시작 → 연쇄 장애!
    jdbcTemplate.queryForObject("SELECT 1", Integer.class);
    return ResponseEntity.ok("OK");
}

Readiness Probe: 트래픽 제어

Readiness Probe는 Pod가 트래픽을 받을 준비가 되었는지 판단합니다. 실패하면 Service 엔드포인트에서 제거되어 트래픽이 차단되지만, Pod는 재시작되지 않습니다:

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 5
  failureThreshold: 3
  timeoutSeconds: 3
  successThreshold: 1

Readiness Probe에는 외부 의존성을 포함해야 합니다. DB나 Redis에 연결할 수 없으면 요청을 처리할 수 없으므로 트래픽을 받으면 안 됩니다:

// Spring Boot Actuator — 자동으로 DB, Redis 등 확인
// application.yml
management:
  endpoint:
    health:
      probes:
        enabled: true       # /actuator/health/liveness, /readiness 활성화
      group:
        liveness:
          include: livenessState      # 앱 프로세스만
        readiness:
          include:
            - readinessState
            - db                      # DataSource 연결
            - redis                   # Redis 연결
            - diskSpace               # 디스크 공간

NestJS Terminus 연동

NestJS Terminus로 Probe 엔드포인트를 구현합니다:

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
    private redis: MicroserviceHealthIndicator,
  ) {}

  // Liveness — 앱 자체만
  @Get('live')
  @HealthCheck()
  liveness() {
    return this.health.check([]);
  }

  // Readiness — 외부 의존성 포함
  @Get('ready')
  @HealthCheck()
  readiness() {
    return this.health.check([
      () => this.db.pingCheck('database'),
      () => this.redis.pingCheck('redis', {
        transport: Transport.REDIS,
        options: { host: 'redis', port: 6379 },
      }),
    ]);
  }
}

3가지 Probe 체크 방식

방식 설정 적합한 상황
HTTP GET httpGet 웹 앱, REST API (가장 일반적)
TCP Socket tcpSocket DB, Redis 등 HTTP 없는 서비스
Exec exec.command 커스텀 스크립트 필요 시
gRPC grpc gRPC 서비스 (K8s 1.24+)
# TCP Socket — DB 컨테이너용
livenessProbe:
  tcpSocket:
    port: 5432
  periodSeconds: 10
  failureThreshold: 3

# Exec — 커스텀 스크립트
livenessProbe:
  exec:
    command:
      - sh
      - -c
      - "pg_isready -h localhost -p 5432 -U postgres"
  periodSeconds: 10
  failureThreshold: 3

# gRPC Health Check (K8s 1.24+)
livenessProbe:
  grpc:
    port: 50051
    service: "my.service.Health"
  periodSeconds: 10

실전 Probe 설정 완성본

Spring Boot + K8s 프로덕션 환경의 권장 설정입니다:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0       # 무중단 배포
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: app
          image: order-service:v2.1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi

          # 1. Startup: 초기화 완료 대기 (최대 120초)
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
            failureThreshold: 24
            timeoutSeconds: 3

          # 2. Liveness: 프로세스 생존 확인 (외부 의존성 제외)
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 15
            failureThreshold: 3
            timeoutSeconds: 5

          # 3. Readiness: 트래픽 수신 가능 여부 (DB, Redis 포함)
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 5
            failureThreshold: 3
            timeoutSeconds: 3
            successThreshold: 1

Graceful Shutdown과 Probe 연계

Rolling Update 시 트래픽 유실을 방지하려면 Probe와 graceful shutdown을 연계해야 합니다:

spec:
  terminationGracePeriodSeconds: 60
  containers:
    - name: app
      lifecycle:
        preStop:
          exec:
            command: ["sh", "-c", "sleep 10"]
            # 10초 대기 → iptables 업데이트 시간 확보

종료 순서:

  • 1단계: Pod가 Terminating 상태로 전환
  • 2단계: Service 엔드포인트에서 제거 (새 트래픽 차단)
  • 3단계: preStop 훅 실행 (sleep 10초 — iptables 전파 대기)
  • 4단계: SIGTERM 전송 → 앱이 진행 중인 요청 완료
  • 5단계: terminationGracePeriodSeconds 초과 시 SIGKILL
# Spring Boot graceful shutdown
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

흔한 실수와 해결

실수 증상 해결
Liveness에 DB 체크 포함 DB 장애 시 전 Pod 재시작 Liveness는 앱만, Readiness에 DB
Startup Probe 미설정 느린 앱 CrashLoopBackOff Startup Probe 추가
timeoutSeconds 너무 짧음 GC pause 중 Probe 실패 3~5초로 설정
Readiness 없이 배포 시작 중인 Pod에 트래픽 전달 Readiness 필수 설정
preStop 훅 미설정 Rolling Update 시 502 에러 preStop sleep 5~10초
# Probe 디버깅 명령어
kubectl describe pod order-service-xxx | grep -A 20 "Conditions"
kubectl get events --field-selector involvedObject.name=order-service-xxx
kubectl logs order-service-xxx --previous  # 재시작 전 로그

정리

Pod Probe는 Kubernetes 운영의 기본이지만, 잘못 설정하면 장애를 악화시킵니다. 핵심 원칙은 Liveness는 가볍게(앱 자체만), Readiness는 무겁게(의존성 포함), Startup은 넉넉하게입니다. PDB와 함께 설정하면 Rolling Update 시 무중단 배포가 완성됩니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux