CLOVER🍀

That was when it all began.

OKD/Minishift上で、Liveness/Readiness Probeを設定する

これは、なにをしたくて書いたもの?

  • OpenShift(OKD)/KubernetesでのLiveness Probe/Readiness Probeを設定して、動作確認してみたい

目的は、いたってシンプルです。

Liveness Probe/Readiness Probe

Liveness Probe、Readiness Probeについての、KubenetesおよびOKDでのドキュメントはこちら。

Container probes

Application Health | Developer Guide | OKD 3.11

Liveness Probe、Readiness Probeについてですが、それぞれこういうものみたいです。

OKDの「oc set probe」のヘルプより参照。

$ oc set probe --help
Set or remove a liveness or readiness probe from a pod or pod template 

Each container in a pod may define one or more probes that are used for general health checking. A liveness probe is
checked periodically to ensure the container is still healthy: if the probe fails, the container is restarted. Readiness
probes set or clear the ready flag for each container, which controls whether the container's ports are included in the
list of endpoints for a service and whether a deployment can proceed. A readiness check should indicate when your
container is ready to accept incoming traffic or begin handling work. Setting both liveness and readiness probes for
each container is highly recommended. 

The three probe types are: 

  1. Open a TCP socket on the pod IP  
  2. Perform an HTTP GET against a URL on a container that must return 200 OK  
  3. Run a command in the container that must return exit code 0 
Liveness Probe

コンテナが、実行中かどうかを確認することができます。いわゆる、ヘルスチェックです。

Liveness Probeで設定したヘルスチェックが失敗した場合、Kubenetesはコンテナを強制終了し、再起動を行います。

デフォルトではLiveness Probeは設定されていないので、常にヘルスチェックに成功している状態と見なされます。
※Pod内のコンテナのプロセスが稼働できなければ、失敗しますが…

ヘルスチェックとして設定できるのは、以下の3つです。

  • HTTP GET … 指定されたURLにHTTP GETを実行し、レスポンスとなるステータスコードが200以上400未満なら成功とみなす
  • TCP Socket … 指定されたポートに、TCP接続が可能であれば成功とみなす
  • Exec … 指定されたコマンドをコンテナ内で実行し、ステータスコードが0であれば成功とみなす
Readiness Probe

設定できることは、Liveness Probeと同じです。Liveness Probeとの違いは、その目的です。

Readiness Probeは、そのPodが「サービスを提供可能な状態になったか?」ということを確認する目的で使われます。

例えば、起動に時間がかかるサーバーアプリケーションなどで、プロセスは起動しているものの、リクエストをまだ受け付けられる
状態になっていない場合は、サービスに参加させたくありません。

Kubenetesにおいては、該当のPodをServiceに対するエンドポイントに追加してよいかを、Readiness Probeとして設定します。

デフォルトではReadiness Probeは設定されていないので、Podが起動したらすぐにService配下に加えられます。

つまり

例えば、Kubernetes上で動作するサーバーアプリケーションをスケールアウトした時に、デフォルト設定のままだと
追加されたPodにルーティングされた場合にアプリケーションが起動しきっていないためレスポンスが返ってこないことが
ありますが、そのようなケースを防止することができます。

Liveness Probe、Readiness Probeはそれぞれ目的が異なりますが、ちゃんと設定しましょうね、と。

では、早速サンプルアプリケーションを作って確認してみましょう。

環境

$ minishift version
minishift v1.26.1+1e20f27


$ oc version
oc v3.11.0+0cbc58b
kubernetes v1.11.0+d4cacc0
features: Basic-Auth GSSAPI Kerberos SPNEGO

Server https://192.168.42.78:8443
kubernetes v1.11.0+d4cacc0

アプリケーションを作成する

Node.js+Expressを使って、簡単なサーバーアプリケーションを作成してみましょう。

$ npm i -S express

今回のExpressのバージョンは、こちら。

  "dependencies": {
    "express": "^4.16.4"
  }

ソースコード
server.js

const express = require('express');
const os = require('os');

const app = express();

app.get('/healthy/liveness', (req, res) => {
    console.log(`[${new Date()}] access liveness`);
    res.send('Liveness OK!!');
});

