KubernetesのPodを定期的に再起動(ローリングリスタート)する

## 概要

KubernetesのCronJobを利用して、定期的にPodを再起動する仕組みを用意します。

必要な手順としては、

- kubectl1.15以降をインストール
- サービスロールを用意し、Kubectlを操作できるロールを用意する
- 定期的に kubectl rollout restart を実行できるようにCronJobを定義する

ローリングリスタートはKubernetes1.15以降で利用可能です。しかしkubectlのバージョンさえ1.15以降ならよく、クラスタのバージョンはそれより古くても大丈夫です。

## 前置き

Ruby on RailsアプリケーションをKubernetes(EKS）上で動かしているのですが、Podを立ち上げっぱなしにしているといつの間にかメモリ使用量がモリモリ増えていって上限に達してしまうという事象が発生していました。

アプリサーバーに利用しているPumaが無限にメモリを使ってくれていたようです。

コンテナ化していない場合には [puma_worker_killer](https://github.com/schneems/puma_worker_killer) を利用して定期的にPumaのワーカーを再起動するようにするというのがよくある対処法ですが、せっかくKubernetesを使っているのだから、Podごと立ち上げ直してしまったほうがよりクリーンかなぁと思いそちらの方向で対処していくことにしました。

こういうの。

https://qiita.com/tkm_kj/items/6d080fc61c8d3c96bb99

## 前準備

まずはKubectlのバージョンを1.15以降にあげます。クラスタ側は1.12でも大丈夫でした。

さて、Podを再起動するだけだとPodが立ち上がっただけでingressのロードバランシング振り分け先に加えられてしまいます。Rails(Puma)が立ち上がっていないのにalb-ingressは「こっちにもふりわけちゃおう」となってしまい、ALBが502を返すという状態になってしまいます。

Railsが立ち上がってリクエストへの応答が可能になるまでは振り分けられると困るので、ReadinessProbeの設定を行っておきましょう。

ReadinessProbeとLivinessProbeについては↓が詳しいのでよく知りたい方は見てください。要するに、ReadinessProbeが失敗している間はロードバランシングの対象にならないので安心というわけです。

[Kubernetes道場 10日目 - LivenessProbe / ReadinessProbeについて](https://cstoku.dev/posts/2018/k8sdojo-10/)

今回必要なのはReadinessProbeですが、ついでにlivenessProbeのほうも書いておきましょう。

```yaml:app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sugoi-app
  spec:
    template:
      spec:
        livenessProbe:
          # an http probe
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 60
          timeoutSeconds: 30
          periodSeconds: 60
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
        ...
```

## サービスアカウント・ロールの準備

Kubernetesの上で動いているコンテナには、Kubernetes APIの設定が自動的にインポートされています。なのでコンテナの中にKubectlを配置するだけで叩くべきKubernetes APIはすでに設定済みという状況になります。

`aws eks update-kubeconfig --name sugoi-eks-cluster`

とかやる必要ありません。(私はEKSでKubernetesを使っています)

しかし、この状態で利用されるサービスアカウントはdefaultであり、Podの再起動をするには権限が足りません。そこで再起動させるCronJob用にサービスアカウントを新しく定義し、そのサービスアカウントからPodの再起動ができるように強い権限をふります。

```yaml:rbac.yaml
---

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: staging
  name: pod-restarter

---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-pod-restart
subjects:
  - kind: ServiceAccount
    name: pod-restarter
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

---
```

どの権限が必要なのか細かく調べて設定するまでは不要かと思い、cluster-admin権限を振っています。

## CronJobの定義

で、このサービスアカウントを利用する再起動CronJobを定義します。

```yaml:restart.yaml
---

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: restart-pods
  labels:
    app: system-cron
    cronjob: restart-pods
spec:
  concurrencyPolicy: Replace
  schedule: "0 0 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          serviceAccount: 'pod-restarter'
          containers:
          - name: app
            image: lachlanevenson/k8s-kubectl:v1.15.4
            command: ['kubectl', 'rollout', 'restart', 'deployment.apps/sugoi-app', ', '-n', 'production']

---
```

ここまでのファイルをapplyすると、１日１回sugoi-appのPodを再起動してくれます。

新しいPodがサーバーとして機能し始めるまでふるいPodにのみ振り分けされるので、ダウンタイムもありません。よいです。