KubernetesでPodをローリングアップデートをする

## 概要
このページでは、Podのローリングアップデートの方法について説明する。
[ダウンタイム](#ダウンタイムについて)についても解説する。

## 目次
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## ローリングアップデートの方法
Deploymentを利用している場合、PodのDockerイメージのversionを上げてapplyするだけで、ローリングアップデートが実行される。

## ハンズオン

### Deploymentを定義
以下のようにDeploymentのDockerイメージのversionを上げる。

```diff:deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-dep
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-dep 
  # このPodTemlateの部分に変更があったときに、ローリングアップデートが実行される
  template:
    metadata:
      labels:
        app: hello-dep
    spec:
      containers:
-     - image: gcr.io/google-samples/hello-app:1.0
+     - image: gcr.io/google-samples/hello-app:2.0
        imagePullPolicy: Always
        name: hello-dep
        ports:
          - containerPort: 8080
```


### ローリングアップデート前のPodの状態を確認
以下２つが現在稼働しているPodである。

```shell-session
$ sudo kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
hello-dep-5ddf884dd9-nt22p   1/1     Running   0          16s
hello-dep-5ddf884dd9-wm6c9   1/1     Running   0          16s
```

### Deploymentをapply
Dockerイメージのversionを更新したdeployment.ymlをapplyする。

```shell-session
$ sudo kubectl apply -f deployment.yml
```

### ローリングアップデート開始
applyしたことにより、ローリングアップデートか開始される。
以下コマンドでPodの状態がリアルタイムで確認できる。

```sh
$ sudo kubectl get pod -w
NAME                         READY   STATUS    RESTARTS   AGE
# 旧Pod
hello-dep-5ddf884dd9-nt22p   1/1     Running   0          20s
hello-dep-5ddf884dd9-wm6c9   1/1     Running   0          20s
# 新Pod1を起動開始
hello-dep-6595cc66c4-kcd8r   0/1     Pending   0          0s
hello-dep-6595cc66c4-kcd8r   0/1     Pending   0          0s
hello-dep-6595cc66c4-kcd8r   0/1     ContainerCreating   0          0s
# 新Pod1がRunning状態になった
hello-dep-6595cc66c4-kcd8r   1/1     Running             0          2s
# 旧Pod1を破棄開始
hello-dep-5ddf884dd9-nt22p   1/1     Terminating         0          30s
# 新Pod2を起動開始
hello-dep-6595cc66c4-wcc7q   0/1     Pending             0          0s
hello-dep-6595cc66c4-wcc7q   0/1     Pending             0          0s
hello-dep-6595cc66c4-wcc7q   0/1     ContainerCreating   0          0s
# 旧Pod2を破棄開始
hello-dep-5ddf884dd9-nt22p   0/1     Terminating         0          31s
# 新Pod2がRunning状態になった
hello-dep-6595cc66c4-wcc7q   1/1     Running             0          2s
# 旧Pod1,2が破棄される
hello-dep-5ddf884dd9-wm6c9   1/1     Terminating         0          32s
hello-dep-5ddf884dd9-nt22p   0/1     Terminating         0          32s
hello-dep-5ddf884dd9-nt22p   0/1     Terminating         0          32s
hello-dep-5ddf884dd9-wm6c9   0/1     Terminating         0          33s
hello-dep-5ddf884dd9-wm6c9   0/1     Terminating         0          34s
hello-dep-5ddf884dd9-wm6c9   0/1     Terminating         0          34s
```

### ローリングアップデート後のPodの状態を確認
Podが新しいものに置き換わっており（NAMEが、アップデート前のものとは違う）Runningになっている。

```shell-session
$ sudo kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
hello-dep-6595cc66c4-kcd8r   1/1     Running   0          19s
hello-dep-6595cc66c4-wcc7q   1/1     Running   0          17s
```

ローリングアップデートの手順としては、以上となる。
しかし、この方法ではダウンタイムが発生する可能性がある。

## ダウンタイムについて
K8sは、旧versionのPodを破棄し、新versionのPodを起動するという形でローリングアップデートを実現するが、この間に微妙なダウンタイムが発生する可能性がある。
何故このような現象が起きるかというと、k8sは新versionのPodが起動したことまでは検知するが、Running状態になったことを検知しない。

つまり、**新Podが起動した時点で旧Podはすぐに破棄されてしまうため、新PodがRunning状態になるまでの間、ダウンタイムが発生する可能性がある。**

と、以下に書いてあった。
[Enable Rolling updates in Kubernetes with Zero downtime](https://medium.com/platformer-blog/enable-rolling-updates-in-kubernetes-with-zero-downtime-31d7ec388c81)
> Once you apply this or edit this, notice that there maybe a little downtime on your application because the old pods are getting terminated and the new ones are getting created.

> This happens because kubernetes doesn’t know when your new pod is ready to start accepting requests, so as soon as your new pod gets created, the old pod is terminated without waiting...

## 対策
ヘルスチェックを入れることで、ダウンタイムが発生しないようにする。
`readinessProbe`は、サービスがリクエストを受け付けられる状態かどうかを確認する。

以下の説明が詳しい。
https://www.ianlewis.org/jp/kubernetes-health-check
>readinessProbeが失敗した場合、そのパッドがサービスのエンドポイントから外される。そうすると、Kubernetesのサービスディスカバリー機能でトラフィックを受けられないポッドにトラフィックを転送しない。例えば、ローリングアップデートの時や、スケールアップした時、新しいポッドが機能されていたんだけど、まだトラフィック受けられないタイミングでリクエストが来たら、困るので、readinessProbeでそういうことを防ぐ。

また合わせて、RollingUpdate時の設定も追加した。

```diff:deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-dep
  namespace: default
spec:
  replicas: 2
+ # RollingUpdate時の設定
+ strategy:
+   # Recreate or RollingUpdate
+   type: RollingUpdate
+   rollingUpdate:
+     # アップデート時に許容する最大pod数 = replicas+maxSurge
+     # 今回の場合はreplicas(2)+maxSurge(1)なので最大Pod数が3になる。
+     maxSurge: 1
+     # アップデート時に許容する一度にunavailableになるPodの数
+     maxUnavailable: 25%
  selector:
    matchLabels:
      app: hello-dep
  # このPodTemlateの部分に変更があったときに、ローリングアップデートが実行される
  template:
    metadata:
      labels:
        app: hello-dep
    spec:
      containers:
      - image: gcr.io/google-samples/hello-app:2.0
        imagePullPolicy: Always
        name: hello-dep
        ports:
          - containerPort: 8080
+       # ヘルスチェックの設定
+       readinessProbe:
+         # ヘルスチェックでアクセスするhttpエンドポイントの情報を書く。
+         httpGet:
+           path: /
+           port: 8080
+         # コンテナが起動してからヘルスチェックを始めるまでの秒数。Default: 0
+         initialDelaySeconds: 5
+         # ヘルスチェックのタイムアウト秒数。最小で1秒。Default: 1
+         timeoutSeconds: 1
+         # ヘルスチェックの間隔。最小で1秒。Default: 10
+         periodSeconds: 5
+         # Failure状態の時、何回成功したらSuccessになるか。最小で1回。Default: 1
+         successThreshold: 1
```

### ローリングアップデート前のPodの状態を確認

```shell-session
$ sudo kubectl get pod
NAME                             READY   STATUS    RESTARTS   AGE
hello-dep-6ff6ff4cbf-j9pw9   1/1     Running   0          23s
hello-dep-6ff6ff4cbf-zgh5t   1/1     Running   0          23s
```

### Deploymentをapply

```shell-session
$ sudo kubectl apply -f deployment.yml
deployment.apps/hello-dep created
```

### ローリングアップデート開始

```sh
$ sudo kubectl get pod -w
NAME                         READY   STATUS    RESTARTS   AGE
# 旧Pod
hello-dep-6ff6ff4cbf-j9pw9   1/1     Running   0          38s
hello-dep-6ff6ff4cbf-zgh5t   1/1     Running   0          38s
# 新Pod1を起動開始
hello-dep-bb849d7c6-j9dqs    0/1     Pending   0          0s
hello-dep-bb849d7c6-j9dqs    0/1     Pending   0          0s
hello-dep-bb849d7c6-j9dqs    0/1     ContainerCreating   0          0s
hello-dep-bb849d7c6-j9dqs    0/1     Running             0          2s
# 新Pod1がRunning状態 & READY1/1になった
hello-dep-bb849d7c6-j9dqs    1/1     Running             0          10s
# 旧Pod1を破棄開始
hello-dep-6ff6ff4cbf-zgh5t   1/1     Terminating         0          64s
# 新Pod2を起動開始
hello-dep-bb849d7c6-wsnhf    0/1     Pending             0          0s
hello-dep-bb849d7c6-wsnhf    0/1     Pending             0          0s
hello-dep-bb849d7c6-wsnhf    0/1     ContainerCreating   0          0s
# 旧Pod2を破棄開始
hello-dep-6ff6ff4cbf-zgh5t   0/1     Terminating         0          64s
hello-dep-6ff6ff4cbf-zgh5t   0/1     Terminating         0          65s
hello-dep-6ff6ff4cbf-zgh5t   0/1     Terminating         0          65s
hello-dep-bb849d7c6-wsnhf    0/1     Running             0          1s
# 新Pod2がRunning状態 & READY1/1になった
hello-dep-bb849d7c6-wsnhf    1/1     Running             0          7s
# 旧Pod1,2を破棄
hello-dep-6ff6ff4cbf-j9pw9   1/1     Terminating         0          71s
hello-dep-6ff6ff4cbf-j9pw9   0/1     Terminating         0          72s
hello-dep-6ff6ff4cbf-j9pw9   0/1     Terminating         0          73s
hello-dep-6ff6ff4cbf-j9pw9   0/1     Terminating         0          73s
```

### ローリングアップデート後のPodの状態を確認
Podが新しいものに置き換わっており（NAMEが、アップデート前のものとは違う）Runningになっている。

```shell-session
$ sudo kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
hello-dep-bb849d7c6-j9dqs   1/1     Running   0          25s
hello-dep-bb849d7c6-wsnhf   1/1     Running   0          15s
```


## 参考
- [Enable Rolling updates in Kubernetes with Zero downtime](https://medium.com/platformer-blog/enable-rolling-updates-in-kubernetes-with-zero-downtime-31d7ec388c81)
- [Liveness/Readiness Probe](https://blog.web-apps.tech/liveness-readiness-probe/)
- [define-readiness-probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#define-readiness-probes)
- [Kubernetesヘルスチェックの使い方](https://www.ianlewis.org/jp/kubernetes-health-check)
- [Google Container Engineで安定してRolling Updateをさせる設定](https://qiita.com/esplo/items/b50d7284cb645977aa5e)
- [Kubernetes: Deployment のローリングアップデートの条件と設定](https://qiita.com/tkusumi/items/7404e422cd6b4a1c6892)