GoldilocksとVPAによるKubernetesリソース最適化:RequestsとLimitsのサイジング手法

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

午前2時14分、手元のスマートフォンがPagerDutyの緊急アラートで震えました。通知されたのはNodeMemoryPressure、続いて2台のワーカーノード全体に連鎖するOOMKilledエラーでした。わずか数分のうちに、決済処理用ネームスペースの6つのPodがCrashLoopBackOffに陥りました。その原因は、あまりにも見覚えのあるものでした。

新しくデプロイされたマイクロサービスは、メモリ要求(request)がわずか100Miに設定されていたもののlimitが未設定だったため、バッチ照合処理の最中にメモリ消費が9.2Giを超えて膨れ上がっていたのです。その隣では、社内向けのWebhookサービスが丸一日わずか15ミリコアしか消費していないにもかかわらず、4 CPUコア分ものリソースを予約確保していました。クラスターは計算上は枯渇しているように見えながら、実際にはほとんどアイドル状態で稼働していたのです。

Kubernetesのrequestsとlimitsを勘で設定することの代償は極めて高額です。使われない余剰リソースに何千ドルもの費用を浪費するか、あるいは深夜2時にLinuxカーネルによって重要なワークロードが強制終了されて対応に追われるかのどちらかです。Fairwinds GoldilocksとKubernetes Vertical Pod Autoscaler(VPA)を組み合わせれば、このような当て推量を排除できます。実際のリソース消費メトリクスに基づき、実行可能で精度の高いPodリソースの推奨値を導き出すことが可能になります。

クイックスタート(5分)

インシデントの真っ只中に必要なのは、アーキテクチャの解説論文ではありません。各コンテナが予約されたリソースに対して実際にどれだけ消費しているのかを、即座に可視化することです。

ステップ1:Metrics Serverの確認

VPAは稼働中のノードおよびPodのメトリクスに依存します。まず、クラスターのメトリクスパイプラインが正常に稼働しているか確認します。

kubectl top nodes
kubectl top pods -A

両方のコマンドでリソース一覧テーブルが返されれば準備完了です。もしerror: Metrics API not availableというエラーが表示された場合は、先へ進む前に公式のmetrics-serverマニフェストをデプロイしてください。

ステップ2:Vertical Pod Autoscaler(VPA)のインストール

Goldilocksはベースラインの算出にVPAのRecommenderエンジンを利用します。autoscalerリポジトリをクローンし、標準のCRDとコントローラーをインストールします。

git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh

VPAの3つのコアコントローラーがすべてアクティブであることを確認します。

kubectl get pods -n kube-system | grep vpa

vpa-recommender、vpa-updater、vpa-admission-controllerがすべてRunning状態になっていることを確認してください。

ステップ3:HelmによるGoldilocksのデプロイ

GoldilocksはDeploymentを検査し、VPAオブジェクトを自動生成して、軽量なビジュアルダッシュボードを通じて推奨値を提示してくれます。リポジトリを追加してHelmチャートをインストールします。

helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update

helm install goldilocks fairwinds-stable/goldilocks \
  --namespace goldilocks \
  --create-namespace \
  --set dashboard.enabled=true

ステップ4:対象ネームスペースでのGoldilocksの有効化

Goldilocksはオプトインされたネームスペースのみを監視します。監視対象のネームスペースに識別用ラベルを付与します。

kubectl label namespace production goldilocks.fairwinds.com/enabled=true

ダッシュボードのサービスポートをローカル端末にポートフォワードします。

kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80

ブラウザでhttp://localhost:8080にアクセスします。ネームスペース内のすべてのDeploymentが表示され、GuaranteedおよびBurstableのQuality of Service(QoS)プロファイルに応じた具体的な推奨値が確認できます。

詳細解説

障害の再発を防ぐには、VPAエンジンがどのようにこれらの数値を算出し、Kubernetesがそれをどのように解釈するのかをエンジニアリングチームが理解しておく必要があります。

