第三篇:Kubernetes部署 Prometheus —— 从安装到监控应用

上一篇结尾我给自己留了个任务:“下一篇该真正把它部署起来了。” 所以这篇是动手篇。目标很朴素:把 Prometheus 装进我的 K8s 集群,亲眼看到数据,再把我自己的应用也拖进来一起监控。

结果发现,“装起来"反而是最简单的一步。真正花我时间的,是搞明白"我到底装了个什么”。


一、装之前先想清楚:为什么不能手搓几个 YAML

如果只是想跑一个 Prometheus,理论上我手写几个 YAML 就够了:

Deployment(跑 Prometheus 进程)
  +
Service(给个访问入口)
  +
ConfigMap(里面是 prometheus.yml,写采集哪些目标)
  +
RBAC(服务发现要调 K8s 的 API,得有权限)
  +
PV/PVC(数据得存下来,不然 Pod 一重启全没了)

写到这儿我就有点犯怵了——光这些还没算 Grafana、Alertmanager、node-exporter。而且 prometheus.yml 里那些采集配置,在 K8s 这种"目标一直在变"的环境里,靠手写根本维护不动(这就是第一篇那个核心矛盾)。

所以社区干脆把这一整套打包成了一个 Helm Chart:kube-prometheus-stack。它像一个全家桶,装一次,这些东西全都有了:

Prometheus         —— 主角,存数据、查数据
Grafana            —— 画仪表盘
Alertmanager       —— 发告警
node-exporter      —— 上一篇的"翻译官",盯节点本身
kube-state-metrics —— 盯 K8s 对象的"状态"

这里我想多说两句 kube-state-metrics,因为它是我第一次看到全家桶清单时最没概念的一个。node-exporter 盯的是操作系统——CPU、内存、磁盘这些。但"我的 Deployment 期望 3 个副本,现在实际 ready 几个"、“集群里一共有多少个 Pod”——这些不是操作系统指标,是 K8s 自己的状态。kube-state-metrics 干的事,就是盯着 K8s 的 API,把这些"对象的当前状态"翻译成指标,比如 kube_deployment_status_replicas_available

(来龙去脉收个尾:这个 stack 以前叫 prometheus-operator,后来功能越加越多,改名成 kube-prometheus-stack。名字已经说明它的定位——不是"一个 Prometheus",而是"一整套 kube + prometheus 生态"。)


二、用 Helm 一键安装

那 Helm 又是什么?一句话:K8s 的包管理器。就像 Linux 里的 apt、yum——它把一堆现成的 YAML 模板打包成"一个包"(叫 Chart),安装的时候把模板里的变量填上,渲染成真正的 YAML,一次性提交给 K8s。

装:

$ helm repo add prometheus-community \
    https://prometheus-community.github.io/helm-charts
$ helm install prometheus prometheus-community/kube-prometheus-stack

跑完先别急着高兴,看一眼是不是真的都起来了:

$ kubectl get pods
NAME                                                      READY   STATUS    RESTARTS   AGE
prometheus-kube-prometheus-operator-7d8b6f9c8f-xxxxx      1/1     Running   0          2m
prometheus-kube-prometheus-prometheus-0                    2/2     Running   0          2m
prometheus-kube-prometheus-node-exporter-xxxxx             1/1     Running   0          2m
prometheus-kube-prometheus-kube-state-metrics-xxxxx        1/1     Running   0          2m
prometheus-grafana-xxxxx                                   3/3     Running   0          2m

注意这些 Pod 名几乎都带 prometheus-kube-prometheus- 前缀,说明它们全是这个 chart 生出来的。这背后其实藏着一个"operator"在干活——它就是全家桶里那个隐形的包工头。它的机制值得稍微讲一下,因为这是 K8s 生态里一个很核心的模式:

