K8sクラウドコスト急増?2026年FinOps実践:5つの戦略でKubernetesコストを60%削減

DevOps

K8sクラウドコスト急増?2026年FinOps実践:5つの戦略でKubernetesコストを60%削減

月末にクラウドの請求書を確認すると、K8s費用がまた予算を30%超過?CPU利用率が15%しかないのに100%分課金?Spotインスタンスが回収されてサービス断絶?これらは2026年のクラウドネイティブチームで日常茶飯事です。FinOpsは単なる節約ではなく、クラウドの支出1円がビジネス価値を生むようにする実践です。本記事では5つの実践的戦略でK8sコストを60%削減します。


背景知識:FinOpsフレームワーク

FinOps(Financial Operations)は、クラウド消費に財務アカウンタビリティを導入する実践フレームワークです:

フェーズ 目標 主要アクション 担当
Inform(可視化) コストの可視化 タグガバナンス、コスト割り当て、請求書分析 FinOpsチーム
Optimize(最適化) 無駄の削減 右sizing、Spotインスタンス、予約インスタンス エンジニアリングチーム
Operate(運用) 継続制御 予算アラート、オートスケーリング、ポリシー実行 プラットフォームチーム

問題分析:K8sコストの無駄はどこから?

無駄の原因 割合 典型的シナリオ
リソース過剰割り当て(request >> 実使用量) 40% CPU request 2コア、実際は0.3コアのみ使用
アイドルリソース(トラフィックなしでも稼働) 25% 開発環境24h稼働、業務後トラフィックなし
Spot/Preemptible未使用 20% 全PodがOn-Demandインスタンス
オートスケーリング未設定 10% HPA未設定、オフピーク時にリソース未解放
ストレージの無駄 5% PVC過大、未クリーンアップのログとイメージ

戦略1:リソース右sizing

ステップ1:metrics-serverとkube-resource-reportのインストール

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

helm install kube-resource-report helm/kube-resource-report \
  --set prometheus.url=http://prometheus:9090

ステップ2:リソース利用率の分析

# 全Podのリソース使用率を確認
kubectl top pods -A --sort-by=cpu

# kubectl-resource-capacityでrequestと実使用量を比較
kubectl resource-capacity --sort cpu.request --pods

ステップ3:自動右sizingレコメンデーション

apiVersion: apps.kubecost.com/v1beta1
kind: RightSizingRecommendation
metadata:
  name: all-deployments
spec:
  targetRef:
    kind: Deployment
    name: order-service
  current:
    requests:
      cpu: "2"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "8Gi"
  recommended:
    requests:
      cpu: "250m"
      memory: "512Mi"
    limits:
      cpu: "500m"
      memory: "1Gi"
  savingsPercent: 85

ステップ4:P99ベースの右sizingスクリプト

# right_sizing.py
import subprocess
import json
from datetime import datetime, timedelta

def get_pod_metrics(namespace: str, days: int = 7) -> dict:
    """获取过去N天的Pod资源使用P99"""
    end_time = datetime.now()
    start_time = end_time - timedelta(days=days)

    query = f'sum(rate(container_cpu_usage_seconds_total{{namespace="{namespace}"}}[5m])) by (pod)'
    result = subprocess.run([
        'kubectl', 'get', '--raw',
        f'/apis/metrics.k8s.io/v1beta1/namespaces/{namespace}/pods'
    ], capture_output=True, text=True)

    pods = json.loads(result.stdout)
    metrics = {}
    for item in pods.get('items', []):
        pod_name = item['metadata']['name']
        containers = item['containers']
        total_cpu = 0
        total_mem = 0
        for c in containers:
            usage = c.get('usage', {})
            cpu_str = usage.get('cpu', '0m')
            mem_str = usage.get('memory', '0Ki')
            total_cpu += parse_cpu(cpu_str)
            total_mem += parse_memory(mem_str)
        metrics[pod_name] = {'cpu_millicores': total_cpu, 'memory_mib': total_mem}

    return metrics

def parse_cpu(s: str) -> int:
    if s.endswith('m'):
        return int(s[:-1])
    return int(float(s) * 1000)

def parse_memory(s: str) -> int:
    if s.endswith('Ki'):
        return int(s[:-2]) // 1024
    if s.endswith('Mi'):
        return int(s[:-2])
    if s.endswith('Gi'):
        return int(s[:-2]) * 1024
    return int(s) // (1024 * 1024)

