目次
28 章

コスト最適化

5部の2番目の章です。26章で5つの出所として挙げたコスト項目を本格的に扱います。コンピュート (ノード) と付加 (LB · ストレージ · ネットワーク · コントロールプレーン) の2軸、requests のコストの意味、VPA · Goldilocks · KRR の right-sizing、Spot · Karpenter · Cluster Autoscaler の決定ツリー、bin packing と descheduler、OpenCost · Kubecost の可視化、namespace ラベル単位の chargeback / showback、PV · ネットワークコストまでを一連の流れで束ね、翌月の請求書 review チェックリストで締めくくります。

5部 (運用 · デバッグ · コスト) の2番目の章です。27章 kubectl デバッグパターン で事故が発生したときどこから見るべきかの要点を整理したなら、本章は事故ではないものの 毎月請求書として入ってくるコスト要因 を扱います。26章 運用チェックリスト §「コスト」で5つの出所として短く挙げた項目が、本格的なコストガバナンスの一連の流れへとつながる段階です。

K8s のコストは意図しなければ速く膨らみます。「なぜ翌月の請求書が2倍になったのか」の最もよくある答えは、1つの大きな変更ではなく 複数の小さな漏れの合算 です。本章の目標は、その漏れがどこで漏れているかを把握し、四半期ごとに1ページの点検チェックリストで防げる視野です。

K8s コストの2軸 #

EKS の請求書を一度広げてみると、コストは2つの軸に分かれます。

項目
コンピュート (ノード)EC2 インスタンス、Karpenter が立ち上げたノード、ARM / x86 / GPU
付加EKS コントロールプレーン、ALB / NLB、EBS / EFS、NAT Gateway、データ転送、ECR ストレージ

比率は環境ごとに異なりますが、一般的な運用クラスタでは コンピュートが 60 ~ 70 %、付加が 30 ~ 40 % 程度です。コンピュート削減は可視的で最も大きな効果を出しますが、付加の NAT Gateway とデータ転送がしばしばコンピュートより大きな漏れを作るという点を忘れやすいです。2軸を一緒に見るのがコスト最適化の出発点です。

21章 EKS クラスタセットアップ の §「コストの第一印象」で prod の開始コストを月 $200 ~ $300 と置いたその3つが、本格的な運用でどう散らばるかが本章の本文です。

requests のコストの意味 #

11章 リソース要求と上限 で requests がスケジューラの入力だという論点を扱ったなら、運用の観点で requests は クラスタがユーザーに請求するリソースの予約 です。requests の合計 = クラスタが立ち上げなければならない最小ノードリソースであり、ノードの可用量の 60 ~ 70 % 以上を requests で埋めてこそ 13章 オートスケーリング の Cluster Autoscaler が新しいノードを立ち上げる決定をします。

問題は over-request です。

over-request のよくある例 — 11章の不足を運用で再び
resources:
  requests:
    cpu: "2"          # 実際の平均使用 0.1 core
    memory: "4Gi"     # 実際の平均使用 200Mi
  limits:
    cpu: "4"
    memory: "8Gi"

このマニフェストが100個の Pod に適用されると、クラスタは 200 core + 400 Gi を予約します。実際に使うのは 10 core + 20 Gi です。空のリソースにノードコストが請求される形です。運用クラスタで最もよくあるコスト漏れの出所であり、単一の変更で最も大きな削減を作れる領域です。

適正な requests のルール は次の程度です。

  • CPU requests = 平均使用量 × 1.2 ~ 1.5
  • Memory requests = P95 使用量 × 1.1 ~ 1.3 (メモリは burstable しにくいです)
  • limits は requests より 2 ~ 4 倍 (Burstable QoS) または同じ (Guaranteed QoS)

平均 / P95 の測定は、25章 モニタリング · アラートcontainer_cpu_usage_seconds_totalcontainer_memory_working_set_bytes メトリクスで1か月ほど観察すると見えます。

VPA recommendation のみ — 推奨値を PR で #

Vertical Pod Autoscaler (VPA) は、ワークロードの実際の使用量を分析して適正な requests / limits を推奨するコントローラです。自動 apply モードは運用では危険ですが (Pod 再起動が頻繁になる)、recommendation のみをオンにするモード が運用で安全なパターンです。

VPA — recommendation のみ
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: myshop-api-vpa
  namespace: myshop
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myshop-api
  updatePolicy:
    updateMode: "Off"   # 推奨のみ、自動適用しない

