Kubernetesでダウンタイムゼロのローリングアップデートを実現する
最近、私の同僚が Kubernetes を使ってアプリケーションをデプロイしようとしていて、いくつかのをアップデートをデプロイしようとしたときに少しダウンタイムが発生していることに気づきました。同僚は Kubernetes ではローリングアップデートがデフォルトで有効になっていると思っていたので、ダウンタイムが発生していて慌てていました。そこで、このようなことが起こって困っている人の助けになればと思い、この記事を書いてみました。
ユーザーはアプリケーションが24時間常に利用可能であることを期待しており、開発者は1日に何度か新しいバージョンをデプロイすることを期待されています。Kubernetesでは、ローリングアップデートによってこの状態を実現できます。ローリングアップデートでは、Podsインスタンスを新しいものにインクリメンタルに更新することで、デプロイメントの更新をダウンタイムゼロで行うことができます。新しいPodsは、利用可能なリソースを持つノード上でスケジュールされます。
簡単なデプロイメントのマニフェストを見てみましょう。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: hello-app
deployment
template:
metadata:
labels:
app: hello-app
spec:
containers:
- image: gcr.io/google-samples/hello-app:1.0
imagePullPolicy: Always
name: hello-app
ports:
- containerPort: 8080
以下のコマンドを実行すると hello-app というデプロイメントが作成されて、問題なく動作するはずです。
kubectl apply -f deployment.yaml
ここで、上記のデプロイメントで実行されているイメージを更新したい場合、kubectl editで編集を行うか、以下のようにyamlファイルを編集して "kubectl apply -f" を使って再デプロイする必要があるでしょう。
編集したyamlファイルを見てみましょう。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: hello-app
deployment
template:
metadata:
labels:
app: hello-app
spec:
containers:
- image: gcr.io/google-samples/hello-app:2.0
imagePullPolicy: Always
name: hello-app
ports:
- containerPort: 8080
これを適用したり、編集したりすると、古いポッドが終了して新しいポッドが作成されるため、アプリケーションに少しだけダウンタイムが発生します。ターミナルで新しいタブを開いて、公開されているサービスを秒単位で curl コマンドを実行すると、ダウンタイムが発生していることが確認できると思います。(*この記事では、デプロイ用のサービスを作成したり公開したりする方法については説明しません。)
それでは、ダウンタイムが発生しないようにするにはどうしたらよいでしょうか。修正は非常に簡単です。ダウンタイムが発生している原因は、kubernetes が新しいPodがリクエストを受け付ける準備ができたかを知らないために起こっています。新しいポッドが作成されると、すぐに新しいPodで必要なサービスやプロセスがすべて開始されてリクエストを受け付ける状態になっているかを待たずに古いPodが終了してしまいます。
ダウンタイムを発生させないようにするには、新しいPodがリクエストを受け付ける状態になるまで、古いPodを終了させないようにする必要があります。これを実現するために、Kubernetesはデプロイ時にReadiness Probeという設定オプションを用意しています。Readiness Probeは、古いPodを終了させる前に、作成した新しいPodがリクエストを受け付ける準備ができていることを確認します。これを有効にするには、最初に実行したいアプリケーションにHTTP GETリクエストで200を返すルートを設定する必要があります。(注意: 他の HTTP リクエストメソッドを使うこともできますが、この記事では GET メソッドを使うことにします)
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-dep
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: hello-dep
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:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
上記で追加したフィールドの詳細情報です。
・initialDelaySeconds:コンテナが起動してからReadiness Probeが開始されるまでの秒数
・periodSeconds: :Readiness Probeを実行する頻度(秒単位)。デフォルトは10秒。最小値は1です。
・timeoutSeconds:Readiness Probeがタイムアウトする秒数。既定値は 1 秒です。最小値は 1 です。
・successThreshold:失敗した後、Readiness Probeが成功したとみなされるための最小連続成功回数。デフォルトは1。簡潔さを保つためには1でなければいけません。最小値は 1 です。
・failureThreshold:Podが起動してReadiness Probeが失敗した場合、Kubernetes はfailureThresholdを何回か試してから実行を諦めます。Readiness Probeの場合、PodはUnreadyとマークされます。デフォルトは3で、最小値は1です。
もう一つ追加しておきたいのはRollingUpdate戦略と呼ばれるもので、以下のように設定することができます。
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 1
上記では、古いPodを新しいPodに置き換えるために使用するストラテジーを指定します。タイプは "Recreate "か "RollingUpdate "す。RollingUpdate がデフォルト値です。そのため、なぜデフォルトではRollingUpdateが動作しないのか分からず、私の同僚は困っていました。実際にはRollingUpdateは動作していましたが、Kubernetesは新しいPodがいつリクエストを受け付ける準備ができたかを知らなかったのでダウンタイムが発生していました。
maxUnavailableはオプションのフィールドで、更新処理中に利用できないPodの最大数を指定します。値は絶対数(例:5)または希望するPodのパーセンテージ(例:10%)です。絶対数はパーセンテージから切り捨てて計算されます。maxSurgeが0の場合、値を0にすることはできません。 デフォルト値は25%です。
maxSurgeはオプションのフィールドで、希望するPodの数以上に作成できるPodの最大数を指定します。値は絶対数(例:5)または希望するPodのパーセンテージ(例:10%)です。MaxUnavailableが0の場合、値を0にすることはできません。 絶対数はパーセンテージから切り上げて計算されます。デフォルト値は25%です。
以上のような構成で、最終的なデプロイメントファイルを見てみましょう。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app
namespace: default
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 25%
selector:
matchLabels:
app: hello-app
template:
metadata:
labels:
app: hello-app
spec:
containers:
- image: gcr.io/google-samples/hello-app:2.0
imagePullPolicy: Always
name: hello-app
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
これでKubernetesでダウンタイムゼロでローリングアップデートを実現できるようになりました。
ここから先は
フルスタックエンジニアになりたい方向け開発Tips
フルスタックエンジニアを目指しているプログラマー・エンジニアの方向けにワンランク上の開発Tipsを提供します。
サポートして頂くと、こちらからもサポート返しさせて頂きます。