app.get('/healthy/readiness', (req, res) => {
    console.log(`[${new Date()}] access readiness`);
    res.send('Readiness OK!!');
});

app.get('/message', (req, res) => {
    console.log(`[${new Date()}] access app`);
    res.send(`Hello App!! from ${os.hostname()}`);
});

console.log(`[${new Date()}] server start.`);
app.listen(8080);

「/message」でホスト名入りのメッセージを返すようにし、あとでLiveness Probe、Readiness Probeとして利用するための
エンドポイントも作成しています。

で、こちらのソースコードを適当なGitリポジトリに登録しておきます。

確認

まずは、デプロイ、Routeの設定。

$ oc new-app [GitリポジトリのURL]
$ oc expose svc/node-probe

デプロイ完了後、アクセスするとメッセージが返ってきます。

$ curl node-probe-myproject.192.168.42.78.nip.io/message
Hello App!! from node-probe-1-5rfsmk

ログを確認。

$ oc logs dc/node-probe
git version 2.9.3
Environment: 
    DEV_MODE=false
    NODE_ENV=production
    DEBUG_PORT=5858
Running as user uid=1000140000(default) gid=0(root) groups=0(root),1000140000
Launching via npm...
npm info it worked if it ends with ok
npm info using npm@6.4.1
npm info using node@v10.12.0
> probe@1.0.0 start /opt/app-root/src
> node server.js
npm info lifecycle probe@1.0.0~prestart: probe@1.0.0
npm info lifecycle probe@1.0.0~start: probe@1.0.0
[Sat Nov 03 2018 12:36:17 GMT+0000 (Coordinated Universal Time)] server start.
[Sat Nov 03 2018 12:36:56 GMT+0000 (Coordinated Universal Time)] access app

当然ですが、Liveness Probe、Readiness Probe用に設定したエンドポイントへのアクセスはありません。

スケールアウト。

$ oc scale --replicas=3 dc/node-probe

今回のサンプルの場合は、追加されたコンテナが割とすぐに使えるようになりますが、「oc start-build」をするなどして
コンテナを入れ替えたりすると、場合によってはcurlが応答しないことがあったりします。

では、Liveness Probe、Readiness Probeを設定してみましょう。

設定方法は、ヘルプで確認。

$ oc set probe --help

...

Examples:
  # Clear both readiness and liveness probes off all containers
  oc set probe dc/registry --remove --readiness --liveness
  
  # Set an exec action as a liveness probe to run 'echo ok'
  oc set probe dc/registry --liveness -- echo ok
  
  # Set a readiness probe to try to open a TCP socket on 3306
  oc set probe rc/mysql --readiness --open-tcp=3306
  
  # Set an HTTP readiness probe for port 8080 and path /healthz over HTTP on the pod IP
  oc set probe dc/webapp --readiness --get-url=http://:8080/healthz
  
  # Set an HTTP readiness probe over HTTPS on 127.0.0.1 for a hostNetwork pod
  oc set probe dc/router --readiness --get-url=https://127.0.0.1:1936/stats
  
  # Set only the initial-delay-seconds field on all deployments
  oc set probe dc --all --readiness --initial-delay-seconds=30

今回は、HTTP GETを使用します。

## Liveness Probe
$ oc set probe dc/node-probe --liveness --get-url=http://:8080/healthy/liveness

## Readiness Probe
$ oc set probe dc/node-probe --readiness --get-url=http://:8080/healthy/readiness

Podが再作成されるので、ログを見るとLiveness Probe、Readiness Probeで設定したエンドポイントにアクセスが来ていることが
わかります。

$ oc logs pod/node-probe-5-2nbwb
git version 2.9.3
Environment: 
    DEV_MODE=false
    NODE_ENV=production
    DEBUG_PORT=5858
