활성, 준비성, 시작 프로브

활성, 준비성, 시작 프로브 (Liveness, Readiness, and Startup Probes)

Kubernetes를 사용하면 프로브(probe) 를 정의해서 Pod 안 컨테이너의 상태를 지속적으로 모니터링할 수 있어요. 프로브는 kubelet이 컨테이너에 대해 주기적으로 수행하는 진단이에요. 진단을 수행하기 위해 kubelet은 컨테이너 안에서 코드를 실행하거나 네트워크 요청을 해요.

프로브 결과에 따라 Kubernetes는 상태가 나쁜 컨테이너를 재시작하거나, 준비되지 않은 컨테이너에 트래픽 보내기를 중단할 수 있어요.

출처: Kubernetes 공식 문서 — Liveness, Readiness, and Startup Probes

프로브의 유형 (Types of probe)

kubelet은 실행 중인 컨테이너에 대해 세 가지 종류의 프로브를 선택적으로 수행하고 반응할 수 있는데, 각각 다른 목적을 제공해요.

  • 시작 프로브(Startup probe)
  • 활성 프로브(Liveness probe)
  • 준비성 프로브(Readiness probe)

시작 프로브 (Startup probe)

시작 프로브는 컨테이너 안의 애플리케이션이 시작되었는지 확인해요. 시작 프로브가 구성되면 Kubernetes는 시작 프로브가 성공할 때까지 활성 또는 준비성 프로브를 실행하지 않아요. 이렇게 해서 애플리케이션이 초기화를 마칠 시간을 줘요.

이 유형의 프로브는 주기적으로 실행되는 활성·준비성 프로브와 달리 시작 시에만 실행돼요. 시작 프로브가 실패하면 kubelet은 컨테이너를 죽이고, 컨테이너는 그 재시작 정책의 적용을 받아요.

활성 프로브 (Liveness probe)

활성 프로브는 컨테이너를 언제 재시작할지 결정해요. 예를 들어 애플리케이션이 실행 중이지만 진행할 수 없는 교착 상태(deadlock)를 활성 프로브가 잡아낼 수 있어요. 이런 상태의 컨테이너를 재시작하면 버그가 있어도 애플리케이션을 더 사용 가능하게 만드는 데 도움이 될 수 있어요.

컨테이너가 활성 프로브를 구성된 허용 횟수보다 더 많이 실패하면 kubelet은 그 컨테이너를 재시작해요. 활성 프로브는 준비성 프로브가 성공하기를 기다리지 않아요. 활성 프로브를 실행하기 전에 기다리고 싶다면 initialDelaySeconds를 정의하거나 시작 프로브를 사용할 수 있어요.

주의:

활성 프로브는 애플리케이션 실패에서 회복하는 강력한 방법이 될 수 있지만 주의해서 사용해야 해요. 활성 프로브는 교착 상태 같은 진짜 회복 불가능한 애플리케이션 실패를 나타내도록 신중하게 구성해야 해요.

활성 프로브를 잘못 구현하면 연쇄 실패로 이어질 수 있어요. 이는 높은 부하에서 컨테이너를 재시작하고, 애플리케이션이 덜 확장 가능해짐에 따라 클라이언트 요청이 실패하고, 일부 실패한 pod로 인해 남은 pod에 워크로드가 증가하는 결과를 낳아요. 활성 프로브와 준비성 프로브의 차이를 이해하고 앱에 언제 적용할지를 파악하세요.

준비성 프로브 (Readiness probe)

준비성 프로브는 컨테이너가 트래픽을 받을 준비가 되었는지 결정해요. 네트워크 연결 구축, 파일 로드, 캐시 예열 같은 시간이 걸리는 초기 작업을 애플리케이션이 수행하기를 기다릴 때 유용해요. 준비성 프로브는 일시적인 결함이나 과부하에서 회복할 때처럼 컨테이너 수명주기 후반에도 유용할 수 있어요.