VPAによるターゲットリソースの算出ロジック

ネームスペースにラベルを付与すると、Goldilocksコントローラーは各Deploymentに対して対応するVerticalPodAutoscalerカスタムリソースを作成します。デフォルトではupdateMode: "Off"に設定されます。この設計により、Podの再起動や稼働中マニフェストの書き換えを行わずに本番トラフィックを安全に観察・評価できます。

自動生成されたVPAオブジェクトは直接確認できます。

kubectl get vpa -n production
kubectl get vpa payment-service -n production -o yaml

status.recommendation.containerRecommendationsを確認すると、直近8日間のスライディングウィンドウで算出された4つの計算値が出力されています。

  • LowerBound: 最小閾値。requestsをこの数値を下回る設定にすると、CPUスロットリングやメモリ枯渇の深刻なリスクが生じます。
  • Target: 算出された最適値。コンテナのrequestsにはこの値をそのまま設定します。
  • UpperBound: ピーク時のスパイクで観測された上限値。この値はコンテナのメモリlimitsを設定する際の目安になります。
  • UncappedTarget: VPAポリシーで定義された最小・最大制限(minAllowed / maxAllowed)を適用する前の、純粋な算出ターゲット値。

GoldilocksにおけるGuaranteedとBurstable QoS

すべてのPodは、CPUとメモリの定義方法に基づいてQuality of Service(QoS)クラスが割り当てられます。

  1. Guaranteed: すべてのコンテナでrequestsとlimitsが一致している状態です。Kubernetesスケジューラはノード上でそのリソース枠を完全に排他的に予約します。Goldilocksでは両方のフィールドにVPA Targetが設定されます。ミッションクリティカルなデータベースやキューコンシューマーに適した設定です。
  2. Burstable: requestsはVPA Targetに設定しつつ、limitsをそれより高く設定するか、CPUのlimitを設定しない(オープンにする)構成です。これにより、アクセスが少ない時間帯にノードリソースを無駄に占有することなく、一時的なトラフィック急増をマイクロサービスが柔軟に吸収できるようになります。

深夜2時のインシデントでは、メインの決済プロキシがリソース定義を全く持っていなかったため、BestEffortクラスで動作していました。ノードのメモリが枯渇した際、Linuxカーネルは真っ先にそのプロキシを終了対象としました。BestEffortのPodはoom_score_adjが1000に設定されているため、ノードのメモリ圧迫時に最初の犠牲者となってしまいます。

応用的な設定と運用

標準設定は基本的なステートレスアプリケーションには適していますが、ステートフルなワークロード、テレメトリ用のサイドカー、スパイクの激しいバックグラウンドワーカーなどには、よりきめ細かな設定が必要です。

カスタムVPA制約(Constraints)の定義

明確な上限・下限(ガードレール)を設けたいケースもあります。例えば、未修正のメモリリークが原因で、16Giのインスタンスノードプールに対してVPAが32Giの予約を推奨してしまうような事態は防がなければなりません。明示的な制約を持たせたカスタムVPAマニフェストを定義することでこれを回避できます。

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: checkout-worker-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: checkout-worker
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: checkout-worker
        minAllowed:
          cpu: 100m
          memory: 256Mi
        maxAllowed:
          cpu: 2000m
          memory: 4Gi
        controlledResources: ["cpu", "memory"]

このマニフェストを適用することで自動生成されたデフォルト設定を上書きできます。VPA recommenderは指定された安全基準(上限・下限)の範囲内で推奨値を算出するようになります。

サービスメッシュサイドカーの除外

EnvoyやLinkerd、Datadogエージェントなどのテレメトリ用サイドカーは、アプリケーションのサイジング計算を歪める要因になります。Goldilocksではこれらを除外フィルターに設定することで、純粋にアプリケーションコンテナのみに焦点を絞った推奨値を得ることができます。

