LoginSignup
7
6

More than 5 years have passed since last update.

Podを自動リスタートするCronJob with RBAC

Last updated at Posted at 2018-08-18

TL;DR

  1. サンプルリポジトリをclone

    $ git clone https://github.com/nmatsui/kubernetes-restartpod-example.git
    $ cd kubernetes-restartpod-example
    
  2. サンプルのDeploymentを起動

    $ kubectl apply -f hello-world-api.yaml
    
  3. 再起動スケジュールを変更

    • スケジュールは、crontabスタイルを用いてUTCで記述する
    • 下記のように 45 3 * * *であれば、毎日UTCの午前3時45分(日本時間で12時45分)に再起動する、ということになる
    $ git diff restart-hello-world-api-cronjob.yaml
    spec:
    concurrencyPolicy: Replace
    -  schedule: "0 1 * * *"
    +  schedule: "45 3 * * *"
    jobTemplate:
    
  4. cronjobを登録

    $ kubectl apply -f restart-hello-world-api-cronjob.yaml
    
  5. 指定した時間になったら、サンプルDeploymentに所属しているPodが再起動される :yum:

はじめに

昔懐かしいアプリケーションでは、リソースリークやネットワーク再接続手順の不備等により、定期的に再起動が必要になるようなものがありました。

そもそもそのような筋の悪いアプリケーションを動かすべきではない、というのはその通りなのですが、既存のアプリケーションを流用してKubernetes上に再構築する場合など、どうにも手の入れようがないため暫定的に日次リスタートでごまかす、という運用をせざるを得ないこともあります。

従来はアプリケーションが動作しているサーバー上のcronに再起動タスクを仕込むようなものですが、これをKubernetesのREST APIとCronJobを利用して実現してみました。

Kuberntes上のアプリケーションを安全にリスタートするには

DeploymentのPod Templateのannotationを更新することでRolling Updateを開始し、従来のPodを一つづつ新しいPodに入れ替えることで、Podに内包されるアプリケーションを再起動します。
(このあたり、 @tkusumi さんの Kubernetes: Deployment のローリングアップデートの条件と設定 を参考にしました。ありがとうございます!)

kube-apiserverのREST APIとRBAC

通常Kubernetesは kubectl コマンドで操作しますが、Podの内部から操作する場合はkube-apiserverのREST APIへ直接アクセスしたほうが手間がかかりません。ただしRBACが有効な場合、kube-apiserverのREST APIを操作するためには、再起動ジョブを実行するPodのServiceAccountに適切な権限を与える必要があります。

設定ファイルのポイント

再起動されるアプリケーションのDeployment

再起動されるアプリケーションを内包したPodのDeploymentには、 spec.template.metadata.annotationsに lastUpdate というエントリを付け加えています。値は何でもかまいませんし、Podの動作には特に意味を持ちません。

詳細な内容は hello-world-api.yaml を参照してください。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world-api
#...(省略)...
spec:
  replicas: 3
#...(省略)...
  template:
    metadata:
#...(省略)...
      annotations:
        lastUpdate: "manual deploy" # <- コレ
    spec:
      containers:
#...(省略)...

再起動するジョブの権限

アプリケーションを再起動するジョブを内包したPodには、Deploymentのannotationsを書き換える(PATCHする)権限が必要です。

詳細な内容は restart-hello-world-api-cronjob.yaml を参照してください。

ServiceAccount

resterterという名前をつけて、再起動ジョブに紐付けるServiceAccountを定義します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: restarter

Role

default namespaceのDeploymentを書き換える(PATCHする)権限を定義します。DeploymentのAPIは apps に属するため、 apiGroupapps を指定します(DeploymentのapiVersionは apps/v1 と指定しますよね)。

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: restarter
  namespace: default
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["patch"]

RoleBinding

定義したServiceAccountに、定義したRoleを結びつけます。これにより、 ServiceAccount restarter に紐付けられたPodは、default namespaceのDeploymentを書き換える(PATCHする)ことが可能になります。

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: restarter
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: restarter
subjects:
- kind: ServiceAccount
  name: restarter
  namespace: default

再起動するCronJob

