Kubernetes OpenCost 成本監控

Kubernetes OpenCost Cost Monitoring

前言

隨著企業大規模採用 Kubernetes 進行容器化部署,雲端成本管理已成為 FinOps 團隊面臨的重大挑戰。OpenCost 是一個開源的 Kubernetes 成本監控工具,由 Kubecost 團隊開發並捐贈給 Cloud Native Computing Foundation (CNCF),現為 CNCF Sandbox 專案。本文將深入介紹如何使用 OpenCost 實現 Kubernetes 叢集的成本可視化與優化。

OpenCost 概述

OpenCost 是一個供應商中立的開源專案,專門用於測量和分配 Kubernetes 基礎設施與容器成本。它提供了即時的成本監控能力,幫助團隊了解工作負載的實際成本。

核心特性

  • 即時成本監控:提供每個 Pod、Namespace、Deployment 的即時成本數據
  • 多雲支援:支援 AWS、GCP、Azure 以及本地部署環境
  • Prometheus 原生整合:將成本指標匯出為 Prometheus 格式
  • 開放標準:基於 OpenCost Specification,確保跨平台一致性
  • 細粒度分配:支援 CPU、Memory、GPU、Storage、Network 成本分解

成本計算原理

OpenCost 透過以下方式計算成本:

  1. 資源定價:從雲端供應商 API 取得即時定價資訊
  2. 使用量追蹤:監控每個容器的實際資源使用量
  3. 成本分配:將節點成本按比例分配給運行的工作負載
1
Pod 成本 = (CPU 使用量 × CPU 單價) + (記憶體使用量 × 記憶體單價) + 儲存成本 + 網路成本

安裝與設定

前置需求

在安裝 OpenCost 之前,請確保您的環境滿足以下條件:

  • Kubernetes 叢集版本 1.20 或以上
  • Helm 3.x 已安裝
  • Prometheus 已部署(用於指標收集)
  • kubectl 已配置並可連接到叢集

使用 Helm 安裝

首先,新增 OpenCost Helm 倉庫:

1
2
3
4
5
# 新增 OpenCost Helm 倉庫
helm repo add opencost https://opencost.github.io/opencost-helm-chart

# 更新倉庫
helm repo update

建立 OpenCost 命名空間並安裝:

1
2
3
4
5
6
7
8
# 建立命名空間
kubectl create namespace opencost

# 安裝 OpenCost
helm install opencost opencost/opencost \
  --namespace opencost \
  --set opencost.prometheus.internal.enabled=true \
  --set opencost.ui.enabled=true

進階安裝配置

對於生產環境,建議使用自訂的 values.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# values.yaml
opencost:
  exporter:
    defaultClusterId: "production-cluster"
    # AWS 定價配置
    aws:
      spotDataRegion: "us-east-1"
      spotDataBucket: "my-spot-data-bucket"
    # 資源限制
    resources:
      requests:
        cpu: "100m"
        memory: "256Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"

  prometheus:
    internal:
      enabled: false
    external:
      enabled: true
      url: "http://prometheus-server.monitoring.svc.cluster.local:9090"

  ui:
    enabled: true
    ingress:
      enabled: true
      hosts:
        - host: opencost.example.com
          paths:
            - path: /
              pathType: Prefix

  # 持久化儲存
  persistence:
    enabled: true
    storageClass: "gp3"
    size: "10Gi"

使用自訂配置安裝:

1
2
3
helm install opencost opencost/opencost \
  --namespace opencost \
  -f values.yaml

驗證安裝

確認 OpenCost 元件正常運行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 檢查 Pod 狀態
kubectl get pods -n opencost

# 預期輸出
# NAME                        READY   STATUS    RESTARTS   AGE
# opencost-5f4b8c9d6-x7k2m   2/2     Running   0          2m

# 查看服務
kubectl get svc -n opencost

# 預期輸出
# NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
# opencost   ClusterIP   10.96.123.45    <none>        9003/TCP,9090/TCP   2m

存取 OpenCost UI:

1
2
3
4
# 使用 port-forward 存取 UI
kubectl port-forward -n opencost svc/opencost 9090:9090

# 在瀏覽器開啟 http://localhost:9090

成本分配模型

OpenCost 提供多種成本分配模型,以滿足不同組織的需求。

基於資源請求的分配