def generate_recommendations(metrics: dict, buffer_percent: int = 20) -> list:
    """生成右sizing推荐,加buffer_percent的缓冲"""
    recommendations = []
    for pod, usage in metrics.items():
        cpu_rec = int(usage['cpu_millicores'] * (1 + buffer_percent / 100))
        mem_rec = int(usage['memory_mib'] * (1 + buffer_percent / 100))
        recommendations.append({
            'pod': pod,
            'recommended_cpu': f'{cpu_rec}m',
            'recommended_memory': f'{mem_rec}Mi',
            'current_cpu': usage['cpu_millicores'],
            'current_memory': usage['memory_mib'],
        })
    return recommendations

if __name__ == '__main__':
    metrics = get_pod_metrics('production')
    recs = generate_recommendations(metrics, buffer_percent=20)
    for r in recs:
        print(f"{r['pod']}: CPU {r['recommended_cpu']}, Memory {r['recommended_memory']}")

戦略2:Spot/Preemptibleインスタンス

# spot-node-pool.yaml
apiVersion: v1
kind: Node
metadata:
  name: spot-pool
  labels:
    cloud.google.com/gke-provisioning: spot
    node-type: spot
  annotations:
    cloud.google.com/spot: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: batch-processor
spec:
  replicas: 5
  selector:
    matchLabels:
      app: batch-processor
  template:
    metadata:
      labels:
        app: batch-processor
    spec:
      nodeSelector:
        node-type: spot
      tolerations:
      - key: "cloud.google.com/gke-provisioning"
        operator: "Equal"
        value: "spot"
        effect: "NoSchedule"
      containers:
      - name: processor
        image: myapp/batch-processor:latest
        resources:
          requests:
            cpu: "500m"
            memory: "1Gi"
      terminationGracePeriodSeconds: 60

Spotインスタンス中断処理

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spot-aware-service
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: spot-aware-service

戦略3:クラスターオートスケーリング

# cluster-autoscaler.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cluster-autoscaler
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: cluster-autoscaler
  template:
    spec:
      containers:
      - image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.30.0
        name: cluster-autoscaler
        command:
        - ./cluster-autoscaler
        - --scale-down-delay-after-add=5m
        - --scale-down-unneeded-time=5m
        - --scale-down-utilization-threshold=0.5
        - --max-nodes-total=50
        - --min-nodes-total=3
        - --balance-similar-node-groups
        - --expander=priority
        env:
        - name: CA_SKIP_NODES_WITH_LOCAL_STORAGE
          value: "false"

HPA + VPAの組み合わせ

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-gateway-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-gateway
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 100
        periodSeconds: 30
---
apiVersion: autoscaling.k8s.io/v1beta2
kind: VerticalPodAutoscaler
metadata:
  name: api-gateway-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-gateway
  updatePolicy:
    updateMode: Auto
  resourcePolicy:
    containerPolicies:
    - containerName: '*'
      minAllowed:
        cpu: 100m
        memory: 128Mi
      maxAllowed:
        cpu: "2"
        memory: 4Gi

戦略4:コスト監視とアラート

# kubecost-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: kubecost-cost-model
  namespace: kubecost
