🐔

maxSurge と maxUnavailable の不思議

2024/02/16に公開

はじめに

Kubernetes の Deployment にはローリングアップデートの挙動を制御するために maxSurgemaxUnavailable と言うフィールドがある。

ご存知の通り、maxSurge はローリングアップデートの最中に一時的に replicas より増えていい Pod の数を、maxUnavailable はローリングアップデートの最中に一時的に replicas より減っていい Pod の数を指定するものだが、こいつらがちょっと不思議だったので調べてみた。

両方同時に 0 にすることはできない

API Reference にもあるように、maxUnavailablemaxSurge を両方同時に 0 にすることはできない。maxSurge 側には "This can not be 0 if MaxUnavailable is 0."、maxUnavailable 側には "This can not be 0 if MaxSurge is 0." と書いてある。

実際、最近話題の nginx で試してみると、

ンギンクス.yaml
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% でも同じで、

ンギンクス%.yaml
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

もちろん、00% の組み合わせでもダメだ。

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 になる場合でもエラーにならないんだけどどういうことなの?」と言うものだ。

例えばこんなヤツは、

ンギンクス0.yaml
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 は明らかに 0maxUnavailablereplicas がデフォルトの 1 なので計算すると 0 になり、両方同時に 0 になるはずなのであるが、特にエラーが出ない。

おいおい、これじゃローリングアップデートできなくね?と思って試しに bookwormalpine に変えて適用してみると、何のエラーもなく適用できる。

$ 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 じゃないんかい!

特別ルール

気になって夜しか寝れないので、ソースを調べてみた。
https://github.com/kubernetes/kubernetes/blob/v1.29.2/pkg/controller/deployment/util/deployment_util.go#L861-L867

計算の結果両方 0 になった時のために特別ルールがある!

コメントをざっくり訳すとこんな感じ。

バリデーションがあるからユーザが明示的に両方 0 にはできないけど、maxUnavailable は切り捨てだから結果的に両方 0 になっちゃう時もあるよね。
そんな時は maxUnavailable1 にしちゃうよ。
だって、リソースクォータのせいで Pod を増やせないかもしれないからね。

つまり、計算の結果両方 0 になった時は、maxUnavailable1 にすると言う訳だ。マジか!

ドキュメント is どこ…

この挙動、kubernetes を普段使いしてる人から見れば「何を当たり前の事を…」と思うのかもしれないが、エアプなので全く知らなかった。
そして、軽くドキュメントを探してみたものの、この挙動を説明する箇所を見つけられなかった。
できればどこかに書いておいて欲しいんだが…[2]

あと、パーセント指定でもバリデーション時にチェックしてエラーにすることはできると思うんだが、なんでやらないのかも良く分からんな。
何となく、HPA があるから今は両方 0 になっても実際にローリングアップデートする時にも両方 0 とは限らないから、なのかと思ったりもするが…[3]

脚注
  1. もし真実を知ってたら教えて欲しい ↩︎

  2. もし記載されているドキュメントを知ってたら教えて欲しい ↩︎

  3. もし真実を知ってたら教えて欲しい ↩︎

Discussion