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_total | CPU 使用率 | > 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_phase | Pod 狀態(Pending/Running/Failed) | Pending > 5 分鐘 |
container_cpu_usage_seconds_total | Container CPU 用量 | 接近 limits |
container_memory_working_set_bytes | Container 實際記憶體用量 | 接近 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:
- productionPrometheus 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 小時。
延伸閱讀
- Metrics & Monitoring:Prometheus + Grafana 基礎
- Alerts & ChatOps:告警通知設定
- Log Management:集中式日誌管理
- 用 Grafana 讀懂壓測數據
本系列文章
- ← 上一篇:Helm:K8s 的套件管理
- 本篇:監控:Prometheus Operator + Grafana on K8s
- 下一篇:Troubleshooting:從 Pending 到 CrashLoopBackOff →