最近在单节点K8s上装了一套Prometheus监控栈,中间踩了不少坑,也把部署链路彻底理了一遍。今天把整个过程整理出来,从组件选型、Helm安装、存储持久化、Grafana可视化到告警配置,一条龙走通。单节点K8s里部署Prometheus,听起来是个小需求,但里面涉及的监控架构、抓取链路、权限设计、存储策略,一个都绕不开。这篇文章适合两类人:一类是刚搭好单节点K8s集群、准备接入监控的新手,另一类是在本地开发和测试环境里需要一套轻量监控方案、又不想直接照搬生产配置的工程师。
1. 单节点K8s部署Prometheus的整体思路
1.1 单节点环境是谁在用
先说清楚“单节点K8s”这个前提。大多数教程默认你是三节点起步的准生产集群,有独立的master节点、多块磁盘、持久化存储类,还有负载均衡器可以分发给Ingress。但实际情况是,大量开发者手里的集群就是一个节点:有人用kubeadm初始化了一个单控制面节点,有人直接用kind或者minikube起了一个实验集群,也有人拿旧笔记本装了个轻量K8s做边缘计算。
这类环境资源有限,CPU可能就2到4核,内存8G左右,还要同时跑业务Pod。如果照着生产方案去部署“完整的可观测性平台”,光Prometheus、Grafana、Loki、Alertmanager、各种Exporter一起上来,内存直接爆掉。所以在单节点上部署Prometheus,本质上是怎么在资源受限的环境里,搭建一条完整但不臃肿的监控链路:既要能看到节点指标、Pod指标、K8s对象状态,又不能把机器拖垮。这就是整篇文章要解决的问题。
1.2 监控栈应该拆成哪些组件
很多人一上来就想“我装个Prometheus就行了”,实际落地时才发现,Prometheus自己不会产生指标,它只是负责采集、存储和查询。要让监控系统真正有用,至少需要四类组件配合:
第一是Prometheus Server本身,负责抓取指标、存储时序数据、响应PromQL查询。第二是节点指标采集器,标准方案是node-exporter,它暴露主机的CPU、内存、磁盘、网络等指标,把宿主机状态拉进监控。第三是K8s对象状态采集器,也就是kube-state-metrics,它从Kubernetes API Server读取对象的状态,生成deployment副本数、pod状态、namespace资源等指标,没有它,你只能看到机器有没有问题,看不到应用层有没有异常。第四是可视化层,Grafana负责把Prometheus里的指标变成图表。
如果还要做告警,就多一个Alertmanager组件,负责接收Prometheus触发的告警,做去重、分组和通知。在真正的生产环境里,还有Thanos、VictoriaMetrics这类高可用长存储方案,但单节点环境完全不需要,反而会增加运维负担。这个组件取舍本身就体现了单节点部署的核心原则:够用就好,少即是多。
1.3 部署方式:为什么我优先选Helm
K8s上部署Prometheus主要有两条路:手工编写YAML文件和用Helm Chart安装。手工方式就是自己写Deployment、Service、ServiceMonitor、RBAC,一步步把Prometheus搭起来,适合学习原理,但组件多起来之后非常费劲,尤其是kube-state-metrics的RBAC授权、Prometheus抓取kubelet指标时的证书配置,手写很容易漏。Helm方式则是直接使用社区维护的kube-prometheus-stack这个Chart,它把Prometheus、Alertmanager、Grafana、node-exporter、kube-state-metrics、默认告警规则、默认Dashboard一次性打包,一条命令装完。
我最终选择Helm,不单是因为它快,而是因为这个Chart经过了大量生产环境验证,里面的默认ServiceMonitor、抓取配置和告警规则比自己手写要成熟得多。单节点部署的核心矛盾是“资源有限”,Helm方式允许我按需裁剪组件,用values.yaml直接关闭不需要的模块,比如不装Alertmanager,或者降低Grafana资源占用,这一点是手写YAML很难比拟的灵活度。对于只想快速验证监控链路的人来说,Helm是第一选择;如果想深入理解原理,我建议后续自己手动拆开Chart里的模板看看。
2. 环境准备与前期设计
2.1 集群验证与资源规划
部署前先确认集群状态,别一上来就装Chart,装完发现集群本身就没就绪。用几个命令快速体检:
kubectl cluster-info kubectl get nodes kubectl get pods -Anode状态必须是Ready,核心组件(coredns、kube-proxy等)都得正常运行。我之前的经验是,单节点集群最容易忽略的是资源预留:Kubernetes本身要占用1G左右内存,Prometheus加上Grafana跑起来至少还要1.5G到2G,如果机器总内存只有4G,业务Pod随时可能被驱逐。所以在安装监控之前,先检查一下节点剩余可用资源:
kubectl describe node | grep -A5 "Allocated resources"建议单节点最低配置是2核4G内存,想跑完整的kube-prometheus-stack并保留一定余量,最好4核8G。内存不足时,不是不能装,而是必须在values里大幅下调各组件的requests和limits,否则很快就会出现OOMKilled。
2.2 命名空间、权限与镜像准备
Prometheus监控体系主体部署在独立的命名空间里,推荐使用monitoring。单独命名空间的好处是安全隔离,以及方便统一管理资源配额。创建命令很简单:
kubectl create namespace monitoring但这里有一个新手容易忽视的问题:Prometheus要抓取集群里的指标,比如kubelet指标、K8s对象状态,必须有相应的RBAC权限。kube-prometheus-stack在安装时,会自动创建ServiceAccount、ClusterRole和ClusterRoleBinding,这类集群级权限的命名通常带有monitoring标签。如果你手工部署,很容易漏掉ClusterRole授权,导致Prometheus能启动却抓不到任何数据。Helm方式帮你把这一步的坑填掉了。
镜像准备也要提前想好。kube-prometheus-stack用到的镜像主要托管在quay.io、ghcr.io和docker.io,部分环境下拉取速度很慢甚至失败。这属于正常的网络连通性问题,解决办法是在节点上配置容器镜像加速,或者在values.yaml里把镜像地址替换为可访问的镜像仓库。具体的配置方案我放在第五章的常见问题里详细展开。
2.3 存储层设计:单节点怎么做持久化
Prometheus是时序数据库,数据需要落盘。这里要先给新手扫个盲:如果什么都不配置,Prometheus会用emptyDir存储,Pod一重启数据就全部消失,监控历史归零。这在临时演示环境问题不大,但只要你希望重启后还能看到历史曲线,就必须做持久化。
单节点环境一个尴尬的事实是:大多数单节点集群没有部署默认StorageClass,动态PVC根本申请不到,PVC会一直Pending。有两条路可以走。
第一条路:直接用hostPath,把Prometheus数据目录映射到宿主机某个路径。优点是简单,缺点是调度约束差,一旦Pod被调度到其他节点会找不到数据,但在单节点场景没有这个问题。第二条路:先手动创建Local PV,再通过PVC绑定,这种方式更加规范,后续如果需要迁移到多节点集群,存储逻辑也是顺的。
local PV的创建方法我放在第三章实际安装步骤里一起讲,因为它和Helm的values参数要配合使用。如果快速验证、不想碰存储,可以先临时用emptyDir跑起来,后面再补PV,这个是我实际反复验证过的最平稳的路径。
3. 完整部署实操:一条龙安装监控栈
3.1 用Helm Chart安装kube-prometheus-stack
先把Helm仓库加上并更新索引:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update然后查看可用的Chart版本,kube-prometheus-stack的版本和Kubernetes版本之间有一套对应关系,太新的Chart在过旧的K8s上部署可能API不兼容。检查方法:
helm search repo prometheus-community/kube-prometheus-stack安装前,先把存储和资源调整写进values文件。以本地PV为例,先创建一个Local PV:
apiVersion: v1 kind: PersistentVolume metadata: name: prometheus-data-pv spec: capacity: storage: 20Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /data/prometheus宿主机上先创建对应目录:mkdir -p /data/prometheus。然后写一套精简的values.yaml:
prometheus: prometheusSpec: storageSpec: volumeClaimTemplate: spec: storageClassName: local-storage accessModes: ["ReadWriteOnce"] resources: requests: storage: 20Gi grafana: persistence: enabled: true storageClassName: local-storage执行安装命令:
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ -n monitoring \ -f values.yaml安装完成后,检查Pod状态:
kubectl get pods -n monitoring正常情况下你会看到alertmanager、grafana、kube-state-metrics、node-exporter、prometheus-operator、prometheus这些Pod。单节点受限时,可以在安装命令里追加参数关闭不用的组件,比如--set alertmanager.enabled=false,或者调低资源占用,后面调优部分会细说。
3.2 检查核心组件与抓取目标是否正常
安装完成不等于监控就通了,最重要的验证步骤是看Prometheus的Targets页面。先通过端口转发访问Prometheus:
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090浏览器打开http://localhost:9090/targets,你会看到一长串抓取目标,重点看这三个:
- kubelet相关的ServiceMonitor目标,确保状态是UP。
- node-exporter对应的目标,状态是UP。
- kube-state-metrics对应的目标,状态是UP。
如果kubelet目标显示Down,大概率是证书校验问题,后面常见问题章节专门排查。
Targets全部UP后,再去Prometheus的Graph页面验证几个核心指标:
up:所有job的采集健康度。node_cpu_seconds_total:节点CPU使用的时间累计值。kube_deployment_status_replicas_available:当前可用副本数。
输入node_cpu_seconds_total如果能出结果,说明整条采集链路已经通了:node-exporter在暴露指标、Prometheus在正常抓取和存储、PromQL查询可以工作。到这里,Prometheus本身的部署就算成功了一半,剩下的是数据可视化和告警。
3.3 暴露服务:临时转发还是固定入口
端口转发适合本地应急查看,但如果你在服务器上搭建集群,希望随时访问Grafana和Prometheus,就需要一个稳定的入口。单节点环境没有LoadBalancer可用,我给两个方案。
最简单的方案是直接把Service改成NodePort。以Prometheus为例:
kubectl edit svc kube-prometheus-stack-prometheus -n monitoring把type改成NodePort,然后查看分配的端口:kubectl get svc -n monitoring,用节点IP:端口就能访问。Grafana的Service同理。
另一个方案是Ingress。但注意:单节点集群一般没有Ingress Controller,你需要先装一个,比如ingress-nginx。NodePort虽然看起来“土”,但在单节点场景下是最稳定、最少依赖的暴露方式。对于开发调试,NodePort足够用,Ingress反倒在单节点上增加了一层不必要的复杂度。
4. 接入Grafana与告警配置
4.1 Grafana首次登录与数据源配置
kube-prometheus-stack默认会部署一个Grafana实例。暴露出来之后,用默认账号登录:用户名admin,密码admin,第一次登录会提示修改密码。这里有个新手容易卡住的点:你可能会觉得Grafana里面应该自动就有数据了。实际上,如果你是第一次进入Grafana,数据源虽然由Helm自动配置好了,但面板还没有自动加载,需要导入Dashboard。
先确认数据源:进入Grafana的Configuration > Data Sources,你会看到名为Prometheus的数据源,它的URL指向集群内部的Prometheus Service地址(形如http://kube-prometheus-stack-prometheus.monitoring.svc:9090)。这个地址必须是Pod网络内可访问的Service DNS,不能写成localhost:9090,因为Grafana和Prometheus是不同Pod,localhost指代的是Grafana自己。很多“Grafana里看不到数据”的问题,都是写错数据源URL导致的。
4.2 导入常用K8s监控Dashboard
Grafana面板不用从头画,直接用社区维护的Dashboard模板效率最高。在Grafana里点击Dashboards > Import,输入模板ID即可。我常用的几个:
| Dashboard ID | 用途 |
|---|---|
| 15759 | Kubernetes集群视图(包含节点资源、Pods状态) |
| 1860 | Node Exporter主机监控 |
| 13329 | kube-state-metrics基础监控 |
导入时数据源选择Prometheus即可。如果导入后图表显示"No data",不要急着怀疑数据源,先检查图表左上角的namespace筛选器,很多K8s相关Dashboard默认筛选特定namespace,改成你实际用的命名空间就会有数据。我实际遇到最多的问题是Dashboard版本和指标集不匹配,有些老模板用的是已经删除的指标名,那就换一个更新更流行的模板。
4.3 Alertmanager告警规则与邮箱通知
监控不只是看图表,告警才是发现问题的手段。kube-prometheus-stack自带一套基础告警规则,比如Pod重启次数过多、节点CPU高负载、服务Down等。查看这些规则:
kubectl get prometheusrule -n monitoring默认规则的内容可以通过查看Prometheus UI的Alerts页面看到。如果只想用默认规则,可以把Alertmanager配好通知渠道就完事。配置邮件通知的方式:
- 创建一个存放邮箱配置的Secret。
- 在values.yaml里通过alertmanager.config覆盖默认配置。
一个极简的邮件告警配置示例:
alertmanager: config: global: smtp_smarthost: "smtp.example.com:465" smtp_from: "alert@example.com" smtp_auth_username: "alert@example.com" smtp_auth_password: "your-password" route: group_by: ["alertname"] group_wait: 30s group_interval: 5m receiver: "email" receivers: - name: "email" email_configs: - to: "ops@example.com"如果你不需要告警,只想减小资源占用,安装时直接加--set alertmanager.enabled=false即可。单节点上Alermanager不是必需品,先把采集和可视化跑稳,告警后面再逐步加。
5. 常见问题与排查技巧实录
5.1 镜像拉不下来怎么办
单节点部署场景里,镜像拉取失败是发生率最高的问题。症状很典型:Pod状态一直是ImagePullBackOff,describe查看Pod发现拉镜像超时。kube-prometheus-stack涉及镜像比较多,分布在quay.io、ghcr.io、docker.io,其中quay.io在某些网络环境下连通性差。
遇到这种问题,第一反应是在节点上配置registry-mirrors,修改Docker或containerd的配置,让拉取请求走镜像加速器。以containerd为例,修改/etc/containerd/config.toml里的mirror配置,然后重启containerd。另外一个更稳妥的办法是直接把镜像重新打标签推到内网仓库,比如把quay.io/prometheus/node-exporter:v1.7.0推到你们内网的Registry,再把values.yaml里的镜像地址改掉。这两个方案都不涉及任何特殊工具,纯属常规运维手段。
5.2 监控Target显示Down
如果你的Targets页面里有一堆Down状态,别慌,逐个分析。最常见的两个:
第一个是kubelet目标Down。这个是因为K8s从1.13之后kubelet默认开启了HTTPS端口,需要证书校验。kube-prometheus-stack默认配置了ca和client证书挂载,正常情况下没问题,但如果你手工修改过证书、或者集群是某些云厂商的托管版,证书不匹配就会失败。临时解法是给kubelet ServiceMonitor加tls_config.insecureSkipVerify: true,但生产环境不建议长期关闭校验。
第二个是node-exporter目标找不到。排查思路:先看node-exporter的Pod是否Running,再看对应的Service的selector是否正确,最后看Prometheus Pod里能否解析到这个Service的DNS。如果node-exporter正常但目标状态一直Down,用kubectl logs看一下Prometheus的日志输出,里面会有详细的抓取错误信息,比瞎猜有效得多。
5.3 PVC卡在Pending
之前说过单节点集群默认没有StorageClass时PVC会Pending。判定方法:
kubectl get pvc -n monitoring如果PVC一直Pending,查看事件:kubectl describe pvc <pvc-name> -n monitoring,内容会显示no storage class。解决办法是提前创建好Local PV,并保证PV的storageClassName和PVC一致。另外要注意,Local PV有nodeAffinity约束,如果集群里有多个节点,Pod被调度到非PV所在节点就会出问题。单节点环境没有这个困扰,但为了一致性,建议给PV都写上节点亲和。
5.4 单节点资源不够怎么调优
资源不够是最现实的问题。我常用的几组调优手段:
第一,在values.yaml里把各组件的requests和limits调低。Grafana默认请求512M内存,可以降到256M;Prometheus默认请求2G,可以降到1G,配合limit内存控制在2G内。第二,缩短Prometheus数据保留时间,默认可能保留较长时间,单节点上建议retention: 7d就够了。第三,把Alertmanager关掉,如果暂时不需要告警,或者把Grafana里面那些大而全的面板砍掉几个。
调完后执行helm upgrade,滚动更新会生效。注意一点:不要一边降配一边还期望监控大量指标,监控本身也会消耗资源,关键指标够用即可,别把单节点机器弄得比监控对象还累。
最后说点个人体会。我在单节点上反复装了三次才彻底理顺整个链路,前两次都是栽在“以为装好了其实没采集到数据”这个坎上。后来我总结出一个适合新手的习惯:先不要直接上Helm一键安装,先手工用YAML部署一个最小化的Prometheus加node-exporter,弄清ServiceMonitor是怎么选择Pod的、抓取链路是怎么走的,再退回用Helm装全家桶,这时候你看values.yaml里的参数,心里会踏实很多。这套方式在单节点上验证过,稳定运行了几周没有出过问题。后面如果集群扩容成多节点,监控架构这套东西不需要推翻重来,把存储换成动态Provisioner再加个Ingress入口就能平滑迁移过去。