KubernetesでLivenessProbeを試す

Kubernetesはコンテナの健全性を測定するために3つの仕組みを持っています。(StartupProbe / LivenessProbe / ReadinessProbe)今回はその一つ、LivenessProbeを実際に試してみたいと思います。

Probeについて、公式ドキュメントは下記が該当します。 https://kubernetes.io/ja/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes

Probeはそれぞれ異なる役割を持っています。LivenessProbeはコンテナをいつ再起動するかを判断します。再起動が必要な状況、例えばメモリリークなどに適用させるのが良いとされています。また、Podを再起動するわけではないことに気をつけてください。

環境

> kind --version
kind version 0.17.0

> docker --version
Docker version 23.0.3, build 3e7cbfd

> cat /etc/os-release
NAME="Fedora Linux"
VERSION="37 (Workstation Edition)"
ID=fedora
VERSION_ID=37
VERSION_CODENAME=""
PLATFORM_ID="platform:f37"
PRETTY_NAME="Fedora Linux 37 (Workstation Edition)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:37"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f37/system-administrators-guide/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=37
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=37
SUPPORT_END=2023-11-14
VARIANT="Workstation Edition"
VARIANT_ID=workstation

必要なリソースの作成

次のコマンドでマニフェストファイルの雛形を作成します。kubectlコマンドのリファレンスはこちらを参考にしました。また、kubectlコマンドに対しkのエイリアスを設定しているので適宜読み替えてください。

https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-deployment-em-

k create deployment wiremock --image=wiremock/wiremock:2.35.0 --dry-run=client -o yaml > deployment.wiremock.yaml

イメージで指定している通り、今回wiremockというツールを使ってLivenessProbeを試してみたいと思います。

この時点でのマニフェストファイルは以下のようになっています。Probeは一つも定義されていません。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: wiremock
  name: wiremock
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wiremock
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: wiremock
    spec:
      containers:
      - image: wiremock/wiremock:2.35.0
        name: wiremock
        resources: {}
status: {}

ここからLivenessProbeを定義していきます。Probeがコンテナの健全性をチェックする方式は3種類が用意されています。(拡張すれば増やせるんでしょうか?)

  • exec
  • httpGet
  • tcpSocket

今回は一番使われていそうなhttpGetを使いたいと思います。

wiremockを設定し特定のパスでアクセスした際HTTP ステータスが200で返ってくるようにします。以下のマニフェストに書き換え、Deploymentをapplyできるようにします。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wiremock
  name: wiremock
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wiremock
  strategy: {}
  template:
    metadata:
      labels:
        app: wiremock
    spec:
      containers:
      - image: wiremock/wiremock:2.35.0
        name: wiremock
        resources: {}

以下のコマンドでDeploymentを作成します。

k apply -f deployment.wiremock.yaml

wiremockにアクセスできるよう、次のコマンドでポートフォワードします。

k port-forward wiremock-6984ddd466-xp2vv 8080:8080

ターミナルを別で開き、次のコマンドでwiremockの設定をします。

curl -X POST \
    --data '{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "Here it is!\n" }}' \
    http://localhost:8080/__admin/mappings/new

これで、次のように200が返ってくるパスを設定できました。

curl -sS -v http://localhost:8080/get/this
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /get/this HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.85.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Matched-Stub-Id: a364d640-b003-46ee-987f-06203495bbea
< Vary: Accept-Encoding, User-Agent
< Transfer-Encoding: chunked
<
Here it is!
* Connection #0 to host localhost left intact

これでwiremockを設定することができましたが、この手順だとLivnessProbeが始まる前に設定を終わらせるのが難しそうです。仮に終わらないとコンテナが再起動されてしまい、一からやり直す事になってしまいます。なので、最初からこのようなレスポンスを返すようにしないといけません。

wiremockのDockerイメージは /home/wiremock/mappings に存在するjsonファイルを起動時に読み込みます。この仕組を利用し、コンテナ起動時に先程のレスポンスが返ってくるようにします。

