본문 바로가기
Container/Kubernetes

[Health Check] 1. Liveness Probe

by wrynn 2021. 11. 15.

 쿠버네티스에서 kubelet은 컨테이너가 정상적으로 사용자 요청을 처리할 수 있는지를 판단하기 위해 Liveness, Readiness, Startup 세가지 Probe를 사용합니다. 아래는 세가지 Probe에 대한 간단한 설명입니다.

Liveness Probe

 Liveness probe는 애플리케이션이 교착 상태에 머무르는 것을 감지하고, 컨테이너를 재시작하여 이를 해결합니다. 이렇게 하여 애플리케이션의 가용성을 확보할 수 있습니다.

Readiness Probe

 Readiness probe는 바쁜 컨테이너를 잠시 서비스에서 제외시켜 트래픽을 받지 않도록 하는 기능입니다. Pod 내의 모든 컨테이너가 Ready 상태가 된 경우, 해당 Pod는 준비가 된 것으로 간주하지만, 그렇지 않은 경우 서비스 로드밸런서로부터 제외되어 더 이상 트래픽을 받을 수 없게 됩니다. 이러한 기능은 Pod 내 일부 컨테이너가 일시적으로 트래픽을 처리할 수 없을 때에 유용하게 활용할 수 있습니다.

Startup Probe

 Startup Probe는 Readiness Probe와 비슷하게 Pod 내 각 컨테이너의 준비 상태를 확인합니다. 차이점은 최초 1회 Pod가 시작될 때에만 적용된다는 점입니다. 또한 모든 컨테이너가 준비 되기 전 까지는 Liveness 및 Readiness Probe를 비활성화합니다. 기동 시간이 오래 걸리는 컨테이너나, 초기화에 걸리는 시간이 예측 불가능한 애플리케이션에 유용합니다.

 이 글에서는 다양한 방법을 통해 Liveness Probe 를 설정해보고, 그 실행 결과를 살펴보겠습니다.


Linveness Probe - Command

 Liveness probe는 특정 조건을 주기적으로 확인하며 정상이 아니라고 판단될 때 컨테이너를 중단하고 다시 시작합니다. 이 조건은 명령 기반, HTTP, TCP 등 다양한 방법으로 지정할 수 있습니다. 가장 먼저 명령 기반으로 설정할 수 있는 방법을 알아보겠습니다. 아래 예제는 컨테이너 내에서 특정 명령을 주기적으로 실행하는 Liveness probe를 설정하는 방법을 나타냅니다. 

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

 우리가 여기서 주목해서 볼 필드는 .spec.containers[0] 아래의 livenessProbe 및 args 필드입니다.

.spec.containers[0].livenessProbe

  • periodSeconds: kubelet이 Liveness probe를 수행하는 주기입니다.
  • initialDelaySeconds: 컨테이너 시작 이후, 이 시간 동안은 Probe를 수행하지 않습니다.
  • exec : 명령 기반 Probe가 수행될 때, kubelet이 수행하는 명령입니다.

 위와 같이 livenessProbe를 설정하면, 컨테이너 시작 후 5초간은 Probe를 수행하지 않으며, 이후 5초마다 Probe를 수행하게 됩니다. Probe는 cat /tmp/healthy 명령으로 수행하며, 성공적으로 실행되어 0을 반환한다면 kubelet은 해당 컨테이너가 정상이라고 판단합니다. 여기서 반환 값은 콘솔 출력이 아닌 실제 함수의 리턴 값을 의미합니다. 만약 이 명령이 정상적으로 실행되지 않아 0이 아닌 값을 반환하는 상황이 failureThreshold 값 만큼 반복된다면(기본값: 3), kubelet은 컨테이너를 중지시킵니다. 중지 신호를 받은 컨테이너는 기본적으로 재시작되며, 특정 restartPolicy 설정에 의해 재시작이 아닌 다르게 동작하도록 설정할 수 있습니다.

