Google Container Engineで安定してRolling Updateをさせる設定

# 背景

ローリングアップデートは、システムがダウンしないように少しずつアップデートをする手段です。
古いバージョンのコンテナを残しつつ、新しいバージョンのコンテナを生み出すことで実現します。
[Blue Green Deployment](http://martinfowler.com/bliki/BlueGreenDeployment.html)と同じような感じですが、新旧システムが混在する時間が存在します。

kubernetesではrolling-updateコマンドがありますが、現在はより上位概念のdeployemntを使うのが推奨されているようです（http://kubernetes.io/docs/user-guide/rolling-updates/）。
以下のようにdeployment.ymlを設定すると、ローリングアップデートを実行してくれます。

```yaml
spec:
  strategy:
    type: RollingUpdate
```

この設定でapplyをするだけで、変更後のコンテナが生まれ古いコンテナが消えてゆきます。
この場合、新コンテナ内の準備が整うまでアクセスできない時間が生じます。
特にDockerfileのCMDに色々と詰め込んでいると、それなりに長い時間アクセスができず、長時間止まってしまいます。
また、コンテナが消えるスピードも調整したいところです。

安定させるにはいくつか設定が必要だったので、以下に記載します。

# 設定

以下の動作を実現するよう、deployment.ymlを書き換えてapplyします。

1. （事前準備）docker imageのtagをデプロイごとに変更する
1. 新しいコンテナの準備が整ったかチェックする
1. 新しいコンテナの準備が整うまで古いコンテナを（いくつか）残す

順番に見ていきます。

## docker imageのtagをデプロイごとに変更する

（もともとlatest以外のタグを使っているなら、この手順は不要です）

これは単純にapply時の制約ですが、dockerのtagにlatestを使っていると、imageだけの更新ができません（設定ファイルの変更がないため）。

kubernetesのドキュメントにも以下のように書かれています。

`The update will fail if IMAGE:TAG is identical to the current value. For this reason, we recommend the use of versioned tags as opposed to values such as :latest.`

http://kubernetes.io/docs/user-guide/rolling-updates/

というわけで、latestを使わずバージョン番号やタイムスタンプを使ってデプロイ時に変更があるようにします。

個人的にテスト用に採用している方法は、「タイムスタンプタグとlatestタグを両方作る」です。
gitで管理するdeployment.ymlにはlatestを記載し、構成に変化がない限り変更されないようにします。
実際デプロイする時は、タイムスタンプのタグがついたイメージを使います。

スクリプトにすると↓のようになります。
CircleCIに載せる前の段階で使えるかと思います。

```bash
#!/bin/bash

set -eux

TIMESTAMP=`date +'%s'`

## app
IMAGE_ID=$(docker build --no-cache -q -f Dockerfile.prod .)
url_base="asia.gcr.io/<Your Project>/<Your Directory>"
reg_url="${url_base}:${TIMESTAMP}"
docker tag ${IMAGE_ID} ${reg_url}
docker tag ${IMAGE_ID} ${url_base}:latest
gcloud docker -- push ${reg_url}
gcloud docker -- push ${url_base}:latest

kubectl set image deployment/app web=${reg_url}
```

手動では`kubectl edit`を使ったり、手元のyamlを書きかえて`kubectl apply`をするとデプロイされます。

## 新しいコンテナの準備が整ったかチェックする

readinessProbeという設定項目で、コンテナごとにアクセスが来ても良い状態かチェックができます。
livenessProbeというほぼ同じ設定項目もありますが、こちらはアクセスが来てしまうので、流したくない場合はreadinessProbeを使いましょう。
[公式ドキュメント](http://kubernetes.io/docs/user-guide/production-pods/#liveness-and-readiness-probes-aka-health-checks)に設定可能項目など詳細があります。

DockerfileのCMDなど初回だけ時間がかかる場合は、`initialDelaySeconds`で初回チェックが走るまでの時間を設定できます。
初回チェックが走るまではアクセスが来ないので、長めに取ればデプロイに時間が掛かりますが安定して動作させることができます。


```yaml
readinessProbe:
  httpGet:
    path: /
    port: 80
  initialDelaySeconds: 70
  timeoutSeconds: 1
```

## 古いコンテナを残す

新しいpodと古いpodをどれくらいのスピードで更新するかは、`maxSurge`と`maxUnavailable`というコマンドで調整できます。
[公式ドキュメント](http://kubernetes.io/docs/user-guide/deployments/#max-unavailable)

maxUnavailableはその名の通り、使えなくなるpodの数を表しています。
例えば10個のレプリカ、maxUnavailable=3の場合を考えます。
デプロイ後すぐさま7個にレプリカが減り、新podがいくつか生まれます。
新podが出来上がり次第、また古いpodが消えてゆき、最終的に10個の新podになるまで繰り返されます。
ここを大きな数字にすると、素早く更新がなされます。

maxSurgeはどれだけの超過podを許すかというパラメーターです。
デプロイ後最初に新podが「いくつか」生まれますが、pod数がreplicas+maxSurgeになるまで新podが生まれます。
maxSurge=2だと、上の例では5つほど生まれる計算（10+2=7+5）になります。

↓はホビーユース向けのそこそこエコな例です。


```yaml
spec:
  replicas: 2
  strategy:
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 1
    type: RollingUpdate
```

# まとめ

おおよそkubernetesの[公式ドキュメント](http://kubernetes.io/docs/user-guide/deployments/)を見れば書いてあるのですが、探すのが結構大変だったのでまとめました。
上記設定の値を調整するだけで、様々なタイプのプロダクトに適用できて便利ですね。