준비성 프로브가 실패 상태를 반환하면, EndpointSlice 컨트롤러는 Pod와 일치하는 모든 Service의 EndpointSlice에서 Pod의 IP 주소를 제거해요.

준비성 프로브는 컨테이너의 전체 수명주기 동안 실행돼요.

참고:

Pod가 삭제될 때 요청을 배수(drain)할 수 있게 하려면 준비성 프로브가 꼭 필요한 것은 아니에요. Pod가 삭제되면 EndpointSlice의 해당 엔드포인트가 그 조건들을 업데이트해요. 엔드포인트 ready 조건이 false로 설정되므로 로드 밸런서는 일반 트래픽에 그 Pod를 사용하지 않아요. kubelet이 Pod 삭제를 처리하는 방법에 대한 자세한 내용은 Pod 종료를 참고하세요.

각 프로브를 언제 사용할까

시작 프로브는 언제 사용해야 하나요?

서비스에 도달하는 데 오래 걸리는 컨테이너가 있는 Pod에는 시작 프로브가 유용해요. 긴 활성 간격을 설정하는 대신, 컨테이너가 시작하는 동안 프로브하는 별도의 구성을 설정해 활성 간격이 허용하는 것보다 더 긴 시간을 줄 수 있어요.

컨테이너가 보통 이상 시작하는 데 걸린다면, 활성 프로브와 같은 엔드포인트를 확인하는 시작 프로브를 지정해야 해요. periodSeconds의 기본값은 10초예요. 그런 다음 활성 프로브의 기본값을 바꾸지 않고 컨테이너가 시작할 수 있도록 failureThreshold를 충분히 높게 설정해야 해요. 이는 교착 상태에 대한 보호에 도움이 돼요.

활성 프로브는 언제 사용해야 하나요?

컨테이너의 프로세스가 문제를 만나거나 상태가 나빠질 때 스스로 충돌할 수 있다면 활성 프로브가 꼭 필요하지 않아요. kubelet이 Pod의 restartPolicy에 따라 올바른 동작을 자동으로 수행할 거예요.

프로브가 실패할 때 컨테이너가 죽고 재시작되길 원한다면 활성 프로브를 지정하고 restartPolicyAlwaysOnFailure로 지정하세요.

활성 프로브의 일반적인 패턴은 준비성 프로브와 같은 저비용 HTTP 엔드포인트를 사용하되 failureThreshold를 더 높게 설정하는 거예요. 이렇게 하면 hard kill이 일어나기 전에 일정 기간 동안 Pod가 준비되지 않은 것으로 관찰되도록 보장해요.

준비성 프로브는 언제 사용해야 하나요?

프로브가 성공할 때만 Pod에 트래픽을 보내기 시작하려면 준비성 프로브를 지정하세요. 준비성 프로브는 활성 프로브와 같을 수 있지만, 스펙에 준비성 프로브가 존재한다는 것은 Pod가 트래픽을 전혀 받지 않고 시작하다가 프로브가 성공하기 시작한 후에야 트래픽을 받기 시작한다는 뜻이에요.

준비성 프로브를 사용해 활성 프로브와는 다른 준비성 전용 엔드포인트를 확인함으로써 컨테이너가 유지보수를 위해 스스로 내려가게 할 수도 있어요.

앱이 백엔드 서비스에 엄격하게 의존할 때 활성 프로브와 준비성 프로브를 모두 구현할 수 있어요. 활성 프로브는 앱 자체가 건강할 때 통과하고, 준비성 프로브는 각 필수 백엔드 서비스가 사용 가능한지 추가로 확인해요. 이렇게 하면 오류 메시지로만 응답할 수 있는 Pod로 트래픽을 보내는 것을 피하는 데 도움이 돼요.