除外リストを指定してGoldilocksのHelmリリースを更新します。

helm upgrade goldilocks fairwinds-stable/goldilocks \
  --namespace goldilocks \
  --set "controller.flags.exclude-containers=istio-proxy\,datadog-agent"

これによりプロキシのオーバーヘッドをコアアプリケーションのパフォーマンスから切り離し、真に実用的なメトリクスを維持できます。

CI/CD自動化に向けたGoldilocksメトリクスのエクスポート

数十規模のクラスター環境では、ダッシュボードを手動で確認する運用はスケールしません。代わりにGoldilocksのデータをPrometheusへ直接エクスポートします。

helm upgrade goldilocks fairwinds-stable/goldilocks \
  --namespace goldilocks \
  --reuse-values \
  --set controller.metrics.enabled=true

エクスポートしたデータを利用すれば、GrafanaやCIパイプラインの静的解析(linting)ステップで不適切な設定のワークロードを検知できます。例えば、設定されたrequestsが推奨ターゲットの2倍を超えている場合や、ターゲット閾値の50%未満にとどまっている場合にアラートを発令するといった運用が可能です。

実践的なTips

本番ワークロードの微調整は反復的な取り組みです。クラスター全体に変更を適用する前に、以下の4つの原則を念頭に置いておきましょう。

1. CPU Limitによるスロットリングの罠に注意する

メモリは非圧縮性(incompressible)のリソースです。limitを超過するとカーネルは即座にコンテナを終了させます。一方、CPUは圧縮性(compressible)のリソースです。CPU limitを超えると、Completely Fair Scheduler(CFS)がクォータ期間ごとにコンテナの実行時間をスロットリング(制限)します。

厳しすぎるCPU limitは、しばしば不自然なレイテンシー急増を引き起こします。毎秒1,200リクエストを処理するあるAPIサービスでは、500mのCPU limitを解除しただけでp99レスポンスタイムが840msから38msへと劇的に改善しました。CPU requestsはGoldilocksのTargetに基づいて設定し、CPU limitsは設定しないか十分に余裕を持たせることで、不要なスロットリングを回避することを推奨します。

2. JVMやランタイムのメモリオーバーヘッドを考慮する

Java、Node.js、Pythonなどのランタイムは、アプリケーションヒープをはるかに超えるメモリを消費します。たとえば-Xmx2gで動作するJVMであっても、スレッドスタック、Metaspace、ガベージコレクション(GC)のメタデータ用などにオフヒープメモリを確保します。そのためコンテナのlimitをぴったり2Giに設定すると、高確率でOOMKillが発生します。Goldilocksは実際のResident Set Size(RSS)をレポートします。2Giのヒープに対しては、ランタイムのオーバーヘッドを安全に吸収できるよう、コンテナのlimitsには少なくとも2.8Gi〜3.0Giを割り当ててください。

3. CPU/メモリベースのHPAとVPA Autoモードを絶対に併用しない

CPUやメモリのメトリクスに基づくHorizontal Pod Autoscaler(HPA)と、updateMode: "Auto"のVPAを同時に稼働させてはなりません。2つのコントローラーが競合し合います。HPAがレプリカ数を増やそうとする一方でVPAがPodサイズを拡大し、ノードのリソースを枯渇させる悪循環(フィードバックループ)を引き起こします。自動的な水平スケーリングが必要な場合は、SQSのキュー滞留数やHTTPリクエストレートなどのカスタムビジネスメトリクスに基づいてHPAを構成してください。

4. 週次のサイジング見直しルーティンを確立する

毎週月曜の朝に20分間、リソース運用の健全性を点検する時間を設けましょう。Goldilocksでハイライトされたワークロードを確認し、ArgoCDやFluxのGitOpsマニフェストを更新します。20個のマイクロサービスでそれぞれCPUを500m、メモリを1Gi適正化するだけでも、毎月のクラウドインフラ費用を簡単に1,200ドル削減できます。

Share: