maxSurge と maxUnavailable の不思議
はじめに
Kubernetes の Deployment にはローリングアップデートの挙動を制御するために maxSurge と maxUnavailable と言うフィールドがある。
ご存知の通り、maxSurge はローリングアップデートの最中に一時的に replicas より増えていい Pod の数を、maxUnavailable はローリングアップデートの最中に一時的に replicas より減っていい Pod の数を指定するものだが、こいつらがちょっと不思議だったので調べてみた。
両方同時に 0 にすることはできない
API Reference にもあるように、maxUnavailable と maxSurge を両方同時に 0 にすることはできない。maxSurge 側には "This can not be 0 if MaxUnavailable is 0."、maxUnavailable 側には "This can not be 0 if MaxSurge is 0." と書いてある。
実際、最近話題の nginx で試してみると、
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 0
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25.4-bookworm
以下のようなエラーが出る。
$ kubectl apply -f ンギンクス.yaml --server-side
The Deployment "nginx" is invalid: spec.strategy.rollingUpdate.maxUnavailable: Invalid value: intstr.IntOrString{Type:0, IntVal:0, StrVal:""}: may not be 0 when `maxSurge` is 0
まぁローリングアップデートする時に Pod の数を増やしても減らしてもダメって言われたら「どないせいっちゅうねん!」ってなるから当たり前ではある。
パーセント指定でもダメ
これらのフィールドには、Pod 数そのものではなくパーセントを指定することもできる。指定できるって言うかむしろこちらの方がメジャーな気もするし、デフォルトも両方とも 25% である。
で、「両方同時に 0 はダメ」と言うのは 0% でも同じで、
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 0%
maxUnavailable: 0%
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25.4-bookworm
以下のようなエラーが出る。
$ kubectl apply -f ンギンクス%.yaml --server-side
The Deployment "nginx" is invalid: spec.strategy.rollingUpdate.maxUnavailable: Invalid value: intstr.IntOrString{Type:1, IntVal:0, StrVal:"0%"}: may not be 0 when `maxSurge` is 0
もちろん、0 と 0% の組み合わせでもダメだ。
maxSurge は切り上げ、maxUnavailable は切り捨て
パーセント指定の場合、実際の Pod 数は replicas の数と掛け合わせて計算されるのだが、その際 maxSurge は切り上げで、maxUnavailable は切り捨てで計算される。
つまり、replicas が 1 の場合 maxSurge: 1% は 1 Pod 増えて良くて、maxUnavailable: 99% は 1 Pod も増えてはいけない。
maxSurge が切り上げなのは「0% じゃないってことは、ローリングアップデートの時は Pod 数増えてもいいと思ってるんだから、切り上げとけばいいよね」ってことなんだと思う。
一方で、maxUnavailable が切り捨てなのは「0% じゃないってことは、ローリングアップデートの時は Pod 数減ってもいいとは思ってるんだとは思うけど、せっかく Deployment にしてるんだからきっと生きてる Pod が 0 個にはなって欲しくないよね」ってことなんじゃないかな?知らんけど…[1]
計算した結果両方 0 になる?
さて、ここまでは前提知識である。
今回不思議に思ったのは、「パーセント指定で計算の結果両方 0 になる場合でもエラーにならないんだけどどういうことなの?」と言うものだ。
例えばこんなヤツは、
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 0%
maxUnavailable: 1%
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25.4-bookworm
特にエラーもなく反映できる。
$ kubectl apply -f ンギンクス0.yaml --server-side
deployment.apps/nginx serverside-applied
見て分かる通り、maxSurge は明らかに 0、maxUnavailable は replicas がデフォルトの 1 なので計算すると 0 になり、両方同時に 0 になるはずなのであるが、特にエラーが出ない。
おいおい、これじゃローリングアップデートできなくね?と思って試しに bookworm を alpine に変えて適用してみると、何のエラーもなく適用できる。
$ kubectl apply -f ンギンクスa.yaml --server-side
deployment.apps/nginx serverside-applied
どういうこと?と思って裏で Pod の状態を眺めてみると、
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
nginx-9fc65c5b6-q57k2 1/1 Running 0 4s
nginx-9fc65c5b6-q57k2 1/1 Terminating 0 17s
nginx-6985cd5669-4h84g 0/1 Pending 0 0s
nginx-6985cd5669-4h84g 0/1 Pending 0 0s
nginx-6985cd5669-4h84g 0/1 ContainerCreating 0 0s
nginx-9fc65c5b6-q57k2 0/1 Terminating 0 17s
nginx-9fc65c5b6-q57k2 0/1 Terminating 0 18s
nginx-9fc65c5b6-q57k2 0/1 Terminating 0 18s
nginx-9fc65c5b6-q57k2 0/1 Terminating 0 18s
nginx-6985cd5669-4h84g 1/1 Running 0 3s
普通に Pod が減ってやがる!両方 0 じゃないんかい!
特別ルール
気になって夜しか寝れないので、ソースを調べてみた。
計算の結果両方 0 になった時のために特別ルールがある!
コメントをざっくり訳すとこんな感じ。
バリデーションがあるからユーザが明示的に両方
0にはできないけど、maxUnavailableは切り捨てだから結果的に両方0になっちゃう時もあるよね。
そんな時はmaxUnavailableを1にしちゃうよ。
だって、リソースクォータのせいで Pod を増やせないかもしれないからね。
つまり、計算の結果両方 0 になった時は、maxUnavailable は 1 にすると言う訳だ。マジか!
ドキュメント is どこ…
この挙動、kubernetes を普段使いしてる人から見れば「何を当たり前の事を…」と思うのかもしれないが、エアプなので全く知らなかった。
そして、軽くドキュメントを探してみたものの、この挙動を説明する箇所を見つけられなかった。
できればどこかに書いておいて欲しいんだが…[2]
あと、パーセント指定でもバリデーション時にチェックしてエラーにすることはできると思うんだが、なんでやらないのかも良く分からんな。
何となく、HPA があるから今は両方 0 になっても実際にローリングアップデートする時にも両方 0 とは限らないから、なのかと思ったりもするが…[3]
Discussion