시작 중에 큰 데이터, 구성 파일, 마이그레이션 로드를 처리해야 하는 컨테이너에는 시작 프로브 사용을 고려하세요. 하지만 실패한 앱과 시작 데이터를 아직 처리 중인 앱을 구분하고 싶다면 준비성 프로브가 더 나을 수 있어요.

확인 메커니즘 (Check mechanisms)

프로브를 사용해 컨테이너를 확인하는 네 가지 방법이 있어요. 각 프로브는 이 네 가지 메커니즘 중 정확히 하나를 정의해야 해요.

  • exec 컨테이너 안에서 지정된 명령을 실행해요. 명령이 상태 코드 0으로 종료되면 진단이 성공한 것으로 간주돼요.
  • grpc gRPC를 사용해 원격 프로시저 호출을 수행해요. 대상은 gRPC 상태 확인을 구현해야 해요. 응답의 statusSERVING이면 진단이 성공한 것으로 간주돼요. 자세한 내용은 gRPC 프로브를 참고하세요.
  • httpGet 지정된 포트와 경로에서 Pod의 IP 주소에 HTTP GET 요청을 수행해요. 응답의 상태 코드가 200 이상 400 미만이면 진단이 성공한 것으로 간주돼요. 자세한 내용은 HTTP 프로브를 참고하세요.
  • tcpSocket 지정된 포트에서 Pod의 IP 주소에 TCP 확인을 수행해요. 포트가 열려 있으면 진단이 성공한 것으로 간주돼요. 원격 시스템(컨테이너)이 열리자마자 연결을 닫으면 이것도 건강한 것으로 간주돼요. 자세한 내용은 TCP 프로브를 참고하세요.

주의:

다른 메커니즘과 달리 exec 프로브의 구현은 실행할 때마다 여러 프로세스를 만들거나 포크하는 것을 수반해요. 그 결과 pod 밀도가 높은 클러스터에서 initialDelaySeconds, periodSeconds 간격이 낮으면 exec 메커니즘으로 프로브를 구성하는 것이 노드의 CPU 사용에 오버헤드를 도입할 수 있어요. 그런 시나리오에서는 오버헤드를 피하기 위해 대체 프로브 메커니즘 사용을 고려하세요.

프로브 결과 (Probe results)

kubelet은 각 프로브 실행의 결과를 평가하고 그에 따라 조치를 취해요. 각 프로브는 세 가지 결과 중 하나를 가져요.

  • Success 컨테이너가 진단을 통과했어요.
  • Failure 컨테이너가 진단에 실패했어요. 활성과 시작 프로브의 경우 kubelet은 컨테이너를 죽이고, 컨테이너는 그 재시작 정책의 적용을 받아요. 준비성 프로브의 경우 kubelet은 컨테이너를 준비되지 않은 것으로 표시하고, Pod는 일치하는 Service로부터 트래픽 받기를 중단해요.
  • Unknown 진단이 실패했어요(조치를 취하지 않아야 하고, kubelet은 추가 확인을 할 거예요).

컨테이너가 특정 프로브를 제공하지 않으면 kubelet은 항상 결과를 Success로 간주해요. 특히 준비성 프로브의 경우 초기 지연 전에는 결과가 Failure로 간주돼요.

구성 필드 (Configuration fields)

프로브에는 시작, 활성, 준비성 확인의 동작을 더 정밀하게 제어하는 데 사용할 수 있는 여러 필드가 있어요. 예를 들어:

apiVersion: v1
kind: Pod
metadata:
  name: probe-example