此模型根據 Pod 的資源請求(Requests)計算成本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 範例 Pod 配置
apiVersion: v1
kind: Pod
metadata:
  name: web-app
  labels:
    app: web
    cost-center: "engineering"
spec:
  containers:
  - name: web
    image: nginx:1.24
    resources:
      requests:
        cpu: "500m"
        memory: "256Mi"
      limits:
        cpu: "1000m"
        memory: "512Mi"

成本計算公式:

1
2
Pod 成本 = (CPU Requests / 節點 CPU 總量) × 節點 CPU 成本
         + (Memory Requests / 節點 Memory 總量) × 節點 Memory 成本

基於實際使用量的分配

此模型根據 Pod 的實際資源使用量計算成本,更精確反映真實消耗:

1
2
# 查詢實際使用量成本
curl -s "http://localhost:9090/allocation/compute?window=24h&aggregate=namespace&accumulate=true" | jq

回應範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "code": 200,
  "data": [
    {
      "name": "production",
      "properties": {
        "cluster": "production-cluster",
        "namespace": "production"
      },
      "window": {
        "start": "2026-01-05T00:00:00Z",
        "end": "2026-01-06T00:00:00Z"
      },
      "cpuCost": 12.45,
      "memoryCost": 8.32,
      "gpuCost": 0,
      "pvCost": 2.15,
      "networkCost": 1.08,
      "totalCost": 24.00
    }
  ]
}

混合分配模型

OpenCost 支援設定 CPU 和記憶體的分配權重:

1
2
3
4
5
6
# 混合模型配置
opencost:
  exporter:
    # CPU 權重 70%,記憶體權重 30%
    cpuWeight: 0.7
    memoryWeight: 0.3

閒置成本處理

叢集中未被分配的資源成本可以透過以下方式處理:

1
2
# 查詢包含閒置成本的分配
curl -s "http://localhost:9090/allocation/compute?window=24h&aggregate=namespace&idle=true" | jq

設定閒置成本分配策略:

1
2
3
4
5
6
opencost:
  exporter:
    # 將閒置成本按比例分配給所有命名空間
    shareIdle: true
    # 或指定特定命名空間承擔閒置成本
    idleByNode: false

Prometheus 整合

OpenCost 原生支援 Prometheus,可將成本指標匯出為 Prometheus 格式以便進一步分析和告警。

配置 Prometheus 抓取

在 Prometheus 配置中新增 OpenCost 作為抓取目標:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# prometheus.yml
scrape_configs:
  - job_name: 'opencost'
    honor_labels: true
    scrape_interval: 1m
    scrape_timeout: 30s
    metrics_path: /metrics
    scheme: http
    static_configs:
      - targets:
        - opencost.opencost.svc.cluster.local:9003
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        replacement: opencost

如果使用 Prometheus Operator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# ServiceMonitor 配置
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: opencost
  namespace: opencost
  labels:
    app: opencost
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: opencost
  endpoints:
    - port: http
      path: /metrics
      interval: 1m
      scrapeTimeout: 30s
  namespaceSelector:
    matchNames:
      - opencost

重要的 Prometheus 指標

OpenCost 匯出多種有用的指標:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 每個容器的 CPU 成本(每小時)
container_cpu_allocation_cost

# 每個容器的記憶體成本(每小時)
container_memory_allocation_cost

# 每個容器的 GPU 成本(每小時)
container_gpu_allocation_cost

# 節點總成本
node_total_hourly_cost

# PV 儲存成本
pv_hourly_cost

Grafana 儀表板

建立 Grafana 儀表板以視覺化成本數據:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
  "dashboard": {
    "title": "OpenCost Dashboard",
    "panels": [
      {
        "title": "Namespace 每日成本",
        "type": "timeseries",
        "targets": [
          {
            "expr": "sum(container_cpu_allocation_cost + container_memory_allocation_cost) by (namespace) * 24",
            "legendFormat": "{{namespace}}"
          }
        ]
      },
      {
        "title": "Top 10 高成本 Pod",
        "type": "table",
        "targets": [
          {
            "expr": "topk(10, sum(container_cpu_allocation_cost + container_memory_allocation_cost) by (pod, namespace))",
            "format": "table"
          }
        ]
      },
      {
        "title": "叢集總成本趨勢",
        "type": "stat",
        "targets": [
          {
            "expr": "sum(node_total_hourly_cost) * 24 * 30",
            "legendFormat": "Monthly Cost"
          }
        ]
      }
    ]
  }
}

