Kubernetes のヘルスチェック(Liveness Probe,Readiness Probe)を検証する

作業メモ。

[Kubernetes完全ガイド impress top gearシリーズ](https://www.amazon.co.jp/dp/B07HFS7TDT/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)を読みながら手元で確認した時のメモ。

# 環境

```bash
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.3", GitCommit:"2bba0127d85d5a46ab4b778548be28623b32d0b0", GitTreeState:"clean", BuildDate:"2018-05-28T20:03:09Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"11+", GitVersion:"v1.11.5-eks-6bad6d", GitCommit:"6bad6d9c768dc0864dab48a11653aa53b5a47043", GitTreeState:"clean", BuildDate:"2018-12-06T23:13:14Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"linux/amd64"}
```

# Kubernetes のヘルスチェックについて

[Configure Liveness and Readiness Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)

Kubernetes のヘルスチェックでは2種類のヘルスチェックがある。

+ Liveness Probe:Pod が正常に起動しているかの確認。ヘルスチェック失敗時には設定によって Pod の再起動も可能
+ Readiness Probe:Readiness(準備ができていること)の確認。Pod がサービスイン出来ているかの確認。ヘルスチェック失敗時にはトラフィックを流さないように出来る(Pod の再起動はしない）

なお、いずれも独立しており、両方設定することも片方のみ設定する事も可能。

# 3種類のヘルスチェック方式について

[Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)

どのような方式でヘルスチェックを行うかという方式については3種類ある。
Liveness/Readiness いずれでもすべて設定可能。

+ ExecAction:指定した任意のコマンドをコンテナ内で実行する。終了ステータスコードが0なら成功
+ TCPSocketAction:コンテナ IP の特定ポートについて TCP チェックを行う。ポートが開いていれば成功
+ HTTPGetAction:コンテナ IP の特定ポート・パスに対して HTTP GET リクエストを行う。レスポンスの HTTP ステータスコードが200以上400未満の場合、成功

# ExecAction を試す(Liveness Probe)

[Define a liveness command](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#define-a-liveness-command)

Liveness Probe で ExecAction を試す。

```exec-liveness.yaml
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
```

`args`に指定があるようにコンテナではヘルスチェック用のファイルを作成し、30秒 sleep 後、ヘルスチェック用ファイルを削除する。
また、ヘルスチェックではヘルスチェック用のファイル `/tmp/healhy` の存在を確認する。
`initialDelaySeconds` は kubelet がヘルスチェックを開始するまで初回に待機する時間で `periodSeconds` はヘルスチェック間隔。

上記より、こちらでは一定期間経過後、LivenessProbe が失敗し、Pod が再起動するはず。
試す。

```bash
$kubectl create -f https://k8s.io/examples/pods/probe/exec-liveness.yaml

# コンテナは起動成功
$kubectl describe pod liveness-exec

Events:
  Type    Reason     Age   From                                                      Message
  ----    ------     ----  ----                                                      -------
  Normal  Scheduled  4s    default-scheduler                                         Successfully assigned default/liveness-exec to ip-172-31-19-51.ap-northeast-1.compute.internal
  Normal  Pulling    3s    kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  pulling image "k8s.gcr.io/busybox"
  Normal  Pulled     2s    kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Successfully pulled image "k8s.gcr.io/busybox"
  Normal  Created    2s    kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Created container
  Normal  Started    2s    kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Started container


# 35秒後状態を確認すると unhealhy になっている
$kubectl describe pod liveness-exec

Events:
  Type     Reason     Age              From                                                      Message
  ----     ------     ----             ----                                                      -------
  Normal   Scheduled  43s              default-scheduler                                         Successfully assigned default/liveness-exec to ip-172-31-19-51.ap-northeast-1.compute.internal
  Normal   Pulling    42s              kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  pulling image "k8s.gcr.io/busybox"
  Normal   Pulled     41s              kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Successfully pulled image "k8s.gcr.io/busybox"
  Normal   Created    41s              kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Created container
  Normal   Started    41s              kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Started container
  Warning  Unhealthy  4s (x2 over 9s)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  
# 追加で待つと RESTART(再起動)が増加
$kubectl get pod liveness-exec
NAME            READY     STATUS    RESTARTS   AGE
liveness-exec   1/1       Running   2          2m

# イベントでも複数回 Created/Started があるのが確認できる.下記の場合、x3 となっている 
$kubectl describe pod liveness-exec
Events:
  Type     Reason     Age               From                                                      Message
  ----     ------     ----              ----                                                      -------
  Normal   Scheduled  3m                default-scheduler                                         Successfully assigned default/liveness-exec to ip-172-31-19-51.ap-northeast-1.compute.internal
  Normal   Pulling    45s (x3 over 3m)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  pulling image "k8s.gcr.io/busybox"
  Normal   Killing    45s (x2 over 2m)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Killing container with id docker://liveness:Container failed liveness probe.. Container will be killed and recreated.
  Normal   Pulled     44s (x3 over 3m)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Successfully pulled image "k8s.gcr.io/busybox"
  Normal   Created    43s (x3 over 3m)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Created container
  Normal   Started    43s (x3 over 3m)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Started container
  Warning  Unhealthy  5s (x8 over 2m)   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
```

# HTTPGetAction を試す(Liveness Probe)

次は LivenessProbe で HTTPGetAction を試す。

```http-liveness.yaml
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: X-Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3
```

パス「healhtz」のポート8080に HTTP GET リクエストを行い、ヘルスチェックを行う。
こちらのコンテナでは以下のようなプログラムが動いており、最初の10秒間はステータスコード200を返し、以降は500を返すようになっている。

```hoge.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"))
    }
})
```

試す。

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

# 時間経過後確認すると Liveness probe が失敗し、コンテナが kill されたメッセージがある
$ kubectl describe pod liveness-http
Events:
  Type     Reason     Age                From                                                      Message
  ----     ------     ----               ----                                                      -------
  Normal   Scheduled  1m                 default-scheduler                                         Successfully assigned default/liveness-http to ip-172-31-19-51.ap-northeast-1.compute.internal
  Normal   Pulled     32s (x3 over 1m)   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Successfully pulled image "k8s.gcr.io/liveness"
  Normal   Created    32s (x3 over 1m)   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Created container
  Normal   Started    32s (x3 over 1m)   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Started container
  Normal   Pulling    16s (x4 over 1m)   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  pulling image "k8s.gcr.io/liveness"
  Warning  Unhealthy  16s (x9 over 58s)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Liveness probe failed: HTTP probe failed with statuscode: 500
  Normal   Killing    16s (x3 over 52s)  kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Killing container with id docker://liveness:Container failed liveness probe.. Container will be killed and recreated.

# RESTARTS が増加している
$kubectl get pod liveness-http
NAME            READY     STATUS    RESTARTS   AGE
liveness-http   1/1       Running   2          54s


```

# TCPSocketAction を試す(Liveness Probe,Readiness Probe)

一つのマニュフェストに Liveness Probe と Readiness Probe を定義して TCPSocketAction を確認する。

```tcp-liveness-readiness.yaml
apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

```

上記マニュフェストでは Liveness Probe と Readiness Probe を定義しており、ポート8080に対して TCP 接続を試みて、接続できる場合にはヘルスチェック成功となる。
こちらのコンテナでは 8080 ポートでの接続は可能なため、いずれのヘルスチェックも成功する。

試す。

```bash
$kubectl create -f https://k8s.io/examples/pods/probe/tcp-liveness-readiness.yaml

# ヘルスチェックは成功しているのでエラーメッセージは出ない
$ kubectl describe pod goproxy
Events:
  Type    Reason     Age   From                                                      Message
  ----    ------     ----  ----                                                      -------
  Normal  Scheduled  36s   default-scheduler                                         Successfully assigned default/goproxy to ip-172-31-19-51.ap-northeast-1.compute.internal
  Normal  Pulling    36s   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  pulling image "k8s.gcr.io/goproxy:0.1"
  Normal  Pulled     33s   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Successfully pulled image "k8s.gcr.io/goproxy:0.1"
  Normal  Created    33s   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Created container
  Normal  Started    33s   kubelet, ip-172-31-19-51.ap-northeast-1.compute.internal  Started container

# RESTARS は0で READY 状態が1/1
$kubectl get pod goproxy
NAME      READY     STATUS    RESTARTS   AGE
goproxy   1/1       Running   0          2m
```

予想通り、Pod は再起動しない。