- はじめに: なぜKubernetesのコスト管理が重要なのか
- FinOpsの中核原則とKubernetesへの適用
- Kubernetesリソースの無駄の主な原因
- コスト可視性の確保: KubecostとOpenCost
- リソース最適化戦略
- オートスケーリングによるコスト削減
- イメージ最適化とストレージコストの削減
- Prometheusを活用したコストメトリクスの監視
- クラウド別のコスト最適化戦略
- 失敗事例: 過度なコスト削減によるサービス障害
- FinOps運用チェックリスト
- FinOpsのチーム文化とコスト意識
- FinOps成熟度モデル
- まとめと推奨事項
- 参考資料

はじめに: なぜKubernetesのコスト管理が重要なのか
Kubernetesの導入が加速するにつれて、クラウドコストの問題が深刻になっています。CNCFの2025 FinOpsレポートによると、企業のKubernetes関連クラウド支出のうち平均30-35%が無駄になっています。これは単にリソースを過剰に割り当てるという問題にとどまらず、コストの可視性不足、チーム間の責任の所在の不明確さ、最適化プロセスの不在という構造的な問題から生じています。
FinOps Foundationが定義するFinOpsは「エンジニアリング、財務、ビジネスの各チームがデータに基づいて協業し、クラウドのビジネス価値を最大化する運用フレームワーク」です。従来のITインフラではCapEx(資本支出)モデルで一度購入すれば終わりでしたが、クラウドはOpEx(運用支出)モデルであり、1時間ごと、1分ごとにコストが発生します。こうした環境ではFinOpsは選択ではなく必須です。
この記事ではKubernetes環境に特化したFinOps戦略を扱います。コストの可視性確保からリソース最適化、オートスケーリング、そしてチーム文化まで、実戦ですぐに適用できるガイドを提供します。
FinOpsの中核原則とKubernetesへの適用
FinOpsの3つの原則
FinOps Foundationが提示する中核原則は次のとおりです。
- チームは自らのクラウド使用量に責任を持つ - エンジニアリングチームがコストを認識し、最適化の判断を下す必要があります
- 意思決定はクラウドのビジネス価値に基づく - 単なるコスト削減ではなく、ビジネス価値に対するコスト効率を追求します
- FinOpsは中央集権型のチームが主導する - ツール、プロセス、ベストプラクティスは中央で管理し、実行は各チームが行います
Inform, Optimize, Operateサイクル
FinOpsは3段階の反復サイクルで運用されます。
| 段階 | 目標 | Kubernetesへの適用 |
|---|---|---|
| Inform | コスト可視性の確保 | Kubecost/OpenCostの導入、ネームスペース別コストダッシュボード |
| Optimize | コスト最適化の実行 | Request/Limitのチューニング、スポットインスタンス、遊休リソースの整理 |
| Operate | 継続的なガバナンス | コストアラート、定期レビュー、チーム別の予算管理 |
Kubernetesリソースの無駄の主な原因
コスト最適化を始める前に、どこで無駄が発生しているのかを正確に理解する必要があります。
1. 過剰なRequest/Limitの設定
最も多い原因です。開発者が「安全に」高い値を設定する傾向があります。
# 問題: 実際の使用量に対して過剰なリソース要求
apiVersion: v1
kind: Pod
metadata:
name: over-provisioned-app
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
cpu: '2' # 実際の使用量: 200m
memory: '4Gi' # 実際の使用量: 512Mi
limits:
cpu: '4'
memory: '8Gi'
上の例ではCPUは実際の使用量の10倍、メモリは8倍を要求しています。このPod 1つだけなら大きな問題ではありませんが、100個のPodがこの調子であれば月に数千ドルの無駄が発生します。
2. スケーリングポリシーの不在
トラフィックが減ってもPod数が減らない、あるいは夜間や週末も同じリソースが維持されている場合です。
3. 遊休リソースの放置
もう使っていないPersistentVolume、LoadBalancer Service、テスト用ネームスペースなどが整理されずに残っている場合です。
4. ノードの断片化(Fragmentation)
小さなPodが複数のノードに分散し、各ノードの利用率が下がる現象です。
# ノードごとのリソース利用率を確認
kubectl top nodes
# 結果の例
# NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
# node-1 800m 20% 2Gi 25%
# node-2 600m 15% 1.5Gi 18%
# node-3 400m 10% 1Gi 12%
# => 3つのノードすべてが利用率25%以下 - 1つのノードに統合可能
コスト可視性の確保: KubecostとOpenCost
コスト最適化の第一歩は「今いくら使っているのか」を知ることです。
OpenCostのインストールと設定
OpenCostはCNCF公式プロジェクトであり、Kubernetesのコストモニタリングのためのオープンソース標準です。Prometheusと統合され、リアルタイムのコストデータを収集します。
# HelmでOpenCostをインストール
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update
helm install opencost opencost/opencost \
--namespace opencost-system \
--create-namespace \
--set opencost.prometheus.internal.enabled=true \
--set opencost.ui.enabled=true
OpenCostのカスタム価格設定により、実際のクラウド料金を反映できます。
# opencost-custom-pricing.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: opencost-custom-pricing
namespace: opencost-system
data:
default.json: |
{
"provider": "custom",
"description": "Custom pricing for on-prem + cloud hybrid",
"CPU": "0.031611",
"spotCPU": "0.012644",
"RAM": "0.004237",
"spotRAM": "0.001694",
"storage": "0.000138888",
"GPU": "0.95"
}
kubectl apply -f opencost-custom-pricing.yaml
Kubecostのインストールと設定
KubecostはOpenCostをベースに、より豊富な機能(アラート、推奨、ガバナンス)を提供する商用/オープンソースのハイブリッドツールです。
# Kubecostのインストール (Free Tier)
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
helm repo update
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--create-namespace \
--set kubecostToken="YOUR_TOKEN" \
--set prometheus.server.persistentVolume.enabled=true \
--set prometheus.server.persistentVolume.size=32Gi
Kubecost APIを通じてプログラムからコストデータを照会できます。
# ネームスペース別のコスト照会 (直近7日)
curl -s "http://kubecost.example.com/model/allocation?window=7d&aggregate=namespace" | \
python3 -m json.tool
# 出力例 (簡略化)
# {
# "data": [{
# "production": {
# "cpuCost": 245.67,
# "ramCost": 123.45,
# "pvCost": 34.56,
# "totalCost": 403.68
# },
# "staging": {
# "cpuCost": 89.12,
# "ramCost": 45.23,
# "pvCost": 12.34,
# "totalCost": 146.69
# }
# }]
# }
コストモニタリングツールの比較表
| 機能 | OpenCost | Kubecost Free | Kubecost Enterprise | CloudHealth | Spot.io (NetApp) |
|---|---|---|---|---|---|
| ライセンス | オープンソース (Apache 2.0) | 無料 (15日分のデータ) | 商用 | 商用 | 商用 |
| CNCFプロジェクト | O | X (ベース) | X | X | X |
| リアルタイムのコスト監視 | O | O | O | O | O |
| ネームスペース別のコスト | O | O | O | O | O |
| コスト削減の推奨 | X | 基本 | 高度 | 高度 | 高度 |
| マルチクラスタ対応 | O (手動) | X | O | O | O |
| 通知/アラート | X | 基本 | 高度 | 高度 | 高度 |
| スポットインスタンス管理 | X | X | X | X | O |
| データ保持期間 | Prometheus依存 | 15日 | 無制限 | 無制限 | 無制限 |
| コスト配分の精度 | 高い | 高い | 非常に高い | 高い | 高い |
| インストールの難易度 | 低い | 低い | 中程度 | 高い | 中程度 |
| 月額コスト | 無料 | 無料 | クラスタ単位 | 応相談 | 削減額の% |
推奨事項: 小規模チームはOpenCostから始め、コスト規模が大きくなったらKubecost EnterpriseやSpot.ioへ移行するのが現実的です。FinOps Foundationのメンバー企業のうち68%がこの経路をたどりました。
リソース最適化戦略
Request/Limitのチューニング: VPAを活用した自動適正化
Vertical Pod Autoscaler(VPA)は実際のリソース使用パターンを分析し、適切なRequest/Limit値を推奨します。
# vpa-recommendation.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: app-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
updatePolicy:
updateMode: 'Off' # 推奨を受け取るだけで自動適用はしない (安全)
resourcePolicy:
containerPolicies:
- containerName: app
minAllowed:
cpu: '100m'
memory: '128Mi'
maxAllowed:
cpu: '2'
memory: '4Gi'
controlledResources: ['cpu', 'memory']
# VPAの推奨値を確認
kubectl describe vpa app-vpa -n production
# 出力例
# Recommendation:
# Container Recommendations:
# Container Name: app
# Lower Bound:
# Cpu: 150m
# Memory: 256Mi
# Target:
# Cpu: 250m
# Memory: 512Mi
# Uncapped Target:
# Cpu: 250m
# Memory: 512Mi
# Upper Bound:
# Cpu: 800m
# Memory: 1Gi
注意事項: VPAの updateMode: "Auto" はPodを再起動します。Kubernetes 1.33から対応するIn-Place Resource Resize(KEP-1287)を活用すれば、再起動なしでリソースを調整できます。本番環境では必ず "Off" モードで開始し、推奨値を検討したうえで段階的に適用する必要があります。
ネームスペース別のResourceQuota
チームごとのリソース使用量を制限し、コストの暴走を防ぎます。
# resourcequota-team-backend.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-backend-quota
namespace: team-backend
spec:
hard:
requests.cpu: '20'
requests.memory: '40Gi'
limits.cpu: '40'
limits.memory: '80Gi'
persistentvolumeclaims: '10'
services.loadbalancers: '2'
pods: '50'
# コストの観点: LoadBalancerサービス数を制限 (AWSではそれぞれ月$18程度)
# ResourceQuotaの使用状況を確認
kubectl describe resourcequota team-backend-quota -n team-backend
# 出力例
# Name: team-backend-quota
# Namespace: team-backend
# Resource Used Hard
# -------- ---- ----
# limits.cpu 12 40
# limits.memory 24Gi 80Gi
# persistentvolumeclaims 3 10
# pods 15 50
# requests.cpu 6 20
# requests.memory 12Gi 40Gi
# services.loadbalancers 1 2
LimitRangeの設定
個々のPod/Container単位でデフォルトのリソース値と範囲を強制します。
# limitrange-default.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-backend
spec:
limits:
- type: Container
default: # Limitのデフォルト値 (Request未設定時に自動適用)
cpu: '500m'
memory: '512Mi'
defaultRequest: # Requestのデフォルト値
cpu: '100m'
memory: '128Mi'
min: # 最小値 (これ未満は不可)
cpu: '50m'
memory: '64Mi'
max: # 最大値 (これを超えるのは不可)
cpu: '4'
memory: '8Gi'
- type: PersistentVolumeClaim
min:
storage: '1Gi'
max:
storage: '100Gi' # PVCの最大サイズ制限
kubectl apply -f limitrange-default.yaml
# Request/Limit未設定のPodを作成すると自動的にデフォルト値が適用される
kubectl run test-pod --image=nginx -n team-backend
kubectl describe pod test-pod -n team-backend | grep -A 5 "Limits\|Requests"
ノードプールの最適化: スポット/プリエンプティブルインスタンスの活用
スポットインスタンスはOn-Demandに比べて60-90%安価ですが、クラウドプロバイダーがいつでも回収できます。正しく活用すればKubernetesのコストを劇的に減らせます。
# spot-tolerant-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: batch-processor
template:
metadata:
labels:
app: batch-processor
spec:
# スポットノードにスケジューリング
nodeSelector:
node.kubernetes.io/capacity-type: spot
tolerations:
- key: 'spot'
operator: 'Equal'
value: 'true'
effect: 'NoSchedule'
# Graceful shutdown - スポットインスタンスの回収時に正常終了
terminationGracePeriodSeconds: 120
containers:
- name: processor
image: batch-processor:latest
resources:
requests:
cpu: '500m'
memory: '1Gi'
limits:
cpu: '1'
memory: '2Gi'
# スポットインスタンスの回収シグナルを処理
lifecycle:
preStop:
exec:
command: ['/bin/sh', '-c', 'kill -SIGTERM 1 && sleep 90']
# Pod Disruption Budgetの設定
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: batch-processor
スポットインスタンスに適したワークロードと適さないワークロードの比較
| 適したワークロード | 適さないワークロード |
|---|---|
| バッチ処理/データ処理 | 単一インスタンスのデータベース |
| CI/CDパイプライン | ステートフルサービス (Kafka, Redis) |
| ステートレスWebサーバー (複数レプリカ) | 長時間実行のトランザクション |
| 開発/テスト環境 | リアルタイムのストリーミング処理 |
| ML学習 (チェックポイント対応) | リーダー選出ベースのサービス |
オートスケーリングによるコスト削減
Karpenter: 次世代のノードプロビジョニング
KarpenterはAWSで始まり、現在はマルチクラウドへ拡張中のノードプロビジョナーです。Cluster Autoscalerより高速で柔軟なノード管理を提供します。
# karpenter-nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: cost-optimized
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ['amd64']
- key: karpenter.sh/capacity-type
operator: In
values: ['spot', 'on-demand'] # スポット優先、失敗時はOn-Demand
- key: karpenter.k8s.aws/instance-category
operator: In
values: ['c', 'm', 'r'] # コンピューティング/汎用/メモリ最適化
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ['5'] # 第6世代以上のみ (コストパフォーマンスが良い)
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: '100'
memory: '400Gi'
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
# 未活用のノードは30秒後に自動統合
weight: 10 # 他のNodePoolより優先して使用
# karpenter-ec2nodeclass.yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: 'al2023@latest'
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: 'my-cluster'
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: 'my-cluster'
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
encrypted: true
KarpenterとCluster Autoscalerの比較
| 項目 | Karpenter | Cluster Autoscaler |
|---|---|---|
| スケールアップ速度 | 数秒 ~ 1分 | 2-5分 |
| インスタンスタイプの選択 | 自動で最適に選択 (多様なタイプ) | ノードグループごとに固定 |
| スケールダウン | 積極的な統合 (consolidation) | 保守的 (10分以上待機) |
| スポットの扱い | ネイティブ対応、自動で切り替え | 別のノードグループが必要 |
| マルチAZ分散 | 自動 | ノードグループごとに設定 |
| ビンパッキング効率 | 高い (Podサイズに基づいて選択) | 低い (固定のノードサイズ) |
| ノード断片化の解消 | 自動 (consolidation) | 手動 |
| クラウド対応 | AWS(GA), Azure(Preview) | AWS, GCP, Azureすべて |
実践のヒント: Karpenterの
consolidationPolicy: WhenEmptyOrUnderutilizedは、リソース利用率が低いノードのPodを他のノードへ自動的に移し、そのノードを終了させます。これだけでもノードコストを20-30%削減できます (Karpenter公式ドキュメントのbest practicesを参照)。
夜間/週末のスケールダウンの自動化
非本番環境のワークロードを業務時間外に自動で縮小すれば、相当なコストを削減できます。
# cronjob-scaledown.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: nighttime-scaledown
namespace: kube-system
spec:
schedule: '0 22 * * 1-5' # 平日22時 (KST基準)
jobTemplate:
spec:
template:
spec:
serviceAccountName: scaler-sa
containers:
- name: scaler
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
# stagingネームスペースのすべてのDeploymentを0に縮小
for deploy in $(kubectl get deploy -n staging -o name); do
kubectl scale $deploy --replicas=0 -n staging
done
echo "Scaled down staging at $(date)"
restartPolicy: OnFailure
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: morning-scaleup
namespace: kube-system
spec:
schedule: '0 8 * * 1-5' # 平日08時 (KST基準)
jobTemplate:
spec:
template:
spec:
serviceAccountName: scaler-sa
containers:
- name: scaler
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
# stagingネームスペースのDeploymentを元の状態に復旧
kubectl scale deploy/api-server --replicas=3 -n staging
kubectl scale deploy/web-frontend --replicas=2 -n staging
kubectl scale deploy/worker --replicas=2 -n staging
echo "Scaled up staging at $(date)"
restartPolicy: OnFailure
イメージ最適化とストレージコストの削減
コンテナイメージの最適化
コンテナイメージのサイズが大きいと、レジストリの保存コスト、イメージのプル時間、ネットワーク転送コストがいずれも増加します。
# Bad: 巨大なイメージ (1.2GB+)
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
# Good: マルチステージビルドで最適化 (150MB以下)
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
CMD ["dist/server.js"]
未使用のPersistentVolumeの整理
放置されたPV/PVCはクラウドストレージのコストを継続的に発生させます。
# Released状態のPVを確認 (バインドが解除されたが削除されていないPV)
kubectl get pv --field-selector=status.phase=Released
# 使用されていないPVCを探す (どのPodにもマウントされていないPVC)
kubectl get pvc --all-namespaces -o json | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
for pvc in data['items']:
ns = pvc['metadata']['namespace']
name = pvc['metadata']['name']
phase = pvc['status'].get('phase', 'Unknown')
if phase == 'Bound':
print(f'{ns}/{name} - Bound but check if any pod uses it')
"
# 特定のPVCを使用しているPodがあるかを確認
kubectl get pods --all-namespaces -o json | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
used_pvcs = set()
for pod in data['items']:
volumes = pod['spec'].get('volumes', [])
for vol in volumes:
if 'persistentVolumeClaim' in vol:
ns = pod['metadata']['namespace']
pvc_name = vol['persistentVolumeClaim']['claimName']
used_pvcs.add(f'{ns}/{pvc_name}')
for pvc in sorted(used_pvcs):
print(f'IN USE: {pvc}')
"
遊休リソース検出スクリプト
#!/bin/bash
# idle-resource-detector.sh
# 遊休リソースを検出してコスト削減の機会を見つけるスクリプト
echo "=== 遊休リソース検出レポート ==="
echo "日付: $(date)"
echo ""
# 1. レプリカ0なのに削除されていないDeployment
echo "--- レプリカ0のDeployment ---"
kubectl get deploy --all-namespaces -o json | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
for d in data['items']:
if d['spec'].get('replicas', 1) == 0:
print(f\" {d['metadata']['namespace']}/{d['metadata']['name']}\")
"
# 2. 7日以上Completed状態のJob
echo ""
echo "--- 完了から7日以上経過したJob ---"
kubectl get jobs --all-namespaces --field-selector=status.successful=1 \
-o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,COMPLETED:.status.completionTime"
# 3. 外部トラフィックのないLoadBalancer Service
echo ""
echo "--- LoadBalancerタイプのService (コスト発生中) ---"
kubectl get svc --all-namespaces --field-selector=spec.type=LoadBalancer \
-o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,EXTERNAL-IP:.status.loadBalancer.ingress[0].hostname"
Prometheusを活用したコストメトリクスの監視
OpenCostとPrometheusを連携すれば、Grafanaダッシュボードでリアルタイムのコスト推移を監視できます。
# prometheus-cost-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cost-alerts
namespace: monitoring
spec:
groups:
- name: cost-optimization
rules:
# CPU要求に対する実使用率が20%以下のコンテナを検出
- alert: LowCPUUtilization
expr: |
(
sum(rate(container_cpu_usage_seconds_total[5m])) by (namespace, pod, container)
/
sum(kube_pod_container_resource_requests{resource="cpu"}) by (namespace, pod, container)
) < 0.2
for: 24h
labels:
severity: warning
category: cost
annotations:
summary: 'CPU使用率がRequestの20%未満'
description: '{{ `{{ $labels.namespace }}` }}/{{ `{{ $labels.pod }}` }}のCPU使用率が24時間にわたりRequestの20%未満です。Request値の引き下げを推奨します。'
# メモリ要求に対する実使用率が30%以下のコンテナを検出
- alert: LowMemoryUtilization
expr: |
(
sum(container_memory_working_set_bytes) by (namespace, pod, container)
/
sum(kube_pod_container_resource_requests{resource="memory"}) by (namespace, pod, container)
) < 0.3
for: 24h
labels:
severity: warning
category: cost
annotations:
summary: 'メモリ使用率がRequestの30%未満'
description: '{{ `{{ $labels.namespace }}` }}/{{ `{{ $labels.pod }}` }}のメモリ使用率が24時間にわたりRequestの30%未満です。'
# ネームスペース別の日次コストがしきい値を超過
- alert: NamespaceCostThresholdExceeded
expr: |
sum(
sum_over_time(opencost_container_cost_cpu_hourly[24h]) +
sum_over_time(opencost_container_cost_memory_hourly[24h])
) by (namespace) > 100
labels:
severity: critical
category: cost
annotations:
summary: 'ネームスペースの日次コストがしきい値を超過'
description: '{{ `{{ $labels.namespace }}` }} ネームスペースの日次コストが100 USDを超えました。'
クラウド別のコスト最適化戦略
AWS EKSのコスト最適化
# AWS Savings Plansの推奨を照会
aws ce get-savings-plans-purchase-recommendation \
--savings-plans-type COMPUTE_SP \
--term-in-years ONE_YEAR \
--payment-option NO_UPFRONT \
--lookback-period-in-days SIXTY_DAYS
# EKSノードに対するReserved Instanceの推奨
aws ce get-reservation-purchase-recommendation \
--service "Amazon Elastic Compute Cloud - Compute" \
--lookback-period-in-days SIXTY_DAYS
GCP GKEのコスト最適化
# GKEのコスト推奨を確認
gcloud recommender recommendations list \
--recommender=google.compute.instance.MachineTypeRecommender \
--project=my-project \
--location=asia-northeast3-a \
--format="table(content.overview.resourceName, content.overview.recommendedMachineType.name, primaryImpact.costProjection.cost.units)"
# Committed Use Discountsを確認
gcloud compute commitments list --project=my-project
マルチクラウドのコスト比較
| 項目 | AWS EKS | GCP GKE | Azure AKS |
|---|---|---|---|
| コントロールプレーン費用 | 月 $73 (クラスタ単位) | 無料 (Standard) / 月 $73 (Enterprise) | 無料 (Standard) / 月 $73 (Premium) |
| スポット割引率 | 60-90% | 60-91% | 60-90% |
| スポットの最小保証 | 2分前の警告 | 30秒前の警告 | 30秒前の警告 |
| Savings Plan/CUD | Compute Savings Plans | Committed Use Discounts | Azure Reservations |
| 最大のコミット割引 | 72% (3年全額前払い) | 70% (3年コミット) | 72% (3年予約) |
| オートパイロット/サーバーレス | Fargate | GKE Autopilot | AKS Virtual Nodes |
失敗事例: 過度なコスト削減によるサービス障害
事例1: スポットインスタンス100%運用による大規模障害
あるスタートアップでは、コスト削減を最大化するために本番クラスタのすべてのノードをスポットインスタンスで運用しました。
発生した状況:
- AWSが特定のリージョンでスポット容量を大規模に回収
- 全ノードの70%が同時に終了
- Podをスケジューリングできるノードがなくなり、サービスの全面障害が発生
- On-Demandノードのプロビジョニングまで8分を要した
教訓:
- 本番の中核サービスは必ずOn-Demandノードに配置する
- スポットの比率は全体の70%を超えないようにする
- PodDisruptionBudgetで最小可用Pod数を保証する
# 必須: PodDisruptionBudgetの設定
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
minAvailable: 2 # 最低2つのPodは常に維持
selector:
matchLabels:
app: api-server
事例2: リソースRequestを低く設定しすぎてOOMが頻発
コスト最適化の過程ですべてのPodのメモリRequestを実使用量に合わせてタイトに設定した結果、トラフィックのピーク時にOOMKilledが頻発しました。
教訓:
- RequestはP99使用量ではなくP95使用量 + 20%のバッファで設定する
- LimitはRequestの1.5-2倍に設定してバーストの余裕を持たせる
- VPAの推奨値に無条件に従わず、トラフィックパターンを考慮して調整する
事例3: テスト環境の削除による開発生産性の低下
コスト削減のために夜間はすべての開発/テスト環境を削除する方針を実施しましたが、海外チームとの時差により協業に深刻な問題が発生しました。
教訓:
- コスト削減と開発者体験(DX)のバランスが必要
- 完全な削除ではなくスケールダウン(replicas=0)のほうが安全
- グローバルチームの場合は各タイムゾーンを考慮したスケジュール設定
FinOps運用チェックリスト
週次コストレビューのチェックリスト
| チェック項目 | 担当 | ツール |
|---|---|---|
| ネームスペース別のコスト推移を確認 | FinOpsチーム | Kubecost/OpenCost |
| CPU/メモリ利用率20%以下のPodリスト | SRE | Prometheus/Grafana |
| スポットインスタンス比率の確認 (目標: 50-70%) | Infra | クラウドコンソール |
| 未使用のPV/PVCの整理 | 開発チーム | kubectlスクリプト |
| LoadBalancer Service数の確認 | Infra | kubectl |
| イメージレジストリの古いタグの整理 | DevOps | レジストリAPI |
月次コストレビューのチェックリスト
| チェック項目 | 担当 | ツール |
|---|---|---|
| 前月比のコスト増減の分析 | FinOpsチーム | クラウドのBilling |
| Savings Plan/CUDのカバレッジ確認 | FinOpsチーム | クラウドコンソール |
| VPA推奨値に基づくRequest/Limitの更新 | 開発チーム | VPA Recommender |
| ノードのインスタンスタイプ最適化の検討 | Infra | Karpenterのログ |
| チーム別コスト配分レポートの共有 | FinOpsチーム | Kubecost |
| コスト異常値(anomaly)の原因分析 | SRE | クラウドのCost Explorer |
四半期ごとのコストレビューのチェックリスト
| チェック項目 | 担当 | ツール |
|---|---|---|
| Reserved Instance/CUDの更新検討 | FinOpsチーム | クラウドコンソール |
| アーキテクチャレベルのコスト最適化 (マイクロサービス統合など) | アーキテクト | 設計レビュー |
| コスト予測モデルの更新 | FinOpsチーム | スプレッドシート/BI |
| FinOps成熟度の自己評価 | FinOpsチーム | FinOps Foundationのフレームワーク |
FinOpsのチーム文化とコスト意識
コストタグ戦略
すべてのKubernetesリソースに一貫したタグ(label)を付与し、コストを正確に追跡できるようにする必要があります。
# コスト追跡のための標準Label定義
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
labels:
app.kubernetes.io/name: api-server
app.kubernetes.io/part-of: payment-platform
# FinOpsのコストタグ
cost-center: 'engineering'
team: 'backend'
env: 'production'
project: 'payment-v2'
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: api-server
template:
metadata:
labels:
app.kubernetes.io/name: api-server
cost-center: 'engineering'
team: 'backend'
env: 'production'
project: 'payment-v2'
spec:
containers:
- name: api-server
image: api-server:v2.1.0
resources:
requests:
cpu: '500m'
memory: '1Gi'
limits:
cpu: '1'
memory: '2Gi'
OPA/Gatekeeperでコストタグを強制する
# cost-label-constraint.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredcostlabels
spec:
crd:
spec:
names:
kind: K8sRequiredCostLabels
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredcostlabels
required_labels := {"cost-center", "team", "env", "project"}
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
missing := required_labels - provided
count(missing) > 0
msg := sprintf("必須のコストタグが不足しています: %v", [missing])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredCostLabels
metadata:
name: require-cost-labels
spec:
match:
kinds:
- apiGroups: ['apps']
kinds: ['Deployment', 'StatefulSet', 'DaemonSet']
namespaces: ['production', 'staging']
コスト意識の文化づくり
FinOpsはツールだけでは成功しません。チーム全体がコストを意識し、最適化に参加する文化が必要です。
コスト意識の文化の中核要素:
- コストの可視性: 毎週チーム別のコストダッシュボードを共有します
- コストの責任: 各チームが自分のネームスペースのコストに責任を持ちます
- インセンティブ: コスト削減の事例を共有し、認める文化をつくります
- 教育: 開発者がリソースのRequest/Limitの意味と影響を理解する必要があります
- 自動化: 手作業を減らし、ポリシーに基づく自動最適化を目指します
FinOps成熟度モデル
FinOps Foundationは組織のFinOps成熟度を3段階で定義しています。
| 段階 | 特徴 | Kubernetesの指標 |
|---|---|---|
| Crawl (開始) | 基本的なコスト可視性の確保、手動での最適化 | OpenCostの導入、月次コストレビューの開始 |
| Walk (発展) | チーム別のコスト配分、自動化の開始 | Kubecostのアラート、VPA推奨の適用、スポット50%+ |
| Run (成熟) | リアルタイム最適化、コスト予測、文化の定着 | Karpenterの自動統合、コスト予測モデル、FinOpsチームの運営 |
まとめと推奨事項
すぐに実行できるQuick Win
- OpenCostのインストール (1時間): コスト可視性を即座に確保
- VPA推奨モードのデプロイ (30分): リソース適正化データの収集を開始
- 未使用リソースの整理 (2時間): Released PV、空のネームスペース、古いJobを削除
- LimitRangeの適用 (1時間): Request未設定のPodにデフォルト値を強制
中期の最適化 (1-3か月)
- Kubecostまたは商用ツールを導入してチーム別のコスト配分を開始
- スポットインスタンスの導入 (非本番から始め、段階的に本番へ拡大)
- Karpenterを導入してノードの自動統合を有効化
- 夜間/週末のスケールダウンCronJobをデプロイ
長期戦略 (3-12か月)
- FinOps専任チームまたは役割の構成
- Savings Plan/CUDの最適化
- コスト予測モデルの構築 (過去データに基づく)
- コスト意識の文化の定着 (教育、ダッシュボード、インセンティブ)
Kubernetesのコスト最適化は一度きりのプロジェクトではなく、継続的なプロセスです。FinOpsのInform-Optimize-Operateサイクルを繰り返しながら、段階的に成熟度を高めていくことが核心です。最も重要な第一歩は「今いくら使っているのかを知ること」です。OpenCostをインストールし、この記事のチェックリストに沿って最初のコストレビューを始めてみてください。
参考資料
- FinOps Foundation - Kubernetes Best Practices - FinOpsのKubernetes適用ガイドライン
- Kubecost Documentation - Kubecostのインストール、設定、API活用方法
- OpenCost - CNCF Project - OpenCostのアーキテクチャとインストールガイド
- AWS EKS Best Practices - Cost Optimization - AWS EKSコスト最適化の公式ガイド
- Karpenter Documentation - Best Practices - Karpenterのノードプロビジョニングと統合戦略
- GCP GKE Cost Optimization - GKEコスト最適化の公式ドキュメント
- Azure AKS Cost Optimization - AKSコスト管理のベストプラクティス