設定成本告警

使用 Prometheus Alertmanager 設定成本告警:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# alerting-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: opencost-alerts
  namespace: monitoring
spec:
  groups:
    - name: opencost.rules
      rules:
        # 命名空間成本超過預算
        - alert: NamespaceCostExceeded
          expr: |
            sum(container_cpu_allocation_cost + container_memory_allocation_cost) by (namespace) * 24 * 30 > 1000            
          for: 1h
          labels:
            severity: warning
          annotations:
            summary: "Namespace {{ $labels.namespace }} 月成本超過 $1000"
            description: "當前預估月成本: ${{ $value | printf \"%.2f\" }}"

        # 閒置資源成本過高
        - alert: HighIdleCost
          expr: |
            (sum(node_total_hourly_cost) - sum(container_cpu_allocation_cost + container_memory_allocation_cost))
            / sum(node_total_hourly_cost) > 0.4            
          for: 6h
          labels:
            severity: warning
          annotations:
            summary: "叢集閒置成本超過 40%"
            description: "建議考慮縮減節點或增加工作負載"

        # 成本異常增長
        - alert: CostAnomalyDetected
          expr: |
            (sum(container_cpu_allocation_cost + container_memory_allocation_cost)
            - sum(container_cpu_allocation_cost + container_memory_allocation_cost offset 1d))
            / sum(container_cpu_allocation_cost + container_memory_allocation_cost offset 1d) > 0.5            
          for: 2h
          labels:
            severity: critical
          annotations:
            summary: "成本異常增長超過 50%"
            description: "過去 24 小時內成本增長異常,請檢查工作負載變化"

多叢集成本追蹤

在大型企業環境中,通常需要管理多個 Kubernetes 叢集。OpenCost 支援多叢集成本聚合與分析。

多叢集架構設計

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
                    ┌─────────────────────┐
                    │   Central Metrics   │
                    │     Aggregator      │
                    └──────────┬──────────┘
           ┌───────────────────┼───────────────────┐
           │                   │                   │
    ┌──────▼──────┐     ┌──────▼──────┐     ┌──────▼──────┐
    │  Cluster A  │     │  Cluster B  │     │  Cluster C  │
    │  OpenCost   │     │  OpenCost   │     │  OpenCost   │
    │  Exporter   │     │  Exporter   │     │  Exporter   │
    └─────────────┘     └─────────────┘     └─────────────┘

配置 Federation Prometheus

使用 Prometheus Federation 收集多叢集指標:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# federation-prometheus.yml
scrape_configs:
  # Cluster A
  - job_name: 'opencost-cluster-a'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="opencost"}'
    static_configs:
      - targets:
        - 'prometheus-cluster-a.example.com:9090'
    relabel_configs:
      - target_label: cluster
        replacement: cluster-a

  # Cluster B
  - job_name: 'opencost-cluster-b'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="opencost"}'
    static_configs:
      - targets:
        - 'prometheus-cluster-b.example.com:9090'
    relabel_configs:
      - target_label: cluster
        replacement: cluster-b

  # Cluster C
  - job_name: 'opencost-cluster-c'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="opencost"}'
    static_configs:
      - targets:
        - 'prometheus-cluster-c.example.com:9090'
    relabel_configs:
      - target_label: cluster
        replacement: cluster-c

設定唯一叢集識別

確保每個叢集的 OpenCost 有唯一識別:

1
2
3
4
5
6
7
8
# Cluster A values.yaml
opencost:
  exporter:
    defaultClusterId: "cluster-a-prod"
    extraEnv:
      CLUSTER_NAME: "cluster-a-prod"
      CLUSTER_REGION: "ap-northeast-1"
      CLUSTER_ENVIRONMENT: "production"

多叢集成本查詢

使用 API 查詢跨叢集成本:

1
2
3
4
5
# 查詢所有叢集的命名空間成本
curl -s "http://central-prometheus:9090/api/v1/query?query=sum(container_cpu_allocation_cost%2Bcontainer_memory_allocation_cost)by(cluster,namespace)*24*30" | jq

# 比較各叢集總成本
curl -s "http://central-prometheus:9090/api/v1/query?query=sum(node_total_hourly_cost)by(cluster)*24*30" | jq