Running as user uid=1000140000(default) gid=0(root) groups=0(root),1000140000
Launching via npm...
npm info it worked if it ends with ok
npm info using npm@6.4.1
npm info using node@v10.12.0
npm info lifecycle probe@1.0.0~prestart: probe@1.0.0
npm info lifecycle probe@1.0.0~start: probe@1.0.0
> probe@1.0.0 start /opt/app-root/src
> node server.js
[Sat Nov 03 2018 12:43:29 GMT+0000 (Coordinated Universal Time)] server start.
[Sat Nov 03 2018 12:43:34 GMT+0000 (Coordinated Universal Time)] access readiness
[Sat Nov 03 2018 12:43:37 GMT+0000 (Coordinated Universal Time)] access liveness
[Sat Nov 03 2018 12:43:42 GMT+0000 (Coordinated Universal Time)] access app
[Sat Nov 03 2018 12:43:44 GMT+0000 (Coordinated Universal Time)] access readiness
[Sat Nov 03 2018 12:43:47 GMT+0000 (Coordinated Universal Time)] access liveness
[Sat Nov 03 2018 12:43:54 GMT+0000 (Coordinated Universal Time)] access readiness
[Sat Nov 03 2018 12:43:57 GMT+0000 (Coordinated Universal Time)] access liveness

また、「--initial-delay-seconds」でヘルスチェックを開始するまでの時間を、「--period-seconds」でヘルスチェクのインターバルを
指定できるようです。

$ oc set probe dc/node-probe --liveness --get-url=http://:8080/healthy/liveness --initial-delay-seconds=5 --period-seconds=5
$ oc set probe dc/node-probe --readiness --get-url=http://:8080/healthy/readiness --initial-delay-seconds=10 --period-seconds=3

結果。

$ oc logs pod/node-probe-7-c4775 -f
git version 2.9.3
Environment: 
    DEV_MODE=false
    NODE_ENV=production
    DEBUG_PORT=5858
Running as user uid=1000140000(default) gid=0(root) groups=0(root),1000140000
Launching via npm...
npm info it worked if it ends with ok
npm info using npm@6.4.1
npm info using node@v10.12.0
npm info lifecycle probe@1.0.0~prestart: probe@1.0.0
npm info lifecycle probe@1.0.0~start: probe@1.0.0
> probe@1.0.0 start /opt/app-root/src
> node server.js
[Sat Nov 03 2018 12:47:07 GMT+0000 (Coordinated Universal Time)] server start.
[Sat Nov 03 2018 12:47:12 GMT+0000 (Coordinated Universal Time)] access liveness
[Sat Nov 03 2018 12:47:17 GMT+0000 (Coordinated Universal Time)] access readiness
[Sat Nov 03 2018 12:47:17 GMT+0000 (Coordinated Universal Time)] access liveness
[Sat Nov 03 2018 12:47:20 GMT+0000 (Coordinated Universal Time)] access readiness
[Sat Nov 03 2018 12:47:22 GMT+0000 (Coordinated Universal Time)] access liveness
[Sat Nov 03 2018 12:47:23 GMT+0000 (Coordinated Universal Time)] access readiness

この時のYAMLは、こんな感じです。

$ oc get dc/node-probe -o yaml
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:

...

spec:
  replicas: 3

...

  template:
    metadata:

...

    spec:
      containers:
      - image: 172.30.1.1:5000/myproject/node-probe@sha256:521e4657c17e4f3efca9a9b284a7bf1f7f290e242d958ff04b0ac6e0580afad8
        imagePullPolicy: Always
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthy/liveness
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          timeoutSeconds: 1
        name: node-probe
        ports:
        - containerPort: 8080
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthy/readiness
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 3
          successThreshold: 1
          timeoutSeconds: 1
        resources: {}

...

その他、「successThreshold」、「failureThreshold」、「timeoutSeconds」などがポイントな感じですね。

設定したProbeを削除するには、「--remove」で。

$ oc set probe dc/node-probe --remove --liveness --readiness

エラーにしてみる

今度は、再度Probeを設定して、意図的にエラーにしてみましょう。

まずは、Probeを再設定。

$ oc set probe dc/node-probe --liveness --get-url=http://:8080/healthy/liveness
$ oc set probe dc/node-probe --readiness --get-url=http://:8080/healthy/readiness

Podが3つあります。

$ oc get pod
NAME                  READY     STATUS      RESTARTS   AGE
node-probe-1-build    0/1       Completed   0          20m
node-probe-10-59j7d   1/1       Running     0          31s
node-probe-10-6z6kg   1/1       Running     0          48s
node-probe-10-c5lqx   1/1       Running     0          42s

このPodは、Probeによるヘルスチェックは成功しています。

