K8s 叢集裡有幾十個 Pod 在跑,哪個快掛了、哪個記憶體爆了、哪個一直重啟——你不可能一個一個 kubectl logs 去看。你需要集中式的監控。好消息是 K8s 生態已經有一個殺手級的解決方案:kube-prometheus-stack

先講結論

kube-prometheus-stack 一個 Helm chart 搞定 Prometheus + Grafana + AlertManager + node-exporter + kube-state-metrics。裝完就有 K8s 叢集的完整監控,包含預設的告警規則和 Grafana dashboard。你自己的 app 只要暴露 /metrics 端點,再建一個 ServiceMonitor CR,Prometheus 就會自動抓。Log 的部分加裝 Loki,搭配 Grafana 做 log aggregation。


安裝 kube-prometheus-stack

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
 
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.adminPassword="your-secure-password" \
  --set prometheus.prometheusSpec.retention=30d \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=fast-ssd \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi \
  --set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.storageClassName=fast-ssd \
  --set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.resources.requests.storage=5Gi

或者用 values file(推薦):

# values-monitoring.yaml
prometheus:
  prometheusSpec:
    retention: 30d
    resources:
      requests:
        cpu: 500m
        memory: 2Gi
      limits:
        cpu: "2"
        memory: 4Gi
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: fast-ssd
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 50Gi
    # 讓 Prometheus 抓所有 namespace 的 ServiceMonitor
    serviceMonitorSelectorNilUsesHelmValues: false
    podMonitorSelectorNilUsesHelmValues: false
 
grafana:
  adminPassword: "your-secure-password"
  persistence:
    enabled: true
    size: 5Gi
  ingress:
    enabled: true
    ingressClassName: nginx
    hosts:
      - grafana.example.com
    tls:
      - secretName: grafana-tls
        hosts:
          - grafana.example.com
 
alertmanager:
  alertmanagerSpec:
    storage:
      volumeClaimTemplate:
        spec:
          storageClassName: fast-ssd
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 5Gi
  config:
    global:
      resolve_timeout: 5m
    route:
      group_by: ['alertname', 'namespace']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 12h
      receiver: 'slack'
      routes:
        - receiver: 'slack-critical'
          matchers:
            - severity = critical
    receivers:
      - name: 'slack'
        slack_configs:
          - api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
            channel: '#alerts'
            title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}'
            text: '{{ range .Alerts }}*{{ .Labels.namespace }}* - {{ .Annotations.summary }}{{ end }}'
      - name: 'slack-critical'
        slack_configs:
          - api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
            channel: '#alerts-critical'
            title: '[CRITICAL] {{ .CommonLabels.alertname }}'
            text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace \
  -f values-monitoring.yaml

裝完你馬上就有:

  • Prometheus:metrics 收集
  • Grafana:dashboard(預設就有十幾個 K8s dashboard)
  • AlertManager:告警路由和通知
  • node-exporter(DaemonSet):每個 Node 的系統指標
  • kube-state-metrics:K8s 物件狀態指標(Pod 狀態、Deployment replicas 等)

關鍵 Metrics

Node 層級

Metric用途告警時機
node_cpu_seconds_totalCPU 使用率> 85% 持續 5 分鐘
node_memory_MemAvailable_bytes可用記憶體< 10%
node_filesystem_avail_bytes磁碟可用空間< 20%
kube_node_status_condition{condition="Ready"}Node 是否正常!= True

Pod 層級

Metric用途告警時機
kube_pod_status_phasePod 狀態(Pending/Running/Failed)Pending > 5 分鐘
container_cpu_usage_seconds_totalContainer CPU 用量接近 limits
container_memory_working_set_bytesContainer 實際記憶體用量接近 limits(快 OOM)
kube_pod_container_status_restarts_total重啟次數5 分鐘內 > 3 次
kube_pod_container_status_waiting_reason等待原因CrashLoopBackOff

Deployment 層級

Metric用途告警時機
kube_deployment_status_replicas_available可用副本數< desired
kube_deployment_status_replicas_unavailable不可用副本數> 0 持續 5 分鐘

ServiceMonitor:讓 Prometheus 抓你的 App

kube-prometheus-stack 用 Prometheus Operator 的 CRD。你不需要去改 Prometheus 的 scrape config,只要建一個 ServiceMonitor

# 你的 app Service
apiVersion: v1
kind: Service
metadata:
  name: api-service
  namespace: production
  labels:
    app: api-server
spec:
  selector:
    app: api-server
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: metrics      # metrics 端點
      port: 9090
      targetPort: 9090
---
# ServiceMonitor — 告訴 Prometheus 去抓這個 Service 的 metrics
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: api-server-monitor
  namespace: production
  labels:
    release: kube-prometheus-stack    # 要匹配 Prometheus 的 selector
spec:
  selector:
    matchLabels:
      app: api-server
  endpoints:
    - port: metrics      # 對應 Service 的 port name
      interval: 15s
      path: /metrics
  namespaceSelector:
    matchNames:
      - production

Prometheus Operator 會自動更新 Prometheus 的 scrape config,不用重啟。


告警規則

kube-prometheus-stack 預設就帶了一堆告警規則。你可以加自己的:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: custom-alerts
  namespace: monitoring
  labels:
    release: kube-prometheus-stack