PromQL 跨叢集分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 各叢集月成本排名
sort_desc(sum(node_total_hourly_cost * 24 * 30) by (cluster))

# 各叢集資源使用效率
sum(container_cpu_allocation_cost + container_memory_allocation_cost) by (cluster)
/
sum(node_total_hourly_cost) by (cluster)

# 跨叢集命名空間成本對比
sum(container_cpu_allocation_cost + container_memory_allocation_cost) by (cluster, namespace) * 24 * 30

自訂成本報告

OpenCost 提供靈活的 API,可用於建立自訂成本報告。

使用 OpenCost API

基本 API 端點:

1
2
3
4
5
6
7
8
# 取得過去 24 小時的成本分配
curl -s "http://localhost:9090/allocation/compute?window=24h&aggregate=namespace"

# 取得特定時間範圍的成本
curl -s "http://localhost:9090/allocation/compute?window=2026-01-01T00:00:00Z,2026-01-06T00:00:00Z&aggregate=deployment"

# 按多個維度聚合
curl -s "http://localhost:9090/allocation/compute?window=7d&aggregate=namespace,label:app"

Python 成本報告腳本

建立自動化成本報告:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
"""
OpenCost 成本報告產生器
"""

import requests
import json
from datetime import datetime, timedelta
import pandas as pd

class OpenCostReporter:
    def __init__(self, opencost_url: str):
        self.base_url = opencost_url.rstrip('/')

    def get_allocation(self, window: str, aggregate: str,
                       accumulate: bool = True) -> dict:
        """取得成本分配數據"""
        params = {
            'window': window,
            'aggregate': aggregate,
            'accumulate': str(accumulate).lower()
        }
        response = requests.get(
            f"{self.base_url}/allocation/compute",
            params=params
        )
        response.raise_for_status()
        return response.json()

    def generate_namespace_report(self, days: int = 30) -> pd.DataFrame:
        """產生命名空間成本報告"""
        data = self.get_allocation(f"{days}d", "namespace")

        records = []
        for item in data.get('data', []):
            if item.get('name') == '__idle__':
                continue
            records.append({
                'Namespace': item['name'],
                'CPU Cost ($)': round(item.get('cpuCost', 0), 2),
                'Memory Cost ($)': round(item.get('memoryCost', 0), 2),
                'GPU Cost ($)': round(item.get('gpuCost', 0), 2),
                'PV Cost ($)': round(item.get('pvCost', 0), 2),
                'Network Cost ($)': round(item.get('networkCost', 0), 2),
                'Total Cost ($)': round(item.get('totalCost', 0), 2)
            })

        df = pd.DataFrame(records)
        df = df.sort_values('Total Cost ($)', ascending=False)
        return df

    def generate_team_report(self, label_key: str = 'team',
                             days: int = 30) -> pd.DataFrame:
        """產生團隊成本報告(基於標籤)"""
        data = self.get_allocation(f"{days}d", f"label:{label_key}")

        records = []
        for item in data.get('data', []):
            team_name = item['name'] or 'Unassigned'
            records.append({
                'Team': team_name,
                'Total Cost ($)': round(item.get('totalCost', 0), 2),
                'CPU Hours': round(item.get('cpuCoreHours', 0), 2),
                'Memory GB Hours': round(item.get('ramByteHours', 0) / (1024**3), 2)
            })

        df = pd.DataFrame(records)
        df = df.sort_values('Total Cost ($)', ascending=False)
        return df

    def export_to_csv(self, df: pd.DataFrame, filename: str):
        """匯出報告為 CSV"""
        df.to_csv(filename, index=False)
        print(f"報告已匯出至: {filename}")

    def generate_summary(self) -> dict:
        """產生成本摘要"""
        # 取得過去 30 天數據
        monthly_data = self.get_allocation("30d", "cluster")

        # 取得昨天數據
        yesterday_data = self.get_allocation("yesterday", "cluster")

        # 取得上月數據進行比較
        last_month_data = self.get_allocation("lastmonth", "cluster")

        monthly_total = sum(
            item.get('totalCost', 0)
            for item in monthly_data.get('data', [])
        )
        yesterday_total = sum(
            item.get('totalCost', 0)
            for item in yesterday_data.get('data', [])
        )
        last_month_total = sum(
            item.get('totalCost', 0)
            for item in last_month_data.get('data', [])
        )

        return {
            'current_month_cost': round(monthly_total, 2),
            'yesterday_cost': round(yesterday_total, 2),
            'last_month_cost': round(last_month_total, 2),
            'month_over_month_change': round(
                (monthly_total - last_month_total) / last_month_total * 100
                if last_month_total > 0 else 0, 2
            )
        }