ここで、Liveness Probeを意図的に存在しないエンドポイントに向けてみましょう。

$ oc set probe dc/node-probe --liveness --get-url=http://:8080/healthy/liveness-notfound

「oc get event」で確認すると、Liveness Probeが失敗していることがわかります。

$ oc get event -w

...

1s        11s       2         node-probe-11-4bzh9.15639e9e7f0f9dff   Pod       spec.containers{node-probe}   Warning   Unhealthy   kubelet, localhost   Liveness probe failed: HTTP probe failed with statuscode: 404
1s        11s       2         node-probe-11-p8m47.15639e9ee671d7fe   Pod       spec.containers{node-probe}   Warning   Unhealthy   kubelet, localhost   Liveness probe failed: HTTP probe failed with statuscode: 404

この状態のPodは、最初は動いているように見えるのですが、再起動を繰り返し

$ oc get pod
NAME                  READY     STATUS             RESTARTS   AGE
node-probe-1-build    0/1       Completed          0          25m
node-probe-11-4bzh9   0/1       CrashLoopBackOff   5          3m
node-probe-11-p8m47   1/1       Running            5          3m
node-probe-11-vz97k   1/1       Running            5          3m

最終的にはすべて停止します。

$ oc get pod
NAME                  READY     STATUS             RESTARTS   AGE
node-probe-1-build    0/1       Completed          0          26m
node-probe-11-4bzh9   0/1       CrashLoopBackOff   5          4m
node-probe-11-p8m47   0/1       CrashLoopBackOff   5          4m
node-probe-11-vz97k   0/1       CrashLoopBackOff   5          4m

こうなると、全Podが停止したので503が返ることになります。

$ curl -i --head node-probe-myproject.192.168.42.78.nip.io/message
HTTP/1.0 503 Service Unavailable
Pragma: no-cache
Cache-Control: private, max-age=0, no-cache, no-store
Connection: close
Content-Type: text/html

では、Liveness Probeを戻しておきます。

$ oc set probe dc/node-probe --liveness --get-url=http://:8080/healthy/liveness

この時のPodはこちら。

$ oc get pod
NAME                  READY     STATUS      RESTARTS   AGE
node-probe-1-build    0/1       Completed   0          39m
node-probe-14-ch8v4   1/1       Running     0          37s
node-probe-14-qcqxh   1/1       Running     0          59s
node-probe-14-tf4qd   1/1       Running     0          49s

ここで、Readiness Probeをエンドポイントが存在しないものに設定してみます。

$ oc set probe dc/node-probe --readiness --get-url=http://:8080/healthy/readiness-notfound

「oc get event」で見ると、Readiness Probeが失敗していることがわかります。

$ oc get event -w

...

1s        1s        1         node-probe-15-s8bf5.15639f9792fbb050   Pod       spec.containers{node-probe}   Warning   Unhealthy   kubelet, localhost   Readiness probe failed: HTTP probe failed with statuscode: 404
1s        11s       2         node-probe-15-s8bf5.15639f9792fbb050   Pod       spec.containers{node-probe}   Warning   Unhealthy   kubelet, localhost   Readiness probe failed: HTTP probe failed with statuscode: 404

この状態になると、新しく作成されたPodが古いPodと入れ替わらなくなるようです。

$ oc get pod
NAME                   READY     STATUS      RESTARTS   AGE
node-probe-1-build     0/1       Completed   0          42m
node-probe-14-ch8v4    1/1       Running     0          3m
node-probe-14-qcqxh    1/1       Running     0          4m
node-probe-14-tf4qd    1/1       Running     0          3m
node-probe-15-deploy   1/1       Running     0          2m
node-probe-15-s8bf5    0/1       Running     0          2m

curlで確認すると、古いPodが動いていることが確認できます。

$ curl node-probe-myproject.192.168.42.78.nip.io/message
Hello App!! from node-probe-14-tf4qd

$ curl node-probe-myproject.192.168.42.78.nip.io/message
Hello App!! from node-probe-14-ch8v4

$ curl node-probe-myproject.192.168.42.78.nip.io/message
Hello App!! from node-probe-14-qcqxh

これで、Probe失敗時にはどうなるか確認できましたね。