spec:
  groups:
    - name: pod-alerts
      rules:
        - alert: PodCrashLooping
          expr: |
            rate(kube_pod_container_status_restarts_total[15m]) * 60 * 5 > 3
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 一直在重啟"
            description: "過去 15 分鐘內重啟了 {{ $value }} 次"
 
        - alert: PodNotReady
          expr: |
            kube_pod_status_phase{phase=~"Pending|Unknown"} == 1
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 超過 10 分鐘未就緒"
 
    - name: resource-alerts
      rules:
        - alert: HighMemoryUsage
          expr: |
            container_memory_working_set_bytes{container!=""}
            / on(namespace, pod, container)
            kube_pod_container_resource_limits{resource="memory"}
            > 0.9
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "{{ $labels.namespace }}/{{ $labels.pod }} 記憶體使用超過 90%"
            description: "Container {{ $labels.container }} 即將 OOM"
 
        - alert: NodeNotReady
          expr: |
            kube_node_status_condition{condition="Ready", status="true"} == 0
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "Node {{ $labels.node }} 不健康"

Grafana Dashboard

kube-prometheus-stack 預設裝好的 dashboard:

  • Kubernetes / Compute Resources / Cluster:叢集整體資源
  • Kubernetes / Compute Resources / Namespace (Pods):某 namespace 的 Pod 資源
  • Kubernetes / Networking / Cluster:叢集網路流量
  • Node Exporter / Nodes:每個 Node 的系統指標

自訂 Dashboard 用 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-dashboard
  namespace: monitoring
  labels:
    grafana_dashboard: "1"    # Grafana sidecar 會自動載入有這個 label 的 ConfigMap
data:
  api-overview.json: |
    {
      "dashboard": {
        "title": "API Overview",
        "panels": [
          {
            "title": "Request Rate",
            "type": "timeseries",
            "targets": [
              {
                "expr": "sum(rate(http_requests_total{namespace=\"production\"}[5m])) by (service)",
                "legendFormat": "{{ service }}"
              }
            ]
          },
          {
            "title": "P95 Latency",
            "type": "timeseries",
            "targets": [
              {
                "expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{namespace=\"production\"}[5m])) by (le, service))",
                "legendFormat": "{{ service }}"
              }
            ]
          },
          {
            "title": "Error Rate",
            "type": "stat",
            "targets": [
              {
                "expr": "sum(rate(http_requests_total{namespace=\"production\", status=~\"5..\"}[5m])) / sum(rate(http_requests_total{namespace=\"production\"}[5m]))"
              }
            ]
          }
        ]
      }
    }

實務建議:不要在 Grafana UI 裡手動建 dashboard。用 JSON 定義、存 Git、用 ConfigMap 部署。不然哪天 Grafana Pod 重建,手動建的 dashboard 就沒了。


Loki:K8s 上的 Log Aggregation

Prometheus 收 metrics,Loki 收 logs。兩者都用 Grafana 看。

安裝 Loki + Promtail

helm repo add grafana https://grafana.github.io/helm-charts
 
# Loki(log 儲存)
helm install loki grafana/loki \
  --namespace monitoring \
  --set loki.storage.type=filesystem \
  --set singleBinary.replicas=1 \
  --set singleBinary.persistence.size=20Gi
 
# Promtail(DaemonSet,每個 Node 收集 container log)
helm install promtail grafana/promtail \
  --namespace monitoring \
  --set config.clients[0].url=http://loki:3100/loki/api/v1/push

在 Grafana 加 Loki Data Source

裝完後在 Grafana 加一個 Loki data source,URL 填 http://loki:3100。然後你可以用 LogQL 查詢:

# 看某個 namespace 的所有 log
{namespace="production"}
 
# 看某個 Pod 的 error log
{namespace="production", pod="api-server-7d4f8b-x9k2z"} |= "ERROR"
 
# 統計每個 service 的 error 數量
sum by (app) (count_over_time({namespace="production"} |= "ERROR" [5m]))

Loki 的優勢:不做全文索引(跟 Elasticsearch 不同),只索引 label。所以儲存成本低很多,查詢速度對 label 過濾後的資料量來說也夠快。


監控架構全景

                    ┌─────────────┐
                    │   Grafana   │ ← Dashboard + Alerting UI
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
       ┌──────┴──────┐ ┌──┴───┐ ┌──────┴──────┐
       │ Prometheus  │ │ Loki │ │ AlertManager│
       │  (metrics)  │ │(logs)│ │  (通知路由)  │
       └──────┬──────┘ └──┬───┘ └─────────────┘
              │           │
    ┌─────────┼─────┐     │
    │         │     │     │
┌───┴───┐ ┌──┴──┐ ┌┴──┐ ┌┴───────┐
│node-  │ │kube-│ │App│ │Promtail│
│export │ │state│ │/m │ │(每Node)│
│(每Node)│ │metrics│ │etrics│ └────────┘
└───────┘ └─────┘ └───┘

常見踩坑

Prometheus 記憶體爆掉:指標太多。檢查 cardinality:prometheus_tsdb_head_series。如果超過 100 萬,找出高 cardinality 的 label 砍掉。

ServiceMonitor 建了但 Prometheus 沒抓:label 不匹配。Prometheus Operator 預設只抓有 release: kube-prometheus-stack label 的 ServiceMonitor。加上就好。

Grafana dashboard 重啟後消失:沒開 persistence 或沒用 ConfigMap 部署。預設 Grafana 的資料存在 emptyDir,Pod 重建就沒了。

AlertManager 狂發通知repeat_interval 設太短。告警觸發後每 repeat_interval 會重發。設 4h 或 12h 比較合理。


監控是基礎設施,不是功能。它不會直接帶來業務價值,但沒有它,任何一個凌晨三點的事故都會讓你後悔。裝 kube-prometheus-stack 只要 5 分鐘,但它能在事故時省你 5 小時。


延伸閱讀


本系列文章