updateMode: Off が核心 — VPA がメトリクスを分析して推奨値を status に書くだけで、Pod の実際の spec は触りません。

推奨値確認
kubectl describe vpa myshop-api-vpa -n myshop

この出力の “Container Recommendations” セクションに適正な CPU / Memory が書かれています。その値を 22章 アプリ配備の骨格 の Helm values に PR で反映する流れが運用の標準です。自動化は測定に、決定は人に — VPA の最も安全な運用モードです。

GoldilocksRobusta KRR が同じ方向を別の UI で提供します。Goldilocks はネームスペース単位で VPA 推奨を1つのダッシュボードにまとめてくれ、KRR は CLI の1コマンドでクラスタ全体の推奨値を出力します。どの道具でも本質は「測定された推奨値を人が PR で反映」のパターンです。

bin packing — ノード活用率と descheduler #

requests が適正でも、ノードが非効率的に埋まればコストが漏れます。bin packing がノードのリソースをどれだけびっしり使うかの指標です。

ノード活用率測定
kubectl top nodes
kubectl describe node <node-name>   # Allocatable vs Allocated

kubectl describe node の “Allocated resources” セクションが、そのノードの requests の合計を見せてくれます。ノードの可用量の 60 ~ 80 % が運用の適正な活用率です。50 % 未満ならノードが過大プロビジョニング、90 % 以上なら新しい Pod がスケジューリングされない危険です。

低い活用率のノードが累積されると descheduler が入ってきます。

descheduler — 使用率の低いノードの Pod を移動
apiVersion: descheduler/v1alpha2
kind: DeschedulerPolicy
profiles:
  - name: LowNodeUtilization
    pluginConfig:
      - name: LowNodeUtilization
        args:
          thresholds:
            cpu: 20
            memory: 20
            pods: 20
          targetThresholds:
            cpu: 60
            memory: 60
            pods: 60
    plugins:
      balance:
        enabled: [LowNodeUtilization]

descheduler が使用率 20 % 未満のノードの Pod を evict すると、Karpenter (または Cluster Autoscaler) がそのノードを回収します。22章 の PodDisruptionBudget がこの時点で再び決定的です — PDB のないワークロードが evict の最初の犠牲者です。

Spot ノード — 安全なワークロードの分類 #

EC2 Spot インスタンスは ON_DEMAND 比で 50 ~ 90 % 安いですが、AWS が2分の通知の後に回収できます。この特性を安全に活用するのがコスト削減の最も大きな領域です。

ワークロードを2つに分類するのが標準です。

分類特徴Spot 適合度
interruptiblestateless、replicas が複数、再起動が速い適合 — myshop-api のような API サーバー
stateful / criticalStatefulSet、単一インスタンス、長い初期化Spot 不適合 — 少なくとも一部は ON_DEMAND

この分類をマニフェストで表現する標準パターンが node taint + tolerations です。

Karpenter NodePool — spot ノードに taint
spec:
  template:
    spec:
      taints:
        - key: karpenter.sh/capacity-type
          value: spot
          effect: NoSchedule
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
Pod — spot ノードに schedulable な toleration
spec:
  tolerations:
    - key: karpenter.sh/capacity-type
      value: spot
      operator: Equal
      effect: NoSchedule

このパターンが 「意図的に spot を受け入れると宣言したワークロードだけが spot ノードに行く」 ようにする安全装置です。新しく作ったワークロードが誤って spot に落ちて回収の影響を受けることを防ぎます。

Fargate Spot も同じ方向のマネージドオプションです。ノード自体を気にせずに 70 % 程度の削減を作りますが、GPU / DaemonSet / privileged Pod のような一部のワークロードを立ち上げられない制約があります。

Karpenter — Cluster Autoscaler との決定ツリー #

13章 オートスケーリング §「Karpenter — EKS のより速い代替」で挙げたモデルを本章で本格的に扱います。Cluster Autoscaler と Karpenter の違いを一表に整理します。

観点Cluster AutoscalerKarpenter
モデルあらかじめ定義された Node Group のサイズ調整Pending Pod を見てその場でインスタンス選択
インスタンスの多様性Node Group のあらかじめ定義されたタイプ100 + インスタンスタイプを自動比較
プロビジョニング速度1 ~ 2 分 (ASG ベース)30 秒 ~ 1 分 (EC2 直接呼び出し)
consolidation別の道具 (descheduler など)内蔵 — 使用率の低いノードを自動統合
運用負担Node Group 単位の設定NodePool / NodeClass の2つの CRD