.spec.containers[0].args 

 컨테이너는 실행 시 args 필드에 명시된 명령을 실행합니다. /tmp/healty 파일은 처음 30초동안은 존재하지만 그 이후에는 삭제되어 컨테이너 내에 존재하지 않게 됩니다. 따라서 cat /tmp/healthy 명령을 수행하는 Probe는 컨테이너 생성 후 처음 30초간은 Probe 실행 시 성공하지만, 그 이후로는 실패하게 됩니다.

$ /bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"

 

Probe 테스트

 이제 실제로 Pod를 하나 생성해서 Probe가 어떻게 수행되는지 확인해보겠습니다. 

$ kubectl apply -f https://k8s.io/examples/pods/probe/exec-liveness.yaml
pod/liveness-exec created

 Pod 생성을 한 다음, event를 확인해봅시다. Pod 생성 후 얼마 지나지 않았을때, kubectl describe 명령의 출력 결과 가장 아래 Events 항목은 다음과 같이 출력될 것입니다. Pod가 정상적으로 실행된 것을 확인할 수 있고, Liveness check가 시작된 것 또한 확인할 수 있습니다.

$ kubectl describe pods liveness-exec
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  30s   default-scheduler  Successfully assigned default/liveness-exec to ip-172-20-50-160.ap-northeast-2.compute.internal
  Normal  Pulling    29s   kubelet            Pulling image "k8s.gcr.io/busybox"
  Normal  Pulled     28s   kubelet            Successfully pulled image "k8s.gcr.io/busybox" in 1.087103548s
  Normal  Created    28s   kubelet            Created container liveness
  Normal  Started    28s   kubelet            Started container liveness

 35초가 지난 후에는 다시 describe를 했을 때에는 컨테이너 내에서 /tmp/healthy 파일이 삭제되었으므로 Events 항목에 아래와 같이 Liveness probe가 실패했다는 이벤트 메시지가 보입니다. 

$ kubectl describe pods liveness-exec
...
Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  37s   default-scheduler  Successfully assigned default/liveness-exec to ip-172-20-50-160.ap-northeast-2.compute.internal
  Normal   Pulling    36s   kubelet            Pulling image "k8s.gcr.io/busybox"
  Normal   Pulled     35s   kubelet            Successfully pulled image "k8s.gcr.io/busybox" in 1.087103548s
  Normal   Created    35s   kubelet            Created container liveness
  Normal   Started    35s   kubelet            Started container liveness
  Warning  Unhealthy  2s    kubelet            Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory

 이후 kubelet은 Liveness probe에 실패한 컨테이너를 재시작합니다. 그렇다면 정확히 어느 시점에 컨테이너가 다시 시작되는걸까요? Liveness Probe 실패가 3회 연속 발생한 바로 그 순간이라고 생각하기 쉽지만 여기서는 아닙니다. 실제로 컨테이너가 재시작되는 시점을 살펴보면 Liveness Probe 실패가 3회 연속 발생한 순간이 아닌 그로부터 30초가 지난 후에 재시작되는 것을 확인할 수 있습니다.

 결론부터 말씀드리자면 이는 쿠버네티스의 Pod Lifecycle 설정 때문입니다. Liveness Probe가 실패한 경우 kubelet은 컨테이너에 terminationGracePeriodSeconds 값과 함께 SIGTERM 신호를 보내게 됩니다. terminationGracePeriodSeconds 시간 내에 컨테이너가 중단되지 않는다면 컨테이너는 SIGKILL 신호를 받고 강제로 종료하게 됩니다. 여기서는 sleep 600 명령을 수행중인 컨테이너가 SIGTERM 신호를 받고도 중단되지 않았기에, SIGKILL 신호를 받고 컨테이너가 재시작되는 모습입니다. terminationGracePeriodSeconds의 기본값은 30초입니다. 


Linveness Probe - HTTP Request

 특정 명령어의 실행 결과가 아니라 HTTP 요청을 통해서도 Liveness probe를 활성화할 수 있습니다. 

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

 이전에 본 매니페스트 파일과 어떤 점이 다른지 파악하셨나요? 아래에서부터 살펴보면, periodSeconds 와 initialDelaySeconds 가 3초로 줄어들었습니다. 또한 livenessProbe의 exec 필드가 httpGet 필드로 대치되었으며 args 필드의 값도 바뀌었음을 확인할 수 있습니다.