def main():
    reporter = OpenCostReporter("http://localhost:9090")

    # 產生命名空間報告
    ns_report = reporter.generate_namespace_report(days=30)
    print("\n=== 命名空間成本報告 (過去 30 天) ===")
    print(ns_report.to_string(index=False))
    reporter.export_to_csv(ns_report, "namespace_cost_report.csv")

    # 產生團隊報告
    team_report = reporter.generate_team_report(label_key='team', days=30)
    print("\n=== 團隊成本報告 (過去 30 天) ===")
    print(team_report.to_string(index=False))
    reporter.export_to_csv(team_report, "team_cost_report.csv")

    # 產生摘要
    summary = reporter.generate_summary()
    print("\n=== 成本摘要 ===")
    print(f"本月成本: ${summary['current_month_cost']}")
    print(f"昨日成本: ${summary['yesterday_cost']}")
    print(f"上月成本: ${summary['last_month_cost']}")
    print(f"月環比變化: {summary['month_over_month_change']}%")


if __name__ == "__main__":
    main()

自動化排程報告

使用 Kubernetes CronJob 定期產生報告:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: batch/v1
kind: CronJob
metadata:
  name: opencost-weekly-report
  namespace: opencost
spec:
  schedule: "0 9 * * 1"  # 每週一早上 9 點
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: reporter
            image: python:3.11-slim
            command:
              - /bin/bash
              - -c
              - |
                pip install requests pandas
                python /scripts/cost_report.py                
            volumeMounts:
              - name: scripts
                mountPath: /scripts
              - name: reports
                mountPath: /reports
            env:
              - name: OPENCOST_URL
                value: "http://opencost.opencost.svc.cluster.local:9090"
              - name: SMTP_SERVER
                valueFrom:
                  secretKeyRef:
                    name: email-config
                    key: smtp-server
          volumes:
            - name: scripts
              configMap:
                name: cost-report-scripts
            - name: reports
              persistentVolumeClaim:
                claimName: cost-reports-pvc
          restartPolicy: OnFailure

成本優化建議

基於 OpenCost 收集的數據,以下是常見的 Kubernetes 成本優化策略。

1. 右尺寸調整 (Right-sizing)

分析資源使用率並調整請求值:

1
2
3
4
# 查詢資源使用效率低的 Pod
curl -s "http://localhost:9090/allocation/compute?window=7d&aggregate=pod" | \
  jq '[.data[] | select(.cpuEfficiency < 0.3 or .ramEfficiency < 0.3)] |
      sort_by(.totalCost) | reverse | .[0:20]'

使用 Vertical Pod Autoscaler (VPA) 自動調整:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-app-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: web-app
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
      - containerName: '*'
        minAllowed:
          cpu: 50m
          memory: 64Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi
        controlledResources: ["cpu", "memory"]

2. 使用 Spot/Preemptible 節點

配置節點親和性以利用低成本節點:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: apps/v1
kind: Deployment
metadata:
  name: batch-processor
spec:
  replicas: 10
  template:
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: node.kubernetes.io/instance-type
                    operator: In
                    values:
                      - spot
      tolerations:
        - key: "kubernetes.io/spot"
          operator: "Equal"
          value: "true"
          effect: "NoSchedule"
      containers:
        - name: processor
          image: batch-processor:latest
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"

3. 自動縮放策略

實施 Horizontal Pod Autoscaler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60

配置 Cluster Autoscaler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: ConfigMap
metadata:
  name: cluster-autoscaler-config
  namespace: kube-system
data:
  config: |
    {
      "scaleDownUtilizationThreshold": 0.5,
      "scaleDownDelayAfterAdd": "10m",
      "scaleDownUnneededTime": "10m",
      "maxNodeProvisionTime": "15m"
    }    

4. 命名空間資源配額

限制各團隊的資源使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "20"
    requests.memory: "40Gi"
    limits.cpu: "40"
    limits.memory: "80Gi"
    persistentvolumeclaims: "10"
    pods: "50"

5. 定期清理未使用資源

