---
title: Kubernetesハンズオン - 3. アプリケーションのアップデート
tags: ["Kubernetes Handson", "Kubernetes", "PKS"]
categories: ["Dev", "CaaS", "Kubernetes"]
date: 2019-09-23T10:11:49Z
updated: 1970-01-01T00:00:00Z
---

アプリケーションのアップデートは`Deployment`によって行われます。

![image](https://user-images.githubusercontent.com/106908/40193336-fa554166-5a41-11e8-8f2f-31d5293844db.png)


アプリケーションのアップデートを行う場合は、Dockerイメージのタグを新しいバージョンに変更し、`kubectl apply`すれば良いです。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-pks
spec:
  # ...
  template:
    # ...
    spec:
      containers:
      - image: making/hello-pks:0.0.2 # <-- 0.0.1からアップデート
```

ただし、ダウンタイムなしにアプリケーションをアップデートしたい場合は、コンテナのヘルスチェックとDeploymentのアップデートストラテジーに気をつける必要があります。

**目次**

<!-- toc -->

### コンテナのヘルスチェック

Deplotmentでアプリケーションを更新する場合は、更新したコンテナが正常に動作しているかどうかを
Deploymentのコントローラーは知る必要があります。このヘルスチェックがないと、次のPodを更新して良いか正しく判断することができません。

コンテナのヘルスチェックにはReadiness ProbeとLiveness Probeの2種類があります。
Readiness Probeはコンテナがトラフィックを受けられるかどうかをチェックし、Liveness Probeはコンテナが生存しているかどうかをチェックします。
アプリケーションの更新の際はReadiness Probeを正しく設定する必要があります。



#### Readiness Probe

Deploymentの`spec.template.spec.containers[]`に`readinessProbe`の設定を行います。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-pks
spec:
  # ...
  template:
    # ...
    spec:
      containers:
      - image: making/hello-pks:0.0.1
        name: hello-pks
        ports:
        - containerPort: 8080
        env:
        - # ...
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
          periodSeconds: 5
```

Readinss ProbeおよびLiveness ProbeはHTTP、TCP、コマンド実行結果によるヘルスチェックをサポートしています。
ここではHTTPヘルスチェック(`httpGet`)のみ扱います。その他の方法は[ドキュメント](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)を参照してください。
`making/hello-pks`は[Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-endpoints.html)を使っており、ヘルスチェックエンドポイントとして`/actuator/health`を`path`に設定できます。

`initialDelaySeconds`はコンテナが起動を開始してから初めのチェックを行うまでの待ち時間(秒)です。
`timeoutSeconds`は1回のチェックで失敗とみなすまでのタイムアウト(秒)です。`failureThreshold`はヘルスチェックを失敗とみなすまでのリトライ回数です。
`periodSeconds`はリトライの間隔(秒)です。

#### Liveness Probe

Liveness Probeはアプリケーションの更新には直接関係しませんが、ヘルスチェックの1種としてここで説明します。
Liveness Probeはコンテナの生存確認に使われ、Liveness Probeが失敗した場合はコンテナが再起動します。
アプリが終了せずに動かなくなったり、デッドロックしてstuckしたアプリを再起動するために使用します。

`Deployment`の`spec.template.spec.containers[]`に`livenessProbe`の設定を行います。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-pks
spec:
  # ...
  template:
    # ...
    spec:
      containers:
      - image: making/hello-pks:0.0.1
        name: hello-pks
        ports:
        - containerPort: 8080
        env:
        - # ...
        readinessProbe:
          # ...
        livenessProbe:
          httpGet:
            path: /actuator/info
            port: 8080
          initialDelaySeconds: 20
          timeoutSeconds: 1
          periodSeconds: 10
          failureThreshold: 1
```

設定項目はReadiness Probeと同じです。

> Readinss ProbeおよびLiveness Probeに同じ内容を設定するのは良くありません。Liveness Probeではデータベースの接続チェック等は行わない方が良いです。

> Readiness ProbeとLiveness Probeの違いは[この記事](https://srcco.de/posts/kubernetes-liveness-probes-are-dangerous.html)が詳しいです。

### Deploymentのアップデートストラテジー

`Deployment`でアプリケーションを更新する場合にアップデートストラテジーとして`RollingUpdate`と`Recreate`が選べます。
デフォルトは`RollingUpdate`です。

#### Rolling Update

Rolling UpdateではPodを少しずつ新しいバージョンに移行し、ダウンタイムなく切り替えることができます。
Rolling Updateの挙動は`maxUnavailable`と`maxSurge`によって調節できます。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-pks
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    # ...
```

`maxUnavailable`はアップデート中に許容できる最大の使用不可Podの数または割合です。
`maxSurge`はアップデート中に`replicas`数を超えて増やして良いPodの数または割合です。
`maxUnavailable`や`maxSurge`は`replicas`に依存しないように`%`で指定した方が良いです。

`maxUnavailable`と`maxSurge`ともにデフォルト値は`25%`です。

例えば`replicas`が`4`の場合で`maxUnavailable`が`2`と`maxSurge`が`0`だとします。
その場合はPod数は次の表のように推移します。(実行タイミングやコンテナの起動速度により異なる場合があります)

| 旧バージョンのPod数(Ready/Desired) | 新バージョンのPod数(Ready/Desired) |
| - | - |
| 4/4 | 0/0 |
| 2/2 | 0/2 |
| 0/0 | 2/4 |
| 0/0 | 4/4 |

移行の途中にReadyなPod数の合計が`2` (`4 - 2`)な状態を経由していることがわかります。

[🎥 asciinemaで見る(ReplicaSetの`DESIRED`と`READY`の数字の推移に注目してください)](https://asciinema.org/a/9QeGlebhdg1hwPFM9hlpwKmBw)

`replicas`が`4`の場合で`maxUnavailable`が`0`と`maxSurge`が`2`だとします。
その場合はReady状態なPod数は次の表のように推移します。(実行タイミングやコンテナの起動速度により異なる場合があります)

| 旧バージョンのPod数(Ready/Desired) | 新バージョンのPod数(Ready/Desired) |
| - | - |
| 4/4 | 0/0 |
| 4/4 | 0/2 |
| 3/3 | 1/3 |
| 2/2 | 2/4 |
| 1/1 | 3/4 |
| 0/0 | 4/4 |

移行中もReadyなPod数の合計が`4`であることがわかります。

[🎥 asciinemaで見る(ReplicaSetの`DESIRED`と`READY`の数字の推移に注目してください)](https://asciinema.org/a/AN50nYWbPyLfvHOkR3CkqkuV7)


例えば`replicas`が`4`の場合で`maxUnavailable`が`2`と`maxSurge`が`2`だとします。
その場合はPod数は次の表のように推移します。(実行タイミングやコンテナの起動速度により異なる場合があります)

| 旧バージョンのPod数(Ready/Desired) | 新バージョンのPod数(Ready/Desired) |
| - | - |
| 4/4 | 0/0 |
| 2/2 | 0/4 |
| 1/1 | 1/4 |
| 0/0 | 2/4 |
| 0/0 | 4/4 |

[🎥 asciinemaで見る(ReplicaSetの`DESIRED`と`READY`の数字の推移に注目してください)](https://asciinema.org/a/O34TVEM3SWs6TL9seJhBnXh30)


#### Recreate

Recreateの場合、全てのPodを停止してから再作成します。
バージョンアップに要する時間は短く余分なリソースも不要ですが、ダウンタイムが発生します。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-pks
spec:
  strategy:
    type: Recreate
  # ...
```

`replicas`が`4`の場合、Pod数は次の表のように推移します。(実行タイミングやコンテナの起動速度により異なる場合があります)

| 旧バージョンのPod数(Ready/Desired) | 新バージョンのPod数(Ready/Desired) |
| - | - |
| 4/4 | 0/0 |
| 0/0 | 0/0 |
| 0/0 | 0/4 |
| 0/0 | 2/4 |
| 0/0 | 4/4 |

[🎥 asciinemaで見る(ReplicaSetの`DESIRED`と`READY`の数字の推移に注目してください)](https://asciinema.org/a/4sfvpDgBYMPozyCUCCmSpsVKf)


---

以上の内容をまとめ、`hello-pks.yml`を次のように編集してください。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-pks
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
  selector:
    matchLabels:
      app: hello-pks
  template:
    metadata:
      labels:
        app: hello-pks
    spec:
      containers:
      - image: making/hello-pks:0.0.2
        name: hello-pks
        ports:
        - containerPort: 8080
        env:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: NODE_IP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /actuator/info
            port: 8080
          initialDelaySeconds: 20
          timeoutSeconds: 1
          periodSeconds: 10
          failureThreshold: 1
---
kind: Service
apiVersion: v1
metadata:
  name: hello-pks
spec:
  type: LoadBalancer
  selector:
    app: hello-pks
  ports:
  - protocol: TCP
    port: 8080
```

`kubectl apply`で変更を適用してください。

```
kubectl apply -f hello-pks.yml
```
出力結果
```
deployment "hello-pks" configured
```