.spec.containers[0].livenessProbe.httpGet

 Probe를 수행하기 위해 kubelet은 컨테이너 내에서 8080포트를 수신하고 있는 서버의 /healthz 경로로 HTTP GET 요청을 매 3초마다 지속적으로 보냅니다. 응답 코드가 2xx, 3xx 이라면 성공으로 간주하고 나머지는 실패로 간주합니다.

args

 서버 프로그램인 server 는 /healthz 요청에 대하여 처음 10초 동안은 http 응답 코드 200을 반환하고 10초가 지나면 500을 반환하도록 작성되어 있습니다. go 언어로 작성된 테스트용 서버 코드의 전체는 여기에서 확인할 수 있으며 아래는 주요한 부분 일부만 가져온 것입니다.

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
})

 

 이전과 같은 방법으로 Pod를 생성해봅시다.

$ kubectl apply -f https://k8s.io/examples/pods/probe/http-liveness.yaml

 곧바로 Pod의 상태를 확인해보면 별다른 이벤트가 없는 것을 확인할 수 있습니다. 생성 후 10초까지는 server 프로그램이 200 응답을 반환하므로, Liveness probe가 성공하여 정상 상태로 인식하게 됩니다.

$ kubectl describe pod liveness-http

 하지만 10초가 지나고 나면 HTTP GET 요청 시 500 응답을 받고 아래와 같이 Liveness probe failed 이벤트가 발생하는 것을 확인할 수 있습니다. 위의 예제와 달리 이번에는 3회의 probe fail 이후 컨테이너가 바로 재시작됩니다.

Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  14s   default-scheduler  Successfully assigned default/liveness-http to ip-172-20-50-160.ap-northeast-2.compute.internal
  Normal   Pulling    13s   kubelet            Pulling image "k8s.gcr.io/liveness"
  Normal   Pulled     12s   kubelet            Successfully pulled image "k8s.gcr.io/liveness" in 955.134748ms
  Normal   Created    12s   kubelet            Created container liveness
  Normal   Started    12s   kubelet            Started container liveness
  Warning  Unhealthy  2s    kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500

 


Linveness Probe - TCP

 TCP 연결 또한 Liveness probe 설정이 가능합니다. 아래는 goproxy 컨테이너가 시작한지 15초가 지난 후부터 Liveness probe를 시작하는 Pod를 나타냅니다. 또한 20초마다 한번씩 TCP 연결을 시도하여 성공하면 Probe가 성공한 것으로 판단하고, 그렇지 않으면 실패한 것으로 간주합니다. 위 예제들과는 달리 일부러 오류를 만들지 않았기 때문에 큰 문제가 발생하지 않는다면 지속적으로 정상 상태로 동작하게 됩니다. TCP Liveness probe 설정은 HTTP 연결이 적합하지 않은 gRPC나 FTP 서버에 활용할 수 있습니다.

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

 HTTP 또는 TCP Liveness Probe를 설정할 때, 다음과 같이 포트에 이름을 부여하여 사용할 수도 있습니다.

spec:
  ports:
  - name: liveness-port
    containerPort: 8080
    hostPort: 8080

  livenessProbe:
    httpGet:
      path: /healthz
      port: liveness-port

 

 다음 글에서는 Readiness Probe 및 Startup Probe에 대해 알아보겠습니다.


참고자료

[1] https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

 

Configure Liveness, Readiness and Startup Probes

This page shows how to configure liveness, readiness and startup probes for containers. The kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch a deadlock, where an application is running, but unable t

kubernetes.io

[2] https://blog.devgenius.io/understanding-kubernetes-probes-5daaff67599a

 

Understanding Kubernetes Probes

Configure readiness, liveness, and startup probes to detect and deal with unhealthy pods.

blog.devgenius.io

[3] https://cloud.redhat.com/blog/liveness-and-readiness-probes

 

Liveness and Readiness Probes

Liveness and Readiness Probes

content.cloud.redhat.com

 

댓글