Karpenter の NodePoolNodeClass のメンタルモデルが核心です。

NodeClass — AWS リソース定義 (どこで立ち上げるか)
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2023
  role: "KarpenterNodeRole-myshop-prod"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: myshop-prod
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: myshop-prod
NodePool — スケジューリングポリシー (どんなノードを立ち上げるか)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["t", "m", "c"]
        - key: karpenter.k8s.aws/instance-cpu
          operator: In
          values: ["2", "4", "8", "16"]
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
  limits:
    cpu: 1000

NodeClass = どこで、NodePool = どのように の分離です。1つの NodeClass の上に複数の NodePool を置き (general / batch / gpu など)、ワークロードのラベル / taint で適切な NodePool が選ばれるように束ねます。

on-demand fallback — spot 回収の安全線 #

Karpenter の強みの1つが on-demand fallback です。requirements に spoton-demand を両方置くと、spot インスタンスの回収が頻繁な時点で自動的に on-demand へ切り替えます。spot のコストの利点を取りながら、回収時に可用性を保証するパターンです。

決定ツリー #

Cluster Autoscaler と Karpenter の選択
- ワークロードのリソース要求が一定で、インスタンスタイプ 1 ~ 2 個で十分
  -> Cluster Autoscaler がシンプル
- ワークロードが多様で、コスト削減が優先
  -> Karpenter
- spot のコストの利点を本格活用
  -> Karpenter (自動インスタンス多様化)
- AWS 以外のクラウド
  -> Cluster Autoscaler (Karpenter は AWS 中心)

本書の標準経路は 21 ~ 22 章で Cluster Autoscaler から始め、トラフィックパターンが定着した後に Karpenter へ転換する流れです。2つの道具を同じクラスタで同時に運用するのは推奨されません — ノード回収の決定主体が2つになると互いの動作を妨げます。

idle リソース回収 — 点検道具のまとめ #

運用が1か月過ぎると unused / idle リソースが累積されます。定期点検の自動化道具です。

道具役割
GoldilocksVPA 推奨値をネームスペース単位のダッシュボードで
Robusta KRRCLI の1コマンドでクラスタ全体の推奨値を出力
AWS Cost Anomaly Detection平常パターン比の急増を自動通知
kubectl-rightsizerequests vs 実際の使用の格差をレポート

これらの道具の出力を四半期ごとに1ページにまとめ、PR の入力として置く流れが標準です。1四半期にワークロードの 20 ~ 30 % が推奨値とずれているというのが普通の結果であり、その程度の right-sizing でコンピュートコストの 20 ~ 40 % 削減が一般的です。

コスト可視化 — OpenCost / Kubecost / Cost Allocation Tags #

OpenCost — オープンソース標準 #

OpenCost インストール
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm install opencost opencost/opencost \
  -n opencost --create-namespace

OpenCost が Prometheus メトリクスと AWS Cost API を連携して、ネームスペース · Deployment · ラベル単位のコストを計算します。25章 の Prometheus と自然に連携し、Grafana データソースとして追加すればコストダッシュボードを作れます。

Kubecost — 商用強化版 #

Kubecost は OpenCost の商用拡張です — より細かい可視化、推奨値の自動化、SSO 統合、multi-cluster コスト統合などが追加されます。小さなチームは OpenCost から始め、コスト管理が本格的な業務になる時点で Kubecost へ移る流れが自然です。

AWS Cost Allocation Tags #

Terraform — default_tags (21章の回想)
provider "aws" {
  region = "ap-northeast-2"

  default_tags {
    tags = {
      Project     = "myshop"
      Environment = "prod"
      Team        = "backend"
      CostCenter  = "engineering"
    }
  }
}

21章 EKS セットアップ で書いた default_tags が本章のコスト配分の基盤です。AWS Cost Explorer でこれらのタグを “Cost Allocation Tags” として有効化すると、請求書がタグ別に分かれて見えます。タグ標準をあらかじめ定めておかないと1年後のコスト配分がほぼ不可能 です — セットアップ時点で定めておくのが核心です。

namespace / ラベル単位のコスト配分 — chargeback vs showback #

