LabHub

ブログ

Kubernetes FinOpsとクラウドコスト最適化

한국어English日本語

Kubernetes 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が提示する中核原則は次のとおりです。

  1. チームは自らのクラウド使用量に責任を持つ - エンジニアリングチームがコストを認識し、最適化の判断を下す必要があります
  2. 意思決定はクラウドのビジネス価値に基づく - 単なるコスト削減ではなく、ビジネス価値に対するコスト効率を追求します
  3. 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
#     }
#   }]
# }

コストモニタリングツールの比較表

機能OpenCostKubecost FreeKubecost EnterpriseCloudHealthSpot.io (NetApp)
ライセンスオープンソース (Apache 2.0)無料 (15日分のデータ)商用商用商用
CNCFプロジェクトOX (ベース)XXX
リアルタイムのコスト監視OOOOO
ネームスペース別のコストOOOOO
コスト削減の推奨X基本高度高度高度
マルチクラスタ対応O (手動)XOOO
通知/アラートX基本高度高度高度
スポットインスタンス管理XXXXO
データ保持期間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の比較

項目KarpenterCluster 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 --from=builder /app/dist ./dist
COPY --from=builder /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 EKSGCP GKEAzure AKS
コントロールプレーン費用月 $73 (クラスタ単位)無料 (Standard) / 月 $73 (Enterprise)無料 (Standard) / 月 $73 (Premium)
スポット割引率60-90%60-91%60-90%
スポットの最小保証2分前の警告30秒前の警告30秒前の警告
Savings Plan/CUDCompute Savings PlansCommitted Use DiscountsAzure Reservations
最大のコミット割引72% (3年全額前払い)70% (3年コミット)72% (3年予約)
オートパイロット/サーバーレスFargateGKE AutopilotAKS Virtual Nodes

失敗事例: 過度なコスト削減によるサービス障害

事例1: スポットインスタンス100%運用による大規模障害

あるスタートアップでは、コスト削減を最大化するために本番クラスタのすべてのノードをスポットインスタンスで運用しました。

発生した状況:

教訓:

# 必須: 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が頻発しました。

教訓:

事例3: テスト環境の削除による開発生産性の低下

コスト削減のために夜間はすべての開発/テスト環境を削除する方針を実施しましたが、海外チームとの時差により協業に深刻な問題が発生しました。

教訓:

FinOps運用チェックリスト

週次コストレビューのチェックリスト

チェック項目担当ツール
ネームスペース別のコスト推移を確認FinOpsチームKubecost/OpenCost
CPU/メモリ利用率20%以下のPodリストSREPrometheus/Grafana
スポットインスタンス比率の確認 (目標: 50-70%)Infraクラウドコンソール
未使用のPV/PVCの整理開発チームkubectlスクリプト
LoadBalancer Service数の確認Infrakubectl
イメージレジストリの古いタグの整理DevOpsレジストリAPI

月次コストレビューのチェックリスト

チェック項目担当ツール
前月比のコスト増減の分析FinOpsチームクラウドのBilling
Savings Plan/CUDのカバレッジ確認FinOpsチームクラウドコンソール
VPA推奨値に基づくRequest/Limitの更新開発チームVPA Recommender
ノードのインスタンスタイプ最適化の検討InfraKarpenterのログ
チーム別コスト配分レポートの共有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はツールだけでは成功しません。チーム全体がコストを意識し、最適化に参加する文化が必要です。

コスト意識の文化の中核要素:

  1. コストの可視性: 毎週チーム別のコストダッシュボードを共有します
  2. コストの責任: 各チームが自分のネームスペースのコストに責任を持ちます
  3. インセンティブ: コスト削減の事例を共有し、認める文化をつくります
  4. 教育: 開発者がリソースのRequest/Limitの意味と影響を理解する必要があります
  5. 自動化: 手作業を減らし、ポリシーに基づく自動最適化を目指します

FinOps成熟度モデル

FinOps Foundationは組織のFinOps成熟度を3段階で定義しています。

段階特徴Kubernetesの指標
Crawl (開始)基本的なコスト可視性の確保、手動での最適化OpenCostの導入、月次コストレビューの開始
Walk (発展)チーム別のコスト配分、自動化の開始Kubecostのアラート、VPA推奨の適用、スポット50%+
Run (成熟)リアルタイム最適化、コスト予測、文化の定着Karpenterの自動統合、コスト予測モデル、FinOpsチームの運営

まとめと推奨事項

すぐに実行できるQuick Win

  1. OpenCostのインストール (1時間): コスト可視性を即座に確保
  2. VPA推奨モードのデプロイ (30分): リソース適正化データの収集を開始
  3. 未使用リソースの整理 (2時間): Released PV、空のネームスペース、古いJobを削除
  4. LimitRangeの適用 (1時間): Request未設定のPodにデフォルト値を強制

中期の最適化 (1-3か月)

  1. Kubecostまたは商用ツールを導入してチーム別のコスト配分を開始
  2. スポットインスタンスの導入 (非本番から始め、段階的に本番へ拡大)
  3. Karpenterを導入してノードの自動統合を有効化
  4. 夜間/週末のスケールダウンCronJobをデプロイ

長期戦略 (3-12か月)

  1. FinOps専任チームまたは役割の構成
  2. Savings Plan/CUDの最適化
  3. コスト予測モデルの構築 (過去データに基づく)
  4. コスト意識の文化の定着 (教育、ダッシュボード、インセンティブ)

Kubernetesのコスト最適化は一度きりのプロジェクトではなく、継続的なプロセスです。FinOpsのInform-Optimize-Operateサイクルを繰り返しながら、段階的に成熟度を高めていくことが核心です。最も重要な第一歩は「今いくら使っているのかを知ること」です。OpenCostをインストールし、この記事のチェックリストに沿って最初のコストレビューを始めてみてください。

参考資料

コメント

まだコメントはありません。

ログインするとコメントできます