jsonファイルの配置はコンテナイメージをビルドする時にコピーする方法もありますが、今回はConfigMapを使おうと思います。

apiversion: v1
kind: configmap
metadata:
  name: wiremock-mappings
immutable: true
data:
  livness_probe_mapping.json: |
    {
      "request": {
        "url": "/get/this",
        "method": "GET"
      },
      "response": {
        "status": 200,
        "body": "here it is!\n"
      }
    }

Deploymentのマニフェストファイルも更新し、上記のConfigMapを使うように設定します。同時に、アクセスログを標準出力へ書き込むよう、起動時引数を追加します。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wiremock
  name: wiremock
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wiremock
  strategy: {}
  template:
    metadata:
      labels:
        app: wiremock
    spec:
      containers:
      - image: wiremock/wiremock:2.35.0
        args:
          - "--verbose"
        name: wiremock
        volumeMounts:
          - name: wiremock-mappings-configmap
            mountPath: /home/wiremock/mappings
            readOnly: true
        resources: {}
      volumes:
      - name: wiremock-mappings-configmap
        configMap:
          name: wiremock-mappings

以下のコマンドでどちらのリソースも作成します。

> k apply -f deployment.wiremock.yaml
> k apply -f configmap.wiremock.yaml

先程試したようにcurlコマンドを打つとコンテナ起動直後からレスポンスが返ってくるようになります。

LivenessProbeの定義

Deploymentのマニフェストファイルを更新しLivenessProbeを設定します。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wiremock
  name: wiremock
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wiremock
  strategy: {}
  template:
    metadata:
      labels:
        app: wiremock
    spec:
      containers:
      - image: wiremock/wiremock:2.35.0
        args:
          - "--verbose"
        name: wiremock
        volumeMounts:
          - name: wiremock-mappings-configmap
            mountPath: /home/wiremock/mappings
            readOnly: true
        livenessProbe:
          httpGet:
            path: "/get/this"
            port: 8080
          periodSeconds: 5
          timeoutSeconds: 1
          failureThreshold: 3
        resources: {}
      volumes:
      - name: wiremock-mappings-configmap
        configMap:
          name: wiremock-mappings

設定値では、LivenessProbeは5秒間隔で実行され(periodSeconds)、1秒以内にレスポンスが200~399であることを確認します(timeoutSeconds)。3回連続で失敗した場合、ヘルスチェックが不合格となります(failureThreshold)。それぞれの設定項目を整理すると次のようになります。

  • periodSeconds ... 実行間隔(秒)
  • timeoutSeconds ... タイムアウト時間(秒)
  • failureThreshold ... 失敗と判断する、不合格回数

LivenessProbeの動作を確認する

それぞれのリソースを作成し、実際に動作していることを観測してみようと思います。Deploymentに関連付けてPodが作成されるので、コンテナのログを見てみましょう。

2023-04-10 11:15:04.017 Verbose logging enabled
2023-04-10 11:15:04.572 Verbose logging enabled
 /$$      /$$ /$$                     /$$      /$$                     /$$
| $$  /$ | $$|__/                    | $$$    /$$$                    | $$
| $$ /$$$| $$ /$$  /$$$$$$   /$$$$$$ | $$$$  /$$$$  /$$$$$$   /$$$$$$$| $$   /$$
| $$/$$ $$ $$| $$ /$$__  $$ /$$__  $$| $$ $$/$$ $$ /$$__  $$ /$$_____/| $$  /$$/
| $$$$_  $$$$| $$| $$  \__/| $$$$$$$$| $$  $$$| $$| $$  \ $$| $$      | $$$$$$/
| $$$/ \  $$$| $$| $$      | $$_____/| $$\  $ | $$| $$  | $$| $$      | $$_  $$
| $$/   \  $$| $$| $$      |  $$$$$$$| $$ \/  | $$|  $$$$$$/|  $$$$$$$| $$ \  $$
|__/     \__/|__/|__/       \_______/|__/     |__/ \______/  \_______/|__/  \__/