data:
  cost-model.json: |
    {
      "clusterName": "production",
      "defaultCPUPrice": "0.031611",
      "defaultRAMPrice": "0.004237",
      "spotLabel": "cloud.google.com/gke-provisioning",
      "spotLabelValue": "spot",
      "spotDiscount": 0.6
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cost-alerts
data:
  alerts.json: |
    [
      {
        "name": "daily-budget-alert",
        "type": "budget",
        "threshold": 500,
        "window": "1d",
        "aggregation": "cluster",
        "notification": {
          "type": "slack",
          "channel": "#finops-alerts"
        }
      },
      {
        "name": "namespace-spike-alert",
        "type": "spendChange",
        "threshold": 0.3,
        "window": "7d",
        "baselineWindow": "7d",
        "aggregation": "namespace",
        "notification": {
          "type": "email",
          "email": "finops-team@company.com"
        }
      }
    ]

戦略5:開発環境のスケジュールスケーリング

# dev-namespace-schedule.yaml
apiVersion: zalando.org/v1
kind: ScheduleSwitch
metadata:
  name: dev-environment-schedule
  namespace: dev
spec:
  switches:
  - startTime: "0 8 * * 1-5"
    endTime: "0 20 * * 1-5"
    replicas: 1
    description: "平日8:00-20:00稼働"
  - startTime: "0 20 * * 1-5"
    endTime: "0 8 * * 1-5"
    replicas: 0
    description: "業務時間外は0にスケールダウン"
  - startTime: "0 0 * * 0,6"
    endTime: "0 0 * * 1"
    replicas: 0
    description: "週末は0にスケールダウン"

落とし穴ガイド

No. 落とし穴 症状 解決策
1 VPA AutoモードでPodが頻繁再起動 サービス可用性低下 まずOffモードで推奨値を観察し、確認後にAutoに切替
2 Spotインスタンス回収でPodが一斉中断 サービス大範囲503 topologySpreadConstraintsでAZ分散、preStopでgraceful shutdown
3 HPAとVPAが同時作用で競合 レプリカ数とリソース量が振動 HPAはレプリカ数、VPAはリソース量を管理、同一指標で併用禁止
4 Cluster Autoscalerが重要ノードを縮小 ステートフルPodが退避 重要ノードに cluster-autoscaler.kubernetes.io/safe-to-evict: "false" アノテーションを付与
5 コスト割り当てタグの不整合 チーム/プロジェクト別のコスト集計不可 タグポリシーを確立し、Kyverno/OPAで強制実行

エラートラブルシューティング

エラーメッセージ 原因 解決方法
metrics-server: no metrics available metrics-server未インストールまたは未Ready metrics-serverをインストール、kubectl top nodes で確認
VPA recommender: OOMKilled VPA Recommenderのメモリ不足 VPA recommenderのmemory requestを増加
HPA: unable to get metric カスタムメトリクス未登録 Prometheus AdapterとカスタムメトリクスAPIを確認
ClusterAutoscaler: node group not found ノードグループ設定エラー --nodes パラメータ形式を確認:min:max:node-group-name
Spot node: preempted Spotインスタンスがクラウド providerに回収 正常動作、十分なPodDisruptionBudgetを確保
PodDisruptionBudget: not enough replicas PDBが厳しすぎて縮小を阻止 PDBの minAvailable または maxUnavailable を調整
Kubecost: pricing data not available クラウド価格APIに到達不能 カスタム価格を設定または defaultCPUPrice/defaultRAMPrice を使用
Scale to 0: jobs still running 未完了Jobが縮小を阻止 Jobの完了を待機または activeDeadlineSeconds を設定
ResourceQuota: exceeded quota 右sizing後テナントクォータ超過 ResourceQuotaを調整またはプラットフォームチームと協調
Node: NotReady after scale-up 新ノードの初期化失敗 ノード起動スクリプトとinitコンテナを確認

高度な最適化

1. Reserved Instance / Savings Plans

プラン 割引率 柔軟性 適用ケース
On-Demand 0% 最高 一時的/バースト負荷
Spot/Preemptible 60-90% 低(回収可能性あり) ステートレス/中断可能
1年Reserved 30-40% 安定ベースライン負荷
3年Reserved 50-60% 長期コアサービス
Savings Plans 20-40% 高(インスタンス横断) ミックスドワークロード

2. マルチクラスターコスト最適化

apiVersion: kubecost.com/v1
kind: MultiClusterCost
spec:
  clusters:
  - name: us-east-prod
    apiEndpoint: https://k8s-us-east.example.com
    costWeight: 1.0
  - name: eu-west-prod
    apiEndpoint: https://k8s-eu-west.example.com
    costWeight: 0.8
  aggregation:
    byTeam: true
    byService: true
    byNamespace: true

3. スマートスケジューリング最適化

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: cost-aware-scheduler
  plugins:
    score:
      enabled:
      - name: NodeResourcesLeastAllocated
      - name: CostAwarePriority
      disabled:
      - name: NodeResourcesMostAllocated

比較分析

FinOpsツール コスト可視性 右sizing推奨 Spot対応 オープンソース 価格
Kubecost ★★★★★ ★★★★★ ★★★★ はい 無料/エンタープライズ$
CloudHealth ★★★★★ ★★★★ ★★★ いいえ クラウド支出の%
AWS Cost Explorer ★★★★ ★★★ ★★★ いいえ 無料
Prometheus + Grafana ★★★ ★★ はい 無料
Vantage ★★★★ ★★★★ ★★★ いいえ シート課金

まとめ:K8sコスト最適化は一律削減ではなく、FinOps3フェーズのシステム工程です——Informフェーズでコストを透明化し、Optimizeフェーズで右sizing+Spot+オートスケーリングで無駄を削減し、Operateフェーズでアラートとポリシーで継続制御します。5つの戦略を組み合わせると:右sizingで40%削減、Spotインスタンスで20%削減、オートスケーリングで15%削減、開発環境スケジューリングで10%削減、コスト監視でリバウンド防止5%——合計60%の削減です。2026年、FinOpsを知らないK8s運用は、請求書を見ない買い物狂と同じです。


オンラインツールおすすめ

ブラウザローカルツールを無料で試す →

#Kubernetes#FinOps#成本优化#云成本#资源调度#Spot实例#右sizing#云原生