spec:
  containers:
  - name: app
    image: registry.k8s.io/e2e-test-images/agnhost:2.40
    ports:
    - containerPort: 8080
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5
      timeoutSeconds: 3
      failureThreshold: 3
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      periodSeconds: 5
  • initialDelaySeconds 컨테이너가 시작된 후 시작, 활성, 준비성 프로브가 시작되기 전의 초 수예요. 시작 프로브가 정의되면 활성·준비성 프로브의 지연은 시작 프로브가 성공한 후에야 시작돼요. 일부 이전 Kubernetes 버전에서는 periodSeconds가 initialDelaySeconds보다 높은 값으로 설정되면 initialDelaySeconds가 무시될 수 있었어요. 하지만 현재 버전에서는 initialDelaySeconds가 항상 존중되며 이 초기 지연 후에야 프로브가 시작돼요. 기본값 0초, 최소값 0이에요.
  • periodSeconds 프로브를 수행하는 빈도(초)예요. 기본값 10초, 최소값 1이에요. 컨테이너가 Ready 상태가 아닌 동안에는 준비성 프로브가 구성된 periodSeconds 간격이 아닌 다른 시점에 실행될 수 있어요. 이는 Pod를 더 빨리 준비시키기 위한 거예요.
  • timeoutSeconds 프로브가 시간 초과되는 초 수예요. 기본값 1초, 최소값 1이에요.
  • successThreshold 실패한 후 프로브가 성공한 것으로 간주되는 데 필요한 최소 연속 성공 횟수예요. 기본값 1, 활성과 시작 프로브에는 1이어야 해요. 최소값 1이에요.
  • failureThreshold 프로브가 연속으로 failureThreshold번 실패하면 Kubernetes는 전체 확인이 실패한 것으로 간주해요: 컨테이너는 준비/건강/활성 상태가 아니에요. 기본값 3, 최소값 1이에요. 시작이나 활성 프로브의 경우 failureThreshold 이상의 프로브가 실패하면 Kubernetes는 컨테이너를 상태가 나쁜 것으로 취급하고 그 특정 컨테이너의 재시작을 촉발해요. kubelet은 그 컨테이너의 terminationGracePeriodSeconds 설정을 존중해요. 준비성 프로브가 실패하면 kubelet은 확인에 실패한 컨테이너를 계속 실행하고 더 많은 프로브도 계속 실행해요. 확인이 실패했기 때문에 kubelet은 Pod의 Ready 조건false로 설정해요.
  • terminationGracePeriodSeconds 실패한 컨테이너의 종료를 촉발한 다음 컨테이너 런타임이 그 컨테이너를 강제로 중지할 때까지 kubelet이 기다리는 유예 기간을 구성해요. 기본값은 Pod 수준의 terminationGracePeriodSeconds 값을 상속하며(지정하지 않으면 30초), 최소값은 1이에요. 자세한 내용은 프로브 수준의 terminationGracePeriodSeconds를 참고하세요.

주의:

준비성 프로브를 잘못 구현하면 컨테이너의 프로세스 수가 계속 늘어나고, 이것을 방치하면 리소스 고갈이 생길 수 있어요.

프로브 수준의 terminationGracePeriodSeconds

기능 상태: Kubernetes v1.28 [stable]

1.25 이상에서는 프로브 사양의 일부로 프로브 수준의 terminationGracePeriodSeconds를 지정할 수 있어요. Pod 수준과 프로브 수준의 terminationGracePeriodSeconds가 모두 설정되면 kubelet은 프로브 수준의 값을 사용해요.

terminationGracePeriodSeconds를 설정할 때 다음을 유의하세요.

  • kubelet은 Pod에 프로브 수준의 terminationGracePeriodSeconds 필드가 있으면 항상 그것을 존중해요.
  • terminationGracePeriodSeconds 필드가 설정된 기존 Pod가 있고 더 이상 프로브별 종료 유예 기간을 사용하고 싶지 않다면, 그 기존 Pod를 삭제해야 해요.

예를 들어:

spec:
  terminationGracePeriodSeconds: 3600 # pod-level
  containers:
  - name: test
    image: ...
    ports:
    - name: liveness-port
      containerPort: 8080
    livenessProbe:
      httpGet:
        path: /healthz
        port: liveness-port
      failureThreshold: 1
      periodSeconds: 60
      # Pod 수준의 terminationGracePeriodSeconds를 재정의
      terminationGracePeriodSeconds: 60

