Claude Media
Claude CodeとOpenCostでKubernetesのrequests削減案を作る

Claude CodeとOpenCostでKubernetesのrequests削減案を作る

OpenCostのallocation APIで要求過多のワークロードを洗い出し、削減後のrequestsをkustomizeとHelm valuesの差分にします。HPAとPDBへの影響も、適用前に確かめます。

Kubernetesのrequestsは、使われなくても予約され続けます。OpenCostはその予約分をコストとして見せてくれるので、「どのワークロードが実使用より大きく確保しているか」を金額順に並べられます。この記事では、OpenCostのallocation APIの値をClaude Codeに読ませ、削減後のrequestsをkustomizeのパッチとHelm valuesの差分にするところまでを進めます。最後に、requestsを下げると動きが変わるHPAと、ドレイン時に効くPDBを適用前に確かめます。

kubectl topの実測から値を見直す一般的な手順は、Claude Codeでrequests・limitsを実測から見直すにあります。ここでは、コストの大きい順に着手先を決める部分に絞ります。

OpenCostのコストは「requestと使用量の大きいほう」で決まる

OpenCostの仕様では、ワークロードのコストはmax(request, usage)として扱われます。CPUもメモリも、要求量と使用量のうち大きいほうが課金対象です。

つまり、requestsを大きく取ったコンテナは、使っていなくてもその分のコストが乗ります。逆にrequestsより多く使っているコンテナは、使用量のほうで計上されます。削減の余地が大きいのは、前者の「要求が使用量を大きく上回る」コンテナです。

もう一つ押さえる点があります。allocation APIの金額はオンデマンドの定価ベースの計算で、請求書の額そのものではありません。削減額を約束する数字ではなく、着手の優先順位をつけるための物差しとして使います。

OpenCostの値をClaude Codeに渡す2つの経路

渡し方は2つあります。どちらも、クラスタへの書き込みは伴いません。

くらべる

OpenCostの値の渡し方

手元で完結

curlでJSONを保存する

port-forwardしたAPIを人が叩き、結果をファイルに保存して渡します。同じ数字を何度でも読み直せ、権限設計も単純です。

対話で掘れる

OpenCostのMCPサーバーを繋ぐ

Claude Codeがget_allocation_costsを直接呼びます。集計軸を変えて聞き直すときに速い一方、出力サイズの上限に当たりやすくなります。

curlで保存する

OpenCostのAPIは、既定でポート9003にあります。別ターミナルでport-forwardを張り、7日分をコンテナ単位で保存します。

kubectl -n opencost port-forward deployment/opencost 9003
 
curl -G http://localhost:9003/allocation \
  -d window=7d -d aggregate=container -d resolution=1m \
  > alloc-7d.json

windowには7dのほかlastweekやmonthも指定できます。resolutionの既定は1mで、細かいほど正確ですが重くなります。短命なジョブを含むなら粗い値は避けます。

応答のフィールド名は、バージョンや設定で増減します。Claude Codeにいきなり集計させず、まず中身を確かめさせると外れません。

jq '.data[0] | to_entries[0].value | keys' alloc-7d.json

出てきたキーの中から、CPU・メモリそれぞれの「要求量」「使用量」「効率」にあたるものをClaude Codeに選ばせます。ここで選んだキー名を、次のプロンプトとCLAUDE.mdに固定します。

OpenCostのMCPサーバーに繋ぐ

OpenCostにはMCPサーバーが同梱されており、Helmチャートの既定で有効、ポートは8081です。使えるツールはget_allocation_costs、get_asset_costs、get_cloud_costsの3つです。requestsの見直しで使うのは、windowとaggregateを取るget_allocation_costsです。

kubectl -n opencost port-forward svc/opencost 8081:8081
 
claude mcp add --transport http --scope project \
  opencost http://localhost:8081

--scope projectにすると.mcp.jsonに書かれ、チームで同じ接続を共有できます。ただしプロジェクトスコープのサーバーは、初回に承認を求められます。

