Deployment とローリングアップデートの仕組み
v1.37 時点(2026年10月23日 確認)
目次
この記事では、ReplicaSet を持ち、Pod を宣言的に更新するリソースである Deployment の仕組みを、図で説明します。Pod のひな形を書き換えたときに、Deployment が新しい ReplicaSet を作り、Pod を少しずつ入れ替える流れ(ローリングアップデート)がわかります。前のリビジョン(版)に戻す切り戻しの仕組みと、そのために古い ReplicaSet が残される理由もわかります。後半では kind のクラスタで Deployment のイメージを更新し、kubectl rollout status で進み具合を見て、kubectl rollout undo で切り戻す手順を示します。
ReplicaSet がラベルセレクタで Pod を選んで数える仕組みを扱った第11回の内容を前提にしています。Deployment・ReplicaSet・Pod の持ち主の関係を扱った第6回の内容も使います。まだの方は先にこちらをどうぞ。
ReplicaSet を動かして理解する同じ Pod を指定した数だけ動かし続ける ReplicaSet の仕組みを図で説明します。ReplicaSet がラベルセレクタで Pod を選んで数える決まり、自分のものにする Pod の条件、数を減らすときに消す Pod の選び方を押さえ、kind のクラスタでラベルを書き換えた Pod が管理から外れる様子を観察する手順を紹介します。以下の図は、イメージを nginx:1.14.2 から nginx:1.16.1 に更新し終えたあとの、Deployment と 2 つの ReplicaSet と Pod の関係を表しています。
Deployment は、Pod のひな形が変わるたびに新しい ReplicaSet を作り、Pod をそちらへ移します。移し終えた古い ReplicaSet は消さずに、数を 0 にして残します。古い ReplicaSet が前のリビジョンのひな形を覚えているので、そこへ戻せば切り戻せます。それぞれの ReplicaSet は pod-template-hash のラベルで自分の Pod だけを選ぶので、2 つの ReplicaSet が同じ Pod を取り合うことはありません。破線の「持つ」と実線の「選ぶ」は、第6回と第11回の図と同じ意味です。
Deployment とは
Deployment は Pod と ReplicaSet を宣言的に更新するためのリソース です。公式ドキュメントは Deployment を、アプリケーションを動かす Pod の集まりを管理するもので、ふつうは状態を持たないアプリケーションに使う、と説明しています。利用者は Deployment に望ましい状態を書き、Deployment のコントローラー(第6回の deployment-controller)が、制御された速さで実際の状態を望ましい状態に変えていきます。
参考:Deployments | Kubernetes、kube-controller-manager | Kubernetes
第6回で見たとおり、Deployment は ReplicaSet を作って持ち、ReplicaSet が Pod を作って持ちます。Pod の数を保つのは ReplicaSet の仕事で、Deployment が受け持つのはその上の段の仕事です。公式ドキュメントが挙げる Deployment の典型的な使い方は、次の 7 つです。この回で扱うかどうかも添えます。
| 公式ドキュメントが挙げる使い方 | この回での扱い |
|---|---|
| Deployment を作り、ReplicaSet を展開する(ReplicaSet が裏で Pod を作る) | 扱う |
| Pod のひな形を書き換えて、Pod の新しい状態を宣言する(新しい ReplicaSet へ制御された速さで移す) | 扱う。入れ替えの速さの調整は第13回 |
| 今の状態が安定しないとき、前のリビジョンに切り戻す | 扱う |
| 負荷が増えたときに、Pod の数を増やす | 数を変えても Pod の入れ替えが起きないことだけ扱う。自動で変える仕組みは第35回 |
| 展開を一時停止し、ひな形を何度か直してから再開する | 注意点で触れるだけ |
| Deployment の状態を、展開が止まったことの目印に使う | 扱う |
| 要らなくなった古い ReplicaSet を片付ける | 残す数の決まりだけ扱う |
表に出てくる言葉を、この回での使い方とあわせて次の表にまとめます。どれも「Deployment の仕組み」で詳しく説明します。
| 言葉 | この回での意味 |
|---|---|
| 展開(rollout) | Deployment が ReplicaSet と Pod を、Deployment に書かれたひな形に合わせていく一連の動き。公式ドキュメントは Deployment を作ることも ReplicaSet の展開として説明している |
| 更新 | ひな形を書き換えたことで始まる展開 |
| ローリングアップデート | 既定のやり方(RollingUpdate)で進む更新。古い Pod を新しい Pod に少しずつ入れ替える |
| リビジョン(revision) | Deployment の版の番号。展開が始まるたび(ひな形を変えたときや、切り戻したとき)に増える |
| 切り戻し(rollback) | 前のリビジョンのひな形に戻すこと |
公式ドキュメントは Deployment が持つ ReplicaSet を、利用者が直接管理しないよう 注意しています。数を変えるのも中身を変えるのも、Deployment を通して行います。
なぜ Deployment が必要なのか
第11回の最後で見たとおり、ReplicaSet は Pod の数を保ちますが、Pod の中身を入れ替えることはしないと考えられます。公式ドキュメントの Pods のページによると、Pod のひな形を変えたり、新しいひな形に切り替えたりしても、すでにある Pod には直接の影響はありません。ひな形を変えたら、ワークロードのリソースが新しいひな形を使った置き換えの Pod を作る必要があります。どう置き換えるかの決まりは、ワークロードのリソースごとに違います。
では、ReplicaSet だけで Pod を新しいイメージに入れ替えるには、どうすればよいでしょうか。ReplicaSet と同じ役目の古いリソース ReplicationController のページに、ReplicationController で Pod を入れ替えるときに勧められるやり方が書かれています。Pod の数が 1 の新しい ReplicationController を、新しいひな形で作ります。そのあと、新しい方を 1 つ増やして古い方を 1 つ減らすことを繰り返し、古い方が 0 になったら消します。2 つの ReplicationController が作る Pod には、イメージのタグのような、両者を見分けるラベルが少なくとも 1 つ要ります。タグは nginx:1.14.2 の 1.14.2 のように、イメージの名前の : の後ろに付けて、同じ系列のイメージのバージョンを見分けるものです。見分けるラベルが要るのは、セレクタが重ならないようにするためだと考えられます。同じページの Pod Selector の節は、セレクタが重なる ReplicationController ができてしまったら、消すときの扱いを自分で管理する必要があるとしています。Pod Template の節もひな形のラベルをほかのコントローラーと重ねないよう注意しています。また同じページは、理想的なローリングアップデートは、アプリケーションの準備ができたかを考えに入れ、要求を受けられる Pod の数を十分に保つものだ、とも書いています。
このやり方を人が手で進めると、何度もコマンドを打つ必要があり、途中で失敗したときに戻すのも手作業になると考えられます。Deployment は、この入れ替えをクラスタの中の deployment-controller が進める仕組みです。第11回で見たとおり、公式ドキュメントはこれを宣言的な、サーバー側(server-side)のローリングアップデートと呼んでいます。利用者は新しいひな形を書いて渡すだけで、新しい ReplicaSet を作ること、数を少しずつ移すこと、古い ReplicaSet を残しておくことは、deployment-controller が引き受けます。ReplicationController のページも、ローリングアップデートを使いたいときは Deployment を勧めています。
参考:ReplicationController | Kubernetes、ReplicaSet | Kubernetes、Deployments | Kubernetes
Deployment の仕組み
マニフェストに書くフィールド
以下は Pod を 3 つ動かす Deployment のマニフェストです。第1回と第6回で引用した公式ドキュメントの例(controllers/nginx-deployment.yaml)と同じものです。後半の「手元で試す」でも、このマニフェストを使います。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80spec の形は第11回の ReplicaSet とほとんど同じで、replicas、selector、template の 3 つが中心です。公式ドキュメントによると、Deployment の spec で必須なのは template と selector の 2 つだけです。この回にかかわるフィールドを、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 | 既定値 |
|---|---|---|
spec.replicas | 望ましい Pod の数 | 1 |
spec.selector | Pod を選ぶラベルセレクタ。必須。ひな形のラベルに合っていなければ API が受け付けない。apps/v1 では作ったあとで変えられない | なし |
spec.template | Pod のひな形。必須。restartPolicy に書けるのは Always だけ | なし |
spec.strategy.type | 古い Pod を新しい Pod に入れ替えるやり方。RollingUpdate か Recreate | RollingUpdate |
spec.revisionHistoryLimit | 切り戻しのために残す、古い ReplicaSet の数 | 10 |
spec.progressDeadlineSeconds | 更新が進まないまま、この秒数がたつと、更新が失敗したとみなされる | 600 |
spec.minReadySeconds | 新しい Pod が、コンテナが落ちずに Ready のまま、この秒数がたつと「使える(available)」とみなされる(第6回) | 0 |
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
公式ドキュメントによると、metadata.name の nginx-deployment は、あとで作られる ReplicaSet と Pod の名前のもとになります。Deployment が作る ReplicaSet の名前は [Deployment の名前]-[ハッシュ] の形で、Pod の名前はさらにその後ろに文字列が付いたものです。公式ドキュメントの例では、ReplicaSet が nginx-deployment-75675f5897、Pod が nginx-deployment-75675f5897-7ci7o でした(第6回)。
pod-template-hash のラベル
ReplicaSet の名前の [ハッシュ] の部分は、pod-template-hash というラベルの値と同じです。第6回で触れたとおり、このラベルは Deployment のコントローラーが付けます。公式ドキュメントの説明を次の表にまとめます。
| 項目 | 内容 |
|---|---|
| 付けるもの | Deployment のコントローラー |
| 付ける先 | Deployment が作るか自分のものにした、すべての ReplicaSet |
| 値 | ReplicaSet の Pod のひな形(PodTemplate)から計算したハッシュ |
| 加わる場所 | ReplicaSet のセレクタ、ReplicaSet が持つ Pod のひな形のラベル、ReplicaSet がすでに持っている Pod |
| 目的 | 同じ Deployment の ReplicaSet どうしが重ならないようにする |
ハッシュはデータから決まった手順で計算する短い値のことです。一般に、もとのデータが同じなら同じ値になり、違えばふつうは違う値になります。公式ドキュメントは計算の手順を説明していません。ただ、ひな形から計算する以上、ひな形を書き換えれば、新しい ReplicaSet には前と違う値が付くと考えられます。値がぶつかった場合に備えて、Deployment の status.collisionCount があります。API リファレンスによると、Deployment のコントローラーは、いちばん新しい ReplicaSet の名前を作るときに、ぶつかるのを避けるためにこのフィールドを使います。
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
このラベルが要るのは、更新のあいだ、同じ Deployment の ReplicaSet が 2 つ同時にあるからです。どちらの Pod もひな形に書いた app: nginx のラベルを持っています。もし 2 つの ReplicaSet のセレクタが app: nginx だけだったら、セレクタが重なります。第11回で見たとおり、同じ Namespace で ReplicaSet のセレクタが重なると、コントローラーが Pod をいくつ動かすべきか決められなくなることがあります。pod-template-hash を加えたセレクタなら、それぞれの ReplicaSet は自分のひな形から作った Pod だけを選びます。公式ドキュメントは このラベルを変えないよう 注意しています。
参考:Deployments | Kubernetes、Labels and Selectors | Kubernetes
一方、Deployment 自身のセレクタは app: nginx のままです。Deployment 自身のひな形にこのラベルが加わるとは、公式ドキュメントには書かれていません。公式ドキュメントの kubectl describe deployments の例でも、Deployment の Pod Template: の Labels: は app=nginx だけです。
API リファレンスによると、Deployment の status.replicas は、その Deployment が対象にしている(ラベルがセレクタに合う)終了中でない Pod の合計です。そのため Deployment は新しい ReplicaSet と古い ReplicaSet の Pod をまとめて数えます。公式ドキュメントの、更新が止まった例の kubectl describe deployment の表示にも、次の行があります。
このとき古い ReplicaSet に Pod が 3 つ、新しい ReplicaSet に Pod が 1 つあり、4 total はその合計にあたります。この例は「更新が止まったときと切り戻し」で扱います。
参考:Deployment | Kubernetes API Reference、Deployments | Kubernetes
ひな形を書き換えるとローリングアップデートが始まる
公式ドキュメントによると、Deployment の展開(rollout)が始まるのは、Pod のひな形(spec.template)が変わったときだけです。たとえば、ひな形のラベルやコンテナのイメージを書き換えたときです。Pod の数を変えるような、ほかの変更では展開は始まりません。
既定の RollingUpdate の戦略では、Deployment は古い ReplicaSet を少しずつ減らしながら、新しい ReplicaSet を増やします。公式ドキュメントによると、Deployment のコントローラーは、新しく作られた Deployment を見つけると、望ましい Pod を作るための ReplicaSet を作ります。そのあと Deployment を更新すると、Deployment の spec.selector に合う Pod を持っていても、ひな形が Deployment の spec.template と合わない ReplicaSet は減らされていきます。最後には、新しい ReplicaSet が spec.replicas の数になり、古い ReplicaSet はすべて 0 になります。
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
入れ替えのあいだ、Pod の数には上と下の限りがあります。公式ドキュメントの説明は次の 2 つです。
- 更新中に使えない(unavailable)Pod の数を限る。既定では、望ましい数の少なくとも 75% の Pod が動いているようにする(
maxUnavailableが 25%) - 望ましい数を超えて作る Pod の数を限る。既定では、動いている Pod が望ましい数の 125% までになるようにする(
maxSurgeが 25%)
割合から出した数は整数に丸められます。そのため望ましい数が 3 のときは、次の例のとおり、使える Pod は少なくとも 3 つ、Pod の合計は多くても 4 つになります。
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
公式ドキュメントの例(replicas: 3)では、Deployment はまず新しい Pod を 1 つ作り、次に古い Pod を 1 つ消し、また新しい Pod を作ります。十分な数の新しい Pod が動き出すまでは古い Pod を消さず、十分な数の古い Pod を消すまでは新しい Pod を作りません。そうして、使える Pod が少なくとも 3 つ、Pod の合計が多くても 4 つになるよう保ちます。replicas が 4 なら、Pod の数は 3 から 5 のあいだになる、とも書かれています。25% という割合から 3 や 4 という数を出すときの丸め方と、値を変えると入れ替え方がどう変わるのかは、第13回で扱います。
以下の図は、イメージを nginx:1.14.2 から nginx:1.16.1 に書き換えたあとに、Pod が入れ替わる様子を、コマ送りで表しています。公式ドキュメントの例のイベント(後で引用します)の順番に沿っています。図の A〜F は Pod の名前の略です。図とキャプションの「動いている」は、第6回の「使える(available)」の状態にあたる Pod を指します。図の「作られた」は、作られたばかりで、まだ使える状態になっていない Pod を指します。kube-apiserver、kube-scheduler、kubelet は省き、deployment-controller が ReplicaSet の数を変える操作だけを描いています。Pod を実際に作ったり消したりするのは、第11回と同じく、ReplicaSet を受け持つ replicaset-controller です。
どのコマでも、使える Pod は少なくとも 3 つで、終了中のものを除いた Pod の合計は多くても 4 つです。公式ドキュメントによると、終了中の Pod は使える Pod の数に入りません。そのため実際には、終了中の Pod も含めると、一時的に 4 つより多く見えることがあります(「注意点」で説明します)。どの古い Pod から先に終了させられるかは、第11回で見た、ReplicaSet が数を減らすときに消す Pod を選ぶ決まりで決まります。図の A→B→C の順は説明のためのものです。
公式ドキュメントの例で、更新を終えたあとに kubectl describe deployments を実行したときの表示の Events: の欄を引用します。上の部分は省いています。
nginx-deployment-2035384211 が最初からあった ReplicaSet、nginx-deployment-1564180365 が更新で作られた ReplicaSet です。公式ドキュメントによると、Deployment を作ったとき、Deployment は 1 つ目の ReplicaSet を作り、すぐに 3 まで増やしました。更新したときは、新しい ReplicaSet を作って 1 まで増やし、それが動き出すのを待ちました。続いて古い方を 2 に減らし、新しい方を 2 に増やしました。こうして、使える Pod が少なくとも 3 つ、作られた Pod が多くても 4 つになるよう保っています。そのあとも同じやり方で増減を続け、最後は新しい ReplicaSet に使える Pod が 3 つ、古い ReplicaSet は 0 になっています。From 列がどの行も deployment-controller なので、ReplicaSet の数を変えたのが Deployment のコントローラーだとわかります。
公式のチュートリアルはこうして Pod を少しずつ置き換えることで、アプリケーションを止めずに更新できると説明しています。ただし同じチュートリアルは、そのためには Pod を複数動かしておく必要があるとしています。Deployment を Service(第21回)で公開していれば、Service は要求を受けられる Pod にだけ通信を送る、とも説明しています。止めずに更新できるのは、こうした仕組みとの組み合わせによると考えられます。この回では Service を作らないので、通信が止まらないかどうかは確かめません。Pod が要求を受けられるかを確かめる仕組み(readiness Probe)は第34回で扱います。
2 つの戦略(RollingUpdate と Recreate)
spec.strategy.type に書ける戦略は 2 つです。v1.37 時点の公式ドキュメントと API リファレンスに沿って、次の表にまとめます。
| 戦略 | 入れ替え方 |
|---|---|
RollingUpdate(既定) | 古い ReplicaSet を少しずつ減らしながら、新しい ReplicaSet を増やす。望ましい数を超えて作れる Pod の数(maxSurge)と、使えなくてもよい Pod の数(maxUnavailable)の上限を決められる |
Recreate | 新しい Pod を作る前に、今ある Pod をすべて終了させる |
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
公式ドキュメントによると、Recreate で Deployment を更新すると、古いリビジョンの Pod はすべてすぐに終了させられ、それらが消えきるのを待ってから新しいリビジョンの Pod が作られます。そのため、動いている Pod が 1 つも無い時間ができます。この回の後半では既定の RollingUpdate だけを使います。maxSurge と maxUnavailable の値を変えて入れ替え方を比べるのは、第13回です。
リビジョンと切り戻し
公式ドキュメントによると、Deployment のリビジョンは展開が始まるときに作られます。つまり、新しいリビジョンが作られるのは、Pod のひな形(spec.template)が変わったときだけです。Pod の数を変えるような、ほかの更新ではリビジョンは作られません。そのため、前のリビジョンに切り戻すときに戻るのは、Deployment のうち Pod のひな形の部分だけ です。replicas は切り戻しでは戻りません。
リビジョンの中身は古い ReplicaSet に残っています。公式ドキュメントによると、Deployment のリビジョンの履歴は、その Deployment が管理する ReplicaSet に保存されています。それぞれのリビジョンのひな形は、そのリビジョンの ReplicaSet が持っています。冒頭の図で、数が 0 になった古い ReplicaSet を消さずに残していたのはこのためです。残す古い ReplicaSet の数は spec.revisionHistoryLimit で決まり、既定は 10 です。古い ReplicaSet が消されると、そのリビジョンには切り戻せなくなります。0 にすると古い履歴がすべて消え、切り戻しができなくなります。古い ReplicaSet を残しすぎると、etcd の容量を使い、kubectl get rs の一覧も見にくくなります。
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
リビジョンを扱う kubectl のコマンドは kubectl rollout にまとまっています。v1.37 時点のリファレンスには kubectl rollout で扱えるリソースとして Deployment、DaemonSet(第15回)、StatefulSet(第14回)が挙がっています。この回で使うサブコマンドを次の表にまとめます。
| コマンド | すること |
|---|---|
kubectl rollout status | 展開の進み具合を表示する。既定では、いちばん新しい展開が終わるまで見続ける |
kubectl rollout history | 前のリビジョンの一覧と設定を表示する。--revision で 1 つのリビジョンのひな形を詳しく見る |
kubectl rollout undo | 前の展開に切り戻す。--to-revision で戻す先のリビジョンを指定できる。付けなければ(既定は 0)、直前のリビジョンに戻る |
参考:kubectl rollout | Kubernetes、kubectl rollout status | Kubernetes、kubectl rollout history | Kubernetes、kubectl rollout undo | Kubernetes
kubectl rollout status は、見ている途中で新しい展開が始まると、いちばん新しいリビジョンを見続けます。公式ドキュメントによると、展開が無事に終わると、kubectl rollout status は終了コード 0 で終わります。終了コードは、コマンドが成功したかどうかをシェルに伝える数で、一般に 0 が成功を表します。更新が進まないまま spec.progressDeadlineSeconds の時間を過ぎると、0 以外の終了コードで終わります。
参考:kubectl rollout status | Kubernetes、Deployments | Kubernetes
kubectl rollout history の一覧の CHANGE-CAUSE 列には、リビジョンを作ったときの Deployment の kubernetes.io/change-cause というアノテーションの値が写されます。アノテーションは第11回で触れた、ラベルとは別のキーと値の組です。公式ドキュメントによると、この値を入れる方法は、kubectl annotate で Deployment に付ける、マニフェストを手で書き換える、値を自動で付けるツールを使う、の 3 つです。この回の手順では付けないので、CHANGE-CAUSE 列は公式ドキュメントの例と同じく <none> になるはずです。
更新が止まったときと切り戻し
更新しても、新しい Pod がいつまでも使える状態にならないことがあります。公式ドキュメントは、Deployment がいちばん新しい ReplicaSet を展開しようとして、終わらないまま止まる原因として、次の 6 つを挙げています。
- 割り当て量(quota)が足りない(第30回)
- readiness Probe が失敗する(第34回)
- イメージを取得できない
- 権限が足りない
- LimitRange の制限にかかる(第30回)
- アプリケーションの設定が誤っている
公式ドキュメントの切り戻しの例では、イメージのタグを nginx:1.16.1 ではなく nginx:1.161 と打ち間違えて更新します。新しい Pod はイメージを取得できず、ImagePullBackOff(第9回)のまま止まります。公式ドキュメントによると、Deployment のコントローラーはこのような悪い展開を自動で止め、新しい ReplicaSet を増やすのをやめます。どこで止まるかは maxUnavailable の値によります。既定の設定を使った公式ドキュメントの例では、古い ReplicaSet の Pod 3 つが動き続け、新しい ReplicaSet の Pod は 1 つで止まっています。
止まったままにしておくと、spec.progressDeadlineSeconds(既定は 600 秒)を過ぎたところで、Deployment の状態(status.conditions)の Progressing が False になり、理由(Reason)が ProgressDeadlineExceeded になります。ただし公式ドキュメントによると、Kubernetes は止まった Deployment に対して、状態を報告する以外に何もしません。自動で切り戻されることはありません。切り戻すのは利用者か、その状態を見て動く上位の仕組みです。
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
以下の図は、打ち間違えたイメージで止まった更新を、kubectl rollout undo で切り戻す様子を、コマ送りで表しています。公式ドキュメントの切り戻しの例の表示をもとにしています。図の D〜G は Pod の名前の略で、D・E・F はコマ送りの 1 つ目の図の最後に動いていた Pod です。数が 0 のリビジョン 1(nginx:1.14.2)の ReplicaSet は省いています。
公式ドキュメントの例では、切り戻したあとの kubectl describe deployment の表示で、Deployment のアノテーション deployment.kubernetes.io/revision が 4 になっています。公式ドキュメントも、切り戻すたびに Deployment のリビジョンが更新されると説明しています。同じ表示の OldReplicaSets: と NewReplicaSet: の欄と、Events: の欄を引用します。
この例の ReplicaSet の名前は、これまでに引用した例の名前と違います。公式ドキュメントの例は、場所ごとに名前やハッシュの値がそろっていません(別々に実行した例を載せているためだと考えられます)。ただし、1 つの ReplicaSet の名前の末尾と、その ReplicaSet の pod-template-hash のラベルの値が同じであることは、公式ドキュメントに書かれています。手元では 1 つのクラスタで順に試すので、後半の kubectl rollout history --revision=2 に出る pod-template-hash の値も、リビジョン 2 の ReplicaSet の名前の末尾と同じになると考えられます。
この例の ReplicaSet は、nginx-deployment-75675f5897 がリビジョン 1、nginx-deployment-c4747d96c がリビジョン 2、nginx-deployment-595696685f が打ち間違えたリビジョン 3 にあたると読み取れます。切り戻しのあとのイベントは DeploymentRollback と、リビジョン 3 の ReplicaSet を 0 に減らした行の 2 つだけです。NewReplicaSet: の欄は nginx-deployment-c4747d96c です。ここから、切り戻しでは新しい ReplicaSet を作らずに、リビジョン 2 の ReplicaSet をもう一度いちばん新しい ReplicaSet として使っていると読み取れます。リビジョン 2 の ReplicaSet は更新が止まっていたあいだも Pod を 3 つ持っていたので、増やす必要がなかったと考えられます。
リビジョン 2 の ReplicaSet がもう一度使われる理由は、公式ドキュメントには書かれていません。pod-template-hash はひな形から計算されるので、ひな形がリビジョン 2 と同じに戻れば、同じ値になり、同じ ReplicaSet にあたるからだと考えられます。
切り戻したあと、Deployment のリビジョンの番号は 4 です。もう一度使われる ReplicaSet が、履歴の上で 2 と 4 のどちらの番号で扱われるかは、公式ドキュメントには書かれていません。図の「リビジョン 2 だった ReplicaSet」は、リビジョン 2 のひな形を持つ ReplicaSet という意味です。Deployment のリビジョンの番号 4 は、この ReplicaSet のひな形を指していると考えられます。
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。Deployment を作ったあと、マニフェストのイメージを書き換えて更新し、kubectl rollout status で進み具合を見ます。続けて、わざと打ち間違えたイメージで更新して止まる様子を見たあと、kubectl rollout undo で切り戻します。コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | シングルノード(クラスタ名 kind):コントロールプレーン 1 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。この回ではシングルノードのクラスタを使います。この回で見る ReplicaSet と Pod の入れ替わりは、ノードが 1 つでも確かめられるからです。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。シングルノードのクラスタを用意する
まず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。
続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
Deployment を作る
「マニフェストに書くフィールド」のマニフェストを、nginx-deployment.yaml という名前で、コマンドを実行するディレクトリに保存します。第6回や第7回で保存した同じ名前のファイルが残っていたら、下の内容で上書きします(第7回では replicas を書き換えていました)。このファイルはあとでエディタで書き換えます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80前の回のクラスタを使い続けている場合は、先に nginx-deployment が無いことを確かめます。
$ kubectl get deployments --context kind-kind
nginx-deployment が表示されたら、kubectl delete deployment nginx-deployment --context kind-kind で消してから進みます。Deployment が 1 つも無ければ、見つからないことを知らせる 1 行が出るはずです。
Deployment を作ります。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
続けて、展開が終わるまで見ます。deployment/nginx-deployment は「種類/名前」の形で、nginx-deployment という Deployment を指します。
$ kubectl rollout status deployment/nginx-deployment --context kind-kind
公式ドキュメントの例では、出力は次のとおりです。
Waiting for rollout to finish で始まる行は、まだ途中であることを表します。作っただけなのに「new replicas have been updated」と出るのは、公式ドキュメントが Deployment を作ることも ReplicaSet の展開として扱っていて、最初の展開で作られる Pod も「新しい」Pod として数えられるからだと考えられます。API リファレンスによると、Deployment の status.updatedReplicas は今のひな形を持つ Pod の数なので、作ったばかりの Pod もこの数に入ります。手元では、この行の数や数字が例と違うことがあります。クラスタを作り直したばかりなら、nginx:1.14.2 のイメージの取得に時間がかかるので、しばらくこの行のまま止まって見えることもあります。最後に successfully rolled out で終わる行が出て、コマンドが終われば、展開は終わっています。
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference
続けて、今のシェルで直前に実行したコマンドの終了コードを表示します。
$ echo $?
公式ドキュメントによると、展開が無事に終わったときの kubectl rollout status の終了コードは 0 なので、0 が表示されるはずです。
最後に、ReplicaSet をラベルの列つきで一覧します。--show-labels はラベルを最後の列に表示する指定です(第6回)。
$ kubectl get rs --show-labels --context kind-kind
nginx-deployment- で始まる名前の ReplicaSet が 1 つ表示され、DESIRED、CURRENT、READY の列はどれも 3 のはずです。公式ドキュメントによると、ReplicaSet の名前の末尾のハッシュは、ReplicaSet の pod-template-hash のラベルの値と同じです。LABELS 列にも pod-template-hash= で始まるラベルがあり、その値が名前の末尾と同じになっているはずです。あとで比べられるように、ReplicaSet の名前を控えておきます。
イメージを更新する
nginx-deployment.yaml をテキストエディタで開き、image: の行を nginx:1.16.1 に書き換えて保存します。書き換えたあとの containers: の部分は次のとおりです。ほかの行は変えません。
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80公式ドキュメントの例では、イメージを kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 のコマンドで書き換えています。この回では例から変えて、マニフェストを書き換えて kubectl apply します。第7回で見たとおり、公式ドキュメントは 1 つのオブジェクトを 1 つの管理の方法だけで扱うよう警告しているからです。Deployment は kubectl apply で作ったので、更新も kubectl apply で行います。どちらのやり方でも Pod のひな形のイメージが変わるので、展開が始まることは同じです。
参考:Deployments | Kubernetes、Kubernetes Object Management | Kubernetes、kubectl set image | Kubernetes
書き換えたマニフェストを渡します。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
すぐに、展開の進み具合を見ます。
$ kubectl rollout status deployment/nginx-deployment --context kind-kind
公式ドキュメントの例では、更新の途中の出力は次の 1 行です。
終わったときの出力は次のとおりです。
手元でも、Waiting for rollout to finish で始まる行が出たあと、最後に successfully rolled out で終わる行が出るはずです。途中の行の数や数字は、例と違うことがあります。nginx:1.16.1 のイメージを取得するあいだは、しばらく止まって見えることがあります。展開がすでに終わってから実行した場合は、successfully rolled out の行だけが出ると考えられます。
更新のあとの ReplicaSet と Pod を確かめる
ReplicaSet と Pod を一覧します。
$ kubectl get rs --context kind-kind
$ kubectl get pods --context kind-kind
公式ドキュメントの例では、更新を終えたあとの kubectl get rs の出力は次のとおりです。
kubectl get pods の出力は次のとおりです。
公式ドキュメントによると、Deployment は新しい ReplicaSet を作って 3 まで増やし、古い ReplicaSet を 0 まで減らしました。手元でも ReplicaSet が 2 つ並び、一方は DESIRED 列が 3、もう一方は 0 になるはずです。0 の方の名前は「Deployment を作る」で控えた名前と同じはずです。Pod は 3 つとも、DESIRED 列が 3 の ReplicaSet の名前で始まる、新しい名前になっているはずです。名前の末尾の文字列は手元では例と違う値になります。
続けて、Deployment を詳しく表示します。
$ kubectl describe deployment nginx-deployment --context kind-kind
表示の末尾の Events: の欄に、「ひな形を書き換えるとローリングアップデートが始まる」で引用した例と同じ形の行が並ぶはずです。新しい ReplicaSet の Scaled up と古い ReplicaSet の Scaled down が交互に並び、最後は古い ReplicaSet の to 0 で終わることを確かめてください。第6回で見たとおり、イベントを残しておく時間の既定は 1 時間なので、時間を空けると古い行は表示されないことがあります。
間違ったイメージで更新して止まる様子を見る
公式ドキュメントの例と同じく、イメージのタグ(「なぜ Deployment が必要なのか」で説明)を打ち間違えて更新します。nginx-deployment.yaml の image: の行を nginx:1.161 に書き換えて保存します。1.16.1 の 2 つ目の . が抜けたタグです。公式ドキュメントの例ではこのイメージを取得できないので、手元でも取得できないと考えられます。書き換えたあとの containers: の部分は次のとおりです。
containers:
- name: nginx
image: nginx:1.161
ports:
- containerPort: 80書き換えたマニフェストを渡します。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
展開の進み具合を見ます。
$ kubectl rollout status deployment/nginx-deployment --context kind-kind
公式ドキュメントの例では、出力は次の 1 行で止まります。
手元でもこの行のまま先に進まなくなるはずです。公式ドキュメントのとおり、Ctrl+C を押して kubectl rollout status の監視を止めます。止めるのは監視だけで、Deployment の中身は変わりません。止めずに待つと、更新が進まないまま spec.progressDeadlineSeconds の 600 秒を過ぎたところで、コマンドは 0 以外の終了コードで終わります。公式ドキュメントの Failed Deployment の節の例では、kubectl rollout status deployment/nginx-deployment の出力は次のとおりです(例の中では、2 行目の名前が nginx と表示されています)。
手元では、error: で始まる行の名前は nginx-deployment と表示されると考えられます。
公式ドキュメントによると、こうして失敗とみなされた Deployment にも、完了した Deployment と同じ操作ができます。この先の手順に進むのが 10 分より遅れても、切り戻しはできます。
もし新しい Pod が Running になり、kubectl rollout status が successfully rolled out で終わったら、このタグのイメージを取得できたということです。その場合は止まる様子は見られませんが、このあとの履歴の確認と切り戻しの手順は、そのまま試せます。
ReplicaSet と Pod を一覧します。
$ kubectl get rs --context kind-kind
$ kubectl get pods --context kind-kind
公式ドキュメントの例では、kubectl get rs の出力は次のとおりです。
kubectl get pods の出力は次のとおりです。
手元でも ReplicaSet が 3 つになり、新しくできた ReplicaSet は DESIRED 列が 1、READY 列が 0 になるはずです。前の更新で作られた ReplicaSet は 3 のままです。Pod は、前の更新で作られた 3 つが Running のまま残り、新しい Pod が 1 つ加わるはずです。新しい Pod の STATUS 列は、公式ドキュメントの例では ImagePullBackOff です。手元では、少し待つまで別の値が表示されることもあります。第9回で見たとおり、ImagePullBackOff はイメージを取得できずコンテナを始められないことを表し、Kubernetes は待ち時間を延ばしながら取得を試し続けます。
続けて、Deployment の状態(Conditions: の欄)を見ます。
$ kubectl describe deployment nginx-deployment --context kind-kind
公式ドキュメントの、更新が止まった例では、表示の Replicas: の行と Conditions: の欄は次のとおりです。
止まってから 600 秒たつまでは、手元でも Progressing の行の Status は True のままだと考えられます。公式ドキュメントによると、spec.progressDeadlineSeconds を過ぎると、Progressing は Status が False、Reason が ProgressDeadlineExceeded になります。10 分ほど待ってからもう一度実行すると、この変化を確かめられます。待たずに先へ進んでもかまいません。
更新の履歴を確かめる
Deployment のリビジョンの一覧を表示します。
$ kubectl rollout history deployment/nginx-deployment --context kind-kind
公式ドキュメントの例では、出力は次のとおりです。
手元でも、リビジョン 1(nginx:1.14.2)、2(nginx:1.16.1)、3(nginx:1.161)の 3 行が並ぶはずです。CHANGE-CAUSE 列は、kubernetes.io/change-cause のアノテーションを付けていないので <none> になるはずです。
リビジョン 2 の中身を詳しく表示します。
$ kubectl rollout history deployment/nginx-deployment --revision=2 --context kind-kind
公式ドキュメントの例では、出力は次のとおりです。
この表示はリビジョン 2 の Pod のひな形です。QoS Tier: と BestEffort は、Pod に割り当てる資源(CPU とメモリ)の設定にかかわる項目で、第29回で扱います。ここでは気にしなくてかまいません。手元では、表示の形が例と違うところがあるかもしれません。Image: が nginx:1.16.1 で、ラベルに pod-template-hash= の行があることを確かめてください。
「更新が止まったときと切り戻し」で書いたとおり、公式ドキュメントの例は場所ごとに値がそろっていないので、この例の pod-template-hash の値は、上の kubectl get rs の例の名前の末尾とは違います。手元では、この値がリビジョン 2 の ReplicaSet(前の更新で作られた ReplicaSet)の名前の末尾と同じになると考えられます。
rollout undo で切り戻す
直前のリビジョン(この手順ではリビジョン 2)に切り戻します。
$ kubectl rollout undo deployment/nginx-deployment --context kind-kind
公式ドキュメントの例では、出力は次のとおりです。
参考:Deployments | Kubernetes、kubectl rollout undo | Kubernetes
戻す先を番号で決めたいときは、--to-revision=2 のように付けます。この手順では直前のリビジョンに戻すので付けません。
続けて、展開の進み具合を見ます。
$ kubectl rollout status deployment/nginx-deployment --context kind-kind
リビジョン 2 の ReplicaSet は Pod を 3 つ持ったままなので、あまり待たずに successfully rolled out で終わる行が出ると考えられます。その前に Waiting for rollout to finish で始まる行が出ることもあります。
ReplicaSet と Pod を一覧します。
$ kubectl get rs --context kind-kind
$ kubectl get pods --context kind-kind
「更新が止まったときと切り戻し」で見た公式ドキュメントの例のとおりなら、nginx:1.161 の ReplicaSet は DESIRED 列が 0 になり、前の更新で作られた ReplicaSet は 3 のままになると考えられます。ReplicaSet は 3 つのまま並ぶはずです。Pod は、ImagePullBackOff だった Pod が消え、切り戻す前から Running だった 3 つが同じ名前のまま残ると考えられます。ImagePullBackOff だった Pod が Terminating で残っていたら、少し待ってからもう一度実行します。
Deployment を詳しく表示します。
$ kubectl describe deployment nginx-deployment --context kind-kind
公式ドキュメントの例のとおりなら、Annotations: の欄の deployment.kubernetes.io/revision が 4 になり、Events: の欄に DeploymentRollback の行と、nginx:1.161 の ReplicaSet を to 0 に減らした行が加わるはずです。Image: が nginx:1.16.1 に戻っていることも確かめてください。NewReplicaSet: の欄は前の更新で作られた ReplicaSet(DESIRED 列が 3 の ReplicaSet)の名前になると考えられます。
この手順では Deployment を kubectl apply で作ったので、Annotations: の欄には、第7回で見た kubectl.kubernetes.io/last-applied-configuration も並ぶと考えられます。公式ドキュメントの例の表示には、この行はありません。
最後に、リビジョンの一覧をもう一度表示します。
$ kubectl rollout history deployment/nginx-deployment --context kind-kind
切り戻したあとの一覧の例は公式ドキュメントにはありません。Deployment のリビジョンが 4 になったので、いちばん下の行の番号は 4 になると考えられます。「更新の履歴を確かめる」の一覧(1、2、3 の 3 行)と比べて、リビジョン 2 の行が残っているかどうかを見てください。リビジョン 2 のひな形を持つ ReplicaSet がリビジョン 4 として使われているなら、2 の行は無くなっているかもしれません。どちらになっても、この先の手順には影響しません。
マニフェストのファイルを合わせる
kubectl rollout undo が書き換えたのは、クラスタにある Deployment だけです。手元の nginx-deployment.yaml は nginx:1.161 のままです。第7回で見たとおり、マニフェストに書いてあるフィールドは、次の kubectl apply でマニフェストの値に戻ります。このファイルのまま kubectl apply すると、ひな形が nginx:1.161 に戻ります。そうなると、また止まる更新が始まると考えられます。
参考:Declarative Management of Kubernetes Objects Using Configuration Files | Kubernetes、Deployments | Kubernetes
そこで、nginx-deployment.yaml の image: の行を nginx:1.16.1 に戻して保存し、マニフェストを渡します。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
$ kubectl rollout history deployment/nginx-deployment --context kind-kind
ファイルのひな形が今の Deployment のひな形と同じになったので、ひな形は変わらず、新しい展開は始まりません。リビジョンの一覧が 1 つ前の kubectl rollout history のときと変わっていなければ、新しいリビジョンは作られていません。
片付け
作った Deployment を、マニフェストを指定して削除します。
$ kubectl delete -f nginx-deployment.yaml --context kind-kind
第6回で見たとおり、Deployment を消すと、既定ではガベージコレクター(不要になった従属物を片付けるコントローラー)が従属物を消すので、Deployment の ReplicaSet と Pod も消えます。数が 0 の古い ReplicaSet も Deployment が持っているので、一緒に消えます。少し待ってから、残っていないかを確かめます。
$ kubectl get deployments,rs,pods --context kind-kind
参考:Garbage Collection | Kubernetes、kubectl delete | Kubernetes、Owners and Dependents | Kubernetes
Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。nginx-deployment の Deployment、ReplicaSet、Pod がどれも表示されなければ、削除できています。
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster
$ kind get clusters
kind delete cluster は存在しないクラスタを指定してもエラーを返しません。kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。
注意点
Deployment の更新と切り戻しでつまずきやすい点と、後から変えられない設定を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
| 展開が始まるのはひな形を変えたときだけ | spec.template のラベルやイメージを変えると始まる。replicas を変えても始まらず、リビジョンも作られない |
| 切り戻しで戻るのはひな形だけ | replicas などのひな形の外の値は戻らない |
| 止まった更新は自動では戻らない | progressDeadlineSeconds(既定 600 秒)を過ぎると ProgressDeadlineExceeded が報告されるだけ。切り戻すのは利用者 |
revisionHistoryLimit を 0 にしない | 古い ReplicaSet が残らず、切り戻せなくなる。既定は 10 |
| Deployment が持つ ReplicaSet を直接いじらない | 数も中身も Deployment を通して変える。pod-template-hash のラベルも変えない |
| セレクタは作ったあとで変えられない | apps/v1 の Deployment の spec.selector は作ったあとで変えられない。変えるには Deployment を作り直す |
rollout undo のあとはファイルを直す | kubectl rollout undo はクラスタの Deployment だけを書き換える。kubectl apply で管理しているなら、マニフェストも合わせる |
| 更新中は Pod が多く見えることがある | 終了中の Pod は使える Pod の数に入らない。そのため更新中は Pod が思ったより多く見えたり、replicas と maxSurge の合計より多くの資源を使ったりすることがある |
| 更新の途中で更新すると、すぐ次へ移る | 展開の途中でひな形を変えると、新しい ReplicaSet を作って増やし始め、途中だった ReplicaSet は古い側に回して減らす |
参考:Deployments | Kubernetes、Deployment | Kubernetes API Reference、Declarative Management of Kubernetes Objects Using Configuration Files | Kubernetes
セレクタは作ったあとで変えられないので、最初に決めておきます。公式ドキュメントによると、Deployment のラベルセレクタは作ったあとでは変えられず、kubectl apply や kubectl edit でも変えられません。変えるには、Deployment を消して作り直します。消すと、--cascade=orphan を付けない限り、その Pod も消えます。
更新中に Pod が多く見えることについて、v1.37 時点では、終了中の Pod の数を Deployment の status.terminatingReplicas で追えます。第6回で見たとおり、beta の機能ですが既定で有効です。
関連記事
ローリングアップデートの速さと安全性を、maxSurge と maxUnavailable で調整する仕組みについては、以下の記事で解説しています。
マニフェストを渡す kubectl apply と、オブジェクトを直接書き換える kubectl edit の違いと、1 つのオブジェクトを 1 つの方法で管理する理由については、以下の記事で解説しています。
Pod が要求を受けられるかを確かめる readiness Probe を含む、3 種類の Probe の違いについては、以下の記事で解説しています。