port:                         8080
enable-browser-proxying:      false
disable-banner:               false
no-request-journal:           false
verbose:                      true

2023-04-10 11:15:08.686 Request received:
10.244.1.1 - GET /get/this

Host: [10.244.1.14:8080]
User-Agent: [kube-probe/1.25]
Accept: [*/*]
Connection: [close]



Matched response definition:
{
  "status" : 200,
  "body" : "here it is!\n"
}

Response:
HTTP/1.1 200
Matched-Stub-Id: [ff2d9379-40f5-4554-be54-25296345a647]

2023-04-10 11:15:13.624 Request received:
10.244.1.1 - GET /get/this

Host: [10.244.1.14:8080]
User-Agent: [kube-probe/1.25]
Accept: [*/*]
Connection: [close]



Matched response definition:
{
  "status" : 200,
  "body" : "here it is!\n"
}

Response:
HTTP/1.1 200
Matched-Stub-Id: [ff2d9379-40f5-4554-be54-25296345a647]

2023-04-10 11:15:18.624 Request received:
10.244.1.1 - GET /get/this

Host: [10.244.1.14:8080]
User-Agent: [kube-probe/1.25]
Accept: [*/*]
Connection: [close]



Matched response definition:
{
  "status" : 200,
  "body" : "here it is!\n"
}

Response:
HTTP/1.1 200
Matched-Stub-Id: [ff2d9379-40f5-4554-be54-25296345a647]

少し分かりづらいですが、5秒間隔で /get/this にGETリクエストが来ていることがわかります。Deploymentをdescribeしてみると、様々な情報を確認できます。

> k describe deploy wiremock
Name:                   wiremock
Namespace:              default
CreationTimestamp:      Tue, 11 Apr 2023 21:45:25 +0900
Labels:                 app=wiremock
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=wiremock
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=wiremock
  Containers:
   wiremock:
    Image:      wiremock/wiremock:2.35.0
    Port:       <none>
    Host Port:  <none>
    Args:
      --verbose
    Liveness:     http-get http://:8080/get/this delay=0s timeout=1s period=5s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      /home/wiremock/mappings from wiremock-mappings-configmap (ro)
  Volumes:
   wiremock-mappings-configmap:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      wiremock-mappings
    Optional:  false
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   wiremock-856c89cccd (1/1 replicas created)
Events:
  Type    Reason             Age    From                   Message
  ----    ------             ----   ----                   -------
  Normal  ScalingReplicaSet  2m12s  deployment-controller  Scaled up replica set wiremock-856c89cccd to 1

Pod Template > Containers > wiremock > Liveness をよく見てみると設定されているLivnessProbeが反映されていることがわかります。

2つのマニフェストファイルを一つにまとめる

今回、DeploymentとConfigMapの2つのYAMLを書きました。これは一つのファイルにまとめることができます。次のようになります。

apiVersion: v1
kind: ConfigMap
metadata:
  name: wiremock-mappings
immutable: true
data:
  livness_probe_mapping.json: |
    {
      "request": {
        "url": "/get/this",
        "method": "GET"
      },
      "response": {
        "status": 200,
        "body": "here it is!\n"
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wiremock
  name: wiremock
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wiremock
  strategy: {}
  template:
    metadata:
      labels:
        app: wiremock
    spec:
      containers:
      - image: wiremock/wiremock:2.35.0
        args:
          - "--verbose"
        name: wiremock
        volumeMounts:
          - name: wiremock-mappings-configmap
            mountPath: /home/wiremock/mappings
            readOnly: true
        livenessProbe:
          httpGet:
            path: "/get/this"
            port: 8080
          periodSeconds: 5
          timeoutSeconds: 1
          failureThreshold: 3
        resources: {}
      volumes:
      - name: wiremock-mappings-configmap
        configMap:
          name: wiremock-mappings