Operator 模式:kube-prometheus-stack 不只是装了一堆现成的 Pod,它还在集群里装了一个"控制器"(operator)。这个控制器一直盯着几种新的"自定义资源"(CRD),比如 PrometheusServiceMonitor。当我创建了一个 Prometheus 资源,operator 就帮我生成真正的 Prometheus 组件;当我创建了 ServiceMonitor,operator 就帮我把采集配置更新进 Prometheus。我写的不是 YAML 流水账,而是"声明我想要什么",剩下的由 operator 去实现。

打个比方:手写 YAML 像自己列购物清单、自己逛超市、自己结账;Helm 像叫了个外卖全家桶;而 operator 是那个"你说想吃什么、后厨自动做"的厨房。机制收尾一句:operator 本质是个死循环,一直 watch K8s 的 API,发现"期望状态"和"实际状态"不一致,就动手把它扭过来——跟 K8s 本身的控制器是同一个思路。


三、怎么"看到"它:kubectl port-forward

装好了,Pod 在 Running,但我打开浏览器访问 localhost:9090,打不开。这很正常——Pod 在集群内部,它的网络跟我本机是隔离的。要在本地访问集群里的服务,最省事的办法是 port-forward。

$ kubectl get svc    # 先看服务到底叫什么名字
$ kubectl port-forward svc/prometheus-kube-prometheus-prometheus 9090:9090

这里我栽了个小跟头:一开始我照着网上的教程敲 svc/prometheus,结果提示找不到服务。因为实际的服务名是 prometheus-kube-prometheus-prometheus,不是 prometheus。所以kubectl get svc 看一眼,别硬抄命令。这也是个教训:网上教程的环境跟我不完全一样,命令得按自己集群的实际名字来。

port-forward 的原理也很有意思。它的机制是:

我的浏览器 localhost:9090
   ↓
kubectl 客户端(跑在本机)
   ↓  走加密通道
K8s API server
   ↓
kubelet(Pod 所在节点)
   ↓
Prometheus Pod 的 9090

说白了,kubectl port-forward 是借道 API server 打的一条"隧道":本机端口 → API server → Pod。它是给开发时临时瞅一眼用的,不是给生产环境做对外访问的——隧道一关,这个入口就没了。

隧道打通后,浏览器访问 http://localhost:9090,我终于看到了那个朴素的 Prometheus 界面。第一篇里那些抽象的概念——“拉”、“时序数据库”、“PromQL”——第一次变成了眼前能点的东西。


四、亲眼看数据:查 Node 指标

先兑现上一篇结尾的承诺:当时我说"curl http://localhost:9100/metrics 就能亲眼看到 node-exporter 的输出"。于是再来一条隧道:

$ kubectl port-forward svc/prometheus-kube-prometheus-node-exporter 9100:9100
$ curl http://localhost:9100/metrics | head -n 20

看到满屏 node_* 指标的那一刻,第二篇那个"翻译官"的比喻彻底落地了——/proc 里的原始数据,真的变成了 node_cpu_seconds_total{cpu="0",mode="idle"} 291617.65 这种格式。

但接下来我遇到一个更实际的困惑:指标是看到了,可我想要的"当前 CPU 使用率是百分之多少",没有现成的指标。 node_cpu_seconds_total 只是一个一直在涨的累计值。光看它,我怎么知道现在 CPU 忙不忙?

这里就要用到 PromQL 的 rate() 了。上一篇我提了一句"_total = 累计值,要看速率得靠 rate()",现在正式解开:

# 每颗核、每种 mode 每秒涨多少 CPU 时间(单位:秒/秒)
rate(node_cpu_seconds_total[5m])

rate() 算的是"这个累计值在过去 5 分钟里,平均每秒涨了多少"。把空闲(idle)那部分的速率算出来,再用 100 一减,就是 CPU 忙的时间占比,也就是使用率:

100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

内存同理。node_memory_MemAvailable_bytes 是"当前还可用多少",除以总数再一减,就是使用率:

100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100)

把这些粘贴进 Prometheus 的查询框,看到曲线出来的一瞬间,第一篇里"拉数据"的整个逻辑——目标暴露接口、Prometheus 定时来取、存进 TSDB、PromQL 查询——变成了我能亲手验证的东西。

(顺带一提,在 Prometheus 里查 up,会看到一堆值为 1 的序列,每一个对应一个被采集的目标。值 1 表示"最近一次拉取成功",0 表示失败。这正是第一篇说过的:pull 模式下,谁死谁活,Prometheus 自己最清楚。)


五、监控自己的应用

现在轮到正题了:我自己的应用怎么接进来?

第一步,应用得先把 /metrics 开出来。约定的格式就是第二篇说的那套"指标名 + 标签 + 值"。以 Java 的 Spring Boot 为例,引入 Micrometer 的 Prometheus registry 后,应用就会自动暴露一个这样的接口(具体路径因框架而异,可能是 /metrics,也可能是 /actuator/prometheus,但格式是同一套)。在代码里顺手暴露一个自己的业务指标,比如处理过的请求数:

http_requests_total{method="GET"} 1000

但"开了接口"只是第一步。第二个问题马上来了:Prometheus 怎么知道要去采它? 这就是"服务发现"落到自己应用上的一步。

在 kube-prometheus-stack 里,最正规的做法是建一个 ServiceMonitor。它的机制前面讲过——operator 盯着它。我写一个 ServiceMonitor,声明"哪些 Service 要被监控、从哪个端口采":

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app
spec:
  selector:
    matchLabels:
      app: my-app        # 选中有这个标签的 Service
  endpoints:
    - port: http          # 从这个端口采(前提是 Service 里有个名为 http 的端口)
      path: /metrics      # 走这个路径

kubectl apply -f servicemonitor.yaml 之后,operator 看到这个 ServiceMonitor,就去 K8s 里找带 app: my-app 标签的 Service,解析出它背后的 Pod 地址,把采集目标更新进 Prometheus。之后整条链路就完整了:

我的应用(暴露 /metrics)
      ↓
ServiceMonitor 告诉 Prometheus 去哪采
      ↓
Prometheus 定时 GET /metrics,保存进 TSDB
      ↓
Grafana 连上 Prometheus,把数据画成仪表盘

最后让 Grafana 也露个脸。再开一条隧道,登录 Grafana(kube-prometheus-stack 默认账号是 admin / prom-operator):

$ kubectl port-forward svc/prometheus-grafana 3000:80
# 浏览器访问 http://localhost:3000

到这一步,从"装一个全家桶"到"自己的应用出现在仪表盘上",整个链路就算真正走通了。


在这里插入图片描述

六、这一篇的复盘

想明白的几件事:

  1. 在 K8s 里手动搭一套监控要写一堆 YAML 和配置,kube-prometheus-stack 是社区打包好的全家桶,一次装齐 Prometheus + Grafana + Alertmanager + node-exporter + kube-state-metrics。
  2. kube-state-metrics 盯的是 K8s 对象的状态,跟盯操作系统的 node-exporter 分工不同。
  3. port-forward 是借道 API server 的调试隧道,不是对外暴露服务的方式。
  4. 裸的 node_cpu_seconds_total 看不出使用率,要配合 rate() 看速率_total 的坑在这里真正填上了。
  5. 自己的应用要被监控,得"开接口 + 让 Prometheus 知道"两步都做,在 stack 里这一步靠 ServiceMonitor + operator 完成。

还留着、准备以后解决的困惑:

  • 数据存在哪?Prometheus 默认把数据存在 Pod 的临时目录里,Pod 一删就没了——持久化(PV/PVC)怎么配?
  • Alertmanager 告警怎么接进来,PrometheusRule 怎么配告警规则?
  • PromQL 里 rateirate 有什么区别?什么时候用哪个?

(2026-08-24,在 WSL2 上折腾 K8s 集群时)

上一篇:[[第二篇:Prometheus工作原理 —— 数据是如何被采集的]]

更多推荐