说到 Kubernetes 监控,你是不是也有过这种崩溃时刻:集群突然报警,你冲进 Slack 一看,告警风暴像洪水一样淹没了你的眼睛。等到你找到那个“罪魁祸首”时,业务已经挂了十分钟。或者更糟糕的是,你明明知道集群在空转,但就是不知道哪里在烧钱,直到月底账单出来才后悔莫及。
别慌,这篇文章就是来救你的。我们不讲那些枯燥的官方文档,直接手把手带你搭建一套真正能用的监控体系,顺便聊聊怎么用这套体系抓出那些偷偷吃资源的“内鬼”。
别急着安装,先想清楚你在监控什么
在动手敲 kubectl apply 之前,我得先泼盆冷水:很多人把 Prometheus 装上,把 Grafana 界面配得漂漂亮亮,然后就觉得万事大吉了。结果呢?告警阈值设得乱成一锅粥,要么没人理会的静默告警,要么一晚上收到几百条重复短信。
监控的核心不是“数据采集”,而是“可观测性”。你需要回答三个问题:
- 集群状态如何?(整体健康度)
- 应用表现怎样?(SLI/SLO 达成情况)
- 哪里出了问题?(根因分析)
对于 Kubernetes 来说,这通常意味着你需要关注三个层面的指标:基础设施层(节点 CPU/内存/磁盘)、容器层(Pod 资源使用/重启次数)和应用层(QPS/延迟/错误率)。
如果你只盯着 CPU 利用率看,那你永远只能看到表象。比如,一个应用 CPU 飙高,可能是因为代码死循环,也可能是因为 GC 频繁,还可能是因为下游依赖挂了导致请求堆积。不同的原因,解决方式天差地别。所以,好的监控体系一定是有分层的,而且要有足够的上下文信息。
搭建基石:Prometheus 与 Kube-Prometheus-Stack
对于 Kubernetes 环境,我强烈推荐 kube-prometheus-stack 这个 Helm Chart。它不是原生的 Prometheus Server,而是一整套方案的打包:包括 Prometheus、Alertmanager、Grafana、Node Exporter、Kube State Metrics 等等。虽然听起来复杂,但它的强大之处在于自动化程度极高。
安装过程其实很简单,但有几个细节决定了你后续的维护成本。
首先,确保你的集群版本是 v1.20+,否则一些新的 API 可能不支持。然后,添加 Helm Repo 并安装:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword="your-secure-password" \
--set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.resources.requests.storage="10Gi" \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage="20Gi"
注意看上面这段代码,我特意加了两个存储配置。Prometheus 是写数据的,如果存储不够,老数据会被自动清理,你就没法回溯一周前的性能问题了。对于中小规模集群,20GB 起步是比较稳妥的。如果你的集群非常大,建议把 Prometheus 和 Alertmanager 的存储分开,甚至考虑用外部对象存储配合 Thanos 或 Cortex。
安装完成后,你需要暴露服务。在生产环境中,绝对不要直接用 ClusterIP 然后通过端口转发访问 Grafana。你可以使用 NodePort 临时测试,但正式上线最好配合 Ingress Controller 和认证机制(比如 OAuth Proxy 或 Kubernetes Dashboard 的访问控制)。
# 临时查看 Grafana 密码
kubectl get secret --namespace monitoring monitoring-grafana -o jsonpath="{.data.admin-password}" | base64 --decode
被忽视的宝藏:Kube State Metrics 和 Node Exporter
很多人只装 Prometheus,然后对着 Prometheus 自带的指标发呆。其实,Kubernetes 真正有价值的信息藏在 kube-state-metrics 里。
kube-state-metrics 会监听 Kubernetes API Server,把集群对象的状态转换成 Prometheus 指标。比如:
kube_pod_status_phase:告诉你在哪个 Pod 处于 Pending、Running 还是 Failed 状态。kube_deployment_status_replicas_unavailable:直接告诉你哪个 Deployment 有滚动更新失败的问题。kube_pod_container_status_restarts_total:这是排查崩溃重启的神器。
如果你想抓内存泄漏,光看容器当前用了多少内存是不够的,你得看内存增长趋势。这时候,kube_pod_container_memory_working_set_bytes 配合 PromQL 的 rate 或 delta 函数就派上用场了。
至于 node-exporter,它负责采集物理机或虚拟机的硬件指标。当 Pod 调度到某个节点后出现性能抖动,通过 node-exporter 的指标,你可以判断是节点资源争抢导致的,还是网络延迟问题。
告警风暴的终结者:如何设置合理的告警规则
这是大多数人踩坑最多的地方。默认安装的 Alertmanager 规则往往过于激进。比如,默认的 KubePodCrashLooping 告警,可能在 Pod 因为配置错误频繁重启时被触发,但你的团队可能更关心的是线上用户受影响的告警。
我的建议是:告警一定要分层,并且要有明确的行动指南。
1. 警告(Warning)vs 严重(Critical)
不要把所有问题都标为 Critical。Critical 应该留给那些需要立即介入、否则服务会中断的情况。Warning 可以是那些需要关注、但不需要半夜爬起来处理的问题。
2. 给告警加上“人话”说明
在 annotations 里,不要只放一个链接。要写清楚:
- 发生了什么?
- 影响范围是什么?
- 第一步该做什么?
groups:
- name: kubernetes-apps
rules:
- alert: KubePodCrashLooping
expr: rate(kube_pod_container_status_restarts_total{namespace="prod"}[15m]) * 60 * 5 > 0
for: 15m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping"
description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} has been restarting frequently. Last restart was at {{ $value }}. \n\nAction: Check logs with `kubectl logs -n {{ $labels.namespace }} {{ $labels.pod }}` and events with `kubectl describe pod -n {{ $labels.namespace }} {{ $labels.pod }}`."
3. 使用 Alertmanager 的分组和抑制
如果有 100 个 Pod 因为底层节点故障而重启,你不想收到 100 条告警。利用 group_by 将它们分组,只发一条聚合告警。同时,使用 inhibit_rules:当 NodeDown 告警触发时,抑制所有基于该节点 Pod 的 PodNotReady 告警。否则,你会被一堆次要告警淹没,而真正的根因(节点挂了)反而被掩盖了。
实战排查:内存泄漏与 CPU 飙高
好,监控搭好了,告警也合理了。现在,警报响了。我们要怎么查?
场景一:内存泄漏排查
内存泄漏在容器化环境中是个经典难题。因为容器的内存上限是 cgroup 限制的,一旦进程占用的内存超过 limits,就会被 OOM Killer 杀掉并重启。
第一步:确认现象 在 Grafana 里找到对应 Pod 的内存使用曲线。如果是缓慢上升,最后垂直下跌(重启),这就是典型的泄漏特征。如果是波动上升,可能是正常的负载压力。
第二步:获取历史快照
Prometheus 的短期存储可能不够回溯。你可以使用 curl 命令直接查询 Prometheus API,导出过去几小时的内存数据:
curl -g 'http://<prometheus-server>:9090/api/v1/query?query=kube_pod_container_memory_working_set_bytes{namespace="prod",pod=~"my-app-.*"}&start=2023-10-01T00:00:00Z&end=2023-10-02T00:00:00Z'
第三步:现场诊断 如果 Pod 还在运行,不要急着重启它(否则样本就没了)。进入容器内部:
kubectl exec -it <pod-name> -n <namespace> -- sh
然后使用系统工具:
top或htop:看实时内存占用最高的进程。ps aux --sort=-%mem:列出所有进程按内存排序。cat /proc/<pid>/status:查看特定进程的内存统计信息,如VmRSS(实际物理内存)和VmSize(虚拟内存)。
第四步:深入应用层
如果是 Java 应用,泄漏几乎肯定发生在堆内存。你需要进入容器安装 jmap 和 jhat(或者直接用 jcmd):
# 找到 Java 进程 ID
PID=$(jps -l | grep MyApp | awk '{print $1}')
# 生成堆转储(Heap Dump)
jmap -dump:format=b,file=/tmp/heap.hprof $PID
# 下载转储文件到本地分析
kubectl cp <namespace>/<pod-name>:/tmp/heap.hprof ./heap.hprof
拿到 .hprof 文件后,使用 Eclipse MAT 或 VisualVM 打开,查看“Dominator Tree”,找出占用内存最多且无法被 GC 回收的对象。这通常能直接定位到是哪个数据结构在无限增长。
如果是 Go 应用,可以用 pprof:
# 确保应用开启了 pprof 端点
# 在 Pod 内访问 localhost:6060/debug/pprof/heap
kubectl port-forward <pod-name> 6060:6060
go tool pprof http://localhost:6060/debug/pprof/heap
场景二:CPU 飙高排查
CPU 飙高通常表现为负载高、响应慢。
第一步:定位瓶颈
同样先进容器,用 top 查看是哪个进程占用 CPU 高。
第二步:火焰图分析
对于 CPU 密集型问题,命令行工具往往不够直观。我们需要生成火焰图。
以 Java 应用为例,可以使用 async-profiler:
# 启动 profilers
docker exec -it <container-id> sh -c "cd /tmp && wget -q https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/build-linux-x64.zip && unzip build-linux-x64.zip"
# 采集 30 秒 CPU 快照
docker exec -it <container-id> /tmp/profile/build-linux-x64/profiler.sh start -e cpu -d 30 -f /tmp/flame.html <pid>
# 停止采集
docker exec -it <container-id> /tmp/profile/build-linux-x64/profiler.sh stop <pid>
# 下载火焰图
docker cp <container-id>:/tmp/flame.html ./flame.html
打开 flame.html,你会看到一棵树状图,最宽的柱子就是消耗 CPU 最多的方法。这比看日志快得多,一眼就能看出是某个算法循环、序列化操作还是锁竞争导致的。
对于 Go 应用,同样可以用 pprof:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
(pprof) top
(pprof) web # 如果安装了 graphviz,可以直接生成图形
成本节省:从监控中找回丢掉的钱
最后,我们来聊聊大家最关心的:省钱。
很多公司不知道自己的云账单为什么这么高。监控数据其实藏着答案。
1. 识别闲置资源 在 Grafana 中创建一个看板,展示每个 Namespace 或 Deployment 的平均 CPU 和内存使用率。如果一个 Deployment 的 CPU 长期低于 5%,内存长期低于 10%,那它很可能配置过剩了。
你可以写一个简单的 PromQL 查询来找出这些“胖手指”:
# 找出过去7天平均 CPU 使用率低于 5% 的 Pod
avg by (pod, namespace) (
rate(container_cpu_usage_seconds_total{namespace!~"kube-system|monitoring"}[7d])
) < 0.05
对于内存同理。找到这些 Pod 后,调整它们的 requests 和 limits。这不仅能降低资源预留,还能让调度器更有效地利用节点容量,甚至减少节点数量。
2. 自动缩容与休眠 结合监控数据,使用 KEDA(Kubernetes Event-driven Autoscaling)或 VPA(Vertical Pod Autoscaler)。
- KEDA:可以根据消息队列长度、HTTP QPS 甚至自定义 Prometheus 指标来伸缩 Pod 数量。比如,非工作时间将开发环境的 Pod 缩容到 0,能省下一大笔钱。
- VPA:可以自动调整 Pod 的资源请求,避免人工配置的偏差。
3. 检查异常消费 利用之前提到的告警,监控资源使用的异常突增。如果某个平时很安静的服务突然 CPU 飙升,可能是被入侵了(挖矿病毒)或者出现了代码 Bug。及时发现并止损,比事后追责更有价值。
4. 右侧成本优化报告 现在有很多第三方工具(如 Kubecost、Prometheus + Thanos + Cost Analysis)可以直接将 Prometheus 指标转化为美元金额。你可以看到每个 Namespace、每个团队、每个应用的实时成本。这能倒逼研发团队关注资源效率,而不是无脑申请资源。
结语:监控是一种习惯,不是一次性任务
搭建 Prometheus 只是开始。真正的挑战在于后续的维护:定期审查告警规则是否合理、清理过时的 Dashboard、更新监控组件版本、以及培养团队“看数据说话”的习惯。
不要指望一套配置能管十年。随着业务增长,你的监控体系也需要不断演进。有时候,你需要引入 Jaeger 做链路追踪;有时候,你需要 Loki 来聚合日志;有时候,你可能需要 Uptrace 来做分布式追踪和错误跟踪。
但无论如何,核心的思路不变:从业务视角出发,用数据驱动决策,让每一个告警都有价值,让每一分钱都花得明白。
希望这篇攻略能帮你建立起一套健康、高效、省钱的 Kubernetes 监控体系。如果你在实际操作中遇到什么奇怪的指标或告警,欢迎随时交流,毕竟,每一个坑都是经验。
