背景
ローリングアップデートは、システムがダウンしないように少しずつアップデートをする手段です。
古いバージョンのコンテナを残しつつ、新しいバージョンのコンテナを生み出すことで実現します。
Blue Green Deploymentと同じような感じですが、新旧システムが混在する時間が存在します。
kubernetesではrolling-updateコマンドがありますが、現在はより上位概念のdeployemntを使うのが推奨されているようです(http://kubernetes.io/docs/user-guide/rolling-updates/)。
以下のようにdeployment.ymlを設定すると、ローリングアップデートを実行してくれます。
spec:
strategy:
type: RollingUpdate
この設定でapplyをするだけで、変更後のコンテナが生まれ古いコンテナが消えてゆきます。
この場合、新コンテナ内の準備が整うまでアクセスできない時間が生じます。
特にDockerfileのCMDに色々と詰め込んでいると、それなりに長い時間アクセスができず、長時間止まってしまいます。
また、コンテナが消えるスピードも調整したいところです。
安定させるにはいくつか設定が必要だったので、以下に記載します。
設定
以下の動作を実現するよう、deployment.ymlを書き換えてapplyします。
- (事前準備)docker imageのtagをデプロイごとに変更する
- 新しいコンテナの準備が整ったかチェックする
- 新しいコンテナの準備が整うまで古いコンテナを(いくつか)残す
順番に見ていきます。
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.
というわけで、latestを使わずバージョン番号やタイムスタンプを使ってデプロイ時に変更があるようにします。
個人的にテスト用に採用している方法は、「タイムスタンプタグとlatestタグを両方作る」です。
gitで管理するdeployment.ymlにはlatestを記載し、構成に変化がない限り変更されないようにします。
実際デプロイする時は、タイムスタンプのタグがついたイメージを使います。
スクリプトにすると↓のようになります。
CircleCIに載せる前の段階で使えるかと思います。
#!/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を使いましょう。
公式ドキュメントに設定可能項目など詳細があります。
DockerfileのCMDなど初回だけ時間がかかる場合は、initialDelaySeconds
で初回チェックが走るまでの時間を設定できます。
初回チェックが走るまではアクセスが来ないので、長めに取ればデプロイに時間が掛かりますが安定して動作させることができます。
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 70
timeoutSeconds: 1
古いコンテナを残す
新しいpodと古いpodをどれくらいのスピードで更新するかは、maxSurge
とmaxUnavailable
というコマンドで調整できます。
公式ドキュメント
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)になります。
↓はホビーユース向けのそこそこエコな例です。
spec:
replicas: 2
strategy:
rollingUpdate:
maxSurge: 2
maxUnavailable: 1
type: RollingUpdate
まとめ
おおよそkubernetesの公式ドキュメントを見れば書いてあるのですが、探すのが結構大変だったのでまとめました。
上記設定の値を調整するだけで、様々なタイプのプロダクトに適用できて便利ですね。