建立清理 CronJob:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup-unused-resources
  namespace: kube-system
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: cleanup-sa
          containers:
          - name: cleanup
            image: bitnami/kubectl:latest
            command:
              - /bin/bash
              - -c
              - |
                # 刪除已完成的 Job(超過 7 天)
                kubectl get jobs --all-namespaces -o json | \
                  jq -r '.items[] | select(.status.succeeded == 1) |
                  select((now - (.status.completionTime | fromdateiso8601)) > 604800) |
                  "\(.metadata.namespace)/\(.metadata.name)"' | \
                  xargs -I {} kubectl delete job -n {}

                # 刪除 Evicted Pod
                kubectl get pods --all-namespaces -o json | \
                  jq -r '.items[] | select(.status.reason == "Evicted") |
                  "\(.metadata.namespace)/\(.metadata.name)"' | \
                  xargs -I {} kubectl delete pod -n {}                
          restartPolicy: OnFailure

成本優化檢查清單

優化項目預期節省實施難度
Right-sizing Pod 資源20-40%
使用 Spot 節點60-90%
實施 HPA/VPA15-30%
刪除閒置資源5-15%
預留執行個體30-50%
儲存類型優化10-30%

與 Kubecost 比較

OpenCost 和 Kubecost 都是 Kubernetes 成本監控工具,但各有特色。

功能比較表

功能OpenCostKubecost (Free)Kubecost (Enterprise)
開源授權Apache 2.0ProprietaryProprietary
CNCF 專案是 (Sandbox)
即時成本監控
多叢集支援是 (需自行配置)有限
雲端整合AWS, GCP, AzureAWS, GCP, AzureAWS, GCP, Azure + 更多
自訂定價有限
成本預測基本進階
告警功能透過 Prometheus內建進階
報告功能API/自行建置基本進階
治理功能
SAML/SSO
技術支援社群有限24/7 企業支援
資料保留無限制15 天無限制

選擇建議

選擇 OpenCost 如果您:

  • 偏好完全開源解決方案
  • 有能力自行整合和維護
  • 預算有限
  • 已有成熟的 Prometheus/Grafana 堆疊
  • 需要高度自訂化
1
2
# OpenCost 安裝(開源、免費)
helm install opencost opencost/opencost --namespace opencost

選擇 Kubecost 如果您:

  • 需要開箱即用的完整解決方案
  • 需要進階報告和預測功能
  • 需要企業級支援
  • 需要治理和存取控制功能
  • 團隊技術能力有限
1
2
3
4
# Kubecost 安裝
helm install kubecost cost-analyzer/cost-analyzer \
  --namespace kubecost \
  --set kubecostToken="YOUR_TOKEN"

從 Kubecost 遷移到 OpenCost

如果您決定從 Kubecost 遷移到 OpenCost:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. 安裝 OpenCost
helm install opencost opencost/opencost -n opencost

# 2. 驗證 OpenCost 正常運行
kubectl get pods -n opencost

# 3. 匯出 Kubecost 歷史數據(如需要)
kubectl exec -n kubecost deploy/kubecost-cost-analyzer -- \
  curl -s "http://localhost:9090/model/allocation?window=30d" > kubecost_backup.json

# 4. 更新 Prometheus 配置指向 OpenCost

# 5. 驗證數據一致性後移除 Kubecost
helm uninstall kubecost -n kubecost

總結

OpenCost 作為 CNCF 官方的 Kubernetes 成本監控專案,提供了一個開源、標準化的解決方案來追蹤和優化容器化工作負載的成本。透過本文介紹的內容,您應該能夠:

  1. 理解 OpenCost 的核心概念:成本計算原理、分配模型
  2. 完成安裝與設定:使用 Helm 部署 OpenCost 並進行基本配置
  3. 整合 Prometheus:匯出指標、建立儀表板和告警
  4. 管理多叢集成本:使用 Federation 進行跨叢集成本追蹤
  5. 建立自訂報告:利用 API 產生客製化成本報告
  6. 實施成本優化:透過 right-sizing、自動縮放等策略降低成本
  7. 評估工具選擇:根據需求選擇 OpenCost 或 Kubecost

成本管理是 FinOps 實踐的核心,而 OpenCost 提供了必要的可視化和數據基礎。建議持續監控成本趨勢,建立成本意識文化,並將成本優化納入日常運維流程中。

參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy