課題:リソース制限という「高くつく推測ゲーム」
DevOpsエンジニアの朝を台無しにするものといえば、午前3時のSlackによる CrashLoopBackOff のアラートほど最悪なものはありません。ログを確認すると、忌々しい OOMKilled ステータスが表示されています。アプリケーションのメモリが不足し、停止してしまったのです。さらに悪いのは、月末のクラウド請求書です。アプリが実際には0.1コアしか使っていないのに、誰かが「念のため」とリソースリクエストを2.0コアに設定したせいで、CPUの80%がアイドル状態のまま料金を支払っていることに気づくかもしれません。
Kubernetesで静的なリソース制限を設定して、正確に的中させることはほとんど不可能です。制限が低すぎれば、負荷がかかったときにアプリケーションがスロットリングを起こしたりクラッシュしたりします。高すぎれば、お金を無駄にし、クラスターの収容密度を下げてしまいます。トラフィックは予測不能であり、アプリケーションの実行時間が長くなるにつれてメモリ使用量が増大することもあるため、静的な設定では対応できません。
根本原因:なぜ大規模環境で静的制限が通用しないのか
Kubernetesは、スケジューリングに requests(要求)を、使用量の上限設定に limits(制限)を利用します。この2つの数値の乖離こそが、無駄が発生する場所です。手動でのチューニングは、マイクロサービスが3つのときはうまくいきますが、50個も管理するようになると悲惨な結果を招きます。100個のポッドで構成されるクラスター全体で、1ポッドあたりわずか0.5 CPUコアを過剰に割り当ててしまうだけで、実際には何の仕事もしていない50コア分の「実体のない」計算リソースに料金を支払うことになります。
多くのチームが、特に次の2つのパターンに苦しんでいます:
- 水平方向のスパイク: 短時間のトラフィック急増により、アプリケーションのインスタンス数を増やす必要がある。
- 垂直方向の成長: JavaやPythonのアプリなどで、大きなデータセットを処理したり長時間実行したりすることで、インスタンスあたりのメモリがさらに必要になる。
クイックスタート:5分でスケーリングを自動化する
オートスケーリングを導入すれば、推測に頼る必要はなくなります。まず、クラスター内で Metrics Server が動作していることを確認してください。このコンポーネントは、オートスケーラーが判断を下すために必要なテレメトリデータを提供します。
1. Horizontal Pod Autoscaler (HPA) のデプロイ
HPAは、需要に基づいてポッドの「数」を変更します。CPU使用率が50%に達したときにデプロイメントをスケールさせるには、次のコマンドを使用します:
kubectl autoscale deployment my-app --cpu-percent=50 --min=2 --max=10
2. Vertical Pod Autoscaler (VPA) のデプロイ
VPAは、個々のポッドの「サイズ」を調整します。HPAがワーカーを増やすのに対し、VPAは既存のワーカーにより強力なエンジンを与えます。VPAコンポーネントをインストールした後、アプリを監視するために次の設定を適用します:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Auto"
仕組みの解説
Horizontal Pod Autoscaler (HPA)
HPAは継続的な制御ループを実行し、15秒ごとに Metrics API に問い合わせを行います。そして、単純な比率を用いて必要なレプリカ数を計算します。ターゲットが50%で現在の使用率が100%の場合、HPAはポッド数を2倍にします。負荷をより多くの「ワーカー」に分散させやすいステートレスなウェブサービスに最適な選択肢です。
Vertical Pod Autoscaler (VPA)
VPAはより精密です。3つの内部コンポーネントを使用してポッド의 サイズを管理します:
- Recommender(リコメンダー): 過去の使用状況を分析し、「ちょうど良い」CPUとメモリの値を提案します。
- Admission Controller(アドミッション・コントローラー): ポッドの作成時に、推奨値を使用するように新しいポッドを修正します。
- Updater(アップデーター): リソース設定が古くなったポッドを削除し、新しい適切なサイズで再作成されるようにします。
注意: Auto モードでは、VPAは変更を適用するためにポッドを再起動します。十分なレプリカ数を構成していない場合や、Pod Disruption Budgets (PDB) を設定していない場合、一時的なダウンタイムが発生する可能性があります。
戦略的な導入:競合の解消
よくある落とし穴は、CPUなどの同じメトリクスに対してHPAとVPAの両方を実行してしまうことです。これにより「綱引き」効果が生じます。VPAがポッドあたりのCPUを増やそうとする一方で、HPAは平均CPUを下げるためにポッドを追加しようとします。両者が干渉し合い、不安定な環境とパフォーマンスの変動を招きます。
推奨される戦略
これらを調和させるには、次の2つのルールに従ってください:
- スケーリングにはHPAを使用する: CPUやリクエストのスループットに基づいたトラフィックのスパイク対応はHPAに任せます。
- 適正サイズ化(ライツサイジング)にはVPAを使用する: メモリの管理には、VPAを
InitialまたはOffモードで使用します。メモリはCPUのように圧縮(スロットリングによる調整)ができません。不足すればアプリはクラッシュします。VPAを使用することで、メモリリクエストが実際のピーク値に合致するように保証できます。
まずは updateMode: "Off" から始めることをお勧めします。これにより、コントローラーによってポッドが自動的に再起動されることなく、推奨値を確認できます:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa-recommend
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Off" # 推奨値の提示のみを行うモード
安定稼働のためのプロダクションのヒント
1. Pod Disruption Budgets (PDB) を強制する
サイズ変更にはポッドの再起動が必要です。VPAを Auto モードで使用する場合、PDBがセーフティネットになります。これにより、一斉にサイズ変更が行われる間でも、トラフィックを処理するために最低限必要な数のポッドが常に利用可能な状態に保たれます。
2. 「まずは観察」のルール
いきなり自動再起動を導入してはいけません。少なくとも1週間はVPAを Off モードで実行してください。Grafanaのダッシュボードで推奨値を確認し、現在の設定と比較します。推奨値が一貫しており安全であると確信できてから、Auto に移行してください。
3. メモリ管理は慎重に
CPUはスロットリングが可能で、その場合アプリの動作が遅くなるだけです。しかし、メモリはそうはいきません。ポッドがメモリ制限に達すると、カーネルは即座にプロセスを強制終了します。VPAは、時間の経過とともにアプリのピーク時のメモリ要件を学習し、下限を調整して午前3時のクラッシュを防いでくれるため、ここでの最良の防御策となります。
4. スタビライゼーション・ウィンドウの調整
HPAは時として過剰にスケールダウンを行い、ポッドが頻繁に作成・削除される「スラッシング」を引き起こすことがあります。最近のKubernetesバージョンでは、stabilizationWindowSeconds を調整できます。スケールダウン操作に対してこれを300秒(5分)に設定することで、一時的なトラフィックの減少による早すぎるポッドの削除を防ぐことができます。
最後に
Kubernetesの最適化とは、魔法のような数値のセットを見つけることではありません。現実を観察し、それに反応するシステムを構築することです。トラフィックのスパイクに対応するHPAと、長期的な適正サイズ化を実現するVPAを組み合わせることで、クラウドコストを大幅に削減しながら、アプリケーションの回復力を高めることができます。まずは小さく始め、データを信頼し、リソース管理という運用の負担をコントローラーに委ねていきましょう。