アプリケーションを再起動するジョブを内包したPodを、CronJobとして登録します。Jobとして実行されるPodのTemplateの serviceAccountName として、先程定義した resterter を指定してください。
明示的にServiceAccountを指定しなかった場合 default というServiceAccountに紐付けられますが、このServiceAccountではDeploymentを操作することができません。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: restart-hello-world-api
spec:
  concurrencyPolicy: Replace
  schedule: "0 1 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: restarter # <- コレ
          containers:
          - name: hello-world-api-restarter
            image: nmatsui/hello-world-api-restarter:latest
            imagePullPolicy: Always
            env:
            - name: NAMESPACE
              value: default
            - name: DEPLOYMENT_NAME
              value: hello-world-api
          restartPolicy: OnFailure

再起動ジョブのコンテナ

Dockerfile

kube-apiserverのREST APIにcurlでアクセスするだけですので、alpine linuxにcurlをインストールするだけでOKです。コンテナサイズはなんと4MBしかありません。

FROM alpine:3.8
MAINTAINER Nobuyuki Matsui <nobuyuki.matsui@gmail.com>

RUN apk update && apk add --no-cache curl
COPY ./entrypoint.sh /opt/entrypoint.sh
ENTRYPOINT ["/opt/entrypoint.sh"]

entrypoint.sh

Kuberntesの内部で動作するPodは、自Podに紐付けられたServiceAccountに対応するTOKENを /var/run/secrets/kubernetes.io/serviceaccount/token から取得することができます。またkube-apiserverのIPアドレスやポートも、Podの環境変数から取得することができます。便利ですね!

これらのTOKENとkube-apiserverのエンドポイントを用い、 /apis/apps/v1/namespaces/${NAMESPACE}/deployments/${DEPLOYMENT_NAME}/ にアクセスすれば、kube-apiserverが持つDeploymentを操作することができます。

今回は spec.template.metadata.annotations.lastUpdate を書き換えたいので、必ず異なる文字列になるUTCの現在日時で書き換えることにしました。

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -isSk -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/strategic-merge-patch+json" https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_PORT_443_TCP_PORT}/apis/apps/v1/namespaces/${NAMESPACE}/deployments/${DEPLOYMENT_NAME}/ -X PATCH -d @- <<__EOS__
{
  "spec": {
    "template": {
      "metadata": {
        "annotations": {
          "lastUpdate": "$(date '+%Y-%m-%dT%H:%M:%S.%sZ')"
        }
      }
    }
  }
}
__EOS__

動作確認

検証した環境

Kubernetes バージョン
クラスタ(AKS) 1.11.1
クライアント(kubectl) 1.11.2

サンプルDeploymentの起動後

サンプルDeployemntをapplyします。 lastUpdate が初期の"manual deploy"になっており、ReplicaSetも一つだけ作られています。

  • Deploymentの状態

    $ kubectl describe deployment hello-world-api
    Name:                   hello-world-api
    Namespace:              default
    CreationTimestamp:      Sat, 18 Aug 2018 12:33:59 +0900
    # ...(省略)...
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Annotations:  lastUpdate=manual deploy # <- コレ
      Containers:
       hello-world-api:
        Image:        nmatsui/hello-world-api:latest
    # ...(省略)...
    Events:
      Type    Reason             Age   From                   Message
      ----    ------             ----  ----                   -------
      Normal  ScalingReplicaSet  6m    deployment-controller  Scaled up replica set hello-world-api-5bd998dd8 to 3
    
  • ReplicaSetの状態

    $ kubectl get replicasets -l app=hell-world-api
    NAME                        DESIRED   CURRENT   READY     AGE
    hello-world-api-5bd998dd8   3         3         3         7m
    
  • Podの状態

    $ kubectl get pods -l app=hello-world-api
    NAME                              READY     STATUS    RESTARTS   AGE
    hello-world-api-5bd998dd8-8cfzh   1/1       Running   0          10m
    hello-world-api-5bd998dd8-mffkw   1/1       Running   0          10m
    hello-world-api-5bd998dd8-ptcfn   1/1       Running   0          10m
    

CronJobの登録

UTC 03:45 (日本時間 12:45) に再起動するように、CronJobを登録します。

  • cronjobの状態

    $ kubectl get cronjobs
    NAME                      SCHEDULE     SUSPEND   ACTIVE    LAST SCHEDULE   AGE
    restart-hello-world-api   45 3 * * *   False     0         <none>          5s
    

指定時間が到来

