Rolling Update / Rollback: Kubernetes でアプリを止めずに Pod を入れ替える
- 公開日
- カテゴリ:Kubernetes
- タグ:Kubernetes,学習メモ

動いているアプリを、アクセスを止めずに新しいバージョンへ入れ替えたい。Kubernetes では、Deployment の spec.template(Pod の定義)の image を書き換えて apply すると、入れ替えは Kubernetes がやる。どう入れ替えるかは Deployment の strategy で決まる。
この記事では、同じ nginx の Deployment(Pod 3 個)で、次の 3 つを確認する。
- 入れ替え(Rolling Update):
imageをnginx:1.16.1からnginx:1.17.10に変え、古い Pod が 1 つずつ新しい Pod に入れ替わる様子 - 失敗した入れ替え: 存在しない image に変えて、入れ替えが途中で止まる様子と、それでもアプリが止まらないこと
- 戻す(Rollback):
kubectl rollout undoで前の revision に戻す
この記事でやること:
- Deployment・Service と、Service に 1 秒ごとにアクセスし続ける Pod を作り、入れ替えの前のバージョンを確認
- YAML の
imageを書き換えてapplyし、kubectl get rs -wで新旧 2 つの ReplicaSet の数が交互に動くことと、アクセスが途切れないことの確認 - Deployment の
Events:とstrategy(maxSurge/maxUnavailable)で、入れ替えの順番が決まる仕組みの確認 kubectl rollout historyで revision を見て、その実体が数 0 で残った古い ReplicaSet であることの確認- 存在しない image で
ImagePullBackOffになり、入れ替えが「新しい Pod 1 つ」で止まることの確認 kubectl rollout undoと--to-revision=Nで戻し、revision の番号がどう変わるかの確認Recreate、maxSurge: 0、progressDeadlineSeconds、CHANGE-CAUSE、pause/resumeの確認
contents
- 環境
- Rolling Update と Rollback
- Deployment・Service・アクセスし続ける Pod を作る
- image を書き換えて apply する
- 入れ替えの順番と strategy
- 履歴を見る(kubectl rollout history)
- 失敗する入れ替え:存在しない image に変える
- 1 つ前に戻す(kubectl rollout undo)
- 特定の revision に戻す(--to-revision)
- 消す
- 全部消してから作る(strategy.type: Recreate)
- 消してから作る順にする(maxSurge: 0)
- 止まった入れ替えを失敗と判定させる(progressDeadlineSeconds)
- CHANGE-CAUSE に理由を残す
- 入れ替えを一時停止する(kubectl rollout pause / resume)
- 用語メモ
- 参考 URL
環境
kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)
- 環境: macOS(Apple Silicon)、Docker Desktop 29.8.0、kind v0.33.0、Kubernetes v1.37.0、kubectl v1.36.1
Rolling Update と Rollback
同じ nginx の Deployment で、「入れ替え」「失敗した入れ替え」「戻す」の 3 つを順に見る。
| 入れ替え | 失敗した入れ替え | 戻す | |
|---|---|---|---|
| やること | image を nginx:1.16.1 → nginx:1.17.10 に変える | 存在しない image(nginx:1.17.10-typo)に変える | kubectl rollout undo で 1 つ前に戻す |
| 何が起きるか | 新しい ReplicaSet ができ、古い ReplicaSet と Pod の数が 1 つずつ入れ替わる | 新しい Pod が 1 つ ImagePullBackOff のまま止まる | 1 つ前の ReplicaSet の数が 3 に戻り、失敗した ReplicaSet は 0 になる |
| アクセス | 途切れない(古い Pod と新しい Pod が混ざって返事する) | 途切れない(古い Pod 3 つがそのまま動いている) | 途切れない |
rollout history | REVISION が 1 → 2 | 3 が増える | 戻した版が新しい番号(4)になる |
この記事の本文と実行結果に出てくる言葉は次のとおり。
| 名前 | 内容 |
|---|---|
| Deployment → ReplicaSet → Pod | Deployment を作ると ReplicaSet が作られ、ReplicaSet が Pod を作る 3 段。ReplicaSet は「この Pod の定義(template)を N 個」というデータで、Pod の定義が変わると新しい ReplicaSet が作られる。古い ReplicaSet は消されず、数 0 で残る(「Deployment: Kubernetes で Pod の数と更新を管理する」を参照) |
| rollout | Deployment の Pod を、新しい定義の Pod に入れ替えること。入れ替えの一連の流れ全体を指す。kubectl rollout というコマンドの名前にもなっている |
| Rolling Update | rollout のやり方の 1 つ。古い Pod を全部消してから作るのではなく、少しずつ(rolling)入れ替える。Deployment の既定(strategy.type: RollingUpdate) |
| revision | Pod の定義が変わるたびに 1 増える番号。ReplicaSet に付く。kubectl rollout history で見える |
Rollback / kubectl rollout undo | 前の revision の Pod の定義に戻すこと。戻すときも「Pod の定義を変える」なので、Rolling Update で入れ替わる |
ImagePullBackOff | kubelet が image を取ってこられず(存在しない、名前の間違い、レジストリに届かない)、間をあけて取り直しを繰り返している、という Pod の STATUS。取り直しに失敗した瞬間は ErrImagePull |
今回は nginx:1.16.1 を nginx:1.17.10 に変える。nginx としての動きは同じ(HTML を返すだけ)なので、違いはバージョン番号そのもので見る。
| 見るもの | nginx:1.16.1 | nginx:1.17.10 |
|---|---|---|
kubectl exec deploy/demo-app -- nginx -v | nginx version: nginx/1.16.1 | nginx version: nginx/1.17.10 |
HTTP の返事の Server ヘッダー | Server: nginx/1.16.1 | Server: nginx/1.17.10 |
kubectl get rs の IMAGES 列と、ReplicaSet の名前のハッシュ | nginx:1.16.1 / demo-app-86547cd9f8 | nginx:1.17.10 / demo-app-799468465 |
| Pod の名前の真ん中(ReplicaSet のハッシュ) | demo-app-86547cd9f8-xxxxx | demo-app-799468465-xxxxx |
Rolling Update はどこで起きているか
Deployment も ReplicaSet も、Control Plane で動いている etcd に保存されるデータ。入れ替えを進めるプログラムは kube-controller-manager(Control Plane の Pod)の中の deployment-controller と replicaset-controller。新しい image を取ってきてコンテナを起動するのは、Pod が置かれた Node の kubelet。
- 角が二重の箱(kube-controller-manager、kubelet)は動いているプログラム。それ以外は etcd に保存されたデータと、Node の上の Pod
- 自分で書き換えるのは Deployment の
spec.templateだけ。deployment-controller が新しい ReplicaSet を作り、古い ReplicaSet と新しい ReplicaSet のDESIREDを 1 つずつ入れ替える。DESIREDに合わせて Pod を作る・消すのは replicaset-controller。replicasを手で変えたときと、ReplicaSet から下は同じ流れ(「HPA: Kubernetes で負荷に応じて Pod を自動でスケールする」を参照) - image を取ってくるのは Control Plane ではなく、Pod が置かれた Node の kubelet。image が取れなければ、その Pod が
ImagePullBackOffになる - 古い Pod は入れ替えが進むにつれて消えていく。図は入れ替えが終わったあとの Pod の配置
Deployment・Service・アクセスし続ける Pod を作る
replicas: 3 の Deployment を作る。この記事で書き換えるのは image の行だけ。
# demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
| フィールド | 意味 |
|---|---|
spec.replicas: 3 | Pod を 3 個。1 個だと「少しずつ入れ替える」が見えないので 3 個にする |
spec.template | Pod の定義。ここが変わると、新しい ReplicaSet が作られて入れ替えが始まる。replicas を変えても入れ替えは起きない |
spec.template.spec.containers[0].image | 今回変える行。nginx:1.16.1 を nginx:1.17.10 にする |
spec.strategy | 書いていない。書かないと RollingUpdate(後の節で確認する) |
demo-service.yaml は、app: demo-app の Pod に振り分ける Service。(Service は「Service: Kubernetes で Pod への宛先を固定する」を参照)
# demo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-service
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
client.yaml は、入れ替えの間もアクセスが途切れないことを見るための Pod。busybox の wget で demo-service に 1 秒ごとにアクセスし、返事の Server ヘッダー(nginx が自分のバージョンを名乗る行)を時刻付きで出力し続ける。
nginx(demo-app。アクセスされる側) | client(アクセスする側) | |
|---|---|---|
| 何か | Web サーバー | 1 秒に 1 回 demo-service に HTTP でアクセスし、返事の Server: の行だけを記録するコマンド |
| 出力 | なし | 04:12:36 Server: nginx/1.16.1 のような行が 1 秒ごとに増える。返事がなければ FAIL |
| 入れ替えるか | する | しない(別の Pod。最後まで同じものが動き続ける) |
# client.yaml
apiVersion: v1
kind: Pod
metadata:
name: client
spec:
restartPolicy: Never
containers:
- name: client
image: busybox:1.36
command:
- sh
- -c
- |
while true; do
r=$(wget -S -q -O /dev/null -T 2 http://demo-service 2>&1 | grep -i 'Server:')
echo "$(date +%T) ${r:-FAIL}"
sleep 1
done
| 部分 | 意味 |
|---|---|
wget -S -q -O /dev/null -T 2 http://demo-service | demo-service にアクセスして、本文は捨て(-O /dev/null)、返事のヘッダーを表示する(-S)。2 秒返事がなければあきらめる(-T 2) |
grep -i 'Server:' | ヘッダーのうち Server: nginx/1.16.1 の行だけ残す |
echo "$(date +%T) ${r:-FAIL}" | 時刻と一緒に出力する。Server: の行が取れなかった(返事がなかった)ときは FAIL と出す |
| 時刻 | コンテナの中の date は UTC。日本時間より 9 時間前の値が出る |
$ kubectl apply -f demo-deployment.yaml -f demo-service.yaml -f client.yaml
deployment.apps/demo-app created
service/demo-service created
pod/client created
$ kubectl get deploy,rs,pods -l app=demo-app -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/demo-app-86547cd9f8 3 3 3 5s nginx nginx:1.16.1 app=demo-app,pod-template-hash=86547cd9f8
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/demo-app-86547cd9f8-9hntt 1/1 Running 0 5s 10.244.1.17 k8s-study-worker <none> <none>
pod/demo-app-86547cd9f8-rtgkv 1/1 Running 0 5s 10.244.2.19 k8s-study-worker2 <none> <none>
pod/demo-app-86547cd9f8-ttx62 1/1 Running 0 5s 10.244.1.18 k8s-study-worker <none> <none>
$ kubectl get pod client
NAME READY STATUS RESTARTS AGE
client 1/1 Running 0 15s
- ReplicaSet は 1 つ。
IMAGESがnginx:1.16.1。名前はdemo-app-<Pod の定義から計算したハッシュ> - Pod は 3 つとも名前の真ん中が ReplicaSet と同じハッシュ
86547cd9f8 -l app=demo-appで Deployment の Pod だけに絞っている。clientはラベルが違うので別に見る
今のバージョンを 2 つの方法で見ておく。
$ kubectl exec deploy/demo-app -- nginx -v
# => nginx version: nginx/1.16.1
$ kubectl logs client --tail=5
04:12:36 Server: nginx/1.16.1
04:12:37 Server: nginx/1.16.1
04:12:38 Server: nginx/1.16.1
04:12:39 Server: nginx/1.16.1
04:12:40 Server: nginx/1.16.1
| コマンド | 意味 |
|---|---|
kubectl exec deploy/demo-app -- nginx -v | Deployment の Pod のどれか 1 つの中で nginx -v(バージョン表示)を実行する |
kubectl logs client --tail=5 | client の出力の最後の 5 行。1 秒ごとに Server: nginx/1.16.1。3 つの Pod のどれが返事しても、今は全部 1.16.1 |
入れ替えの前に、kubectl rollout history deployment/<Deployment 名> で、この Deployment の「Pod の定義の履歴」を見ておく。
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
1 <none>
| 列 | 意味 |
|---|---|
REVISION | Pod の定義の版の番号。作った直後は 1 だけ。Pod の定義が変わるたびに 1 つ増える |
CHANGE-CAUSE | 「この revision は何のために変えたか」を書いておく欄。自動では何も入らない(後の節で確認する) |
image を書き換えて apply する
demo-deployment.yaml の image の行を書き換える。変えるのはこの 1 行だけ。
- name: nginx
image: nginx:1.17.10
kubectl get rs -w で入れ替わりを見る
apply のすぐあとに kubectl get rs -w を続けて打つ。-w を付けると、ReplicaSet の数が変わるたびに行が追加される。古い ReplicaSet が 0 0 0、新しい ReplicaSet が 3 3 3 になったら Ctrl+C で止める。
$ kubectl apply -f demo-deployment.yaml; kubectl get rs -l app=demo-app -w
deployment.apps/demo-app configured
NAME DESIRED CURRENT READY AGE
demo-app-799468465 1 1 0 0s
demo-app-86547cd9f8 3 3 3 17m
demo-app-799468465 1 1 1 1s
demo-app-86547cd9f8 2 3 3 17m
demo-app-86547cd9f8 2 3 3 17m
demo-app-86547cd9f8 2 2 2 17m
demo-app-799468465 2 1 1 1s
demo-app-799468465 2 1 1 1s
demo-app-799468465 2 2 1 1s
demo-app-86547cd9f8 2 2 2 17m
demo-app-799468465 2 2 2 2s
demo-app-86547cd9f8 1 2 2 17m
demo-app-799468465 3 2 2 2s
demo-app-86547cd9f8 1 2 2 17m
demo-app-86547cd9f8 1 1 1 17m
demo-app-799468465 3 2 2 2s
demo-app-799468465 3 3 2 2s
demo-app-86547cd9f8 1 1 1 17m
demo-app-799468465 3 3 3 3s
demo-app-86547cd9f8 0 1 1 17m
demo-app-86547cd9f8 0 1 1 17m
demo-app-86547cd9f8 0 0 0 17m
demo-app-86547cd9f8 0 0 0 17m
同じ内容の行が 2 回出ることがある。列の意味は次のとおり。すべて「その ReplicaSet が持つ Pod の数」で、DESIRED だけが目標、あとの 2 つは今の実際の数。
| 列 | 意味 |
|---|---|
NAME | ReplicaSet の名前。<Deployment 名>-<ハッシュ> で、ハッシュは Pod の定義(image など)から決まる。定義が違えば名前も違う |
DESIRED | deployment-controller が「この ReplicaSet に持たせたい Pod の数」として書いた値。入れ替え中はこの数が 1 ずつ増えたり減ったりする |
CURRENT | 実際に存在している Pod の数(起動中・終了中も含む)。DESIRED に合わせて replicaset-controller が Pod を作ったり消したりするので、少し遅れて追いつく |
READY | そのうち、起動が終わってリクエストを受けられる Pod の数。image を取ってきて nginx が立ち上がるまでは CURRENT より小さい |
AGE | その ReplicaSet が作られてからの時間 |
行を上から追うと、2 つの ReplicaSet の DESIRED がこう動いている。
| 順 | 古い demo-app-86547cd9f8(1.16.1) | 新しい demo-app-799468465(1.17.10) | 何が起きたか |
|---|---|---|---|
| 0 | 3 | (なし) | 入れ替えの前 |
| 1 | 3 | 1 | 新しい ReplicaSet が作られ、Pod が 1 つ作られる。READY が 1 になるまで 1 秒 |
| 2 | 2 | 1 | 新しい Pod が準備できたので、古い Pod を 1 つ消す |
| 3 | 2 | 2 | 新しい Pod をもう 1 つ作る |
| 4 | 1 | 2 | 準備できたら、古い Pod をもう 1 つ消す |
| 5 | 1 | 3 | 新しい Pod を 3 つ目 |
| 6 | 0 | 3 | 最後の古い Pod を消す。入れ替え完了 |
- 古い Pod を消すのは、新しい Pod が
READYになってから。新しい Pod が準備できるまでは、古い 3 つがそのまま動いている - 同時に存在する Pod は最大 4 つ(古い 3 + 新しい 1)、最低 3 つ。この「最大 4・最低 3」を決めているのが
strategyのmaxSurge/maxUnavailable(後の節で確認する) - 古い ReplicaSet は
0 0 0になっても消えない。一覧に残り続ける - 1.17.10 の image はこのクラスタに一度も取ってきていなかったが、
READYまで 1 秒で済んだ。ここは image の大きさと回線で変わる
kubectl rollout status で待つ
入れ替えが終わるのを待つコマンドが kubectl rollout status deployment/<Deployment 名>。終わっていれば successfully rolled out とだけ出る。
$ kubectl rollout status deployment/demo-app
# => deployment "demo-app" successfully rolled out
apply の直後に打つと、終わるまで進み具合を出し続ける。
$ kubectl rollout status deployment/demo-app
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
deployment "demo-app" successfully rolled out
| 行 | 意味 |
|---|---|
1 out of 3 new replicas have been updated... | 3 つのうち 1 つが新しい定義になった |
1 old replicas are pending termination... | 新しい Pod は 3 つそろい、古い Pod が 1 つ消えるのを待っている |
successfully rolled out | 入れ替え完了。このコマンドは終了する。止まった入れ替えでは、ここに来ない(後の節で確認する) |
アクセスが途切れていないことを見る
$ kubectl logs client | grep -m1 -B5 -A3 '1.17.10'
04:29:10 Server: nginx/1.16.1
04:29:11 Server: nginx/1.16.1
04:29:12 Server: nginx/1.16.1
04:29:13 Server: nginx/1.16.1
04:29:14 Server: nginx/1.16.1
04:29:15 Server: nginx/1.17.10
04:29:16 Server: nginx/1.17.10
04:29:17 Server: nginx/1.17.10
04:29:18 Server: nginx/1.17.10
$ kubectl logs client | grep -c FAIL
# => 0
$ kubectl exec deploy/demo-app -- nginx -v
# => nginx version: nginx/1.17.10
| コマンド | 意味 |
|---|---|
grep -m1 -B5 -A3 '1.17.10' | 最初に 1.17.10 が出た行(-m1)の前 5 行(-B5)と後 3 行(-A3)。返事が 1.16.1 から 1.17.10 に切り替わった瞬間 |
grep -c FAIL | 返事がなかった回数。0 なら、1 秒に 1 回のアクセスは一度も失敗していない |
- 入れ替え中も毎秒返事が来ていて、
FAILは 0。アプリは止まっていない - 入れ替え中は古い Pod と新しい Pod が混ざって動いているので、
1.16.1と1.17.10が交互に出ることもある(Service はそのとき準備できている Pod に振り分ける)。今回は04:29:15からきれいに1.17.10だけになった
入れ替えの順番と strategy
終わったあとの形
$ kubectl get deploy,rs,pods -l app=demo-app -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/demo-app-799468465 3 3 3 7m8s nginx nginx:1.17.10 app=demo-app,pod-template-hash=799468465
replicaset.apps/demo-app-86547cd9f8 0 0 0 24m nginx nginx:1.16.1 app=demo-app,pod-template-hash=86547cd9f8
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/demo-app-799468465-khxct 1/1 Running 0 7m7s 10.244.1.19 k8s-study-worker <none> <none>
pod/demo-app-799468465-kkc5b 1/1 Running 0 7m6s 10.244.2.22 k8s-study-worker2 <none> <none>
pod/demo-app-799468465-kkrk2 1/1 Running 0 7m8s 10.244.2.21 k8s-study-worker2 <none> <none>
- ReplicaSet が 2 つ。古い
86547cd9f8(nginx:1.16.1)は0 0 0、新しい799468465(nginx:1.17.10)が3 3 3 - Pod は 3 つとも新しいハッシュ。最初に作った Pod は 1 つも残っていない。「image を変える」は「Pod を作り直す」ことで、動いている Pod のコンテナを差し替えるのではない
- Pod の
AGEが7m8s/7m7s/7m6sと 1 秒ずつずれている。1 つずつ順に作られた
Events で順番を見る
$ kubectl describe deploy demo-app | grep -A 10 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 24m deployment-controller Scaled up replica set demo-app-86547cd9f8 from 0 to 3
Normal ScalingReplicaSet 7m37s deployment-controller Scaled up replica set demo-app-799468465 from 0 to 1
Normal ScalingReplicaSet 7m36s deployment-controller Scaled down replica set demo-app-86547cd9f8 from 3 to 2
Normal ScalingReplicaSet 7m36s deployment-controller Scaled up replica set demo-app-799468465 from 1 to 2
Normal ScalingReplicaSet 7m35s deployment-controller Scaled down replica set demo-app-86547cd9f8 from 2 to 1
Normal ScalingReplicaSet 7m35s deployment-controller Scaled up replica set demo-app-799468465 from 2 to 3
Normal ScalingReplicaSet 7m34s deployment-controller Scaled down replica set demo-app-86547cd9f8 from 1 to 0
- 1 行目は Deployment を作ったときの記録。2 行目からが入れ替え
Scaled up ... 799468465 from 0 to 1(新しいのを 1 つ)→Scaled down ... 86547cd9f8 from 3 to 2(古いのを 1 つ)→up ... 1 to 2→down ... 2 to 1→up ... 2 to 3→down ... 1 to 0。-wで見た動きと同じFromはすべてdeployment-controller。ReplicaSet の数を書き換えているのは deployment-controller で、違いは「ReplicaSet が 2 つあって、片方を増やし、もう片方を減らす」こと。Pod を実際に作る・消すのは replicaset-controller
順番を決めている strategy
kubectl describe deploy の上のほうに、入れ替えのやり方が出ている。
$ kubectl describe deploy demo-app | grep -E 'Replicas:|StrategyType|RollingUpdateStrategy'
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
RollingUpdateStrategy: 25% max unavailable, 25% max surge
| 行 | 意味 |
|---|---|
StrategyType: RollingUpdate | 入れ替えのやり方。YAML の spec.strategy.type。書いていないので既定の RollingUpdate(少しずつ入れ替える)。もう 1 つの値は Recreate(全部消してから作る。後の節で確認する) |
25% max surge | replicas を超えて作ってよい Pod の数。YAML の spec.strategy.rollingUpdate.maxSurge。既定 25%。3 個の 25% = 0.75 → 切り上げて 1。だから最大 3 + 1 = 4 個 |
25% max unavailable | 入れ替え中に足りなくてよい Pod の数。同じく maxUnavailable。既定 25%。3 個の 25% = 0.75 → 切り捨てて 0。だから最低 3 個は動いていないといけない |
Replicas: 3 desired / 3 updated / 3 total / 3 available | 欲しい数 / 新しい定義になった数 / 今ある数 / 使える数。入れ替え中は 1 updated / 4 total のように動く |
「最大 4、最低 3」なので、1 つ作って 1 つ消す、しかできない。これが上で見た順番の理由。deployment-controller は順番そのものを設定で持っているわけではなく、この上限と下限を守って「作れるなら作る、作れなければ消せるだけ消す」を繰り返している。
YAML に書くなら、spec に replicas と並べて書く(今回は書いていないので既定値)。
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
履歴を見る(kubectl rollout history)
入れ替えのあと、REVISION が 2 つになっている。それぞれの revision がどの image かを --revision=N で見る。
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
1 <none>
2 <none>
$ kubectl rollout history deployment/demo-app --revision=1
deployment.apps/demo-app with revision #1
Pod Template:
Labels: app=demo-app
pod-template-hash=86547cd9f8
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Host Port: 0/TCP
Limits:
cpu: 200m
memory: 128Mi
Requests:
cpu: 100m
memory: 64Mi
Environment: <none>
Mounts: <none>
Volumes: <none>
Node-Selectors: <none>
Tolerations: <none>
| 表示 | 意味 |
|---|---|
REVISION 1 / 2 | 作ったときの Pod の定義が 1、image を変えたあとの定義が 2 |
--revision=1 の Pod Template: | revision 1 の Pod の定義そのもの。Image: nginx:1.16.1、pod-template-hash=86547cd9f8(古い ReplicaSet のハッシュ) |
revision の実体は ReplicaSet
revision は Deployment が別に持っているデータではなく、各 ReplicaSet に付いたアノテーション deployment.kubernetes.io/revision の値。kubectl rollout history は、Deployment の ReplicaSet を全部集めて、このアノテーションの番号順に並べているだけ。
$ kubectl get rs -l app=demo-app -o custom-columns='NAME:.metadata.name,REVISION:.metadata.annotations.deployment\.kubernetes\.io/revision,DESIRED:.spec.replicas,IMAGE:.spec.template.spec.containers[0].image'
NAME REVISION DESIRED IMAGE
demo-app-799468465 2 3 nginx:1.17.10
demo-app-86547cd9f8 1 0 nginx:1.16.1
-o custom-columns= で、表示する列を自分で決めている。アノテーションの名前の . は \. と書く。
- 古い ReplicaSet
86547cd9f8がDESIRED 0で残っているのは、この revision 1 の定義を取っておくため。kubectl rollout undoは、この ReplicaSet の定義を使って戻す - 残す数は Deployment の
spec.revisionHistoryLimitで決まり、既定は 10。それを超えた古い ReplicaSet から消される(消えた revision には戻れない) replicasだけ変えても revision は増えない。増えるのはspec.template(Pod の定義)が変わったときだけ
失敗する入れ替え:存在しない image に変える
kubectl set image で変える
YAML を書き換えずに image だけを変える近道が kubectl set image deployment/<Deployment 名> <コンテナ名>=<image>。ここでは、わざと存在しない tag(nginx:1.17.10-typo)を指定する。
$ kubectl set image deployment/demo-app nginx=nginx:1.17.10-typo
# => deployment.apps/demo-app image updated
| 部分 | 意味 |
|---|---|
nginx=nginx:1.17.10-typo | コンテナ nginx(YAML の containers[].name)の image を nginx:1.17.10-typo にする。Deployment の spec.template.spec.containers[0].image を書き換えている。YAML を書き換えて apply したのと、変わるフィールドは同じ |
image updated | 書き換えた。入れ替えが始まる |
kubectl scale と同じ位置づけで、YAML ファイルの image: nginx:1.17.10 はそのまま残る。
止まっているのを見る
$ kubectl rollout status deployment/demo-app
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
1 out of 3 のまま進まない。このコマンドは入れ替えが終わるまで終了しないので、Ctrl+C で止める。
$ kubectl get deploy,rs,pods -l app=demo-app
NAME DESIRED CURRENT READY AGE
replicaset.apps/demo-app-6c4dcbb5b8 1 1 0 27s
replicaset.apps/demo-app-799468465 3 3 3 11m
replicaset.apps/demo-app-86547cd9f8 0 0 0 28m
NAME READY STATUS RESTARTS AGE
pod/demo-app-6c4dcbb5b8-7q8pj 0/1 ImagePullBackOff 0 27s
pod/demo-app-799468465-khxct 1/1 Running 0 11m
pod/demo-app-799468465-kkc5b 1/1 Running 0 11m
pod/demo-app-799468465-kkrk2 1/1 Running 0 11m
Pod の STATUS は、打つタイミングで ErrImagePull と ImagePullBackOff のどちらかが出る。
| 行 | 見るところ |
|---|---|
| ReplicaSet | 3 つ目の ReplicaSet 6c4dcbb5b8(typo の定義)が DESIRED 1 / READY 0。2 つ目の 799468465(1.17.10)は 3 3 3 のまま |
| Pod | 新しい Pod が 1 つ 0/1、ImagePullBackOff。1.17.10 の Pod 3 つはそのまま Running |
なぜ 1 つで止まるか。maxSurge 1 / maxUnavailable 0 なので、「新しい Pod を 1 つ作る」までは進めるが、その Pod が READY にならないので「古い Pod を 1 つ消す」に進めない。作ってよい数も 1 つが上限なので、2 つ目も作れない。deployment-controller はこの状態で待ち続け、自分では戻さない。
Pod の Events で原因を見る
止まっている Pod の名前を、上の kubectl get pods から取って describe する。
$ kubectl describe pod demo-app-6c4dcbb5b8-7q8pj | grep -A 10 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m17s default-scheduler Successfully assigned default/demo-app-6c4dcbb5b8-7q8pj to k8s-study-worker
Normal Pulling 45s (x4 over 2m16s) kubelet spec.containers{nginx}: Pulling image "nginx:1.17.10-typo"
Warning Failed 44s (x4 over 2m15s) kubelet spec.containers{nginx}: Failed to pull image "nginx:1.17.10-typo": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/nginx:1.17.10-typo": failed to resolve reference "docker.io/library/nginx:1.17.10-typo": docker.io/library/nginx:1.17.10-typo: not found
Warning Failed 44s (x4 over 2m15s) kubelet spec.containers{nginx}: Error: ErrImagePull
Normal BackOff 5s (x8 over 2m15s) kubelet spec.containers{nginx}: Back-off pulling image "nginx:1.17.10-typo"
Warning Failed 5s (x8 over 2m15s) kubelet spec.containers{nginx}: Error: ImagePullBackOff
| 行 | 意味 |
|---|---|
Pulling image "nginx:1.17.10-typo"(From: kubelet) | Node の kubelet が Docker Hub から image を取ろうとした |
Failed to pull image ... not found | Docker Hub に nginx:1.17.10-typo という tag はない、と言われた |
Error: ErrImagePull | 取れなかった。STATUS が ErrImagePull になる瞬間 |
Back-off pulling image / Error: ImagePullBackOff | 間をあけて取り直す(待ち時間は失敗するたびに延びる)。待っている間の STATUS が ImagePullBackOff |
(x4 over 2m16s) | 同じことが 2 分 16 秒の間に 4 回起きた |
image を取るのは、Control Plane ではなく Pod が置かれた Node の kubelet。だから、その Node から Docker Hub に届かないときも同じ ImagePullBackOff になる。
アクセスは途切れていない
$ kubectl logs client --tail=3
04:43:07 Server: nginx/1.17.10
04:43:08 Server: nginx/1.17.10
04:43:09 Server: nginx/1.17.10
$ kubectl logs client | grep -c FAIL
# => 0
$ kubectl describe deploy demo-app | grep -E -A4 'Replicas:|Conditions:'
Replicas: 3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
--
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
1 <none>
2 <none>
3 <none>
clientは 1.17.10 から返事をもらい続けている。FAILは 0。入れ替えが失敗しても、アプリは止まっていないReplicas: 3 desired | 1 updated | 4 total | 3 available | 1 unavailable。Pod は 4 つあって(古い 3 + 新しい 1)、使えるのは 3 つConditions:のProgressing True ReplicaSetUpdated。「入れ替えは進行中」のまま。Kubernetes から見ると「失敗」ではなく「まだ終わっていない」(既定では 10 分たつとFalse/ProgressDeadlineExceededに変わる。後の節で確認する)- typo の定義も
REVISION 3として履歴に入っている
アプリが止まらなかったのは、maxUnavailable が 0 で「使える Pod の下限が 3」だったため。古い Pod は、新しい Pod が使えるようになるまで 1 つも消されない。入れ替えに失敗しても古いほうがそのまま動き続けるので、人が気づいて戻すまでの時間がある。
1 つ前に戻す(kubectl rollout undo)
kubectl rollout undo deployment/<Deployment 名> で、1 つ前の revision に戻す。
$ kubectl rollout undo deployment/demo-app
Warning: resource deployments/demo-app was previously managed with 'kubectl apply'. Rolling back will not update the kubectl.kubernetes.io/last-applied-configuration annotation, which may cause unexpected behavior on future 'kubectl apply' operations. Consider using 'kubectl apply' with your previous configuration file instead.
deployment.apps/demo-app rolled back
$ kubectl rollout status deployment/demo-app
# => deployment "demo-app" successfully rolled out
| 行 | 意味 |
|---|---|
rolled back | 戻した。--to-revision を付けないと、今の revision の 1 つ前(今は 3 なので 2)に戻る。最初の状態に戻るのではない |
Warning: ... previously managed with 'kubectl apply' | 「この Deployment は kubectl apply で管理されているので、YAML のほうを直して apply するやり方も考えて」という注意。この節の最後で確認する |
successfully rolled out がすぐ出る | 戻り先の ReplicaSet 799468465 は止まっている間も 3 3 3 のまま動いていたので、新しく作る Pod がない。typo の Pod を消すだけ |
$ kubectl get rs,pods -l app=demo-app
NAME DESIRED CURRENT READY AGE
replicaset.apps/demo-app-6c4dcbb5b8 0 0 0 4m33s
replicaset.apps/demo-app-799468465 3 3 3 15m
replicaset.apps/demo-app-86547cd9f8 0 0 0 32m
NAME READY STATUS RESTARTS AGE
pod/demo-app-799468465-khxct 1/1 Running 0 15m
pod/demo-app-799468465-kkc5b 1/1 Running 0 15m
pod/demo-app-799468465-kkrk2 1/1 Running 0 15m
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
1 <none>
3 <none>
4 <none>
$ kubectl get rs -l app=demo-app -o custom-columns='NAME:.metadata.name,REVISION:.metadata.annotations.deployment\.kubernetes\.io/revision,DESIRED:.spec.replicas,IMAGE:.spec.template.spec.containers[0].image'
NAME REVISION DESIRED IMAGE
demo-app-6c4dcbb5b8 3 0 nginx:1.17.10-typo
demo-app-799468465 4 3 nginx:1.17.10
demo-app-86547cd9f8 1 0 nginx:1.16.1
$ kubectl describe deploy demo-app | grep -A 12 'Events:' | tail -2
Normal ScalingReplicaSet 4m53s deployment-controller Scaled up replica set demo-app-6c4dcbb5b8 from 0 to 1
Normal ScalingReplicaSet 73s deployment-controller Scaled down replica set demo-app-6c4dcbb5b8 from 1 to 0
$ kubectl exec deploy/demo-app -- nginx -v
# => nginx version: nginx/1.17.10
- typo の ReplicaSet
6c4dcbb5b8が0 0 0になり、ImagePullBackOffの Pod は消えた。1.17.10 の Pod 3 つはそのまま(名前も変わっていない) REVISIONが1, 3, 4になった。2 が消えて 4 が増えている。戻し先の ReplicaSet799468465のアノテーションが2→4に書き換えられた。「revision 2 に戻る」のではなく、「revision 2 と同じ定義を、新しい revision 4 として出し直す」。履歴の番号は減らないEvents:はScaled down replica set demo-app-6c4dcbb5b8 from 1 to 0だけ。戻すときも deployment-controller が ReplicaSet の数を変えているだけで、入れ替えと同じ仕組みrollout undoがやっていることは、1 つ前の ReplicaSet の Pod の定義を取り出して、Deployment のspec.templateに書き戻すこと。あとは「template が変わった」として、いつもの入れ替えが動く。戻し先の Pod が動いていなければ、Rolling Update で入れ替わる(次の節で確認する)
Warning の意味:YAML ファイルとのずれ
rollout undo も kubectl set image も、YAML ファイルを書き換えない。今は、
- YAML ファイルの
image:nginx:1.17.10(書き換えたまま) - 実物の Deployment の
image:nginx:1.17.10(typo にして、戻した)
でたまたま一致している。確かめるために、YAML を apply してみる。
$ kubectl apply -f demo-deployment.yaml
# => deployment.apps/demo-app unchanged
unchanged。YAML と実物が同じなので、何も起きない。もし typo を YAML に書いて apply していたら、YAML は typo のままで、undo しても YAML は直らない。YAML で管理しているなら、戻すときも YAML を直して apply する、というのが Warning の言っていること。rollout undo は「今すぐ戻したい」ときの近道。
特定の revision に戻す(--to-revision)
--to-revision=1 で最初の定義(1.16.1)に戻す。今度は 1.16.1 の Pod が動いていないので、Rolling Update で入れ替わる。
$ kubectl rollout undo deployment/demo-app --to-revision=1; kubectl rollout status deployment/demo-app
Warning: resource deployments/demo-app was previously managed with 'kubectl apply'. Rolling back will not update the kubectl.kubernetes.io/last-applied-configuration annotation, which may cause unexpected behavior on future 'kubectl apply' operations. Consider using 'kubectl apply' with your previous configuration file instead.
deployment.apps/demo-app rolled back
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
deployment "demo-app" successfully rolled out
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
3 <none>
4 <none>
5 <none>
$ kubectl get rs -l app=demo-app -o custom-columns='NAME:.metadata.name,REVISION:.metadata.annotations.deployment\.kubernetes\.io/revision,DESIRED:.spec.replicas,IMAGE:.spec.template.spec.containers[0].image'
NAME REVISION DESIRED IMAGE
demo-app-6c4dcbb5b8 3 0 nginx:1.17.10-typo
demo-app-799468465 4 0 nginx:1.17.10
demo-app-86547cd9f8 5 3 nginx:1.16.1
$ kubectl exec deploy/demo-app -- nginx -v
# => nginx version: nginx/1.16.1
--to-revision=1で、1.16.1 の定義(最初の ReplicaSet86547cd9f8)に戻した。今度は 1.16.1 の Pod が 1 つも動いていなかったので、rollout statusに1 out of 3 ...が出て、Rolling Update で入れ替わったREVISIONは3, 4, 5。1 が 5 になった(ReplicaSet86547cd9f8のアノテーションが1→5)- ReplicaSet は作り直されず、同じ
86547cd9f8が再利用された。同じ Pod の定義なら同じハッシュになるため。Pod は新しく作られる - ない番号を指定すると
error: unable to find specified revision N in historyになる
YAML を apply すると、YAML の image に進む
今、YAML ファイルは nginx:1.17.10、実物は nginx:1.16.1。ずれている。この状態で apply すると、YAML が正になって 1.17.10 に入れ替わる。
$ kubectl apply -f demo-deployment.yaml; kubectl rollout status deployment/demo-app
deployment.apps/demo-app configured
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
deployment "demo-app" successfully rolled out
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
3 <none>
5 <none>
6 <none>
$ kubectl exec deploy/demo-app -- nginx -v
# => nginx version: nginx/1.17.10
configured。rollout undoで戻した結果が、applyで上書きされた。kubectl scaleの結果がapplyで上書きされるのと同じ- 1.17.10 の ReplicaSet
799468465が再利用され、revision が4→6になった
image を変える方法と戻す方法の関係はこうなる。
YAML を書き換えて apply | kubectl set image | kubectl rollout undo | |
|---|---|---|---|
| 変えるフィールド | Deployment の spec.template(の image) | 同じ | 同じ(前の ReplicaSet の定義を書き戻す) |
| そのあとの動き | 新しい ReplicaSet を作って Rolling Update | 同じ | 同じ(戻し先の ReplicaSet は再利用) |
| YAML ファイル | ファイルの値が正 | 変わらない(ずれる) | 変わらない(ずれる)。Warning が出る |
| 向いている場面 | ふだんの更新・戻し(Git に残る) | 今すぐ試したい | 今すぐ戻したい。そのあと YAML も直して apply |
消す
$ kubectl delete -f demo-deployment.yaml -f demo-service.yaml -f client.yaml
deployment.apps "demo-app" deleted from default namespace
service "demo-service" deleted from default namespace
pod "client" deleted from default namespace
$ kubectl get all
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14d
数 0 で残っていた古い ReplicaSet(履歴)も、Deployment と一緒に消える。
全部消してから作る(strategy.type: Recreate)
Recreate にすると、古い Pod 3 つを全部消してから新しい Pod を作る。その間、アプリは止まる。
spec に replicas と並べて strategy を足し、image を nginx:1.17.10 にする。
# demo-deployment-recreate.yaml(spec の部分。ほかは demo-deployment.yaml と同じ)
spec:
replicas: 3
strategy:
type: Recreate
...
- name: nginx
image: nginx:1.17.10
image: nginx:1.16.1 の Deployment・Service・client を作り直してから、Recreate の YAML を apply する。
$ kubectl apply -f demo-deployment.yaml -f demo-service.yaml -f client.yaml
deployment.apps/demo-app created
service/demo-service created
pod/client created
$ kubectl rollout status deployment/demo-app
# => deployment "demo-app" successfully rolled out
$ kubectl apply -f demo-deployment-recreate.yaml; kubectl rollout status deployment/demo-app
deployment.apps/demo-app configured
Waiting for deployment "demo-app" rollout to finish: 0 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 0 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 0 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 0 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 0 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 0 of 3 updated replicas are available...
Waiting for deployment "demo-app" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "demo-app" rollout to finish: 2 of 3 updated replicas are available...
deployment "demo-app" successfully rolled out
$ kubectl describe deploy demo-app | grep -E 'StrategyType'
# => StrategyType: Recreate
$ kubectl describe deploy demo-app | grep -A 12 'Events:' | tail -2
Normal ScalingReplicaSet 13s deployment-controller Scaled down replica set demo-app-86547cd9f8 from 3 to 0
Normal ScalingReplicaSet 12s deployment-controller Scaled up replica set demo-app-799468465 from 0 to 3
$ kubectl logs client | grep -B2 -A2 FAIL
04:59:13 Server: nginx/1.16.1
04:59:14 Server: nginx/1.16.1
04:59:17 FAIL
04:59:18 Server: nginx/1.17.10
04:59:19 Server: nginx/1.17.10
rollout statusが0 out of 3 new replicas have been updated...から始まる。新しい Pod が 1 つもできる前に、古い Pod を全部消しているEvents:はScaled down ... from 3 to 0→Scaled up ... from 0 to 3の 2 行だけ。Rolling Update の 6 行(1 つずつ)と違うclientにFAILが出た。04:59:14の返事のあと04:59:17まで返事がなく、3 秒ほど止まった。nginx の起動が速いので短いが、起動に 30 秒かかるアプリなら 30 秒止まるRecreateを使うのは、古いバージョンと新しいバージョンが同時に動いてはいけないとき(例: 同じデータを別の形式で書く)
消してから作る順にする(maxSurge: 0)
maxSurge: 0(replicas を超えて作らない)にすると、「古いのを 1 つ消してから、新しいのを 1 つ作る」順になる。
spec に strategy を足し、image は nginx:1.16.1(1.17.10 から戻す形)。
# demo-deployment-surge.yaml(spec の部分。ほかは demo-deployment.yaml と同じ)
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
...
- name: nginx
image: nginx:1.16.1
| 設定 | 3 個のとき | 順番 |
|---|---|---|
既定(maxSurge: 25% / maxUnavailable: 25%) | 最大 4・最低 3 | 作ってから消す |
maxSurge: 0 / maxUnavailable: 1 | 最大 3・最低 2 | 消してから作る |
1.17.10 が動いている状態から apply し、kubectl get deploy -w で見る。UP-TO-DATE が 3、READY が 3/3 になったら Ctrl+C。
$ kubectl apply -f demo-deployment-surge.yaml; kubectl get deploy demo-app -w
deployment.apps/demo-app configured
NAME READY UP-TO-DATE AVAILABLE AGE
demo-app 2/3 1 2 94s
demo-app 2/3 1 2 94s
demo-app 3/3 1 3 95s
demo-app 2/3 1 2 95s
demo-app 2/3 2 2 95s
demo-app 2/3 2 2 95s
demo-app 3/3 2 3 95s
demo-app 2/3 2 2 95s
demo-app 2/3 3 2 95s
demo-app 2/3 3 2 95s
demo-app 3/3 3 3 96s
$ kubectl describe deploy demo-app | grep -E 'RollingUpdateStrategy'
# => RollingUpdateStrategy: 1 max unavailable, 0 max surge
$ kubectl describe deploy demo-app | grep -A 14 'Events:' | tail -6
Normal ScalingReplicaSet 20s deployment-controller Scaled down replica set demo-app-799468465 from 3 to 2
Normal ScalingReplicaSet 20s deployment-controller Scaled up replica set demo-app-86547cd9f8 from 0 to 1
Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set demo-app-799468465 from 2 to 1
Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set demo-app-86547cd9f8 from 1 to 2
Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set demo-app-799468465 from 1 to 0
Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set demo-app-86547cd9f8 from 2 to 3
READYが2/3→3/3→2/3と上下する。先に 1 つ消すので、一瞬 2 個になるEvents:がScaled down→Scaled upの順。既定の Rolling Update ではScaled up→Scaled downだった。1 つずつ入れ替えるのは同じで、作ると消すの順番だけが逆- 順番が逆になるのは、上限が 3 + 0 = 3 で「3 個そろった状態からは作れない」ため。deployment-controller は「作れるなら作る、作れなければ消せるだけ消す」を繰り返すだけで、設定を変えると同じ手順から逆の順番が出てくる
- 使いどころは、Node に Pod を増やす余裕がないとき。逆に、1 つも減らしたくないなら
maxUnavailable: 0(そのときmaxSurgeは 1 以上が必要。両方 0 にはできない)
止まった入れ替えを失敗と判定させる(progressDeadlineSeconds)
止まった入れ替えを放っておくと、Conditions: の Progressing が False / ProgressDeadlineExceeded になり、kubectl rollout status がエラー終了する。それでも Kubernetes は戻さない。
spec に replicas と並べて progressDeadlineSeconds: 60 を足し、image を存在しない nginx:1.17.10-typo にする。
# demo-deployment-deadline.yaml(spec の部分。ほかは demo-deployment.yaml と同じ)
spec:
replicas: 3
progressDeadlineSeconds: 60
...
- name: nginx
image: nginx:1.17.10-typo
| フィールド | 意味 |
|---|---|
progressDeadlineSeconds: 60 | この秒数進まなかったら「進んでいない」と status に書く。既定は 600(10 分) |
apply して、20 秒後と 75 秒後に Conditions: を見る。
$ kubectl apply -f demo-deployment-deadline.yaml
# => deployment.apps/demo-app configured
$ kubectl describe deploy demo-app | grep -A 4 'Conditions:'
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
$ kubectl describe deploy demo-app | grep -A 4 'Conditions:'
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded
$ kubectl get deploy demo-app -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'
Available True MinimumReplicasAvailable Deployment has minimum availability.
Progressing False ProgressDeadlineExceeded ReplicaSet "demo-app-6c4dcbb5b8" has timed out progressing.
$ kubectl rollout status deployment/demo-app
# => error: deployment "demo-app" exceeded its progress deadline
$ echo $?
# => 1
$ kubectl get pods -l app=demo-app
NAME READY STATUS RESTARTS AGE
demo-app-6c4dcbb5b8-qjf9k 0/1 ImagePullBackOff 0 2m50s
demo-app-86547cd9f8-h7ffg 1/1 Running 0 6m13s
demo-app-86547cd9f8-pzlpg 1/1 Running 0 6m13s
demo-app-86547cd9f8-qmpgk 1/1 Running 0 6m14s
- 20 秒後は
Progressing True ReplicaSetUpdated(まだ進行中扱い)。60 秒を過ぎるとProgressing False ProgressDeadlineExceeded、messageがReplicaSet "..." has timed out progressing. kubectl rollout statusがerror: deployment "demo-app" exceeded its progress deadlineで終了し、終了コードが1。期限なしのときは終わらず待ち続けていたのと違う。CI などで「更新が失敗したら止める」に使えるのはこの終了コード- それでも Pod はそのまま。古い 3 つが動き、typo の 1 つが
ImagePullBackOffのまま。Available Trueも変わらない。公式ドキュメントは、Kubernetes は status を書く以外のことはしない、としている。戻すのは人か上位のツール
戻して終わる。
$ kubectl rollout undo deployment/demo-app
Warning: resource deployments/demo-app was previously managed with 'kubectl apply'. Rolling back will not update the kubectl.kubernetes.io/last-applied-configuration annotation, which may cause unexpected behavior on future 'kubectl apply' operations. Consider using 'kubectl apply' with your previous configuration file instead.
deployment.apps/demo-app rolled back
$ kubectl describe deploy demo-app | grep -A 4 'Conditions:'
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
Progressing True NewReplicaSetAvailable が「入れ替えが完了している」状態。
CHANGE-CAUSE に理由を残す
kubectl rollout history の CHANGE-CAUSE は、ずっと <none> だった。Deployment の metadata.annotations(リソースに付ける自由なメモ。キー: 値 の形)に kubernetes.io/change-cause というキーで文を書いて apply すると、その文がこの欄に入る。
demo-deployment.yaml の metadata に、name と並べて annotations を足す(image は nginx:1.16.1 に戻す)。
metadata:
name: demo-app
annotations:
kubernetes.io/change-cause: "nginx を 1.16.1 に戻す"
$ kubectl apply -f demo-deployment.yaml; kubectl rollout status deployment/demo-app
deployment.apps/demo-app configured
deployment "demo-app" successfully rolled out
$ kubectl rollout history deployment/demo-app
deployment.apps/demo-app
REVISION CHANGE-CAUSE
2 <none>
4 <none>
5 nginx を 1.16.1 に戻す
- アノテーションは Deployment に書くが、deployment-controller がそのときの ReplicaSet にコピーする。
rollout historyが読んでいるのは ReplicaSet のほう - revision の番号は、それまでに何回変えたかで変わる
- 公式ドキュメントでは、この値に
kubectl set image ...のようなコマンドの文字列を入れる例を載せている。kubectl annotate deployment/<名前> kubernetes.io/change-cause="..." --overwriteで付け直すと、今の revision のCHANGE-CAUSEが書き換わる(新しい revision は増えない)
入れ替えを一時停止する(kubectl rollout pause / resume)
pause しておくと、image を変えても入れ替えが始まらない。resume で始まる。複数の変更をまとめて 1 回の入れ替えにしたいとき用。
$ kubectl rollout pause deployment/demo-app
# => deployment.apps/demo-app paused
$ kubectl set image deployment/demo-app nginx=nginx:1.17.10
# => deployment.apps/demo-app image updated
$ kubectl get rs -l app=demo-app
NAME DESIRED CURRENT READY AGE
demo-app-6c4dcbb5b8 0 0 0 7m33s
demo-app-799468465 0 0 0 12m
demo-app-86547cd9f8 3 3 3 12m
$ kubectl describe deploy demo-app | grep -A 4 'Conditions:'
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing Unknown DeploymentPaused
$ kubectl rollout resume deployment/demo-app; kubectl rollout status deployment/demo-app
deployment.apps/demo-app resumed
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demo-app" rollout to finish: 1 old replicas are pending termination...
deployment "demo-app" successfully rolled out
pause中にimageを変えても、ReplicaSet の数は動かない(1.16.1 の86547cd9f8が3のまま)。Conditions:はProgressing Unknown DeploymentPausedresumeした瞬間に、いつもの Rolling Update が始まる- 止めている間は、
progressDeadlineSecondsの時間も数えられない
用語メモ
| 用語 | 読み方 | 意味 |
|---|---|---|
| rollout | ロールアウト | Deployment の Pod を、新しい定義(spec.template)の Pod に入れ替えること。kubectl rollout コマンドの名前 |
| Rolling Update | ローリング アップデート | 古い Pod を少しずつ新しい Pod に入れ替える rollout のやり方。Deployment の strategy.type の既定値 RollingUpdate |
| Recreate | リクリエイト | 古い Pod を全部消してから新しい Pod を作るやり方。strategy.type: Recreate。止まる時間ができる |
| strategy | ストラテジー | Deployment のフィールド spec.strategy。入れ替えのやり方(type)と、その細かい設定(rollingUpdate) |
| maxSurge | マックス サージ | spec.strategy.rollingUpdate.maxSurge。replicas を超えて作ってよい Pod の数。既定 25%(切り上げ) |
| maxUnavailable | マックス アンアベイラブル | spec.strategy.rollingUpdate.maxUnavailable。入れ替え中に足りなくてよい Pod の数。既定 25%(切り捨て) |
| revision | リビジョン | Pod の定義の版の番号。ReplicaSet のアノテーション deployment.kubernetes.io/revision。spec.template が変わるたびに 1 増える |
| revisionHistoryLimit | リビジョン ヒストリー リミット | spec.revisionHistoryLimit。数 0 で残す古い ReplicaSet の数。既定 10 |
| Rollback | ロールバック | 前の revision の定義に戻すこと。kubectl rollout undo |
| kubectl rollout status | キューブ コントロール ロールアウト ステータス | 入れ替えが終わるまで進み具合を表示して待つコマンド。終わっていれば successfully rolled out |
| kubectl rollout history | キューブ コントロール ロールアウト ヒストリー | revision の一覧。--revision=N でその定義 |
| kubectl rollout undo | キューブ コントロール ロールアウト アンドゥ | 1 つ前(--to-revision=N で指定の revision)の定義を Deployment に書き戻すコマンド。出力は rolled back |
| kubectl rollout pause / resume | キューブ コントロール ロールアウト ポーズ / リジューム | 入れ替えを止める / 再開する |
| kubectl set image | キューブ コントロール セット イメージ | YAML を書き換えずに、Deployment の image だけを変える近道のコマンド。出力は image updated |
| UP-TO-DATE | アップ トゥ デート | kubectl get deploy の列。今の spec.template で動いている Pod の数 |
| ImagePullBackOff | イメージ プル バックオフ | kubelet が image を取れず、間をあけて取り直しを待っている Pod の STATUS。失敗した瞬間は ErrImagePull |
| Progressing | プログレッシング | Deployment の Conditions: の 1 つ。True / ReplicaSetUpdated は入れ替え中、True / NewReplicaSetAvailable は完了、False / ProgressDeadlineExceeded は期限切れ、Unknown / DeploymentPaused は一時停止中 |
| progressDeadlineSeconds | プログレス デッドライン セカンズ | spec.progressDeadlineSeconds。入れ替えが進まないとき、Progressing False にするまでの秒数。既定 600 |
| change-cause | チェンジ コーズ | アノテーション kubernetes.io/change-cause。rollout history の CHANGE-CAUSE 列に出るメモ |
| deployment-controller | デプロイメント コントローラー | kube-controller-manager の中の係。spec.template が変わったら新しい ReplicaSet を作り、古い ReplicaSet と新しい ReplicaSet の DESIRED を入れ替える |
| last-applied-configuration | ラスト アプライド コンフィギュレーション | アノテーション kubectl.kubernetes.io/last-applied-configuration。kubectl apply が「前回 apply した YAML の中身」を残しているもの。rollout undo はこれを更新しないので Warning が出る |
まとめ
- アプリの更新は、Deployment の
spec.template(Pod の定義)を変えること。YAML を書き換えてapplyしても、kubectl set imageでも、変わるフィールドは同じ。replicasを変えるだけでは更新は起きない - template が変わると、deployment-controller が新しい ReplicaSet を作り、「新しいのを 1 つ増やす →
READYになったら古いのを 1 つ減らす」を繰り返す(Rolling Update。strategy.typeの既定)。入れ替え中は古い Pod と新しい Pod が混ざって動き、Service からのアクセスは途切れない - 一度に増やせる数・減らせる数は
maxSurge/maxUnavailable(既定 25%)で決まる。3 個なら最大 4・最低 3 → 1 つずつ。maxSurge: 0にすると消してから作る順になり、Recreateにすると全部消してから作る(止まる時間ができる) - 古い ReplicaSet は数 0 で残り、それが revision(履歴)。
kubectl rollout historyで一覧、--revision=Nで中身。残す数はrevisionHistoryLimit(既定 10) - 新しい Pod が動かない(
ImagePullBackOffなど)と、入れ替えは「1 つ作った」ところで止まる。古い Pod は消されないのでアプリは止まらない。Kubernetes は自分では戻さない(progressDeadlineSecondsを過ぎるとProgressing Falseになるだけ) kubectl rollout undoは、1 つ前の ReplicaSet の定義を Deployment に書き戻すコマンド。戻した定義は新しい revision 番号になる。--to-revision=Nで指定の revision に戻せるkubectl set imageもrollout undoも YAML ファイルを変えない。YAML をapplyすると YAML の値に戻る(上書きされる)。ふだんは YAML を直してapplyする
参考 URL
- [official] Deployment | Kubernetes
- [official] ReplicaSet | Kubernetes
- [official] イメージ | Kubernetes
- [official] ローリングアップデートの実行 | Kubernetes
- [official] kubectl rollout | Kubernetes