複数のチームが1つのクラスタを共有する環境では、誰がいくら使ったか の問題が本格的な運用イシューになります。2つのモデルがあります。

モデル意味
showback各チームの使用量を見せます。コストの請求はしません。行動変化を促す
chargeback実際にチーム別の予算から差し引く。強制的な削減効果

ほとんどの運用環境の出発点は showback です。OpenCost の出力で毎月チーム別の使用量レポートを送ると、自然にチームが自分のワークロードの requests とラベルの整理を始めます。7章 Namespace とラベルteam / cost-center 標準ラベルが本節で本格的なコスト責任のキーとして活用されます。

20章 GitOps の App of Apps が導入された環境では、各 ArgoCD Application マニフェストにチームラベルを刻んでおくと、ラベルが自動的にマニフェスト全体に伝播します。

PV / EBS コスト — gp3 と lifecycle #

ストレージコストはインスタンスコストの次に大きな比重を占める場合が多いです。3つの観点を挙げます。

gp3 vs gp2 #

EBS の基本オプションが gp2 から gp3 へ変わったのがコスト面の最も大きな変化です。gp3 が同じ IOPS · スループットで 約 20 % 安い です。9章 PV / PVC / StorageClass の StorageClass マニフェストで gp3 と置くのが運用標準です。

StorageClass — gp3 がデフォルト
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
allowVolumeExpansion: true

snapshot の累積 #

EBS snapshot は自動的に累積されます。RDS の自動スナップショットは 23章backup_retention_period で管理されますが、K8s の VolumeSnapshot や Velero のバックアップで作られた snapshot は別の lifecycle がなければ永遠に積み重なります。

snapshot lifecycle 点検
aws ec2 describe-snapshots \
  --owner-ids self \
  --query 'Snapshots[?StartTime<=`2024-01-01`].[SnapshotId,VolumeSize,Description]' \
  --output table

未使用 PV の回収 #

Released 状態の PV 点検
kubectl get pv | grep Released

Released 状態の PV は、その PVC を持っていた Pod が削除されたが PV 自体は残っている状態です。EBS ボリュームが請求され続けます。ReclaimPolicy が Retain なら手動の整理が必要で、Delete なら PVC 削除時に EBS も一緒に削除されます。

ネットワークコスト — 最も隠れた漏れ #

AWS のネットワークコストは請求書で最も発見しにくい項目です。3つの出所があります。

AZ 間トラフィック #

同じ region の中でも AZ が異なる Pod 同士の通信は GB 当たり $0.01 ~ $0.02 が請求されます (双方向なので実際には2倍)。myshop-api の5個の Pod が3つの AZ に散らばっていて、各 Pod が PgBouncer と通信するたびに一部は AZ 間トラフィックになります。

Topology-aware routing がこれを減らす K8s 1.23+ の機能です。

Service — topology-aware hints
apiVersion: v1
kind: Service
metadata:
  name: pgbouncer
  namespace: myshop
  annotations:
    service.kubernetes.io/topology-mode: Auto
spec:
  # ...

この annotation がオンになっていれば、同じ AZ の endpoint が優先されます。AZ 間トラフィックが半分以下に下がるのが一般的です。

NAT Gateway データ転送 #

21章 EKS セットアップ の §「single NAT vs NAT per AZ」で挙げた NAT コストは時間当たり + GB 当たりです。時間当たりは NAT 1個に約 $32/月で、GB 当たりはデータ転送量に比例します。

NAT データ転送削減パターン (26章の回想 + 本格)
- VPC Endpoint — S3, ECR, DynamoDB, CloudWatch, Secrets Manager
  -> 最も大きな効果。NAT トラフィックの 60 ~ 80% を迂回。
- 外部 API キャッシング — インスタンスの中のキャッシュまたは ElastiCache
  -> 反復呼び出しを減らす。
- 同じ region の他の AWS サービス通信は VPC 内部ルーティングで
  -> ECR, S3, DynamoDB は無料。

ALB の LCU #

ALB のコスト単位は LCU (Load Balancer Capacity Unit) です — 新規接続、アクティブ接続、処理されたデータ、ルール評価の4次元のうち最も大きな値で請求されます。22章 アプリ配備の骨格 で作った1つの ALB が myshop-api、ArgoCD、Grafana の3つの入り口を束ねるパターンが LCU 削減にも役立ちます — ワークロードごとに ALB を別々に作りません。