指定時間が到来した後に、サンプルDeploymentの状態を確認します。 lastUpdate の値がジョブPod起動時間に変わり、ReplicaSetが追加されて、Podが全てRoling Updateで入れ替わっていることが確認できます。

  • Deploymentの状態

    $ kubectl describe deployment hello-world-api
    Name:                   hello-world-api
    Namespace:              default
    CreationTimestamp:      Sat, 18 Aug 2018 12:33:59 +0900
    # ...(省略)...
    Pod Template:
      Annotations:  lastUpdate=2018-08-18T03:45:12.1534563912Z # <- コレ
      Containers:
       hello-world-api:
        Image:        nmatsui/hello-world-api:latest
    # ...(省略)...
    Events:
      Type    Reason             Age   From                   Message
      ----    ------             ----  ----                   -------
      Normal  ScalingReplicaSet  12m   deployment-controller  Scaled up replica set hello-world-api-5bd998dd8 to 3
      Normal  ScalingReplicaSet  1m    deployment-controller  Scaled up replica set hello-world-api-79987786fc to 1
      Normal  ScalingReplicaSet  1m    deployment-controller  Scaled down replica set hello-world-api-5bd998dd8 to 2
      Normal  ScalingReplicaSet  1m    deployment-controller  Scaled up replica set hello-world-api-79987786fc to 2
      Normal  ScalingReplicaSet  1m    deployment-controller  Scaled down replica set hello-world-api-5bd998dd8 to 1
      Normal  ScalingReplicaSet  1m    deployment-controller  Scaled up replica set hello-world-api-79987786fc to 3
      Normal  ScalingReplicaSet  1m    deployment-controller  Scaled down replica set hello-world-api-5bd998dd8 to 0
    
  • ReplicaSetの状態

    $ kubectl get replicasets -l app=hello-world-api
    NAME                         DESIRED   CURRENT   READY     AGE
    hello-world-api-5bd998dd8    0         0         0         12m
    hello-world-api-79987786fc   3         3         3         1m
    
  • Podの状態

    $ kubectl get pods -l app=hello-world-api
    NAME                               READY     STATUS    RESTARTS   AGE
    hello-world-api-79987786fc-l2q8n   1/1       Running   0          41s
    hello-world-api-79987786fc-n6l6d   1/1       Running   0          36s
    hello-world-api-79987786fc-r28z8   1/1       Running   0          32s
    

またJobの状態を確認すると、CronJobがJobを一つ起動したことがわかります。

  • Jobの状態

    $ kubectl get jobs
    NAME                                 DESIRED   SUCCESSFUL   AGE
    restart-hello-world-api-1534563900   1         1            2m
    

そのJobのPodのログを確認すると、kube-apiserverのAPIを使ってDeploymentを変更したことがわかります。

  • Jobのログ

    $ kubectl logs restart-hello-world-api-1534563900-txwc7
    #### start job 2018-08-18T03:45:12.1534563912Z ####
    HTTP/1.1 200 OK
    Server: nginx/1.13.6
    Date: Sat, 18 Aug 2018 03:45:12 GMT
    Content-Type: application/json
    Transfer-Encoding: chunked
    Connection: close
    Strict-Transport-Security: max-age=15724800; includeSubDomains;
    
    {
      "kind": "Deployment",
      "apiVersion": "apps/v1",
      "metadata": {
        "name": "hello-world-api",
        "namespace": "default",
        "selfLink": "/apis/apps/v1/namespaces/default/deployments/hello-world-api",
        "uid": "8b59788e-a297-11e8-b098-2aa0be8615f1",
        "resourceVersion": "31971",
        "generation": 2,
        "creationTimestamp": "2018-08-18T03:33:59Z",
    #...省略...
      },
      "spec": {
        "replicas": 3,
        "selector": {
          "matchLabels": {
            "app": "hello-world-api"
          }
        },
        "template": {
          "metadata": {
            "creationTimestamp": null,
            "labels": {
              "app": "hello-world-api"
            },
            "annotations": {
              "lastUpdate": "2018-08-18T03:45:12.1534563912Z"
            }
          },
          "spec": {
    #...省略...
    }
    #### end job 2018-08-18T03:45:12.1534563912Z ####
    

さいごに

ということで、CronJobを用いてPodを自動でリスタートさせることができました。どことなくバッドノウハウな気がしなくもないですが、お役に立てば幸いです。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6