1. ある夜、K3sクラスタの全Podが同時に消えた
コスト削減のために、K3sクラスタにリソース使用量を自動で最適化するVertical Pod Autoscaler(VPA)を有効化した。効果はすぐに出た。過剰に確保していたCPU・メモリのリクエスト値が実使用量に近づき、ノードの空きリソースが増えた。
ところがある夜、監視ダッシュボードに503エラーが並んだ。原因を追うと、あるDeploymentのレプリカがすべて同時に再作成されていた。1台ずつローリングで更新されるはずのPodが、なぜ一斉に消えたのか。
答えは単純だった。PodDisruptionBudget(PDB)を設定していなかった。VPAのようなリソース最適化の仕組みは、放っておくと「安全装置なし」で稼働する。K3sのような小〜中規模クラスタほど、レプリカ数が少ない分この事故の影響は大きい。この記事では、なぜリソース最適化が全滅事故を引き起こすのか、そしてPodDisruptionBudgetがどう「最後の防波堤」になるのかを、実際の設定例とともに解説する。
こうした「安全装置の設定漏れ」は、クラスタを手動で構築・運用している限りどこでも起こりうる。KuboのようなマネージドK3s環境なら、この手の運用ノウハウが標準構成に組み込まれている。
2. なぜ「リソース最適化」が全滅を引き起こすのか — Podの退避メカニズム

VPAは「Recommender」「Updater」「Admission Controller」という3つのコンポーネントで構成される。Kubernetesの公式ドキュメントによれば、UpdateModeにはOff(推奨値を記録するだけ)、Initial(Pod作成時のみ適用)、Recreate(差分が大きいPodを退避して再作成)、InPlaceOrRecreate(まずインプレース更新を試みる)などの種類がある。
問題はRecreate系のモードだ。稼働中のPodのリクエスト値がRecommenderの推奨値と大きくズレると、UpdaterはそのPodを退避(evict)して再作成する。kubernetes/autoscalerリポジトリの公式ドキュメントには、Updaterは「Eviction APIを使ってPodを削除する」と明記されている。
Eviction APIは通常、KubernetesのPod Disruption(中断)に関する公式ドキュメントが定義する「自発的な中断(voluntary disruption)」の一種として扱われる。PodDisruptionBudgetが存在しない場合、Eviction APIによる退避を止めるものが何もない。 つまりUpdaterは、同じDeploymentの複数レプリカを同時に退避対象と判定した場合、それを妨げる仕組みがないまま処理を進めてしまう。
3. HPAとの併用で状況はさらに悪化する

多くのK3sクラスタでは、VPAだけでなくHorizontal Pod Autoscaler(HPA)も併用されている。ここでさらに厄介な問題が起きる。HPAはrequests.cpuに対する使用率(%)でスケール判断をするため、VPAがリクエスト値を変更した瞬間、実際の使用量は変わらないのに使用率の計算結果だけが変わる。
kubernetes/autoscalerの既知の制限事項ドキュメントは、「VPAは同一のリソースメトリクスでHPAと併用すべきではない」と明確に警告している。ScaleOpsの解説記事は、このメカニズムを「VPAがリクエストを削減→HPAの使用率が高く見える→過剰スケールアウト→Pod当たり使用量が下がりVPAがさらに削減を推奨」という無限ループとして説明している。
このHPA/VPAの相互作用がなぜ起きるか、メトリクスが「不明」判定になるケースまで含めた詳しい診断は、以前取り上げたK3sで起きる『メトリクス不明』の正体とHPA/VPAの死のスパイラルで解説している。イベント駆動での代替手段を検討したい場合はHPAとKEDA、どちらを選ぶかの判断基準も参考にしてほしい。今回のPDB事故は、この悪循環がなくても単独で起きうる、より根本的な設定漏れの問題だ。
4. PodDisruptionBudgetという「最後の防波堤」の設定方法

PodDisruptionBudgetは、自発的な中断によって同時にダウンできるPod数の上限を定義する仕組みだ。Kubernetes公式のDisruptionsドキュメントでは、minAvailable(最低限稼働させる数)とmaxUnavailable(同時に停止できる最大数)のどちらかを指定できると説明されている。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 75%
selector:
matchLabels:
app: my-app
このPDBがある状態では、kubernetes/autoscalerの公式ドキュメントが示すとおり、Updaterは「PodDisruptionBudgetを尊重してEviction APIを使う」ため、退避がPDBに違反する場合は処理を保留し、1台ずつ安全に更新を進める。設定の目安としては、minAvailableを50〜75%程度に保ち、minAvailableをレプリカ数と同じ値にしてはならない(自発的な中断を完全にブロックしてしまう)。
5. K3s本番運用でVPAを安全に使うための設定チェックリスト

K3s本番運用でVPAを安全に併用するために、次の4点を順に確認したい。
- updateModeを段階的に選ぶ: 最初から
RecreateやInPlaceOrRecreateで自動適用するのではなく、まずOffまたはInitialで推奨値を観察してから本番適用に進む resourcePolicy.maxAllowedを必ず設定する: ノードの最大リソースを超える推奨値を出さないよう上限を明示する。上限がないと、推奨値がノード容量を超えて永久にPending状態になるPodが発生しうる- PodDisruptionBudgetを必ず作成する: 前章の設定で、Updaterによる同時退避を1台ずつのローリングに制限する
- HPAとメトリクスを分離する: HPAをCPU/メモリで動かす場合はVPAを
Off/Initialにとどめる。逆にVPAでCPU/メモリを自動管理する場合は、HPAはリクエスト数やキュー深度などのカスタムメトリクスに寄せる。Google CloudのGKE向けVPAドキュメントやMicrosoft LearnのAKS向けVPAドキュメントも同様の分離を推奨している
なお、Podを退避せずにリソースを変更する「In-Place Pod Resize」はKubernetes v1.33でBetaに昇格し、v1.35で正式にStable(GA)になった。VPAのInPlaceOrRecreateモードはこの機能を活用することで、退避そのものを減らせる可能性がある。ただし対応バージョンでない環境では従来どおり退避が発生するため、PDBの設定は依然として必須だ。
このチェックリストを毎回自分のクラスタで組み立て直すのは、地味に手間がかかる作業だ。Kubo CloudはPrometheus + Grafanaによるモニタリングを標準搭載しており、Rancher管理基盤でUpdaterのevictイベントやVPAの推奨値の変化を複数クラスタにわたって一元的に可視化できる。K3sベースのため、今回解説したVPA/HPA/PDBの挙動もそのまま標準のKubernetesとして扱える。
6. まとめ
「便利な自動化」ほど、安全装置の設定漏れが事故につながりやすい。VPAによるリソース最適化は本番のコスト効率を確実に改善するが、PodDisruptionBudgetという最後の防波堤なしに本番投入すると、ある夜突然、全レプリカが同時に消えるリスクを抱えたままになる。
もし自社のデータセンター内でPDB設計やリソース最適化の運用を落とし込む必要がある場合は、Kubo On-Premiseであれば、自社の管理下でフルマネージドに近い運用体制を構築できる。PodDisruptionBudgetの設計や退避制御の運用負荷を減らしたいという方は、料金プランで構成例を確認してみてほしい。