프로브 수준의 terminationGracePeriodSeconds는 준비성 프로브에는 설정할 수 없어요. API 서버가 거부할 거예요.

프로브 메커니즘 상세 (Probe mechanism details)

HTTP 프로브

HTTP 프로브에는 httpGet에 설정할 수 있는 추가 필드가 있어요.

  • host: 연결할 호스트 이름. 기본값은 pod IP예요. 보통은 대신 httpHeaders에 "Host"를 설정하고 싶을 거예요.
  • scheme: 호스트에 연결하기 위해 사용할 스킴(HTTP 또는 HTTPS). 기본값은 "HTTP"예요.
  • path: HTTP 서버에서 접근할 경로. 기본값은 "/"예요.
  • httpHeaders: 요청에 설정할 커스텀 헤더. HTTP는 반복 헤더를 허용해요.
  • port: 컨테이너에서 접근할 포트의 이름 또는 번호. 번호는 1~65535 범위에 있어야 해요.

HTTP 프로브의 경우 kubelet은 지정된 포트와 경로에 HTTP 요청을 보내 확인을 수행해요. kubelet은 httpGet의 선택적 host 필드로 주소가 재정의되지 않는 한 프로브를 Pod의 IP 주소로 보내요. scheme 필드가 HTTPS로 설정되면 kubelet은 인증서 검증을 건너뛰고 HTTPS 요청을 보내요. 대부분의 시나리오에서는 host 필드를 설정하고 싶지 않을 거예요. 설정해야 하는 한 가지 시나리오는 이렇습니다. 컨테이너가 127.0.0.1에서 수신하고 Pod의 hostNetwork 필드가 true라고 가정해보세요. 그러면 httpGet 아래의 host를 127.0.0.1로 설정해야 해요. 가상 호스트에 의존하는 pod라면(아마 더 흔한 경우), host를 사용하지 말고 httpHeaders에서 Host 헤더를 설정해야 해요.