権限は、読み取り専用のOpenCostのツールだけを許可します。MCPツールの許可ルールはmcp__<サーバー名>__<ツール名>の形で書きます。

{
  "permissions": {
    "allow": ["mcp__opencost__get_allocation_costs"],
    "deny": [
      "Bash(kubectl apply *)",
      "Bash(kubectl patch *)",
      "Bash(kubectl edit *)",
      "Bash(kubectl scale *)",
      "Bash(kubectl delete *)"
    ]
  }
}

コンテナ単位で全クラスタを引くと、結果が大きくなります。Claude Codeは、MCPの出力が10,000トークンを超えると警告を出し、既定では25,000トークンで切ります。50,000文字を超えるテキストはファイルに保存されます。最初はaggregate=namespace,controllerのように粗く引き、候補の名前空間だけコンテナ単位に掘る順が安全です。

要求過多のワークロードを洗い出す

保存したJSON(またはMCPの結果)を渡して、候補を出させます。判断基準を先に書いておくと、出力が揃います。

alloc-7d.json は OpenCost の allocation(7日、コンテナ単位)です。
キーは上で確認した次の名前を使います: <要求量・使用量のキー名>
- CPU・メモリそれぞれで、要求量に対する使用量の比が低い順に並べる
- 使用量が極端に小さいもの(停止中や未使用の疑い)は別表にする
- 各行に、名前空間・コントローラ・現在のrequest・7日の使用量を入れる
- 削減案は、使用量の平均ではなく kubectl top とPrometheusの
  p95・最大値で裏を取ってから出す。まだ数値は書き換えない

最後の一行が肝です。allocation APIが返すのは、windowで区切った期間の集計値です。集計に合わせてrequestsを切ると、夜間バッチやピークで足りなくなるおそれがあります。候補を絞るのにはOpenCost、値を決めるのには期間のp95と最大値、と役割を分けます。

候補が出たら、上位のワークロードだけkubectl top pod --containersで現在値を取り、Prometheusの期間集計と突き合わせます。この裏取りの手順とクエリは、先の実測から見直す記事に書いています。

削減後のrequestsを差分にする

数字が決まったら、manifestを直接書き換えさせず、管理している単位の差分にします。数値は架空の例です。実測でp95がCPU 120m、メモリ300Mi前後、最大でメモリ420Miだったコンテナを考えます。

前と後

requestsの書き換え例(架空の数値)

BEFORE

現在

resources:
  requests:
    cpu: 1000m
    memory: 2Gi
  limits:
    memory: 2Gi
AFTER

削減案

resources:
  requests:
    cpu: 250m
    memory: 512Mi
  limits:
    memory: 1Gi

メモリのlimitsは、実測の最大(420Mi)を下回らない値にしています。requestsだけを下げて、limitsを触らない選択肢もあります。コスト起点の見直しで効くのはrequestsなので、limitsは別の判断で動かします。

kustomizeならパッチ1枚にする

overlayを使っているなら、変更はパッチ1枚に収めます。overlayの組み方はkustomizeのoverlayを組む手順にあります。

# overlays/prod/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
        - name: api
          resources:
            requests:
              cpu: 250m
              memory: 512Mi

kustomization.yamlのpatchesに、このファイルを足します。

Helmならvaluesの差分にする

Helmチャートなら、チャートは触らずvaluesだけを変えます。

# values-prod.yaml(差分)
resources:
  requests:
    cpu: 250m
    memory: 512Mi

適用前に本番との差分を見る

どちらも、kubectl diffで「適用したらどうなるか」を読み取りだけで確かめます。差分があると終了コードが1になるので、スクリプトに組むときは0と1を区別します。

kubectl diff -k overlays/prod
 
helm template api ./chart -f values-prod.yaml | kubectl diff -f -

Helmのアップグレード単位で差分をレビューしたいときは、helm diffでアップグレード前の差分を見る手順が使えます。適用そのものは人が行い、Claude Codeには差分の確認までを任せます。

HPAとPDBへの影響を確かめる

requestsを下げるときに、見落としやすいのが次の2点です。