翌月の請求書 review チェックリスト #

月別のコスト点検の1ページ標準チェックリストを整理します。

月間コスト review — 1ページ
[コンピュート]
- ノード活用率 — kubectl top nodes の平均が 60 ~ 80%?
- spot 比率 — interruptible ワークロード比の spot 使用率
- Karpenter consolidation — 直近1か月のノード統合イベント数
- VPA / KRR 推奨値 — 未反映ワークロードのリストと PR 進行状況

[付加]
- NAT データ転送量 — 先月比の変化
- VPC Endpoint 導入可能サービス — S3 / ECR / Secrets Manager
- AZ 間トラフィック比率 — topology-aware routing 適用可能な Service リスト
- ALB / NLB 個数 — 統合可能な入り口があるか

[ストレージ]
- gp2 PV の残り — gp3 への移行候補
- Released PV の EBS ボリューム — 回収可能量
- 古い EBS / RDS snapshot — lifecycle 未適用リソース
- ECR イメージ — lifecycle policy 適用状態

[可視化]
- OpenCost のチーム別 / ワークロード別コスト 1, 2, 3 位
- 先月比 +20% 以上増加した項目
- Cost Anomaly Detection の通知履歴
- 翌月の予測 vs 予算

このチェックリストが1ページに収まり、毎月1回定期的に埋められるのが運用の目標です。すべての項目を一度に解こうとせず、毎月1つ2つの項目だけを改善する累積モデル が持続可能な流れです。

練習問題 #

  1. dev クラスタに VPA recommendation モードと Goldilocks をインストールし、myshop-api の1か月分のデータを分析して推奨 requests / limits を得ます。現在のマニフェストの requests と推奨値の格差を百分率で計算し、その格差が1か月のノードコストにどう反映されるかを OpenCost の出力で検証します。推奨値を 22章 の Helm values に反映する PR を作成し、適用前後のノード活用率の変化を kubectl top nodes で比較します。
  2. Karpenter の NodePool に spot + on-demand の2つの capacity-type を両方書き、myshop-api Deployment に spot taint を受け入れる toleration を追加します。1週間の間 spot インスタンスが回収される頻度を測定し、回収時に Karpenter が on-demand へ fallback する形を kubectl get events で追跡します。spot のコスト削減効果と 22章 の PodDisruptionBudget が回収時点でどう保護の役割をするかを一段落で整理します。
  3. 自分の運用クラスタ (または学習クラスタ) に §「翌月の請求書 review チェックリスト」の1ページを埋めてみます。各項目に現在の値 · 先月比の変化 · 翌月の改善可能性を書き、最も大きな漏れの出所3ヶ所を優先順位として定めます。26章 の定期運用カレンダーと連携し、四半期単位でどの項目をどの時点で点検するかをカレンダーに入れておきます。

一行まとめ: K8s のコストはコンピュート 60 ~ 70 % + 付加 30 ~ 40 % の2軸であり、最も大きな漏れは over-request と NAT Gateway · AZ 間データ転送。VPA recommendation + Goldilocks / KRR で推奨値を測定し PR で人が反映するモデル、Spot + Karpenter の on-demand fallback、descheduler + bin packing のノード活用率 60 ~ 80 % 維持、gp3 + ECR lifecycle のストレージ掃除、topology-aware routing + VPC Endpoint のネットワーク削減が一つの流れで回る。OpenCost · ラベル単位の chargeback / showback でコストをチームに可視化すれば、自然に自己整理が始まる。毎月1つ2つの項目ずつだけ改善する累積モデルが持続可能な道。

次の章 #

本章でコストの要点を扱ったなら、次の章は セキュリティの要点 です。運用クラスタのセキュリティは一度のセットアップではなく定期点検の累積であり、本書の複数の章 (6章 ConfigMap · Secret、14章 RBAC / NetworkPolicy、16章 IRSA、17章 Admission Controller、23章 External Secrets) で断片的に挙げてきた要点を一つの運用マニュアルとして束ねる段階です。

29章 シークレット運用 では、K8s Secret の本質的な限界 (base64 は暗号化ではないという点) から sealed-secrets / external-secrets / SOPS の3つのオプション比較、IRSA + RDS IAM auth の「パスワード0」運用、回転 · 注入 · 監査の4軸までを一連の流れで扱います。

X