HTTP 프로브의 경우 kubelet은 필수 Host 헤더 외에 두 개의 요청 헤더를 추가로 보내요.

  • 기본값이 kube-probe/1.36User-Agent(여기서 1.36은 kubelet의 버전)
  • 기본값이 */*Accept

프로브에 httpHeaders를 정의해 이 헤더들을 재정의할 수 있어요. 예를 들어:

livenessProbe:
  httpGet:
    httpHeaders:
    - name: Accept
      value: application/json
startupProbe:
  httpGet:
    httpHeaders:
    - name: User-Agent
      value: MyUserAgent

이 두 헤더를 빈 값으로 정의해 제거할 수도 있어요.

livenessProbe:
  httpGet:
    httpHeaders:
    - name: Accept
      value: ""
startupProbe:
  httpGet:
    httpHeaders:
    - name: User-Agent
      value: ""

리다이렉트 처리

kubelet이 HTTP를 사용해 컨테이너를 프로브할 때, 리다이렉트가 같은 호스트로 가는 경우에만 리다이렉트를 따라가요. 여기에는 프로브가 scheme: HTTP로 구성됐어도 HTTP에서 HTTPS로 프로토콜을 바꾸는 리다이렉트도 포함돼요.

리다이렉트가 다른 호스트 이름으로 가면 kubelet은 그것을 따르지 않아요. 대신 kubelet은 프로브를 성공으로 취급하고 ProbeWarning 이벤트를 기록해요.

kubelet이 리다이렉트를 따라가고 총 11개 이상의 리다이렉트를 받으면 프로브는 성공한 것으로 간주되고 ProbeWarning 이벤트를 기록해요. 예를 들어:

Events:
  Type     Reason        Age                From               Message
  ----     ------        ----               ----               -------
  Normal   Scheduled     29m                default-scheduler  Successfully assigned default/httpbin-7b8bc9cb85-bjzwn to daocloud
  Normal   Pulling       29m                kubelet            Pulling image "docker.io/kennethreitz/httpbin"
  Normal   Pulled        24m                kubelet            Successfully pulled image "docker.io/kennethreitz/httpbin" in 5m12.402735213s
  Normal   Created       24m                kubelet            Created container httpbin
  Normal   Started       24m                kubelet            Started container httpbin
  Warning  ProbeWarning  4m11s (x1197 over 24m)  kubelet      Readiness probe warning: Probe terminated redirects

주의:

httpGet 프로브를 처리할 때 kubelet은 응답 본문을 10KiB 후에 읽기를 중단해요. 프로브의 성공은 응답 헤더에 있는 응답 상태 코드에 의해서만 결정돼요.

10KiB보다 큰 응답 본문을 반환하는 엔드포인트를 프로브하면 kubelet은 상태 코드에 따라 프로브를 여전히 성공으로 표시하지만, 10KiB 한도에 도달하면 연결을 닫아요. 이 갑작스러운 종료는 애플리케이션 로그에 connection reset by peerbroken pipe 오류를 나타나게 할 수 있는데, 정당한 네트워크 문제와 구별하기 어려울 수 있어요.

신뢰할 수 있는 httpGet 프로브를 위해 최소한의 응답 본문을 반환하는 전용 상태 확인 엔드포인트를 사용하는 것이 강력히 권장돼요. 큰 페이로드가 있는 기존 엔드포인트를 사용해야 한다면 HEAD 요청을 수행하도록 exec 프로브 사용을 고려하세요.

TCP 프로브

TCP 프로브의 경우 kubelet은 Pod가 아닌 노드에서 프로브 연결을 만들어요. 즉 kubelet이 service 이름을 해석할 수 없으므로 host 파라미터에서 service 이름을 사용할 수 없어요.

gRPC 프로브

기능 상태: Kubernetes v1.27 [stable]

애플리케이션이 gRPC Health Checking Protocol을 구현한다면, Kubernetes가 그것을 애플리케이션 시작, 활성, 준비성 확인에 사용하도록 구성할 수 있어요.

다음은 예시 매니페스트예요:

apiVersion: v1
kind: Pod
metadata:
  name: etcd-with-grpc
spec:
  containers:
  - name: etcd
    image: registry.k8s.io/etcd:3.5.1-0
    command: [ "/usr/local/bin/etcd", "--data-dir", "/var/lib/etcd", "--listen-client-urls", "http://0.0.0.0:2379", "--advertise-client-urls", "http://127.0.0.1:2379", "--log-level", "debug" ]
    ports:
    - containerPort: 2379
    livenessProbe:
      grpc:
        port: 2379
      initialDelaySeconds: 10

gRPC 프로브를 사용하려면 port를 구성해야 해요. 다른 유형의 프로브와 다른 기능의 프로브를 구분하려면 service 필드를 사용할 수 있어요. service 값을 liveness로 설정하고 gRPC Health Checking 엔드포인트가 readiness로 설정할 때와는 다르게 이 요청에 응답하게 할 수 있어요. 이렇게 하면 두 개의 다른 포트에서 수신하는 대신 같은 엔드포인트를 다양한 종류의 컨테이너 상태 확인에 사용할 수 있어요. 자체 커스텀 service 이름과 프로브 유형을 모두 지정하려면, Kubernetes 프로젝트는 그것들을 연결하는 이름을 사용할 것을 권장해요. 예를 들어 myservice-liveness(-를 구분자로 사용)처럼요.

참고:

HTTP나 TCP 프로브와 달리 상태 확인 포트를 이름으로 지정할 수 없고, 커스텀 호스트 이름도 구성할 수 없어요.

구성 문제(예: 잘못된 포트나 service, 미구현 상태 확인 프로토콜)는 HTTP나 TCP 프로브와 비슷하게 프로브 실패로 간주돼요.

다음으로 볼 것 (What's next)