HPAは「requestsに対する比率」で動く

HPAのリソース使用率は、現在の使用量とrequestsの比で計算されます。requestsを下げると、同じ使用量でも使用率の数字が上がります。

先の例で、CPUの使用量が120mのまま、requestを1000mから250mに下げたとします。使用率は12%から48%に変わります。HPAの目標使用率が60%なら、余裕は大きく減ります。ピークで200mを使えば80%になり、レプリカが増えます。

落とし穴

requestsを下げるとレプリカ数が増えることがある

HPAの目標が使用率(averageUtilization)のワークロードは、requestsの変更でスケールの閾値も動きます。削減によるコスト減が、レプリカ増で打ち消されないか確認します。目標値を見直すか、削減幅を小さくします。

Claude Codeには、HPAのmanifestを読ませて、変更後の使用率を計算させます。

削減案の各ワークロードについて、同じnamespaceのHPAを読み、
metricsがUtilizationのものを抜き出してください。
変更後のrequestsで、7日のp95使用量時点の使用率と、
最大使用量時点の使用率を表にします。目標値を超えるものを赤く示します。

PDBはレプリカ数が変わると効き方が変わる

PDBは、自発的な中断(ドレインによる退避など)で、同時に落としてよいPodの数を制限します。minAvailableやmaxUnavailableは絶対数でも割合でも指定できます。

HPAでレプリカ数が増減するワークロードは、同じPDBでも許可される退避数が変わります。変更後の姿を想定して、現在のPDBが退避を許す状態か、kubectl get pdbのALLOWED DISRUPTIONSで確かめます。ここが0のままだと、ドレインが止まります。PDBの足し方と動作確認はPodDisruptionBudgetを足してドレインを確かめる記事にまとめています。

なお、requestsを書き換えるとPodテンプレートが変わるため、Deploymentでは新しいReplicaSetへの入れ替えが始まります。適用のタイミングは、トラフィックの少ない時間帯に合わせます。

手順をCLAUDE.mdに固定する

毎回同じ順で動かすため、判断基準をCLAUDE.mdに書いておきます。

## requests削減のルール
- 候補の抽出は OpenCost の allocation(7日、コンテナ単位)を使う
- allocationは期間の集計値。値の決定はp95と最大値で行う
- メモリlimitは期間の最大値を下回らせない
- 変更はkustomizeのパッチ、またはHelm valuesの差分で出す
- 差分は kubectl diff で確認する。kubectl apply は実行しない
- HPAのUtilization目標があるワークロードは、変更後の使用率を表にする

これで、Claude Codeは同じ入口から同じ基準で案を出します。MCPで繋ぐ場合は、サーバーの接続状態をclaude mcp get opencostで確かめてから始めます。

つまずきやすい点

  • 短い期間で判断する: 1日分では週次のバッチやリリース直後の山を拾えません。7日以上を基本にします
  • 使用量がほぼ0のものを削る: 停止中のCronJobや、障害時だけ動くワークロードが紛れます。別表に分けて、持ち主に確認します
  • 定価ベースの金額を請求額と読む: 定価ベースの計算なので、請求書の額とは一致しません。順位付けにだけ使います
  • kubectl topとOpenCostの値が合わない: 前者は直近の瞬間値、後者は期間の集計です。差が出るのが普通で、見る目的が違います

まとめ

OpenCostは「どこを削るか」を決める道具で、「いくつにするか」はp95と最大値が決めます。この分担を守ると、平均値に合わせすぎて足りなくなる失敗を避けられます。削減案はkustomizeかHelm valuesの差分として出し、HPAの使用率とPDBの退避数を適用前に確かめる流れまで含めて、1つの手順にしておくと再利用できます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/mcp
  2. code.claude.com/docs/en/permissions
  3. opencost.io/docs/integrations/api
  4. opencost.io/docs/integrations/mcp
  5. opencost.io/docs/specification
  6. kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autosca…
  7. kubernetes.io/docs/tasks/run-application/configure-pdb
この記事を共有:XはてブLinkedIn