概要 #
ノードプールのアップグレード戦略はSURGEとBLUE_GREENの2つです。どちらを選ぶべきか決めたいのに、判断材料がありません。「BLUE_GREENは安全」「SURGEは速い」といった説明はあっても、どれくらい違うのかが書かれていないので、比較ができません。
前回はBlue/Greenを測りました。今回は同じ条件でSURGEを測って、並べます。ノード数もワークロードも計測方法も前回と同一にして、違いはupgrade_settingsだけにしてあります。
あわせてmax_surge / max_unavailableの2つの組み合わせも比べます。この2つは「速さと引き換えに何を失うか」を決める設定です。
対象読者は、GKEのノードプールを運用でアップグレードする立場の人です。Kubernetesの入門は扱いません。
Autopilotクラスタ、コントロールプレーンのアップグレード戦略、メンテナンスウィンドウは扱いません。
検証すること #
- SURGEでアップグレード中にどれだけ止まるか
max_surge = 0にすると何が変わるか- 一時的にノードがどれだけ増えるか
- Blue/Greenとどちらを選ぶべきか
前提環境 #
- Google Cloud CLI、Terraform
- Billingが有効な検証用Google Cloud Project
- GKE、Cloud KMS、Compute Engineを操作できるGoogleアカウント
- 検証時のバージョン:Terraform 1.14.5、google 7.46.0
- GKE 1.35.7-gke.1027000 → 1.35.7-gke.1150000 → 1.36.2-gke.2064000(REGULARチャンネル)
使用するTerraformコード #
SURGEとBlue/Greenの進み方の違い #
flowchart TB
subgraph SURGE["SURGE"]
S1["余剰ノードを max_surge 台足す"]
S2["1バッチずつ入れ替える"]
S3["古いノードを削る"]
S1 --> S2 --> S3
end
subgraph BG["Blue/Green"]
B1["新しいノード群を全部作る"]
B2["batch_percentage ずつ移す"]
B3["node_pool_soak_duration 待つ"]
B4["古い群をまとめて消す"]
B1 --> B2 --> B3 --> B4
end
SURGE は少しずつ入れ替え、Blue/Green は新旧を並べてから移します。この違いが所要時間と切り戻しやすさに出ます。
比較を成立させる条件 #
測る条件が違えば比較になりません。 前回と揃えたものを先に示します。
| 項目 | 内容 |
|---|---|
| ノード数 | 2(e2-medium、Spot) |
| ワークロード | nginx 2レプリカ + podAntiAffinity + PDB(minAvailable: 1)+ preStop |
| 計測 | 踏み台から内部LBへ毎秒1回リクエスト |
| クラスタ | プライベートクラスタ、ゾーナル |
違うのはupgrade_settingsだけです。
upgrade_settings {
strategy = "SURGE"
max_surge = var.max_surge # 通常サイズを超えて足せるノード数
max_unavailable = var.max_unavailable # 同時に落としてよいノード数
}
blue_green_settingsはBLUE_GREEN専用で、SURGEとは併用できません。どちらか一方が0でも動きますが、両方0にはできません。進められなくなるためです。
アップグレード先を2段用意する #
max_surgeの設定を変えて2回測るので、コントロールプレーンを2段先で作ります。
| バージョン | |
|---|---|
| コントロールプレーン | 1.36.2-gke.2064000 |
| ノードプール(初期) | 1.35.7-gke.1027000 |
| 1回目の上げ先 | 1.35.7-gke.1150000 |
| 2回目の上げ先 | 1.36.2-gke.2064000 |
これで途中でコントロールプレーンを上げ直さずに済みます。前回はこれをやっておらず、2回目のために9分かけてコントロールプレーンを上げ直しました。
実行 #
terraform apply
Apply complete! Resources: 27 added, 0 changed, 0 destroyed.
設定がGKEに届いていることを確認します。
gcloud container node-pools describe tf-adv-gke-surge-np --cluster=tf-adv-gke-surge \
--zone=asia-northeast1-a --format="yaml(upgradeSettings)"
upgradeSettings:
maxSurge: 1
strategy: SURGE
maxUnavailable: 0は既定値なので出力に現れません。
1回目: max_surge = 1 / max_unavailable = 0 #
gcloud container clusters upgrade tf-adv-gke-surge --node-pool=tf-adv-gke-surge-np \
--cluster-version=1.35.7-gke.1150000 --zone=asia-northeast1-a --async
ノード数の推移にmax_surge = 1の動きがそのまま出ます。
=== 21:16:02 nodes=2 ===
=== 21:16:18 nodes=3 === ← 先に1台足す
...
=== 21:18:13 nodes=2 === ← 旧ノードを1台落とす
=== 21:18:45 nodes=3 === ← また1台足す
...
=== 21:21:12 nodes=2 === ← 完了
MAX_NODES=3
先に足してから落とすので、処理中も稼働ノードが2台を下回りません。
tr ' ' '\n' < /tmp/runA-probe.txt | grep -v '^$' | sort | uniq -c
359 200
gcloud container operations list --zone=asia-northeast1-a \
--filter="operationType=UPGRADE_NODES AND targetLink~tf-adv-gke-surge" \
--format="table(status,startTime,endTime)"
STATUS START_TIME END_TIME
DONE 2026-09-04T21:15:06.494755341Z 2026-09-04T21:21:02.860952638Z
失敗0回、5分56秒。
設定変更はノードを作り直さない #
max_surgeを0にして、2回目を測ります。
terraform apply
# google_container_node_pool.primary will be updated in-place
~ max_surge = 1 -> 0
~ max_unavailable = 0 -> 1
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
upgrade_settingsはin-placeで変わります。次のアップグレードから新しい設定が使われます。
2回目: max_surge = 0 / max_unavailable = 1 #
追加ノードなしで同じことをします。
=== 21:22:41 nodes=2 ===
...
=== 21:27:01 nodes=1 === ← 増えないまま、1台に減る
=== 21:27:33 nodes=2 ===
MAX_NODES=2
19 000
236 200
19秒の完全断。 生の記録では000が連続して並びます。
200 200 ... 200 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 200 200 ...
所要時間は3分20秒(21:22:35 → 21:25:55)で、1回目より速くなりました。
なぜ止まったのか #
原因はPod側とLB側の両方にありました。
kubectl get events --sort-by=.lastTimestamp | grep FailedScheduling
Warning FailedScheduling pod/nginx-cmek-668999dbd8-f4kls
0/2 nodes are available: 1 node(s) didn't match pod anti-affinity rules,
1 node(s) were unschedulable.
追加ノードがないので、1台をcordonすると退避したPodの行き先がありません。もう1台には既にnginxがいて、podAntiAffinityが同居を拒みます。レプリカが1個に減ったまま次のノードの番が来て、受け先がゼロになりました。
内部LB側でも起きていました。
Warning EnterDegradedMode service/nginx-cmek-ilb
Entering degraded mode for NEG default/nginx-cmek-ilb-... due to sync err:
endpoint information for attach operation is incorrect
Warning AttachFailed service/nginx-cmek-ilb
Failed to Attach 1 network endpoint(s): googleapi: Error 400:
Invalid value for field 'resource.instance': 'gke-tf-adv-gke-surge-...-ywfi'.
Couldn't find instance with a specified name ...
ノードが作り直される間、NEGが存在しないインスタンスを参照して縮退しました。
max_surge = 0は「追加ノードが要らない」設定ではありません。「余力を持たずに入れ替える」設定です。 ワークロードがノード数に対して余裕を持っていないと、そのまま断になります。
3方式を並べる #
同じクラスタ構成・同じワークロード・同じ計測方法で測った結果です。
| 観点 | Blue/Green | SURGE max_surge=1 |
SURGE max_surge=0 |
|---|---|---|---|
| 断 | 0回 | 0回 | 19秒の完全断 |
| 所要時間 | 17分3秒 | 5分56秒 | 3分20秒 |
| 最大ノード数(2ノード時) | 4(倍) | 3(+1) | 2(増えない) |
| 追加ノードのコスト | 倍のノード × 17分 | +1ノード × 6分 | なし |
| Pod IPの一時消費 | 倍 | +1ノード分 | なし |
| vCPUクォータの一時増 | 倍 | +1ノード分 | なし |
| ロールバック | soak期間中は可能 | 不可 | 不可 |
| 切り戻しの猶予 | node_pool_soak_duration |
なし | なし |
| 途中で気付く機会 | バッチ間に待機あり | 逐次 | 逐次 |
| 手順の複雑さ | cancel → rollback が要る | 単純 | 単純 |
| 向く場面 | 戻せることが最優先 | 既定として妥当 | 検証環境・コスト最優先 |
この表から読めること #
断と時間だけ見るとSURGEが有利です。 Blue/Greenは3倍近く遅く、ノードも倍要ります。それでいて断はmax_surge = 1と同じ0回でした。
Blue/Greenの価値は「上げたあとに問題が分かったとき」にあります。 SURGEは置き換えたノードが残らないので、戻すにはもう一度アップグレードするしかありません。ダウングレードはできません。Blue/Greenはnode_pool_soak_durationの間なら旧ノードが残っていて、前回の実測では3分10秒で戻せました。
この差は所要時間の表に出てきません。「アップグレードが速いか」ではなく「間違えたときに戻せるか」で選ぶ、という整理になります。
max_surge = 0は安易に選べません。 速くて無料ですが、ワークロードにノードの余裕がないと断が出ます。今回は2ノードに2レプリカ+ハードなアンチアフィニティという、余裕がまったくない構成でした。
裏を返すと、断が出るかどうかはノード数とワークロードの配置に余裕があるかで決まる部分が大きいとも言えます。戦略の選択だけで無停止になるわけではありません。
アップグレード後の terraform plan で確認する #
terraform plan
No changes. Your infrastructure matches the configuration.
lifecycle.ignore_changes = [version]が効いています。前回はコントロールプレーンもgcloudで上げたため、読み取り専用のmaster_versionに差分が出ました。今回は最初から上げ先のバージョンで作ってあるので差分が出ません。
後片付け #
内部LBはKubernetes側で作ったものなので、先に消します。残しておくと転送ルールがVPCに残ります。
kubectl delete -f k8s/03-nginx-ilb.yaml
terraform destroy
Destroy complete! Resources: 27 destroyed.
Cloud KMSのキーリングと鍵は削除できず、鍵バージョンは破棄がスケジュールされます。再検証するときは別の名前を指定します。
まとめ #
- SURGEは速い。
max_surge = 1で5分56秒。Blue/Greenの17分3秒に対して3分の1以下 max_surge = 1でも断は0回だった。 ノードを先に足してから落とすので、稼働ノードが初期値を下回らないmax_surge = 0は19秒止まった。 追加ノードがないと退避したPodの行き先がなく、レプリカが減った状態で次のノードの番が来る- 止まる原因はPod側だけではない。 内部LBのNEGも、消えたインスタンスを参照して縮退していた
- 選択の軸は速さではなく「戻せるか」。 SURGEは置き換えたノードが残らない。Blue/Greenだけがsoak期間中に戻せる
upgrade_settingsの変更はノードを作り直さず、in-placeで反映される