单节点K8s部署Prometheus监控全家桶完整指南
从一台4核8G的云服务器上把一套微服务应用用kubeadm搭成单节点K8s跑起来之后我最初是有点懒得再去碰监控这块的。觉得就一个节点Pod大不了重启一下能出多大事。结果有一次这台机器磁盘悄悄被容器日志打满整个节点直接进入NotReady状态业务全部中断我登录上去才发现问题。从那之后我决定老老实实给单节点K8s装上Prometheus把节点、Pod、K8s组件的数据全部收上来。折腾了几天踩了不少坑这篇就记录我从选型到部署再到排查问题的完整过程给同样在单节点上跑K8s的人一个可以直接照抄的方案。1. 单节点的监控需求和正规集群真不太一样1.1 什么场景下你会在单节点上跑K8s先聊一下我为什么会把K8s跑在单节点上。现在用单节点K8s的人其实不少主要分三类第一类是个人开发者拿一台云服务器搭K8s学习或者跑自己的小项目第二类是测试团队在测试环境里模拟生产集群做联调但又不愿意维护多台机器第三类像我这样业务规模本来就不大一台配置还行的服务器就能扛住用单节点K8s统一管理容器图个省事。这三类场景有个共同点机器数量少但业务重要性并不低。尤其是如果你在上面跑了定时任务、跑了对外提供服务的API节点一旦出问题损失是实打实的。单节点K8s虽然简单但正因为只有一个节点出了问题没有第二台机器帮你扛监控的必要性反而比多节点集群更高。1.2 单节点环境装监控的三个特殊约束但单节点环境装监控和正规多节点集群有本质区别主要体现在三个方面。第一是资源紧张。监控这套东西本身是要吃资源的。一套完整监控全家桶Prometheus、Grafana、Alertmanager、exporter们跑起来内存基本要占到2GB左右CPU在负载不高的时候还好但采集数据的时候会有一个明显的峰值。4C8G的机器装完K8s再装监控剩下的资源才是真正能给业务用的。第二是单点故障。这是最讽刺的地方Prometheus把整个节点都监控了但它自己也跑在这台节点上节点挂了它也跟着挂。这意味着监控系统没法在节点彻底宕机的时候主动通知你。所以单节点场景下监控的意义更多是提前发现趋势问题比如磁盘缓慢增长、内存逐步耗尽、Pod反复重启而不是等节点彻底挂掉的那一瞬间。真有高可用告警需求那是另一套方案不在本篇范围。第三是组件精简压力。多节点集群里很多默认组件是有意义的比如etcd集群监控、kube-proxy多副本但在单节点上有些组件你装完发现它永远抓不到数据或者抓到的是同一个节点的数据纯属浪费资源。安装的时候要有取舍哪些保留、哪些关掉心里要有数。2. 动手之前的三个选型决定2.1 K8s发行版kubeadm、k3s还是minikube在单节点上装K8s市面上主流有三个选择kubeadm、k3s、minikube。我最终选了kubeadm原因是它最贴近生产环境的真实表现。这里说下对比。minikube用起来确实简单一条命令就能起一个集群但它封装了太多东西很多生产环境会遇到的问题比如kubelet配置、CNI网络插件选择、证书管理它都帮你处理了用来学习K8s基础操作还行但当你准备从这个环境迁移到生产环境时会发现中间的沟壑很大。k3s虽然轻量省内存适合边缘设备但它用了一些自己的实现比如内置的traefik ingress、sqlite替代etcd当然也可以切回etcd在k3s上排查出的问题放到标准K8s环境不一定能复现。kubeadm看起来绕一些但它是官方推荐的集群搭建工具搭建出来的集群结构和生产环境完全一致你在上面装Prometheus踩到的坑、总结的经验换到任何一套标准K8s集群都能复用。这是我认为做技术选型时最看重的一点环境的一致性。2.2 Prometheus全家桶手动YAML还是Helm Chart确定用kubeadm之后下一步是决定Prometheus的部署方式。我评估过两条路一条是全手动写YAML一条是用kube-prometheus-stack这个Helm Chart。手动方式的好处是你能看到每一个资源对象长什么样对理解Prometheus各组件之间的关系非常有帮助。装完之后你对ServiceMonitor、PrometheusRule这些CRD的理解会很深。但坏处也很明显所有配置文件要自己维护升级非常痛苦尤其是Prometheus Operator本身、Grafana版本升级的时候那些CRD的兼容性问题会让你怀疑人生。kube-prometheus-stack则把Prometheus Operator、Prometheus、Alertmanager、Grafana、node-exporter、kube-state-metrics全部打包在一起默认就带了一整套开箱即用的告警规则和Grafana Dashboard。对单节点这种资源紧张的场景它还允许你通过values.yaml关闭或者降配不需要的组件。我最后选择用kube-prometheus-stack因为我知道时间应该花在理解监控逻辑和业务接入上而不是纠结每个组件该配什么参数。2.3 存储方案单节点的持久化怎么处理这个选型最容易被人忽略但对单节点来说非常关键。K8s本身不带存储默认没有StorageClass如果一个Pod写了PVC而集群里没有提供存储这个Pod会一直Pending。kube-prometheus-stack默认情况下的Prometheus和Grafana虽然用的是emptyDir也就是说Pod一删数据就没了但你想让监控数据跨Pod重启保留必须给它们配上持久化存储。单节点环境里最合适的是本地存储也就是local-path-provisioner这个方案。它会在节点上自动创建目录来模拟PV声明StorageClass之后PVC就能自动绑定到本地磁盘。听起来很简单但有一个必须提前想清楚的问题如果Pod被调度到别的节点但PV数据绑在原来节点上数据就找不到了。单节点环境没有这个问题反正只有一个节点所以local-path-provisioner反而是最适合的方案。如果你用的云服务器也可以直接挂一块云盘然后用CSI驱动但单节点场景local-path-provisioner是我验证过最简单直接的方案。3. 完整部署过程从裸机到监控面板跑起来3.1 基础环境准备我用的是一台Ubuntu 22.04的云服务器4C8G配置。首先需要把运行时和K8s组件装好。网上关于kubeadm部署的教程非常多我这里只把关键步骤和参数列一下重点讲Prometheus相关的内容。系统准备好之后需要安装containerd作为容器运行时。这里有个容易踩的坑使用kubeadm初始化的时候如果containerd的默认配置没有修改创建Pod时会一直报failed to pull image相关的错误因为默认配置里找不到sandbox_image。你需要在初始化之前执行containerd config default /etc/containerd/config.toml把SystemdCgroup改成true然后重启containerd。接下来安装kubeadm、kubelet、kubectl。如果你用国内源安装注意让apt源指向阿里云或中科大的镜像站。然后执行初始化sudo kubeadm init \ --apiserver-advertise-address你的内网IP \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12因为只有一个节点初始化完成后需要把master节点的污点去掉否则普通Pod调度不上去下面第6章会详细说这个问题。然后装一个CNI插件我用的是flannelkubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml到这里执行kubectl get nodes应该能看到节点处于Ready状态。3.2 安装Helm和配置仓库kube-prometheus-stack通过Helm部署所以需要先安装Helm。安装Helm本身很简单下载二进制解压就行。唯一要注意的是Helm版本和K8s版本的兼容性建议装最新版省的后面出现奇怪问题。curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash helm version然后添加Prometheus社区仓库并更新索引helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update用helm search repo kube-prometheus-stack能看到对应的chart版本。这里建议提前查看一下chart版本对应的K8s版本兼容范围避免装完出现API不兼容的问题。3.3 通过Helm安装kube-prometheus-stack安装时我会用一个values.yaml文件覆盖默认配置。先建一个目录保存配置文件mkdir -p ~/kube-prometheus cd ~/kube-prometheus最简安装命令kubectl create namespace monitoring helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring \ --version 最新版本号但我不建议直接这么装因为默认配置在单节点上会带来不少问题。我的values.yaml里主要改了几个地方prometheus: prometheusSpec: storageSpec: volumeClaimTemplate: spec: storageClassName: local-path accessModes: [ReadWriteOnce] resources: requests: storage: 20Gi retention: 15d resources: requests: memory: 1Gi cpu: 500m limits: memory: 2Gi cpu: 1 grafana: persistence: enabled: true storageClassName: local-path accessModes: [ReadWriteOnce] size: 5Gi adminPassword: 你自己设置的密码 alertmanager: alertmanagerSpec: storage: volumeClaimTemplate: spec: storageClassName: local-path accessModes: [ReadWriteOnce] resources: requests: storage: 5Gi这里我先假设local-path这个StorageClass已经存在了实际上需要提前安装。如果你先不配持久化也可以等装完后补。但最好一次到位。安装命令变为helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring \ --values values.yaml装完后查看Pod状态kubectl -n monitoring get pods正常情况下你会看到prometheus-operator、alertmanager、grafana、node-exporter、kube-state-metrics这几个Pod都处于Running状态。如果一直是Pending或者CrashLoopBackOff大部分原因都在第6章里你翻到那边去对照排查。3.4 访问Grafana和Prometheus UI全家桶跑起来之后最关心的问题就是怎么看数据。我这里用的方式是kubectl port-forward适合快速验证不需要改Service类型kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 3000:80 kubectl -n monitoring port-forward svc/kube-prometheus-stack-prometheus 9090:9090浏览器打开http://localhost:3000用admin和你设置的密码登录Grafana。登录后左侧菜单点Dashboards能看到Kubernetes相关的面板已经自带配置好了。打开一个节点监控面板如果曲线在跳动说明数据链路通了。如果你想长期访问可以把Prometheus的Service改成NodePort或者配置Ingress。我个人在单节点测试环境直接用NodePort毕竟就一台机器省得再折腾Ingress controller。4. 部署完之后这些东西分别是什么角色4.1 全家桶成员一览装完之后很多人看着kubectl -n monitoring get pods输出的一堆Pod有点懵我先用一张表把这些组件关系捋清楚组件职责备注prometheus-operator监听CRD自动创建/更新Prometheus和Alertmanager全家桶的调度中心prometheus指标存储、查询、抓取核心数据引擎alertmanager接收告警、做去重/分组/通知告警出口grafana可视化面板展示层node-exporter采集节点物理指标如CPU/内存/磁盘DaemonSet方式跑在每个节点kube-state-metrics采集K8s对象状态如Deployment副本数、Pod状态监控K8s自身kube-prometheus-adapter提供自定义指标API给HPA用默认安装可关单节点环境下node-exporter只跑一个实例kube-state-metrics也只有一个副本所以整体资源占用可控。4.2 Prometheus Operator在全家桶里到底干什么很多刚接触的人会把Prometheus和Prometheus Operator搞混。简单来说Prometheus本身是一个存储和查询引擎它需要配置文件告诉它去抓取哪些targets、怎么抓、告警规则是什么。而Prometheus Operator是一个控制器它把Prometheus的部署和配置变成了K8s里的一种声明式资源。举个例子你想让Prometheus抓取一个新的服务指标传统做法是去改Prometheus配置文件然后reload。而有了Operator之后你只需要创建一个ServiceMonitor对象Operator发现这个对象后会自己去修改Prometheus的配置并且告诉Prometheus重新加载。这就是为什么kube-prometheus-stack装完Prometheus就自动把所有Node、Pod、K8s组件的targets都配置好了的原因——不是自己写进去的是Operator根据CRD动态生成的。4.3 node-exporter和kube-state-metrics别搞混我见过不少新手把node-exporter和kube-state-metrics的职责混在一起总觉得它们都在采集K8s集群的数据是不是重复了。其实它们采集的数据维度完全不同。node-exporter采集的是机器的物理指标比如CPU使用率、内存使用量、磁盘IO、网络流量。它跑在宿主机上用的是宿主机的/proc和/sys文件系统。你看到Grafana面板上某台节点CPU飙到90%那个数据来源就是node-exporter。kube-state-metrics采集的是K8s对象的状态指标比如某Deployment期望副本数是3、实际可用副本数是2、某个Pod当前处于Pending状态。它监控的是K8s这个系统本身的状态而不是机器的物理状态。举个例子如果你想监控业务容量想知道某个工作负载现在有几个副本用的是kube-state-metrics的数据如果你想知道Pod跑的那台机器CPU还够不够用的是node-exporter的数据。是不是有这样一个场景你看到Pod一直是CrashLoopBackOff但节点CPU内存都正常。这时候就是kube-state-metrics帮了大忙它把Pod的状态暴露成了指标你才能做出这个Pod已经连续重启了5次的告警。5. 核心机制Prometheus怎么自动发现并监控所有组件的5.1 ServiceMonitor从手工维护targets到声明式发现在没有Prometheus Operator之前你需要在Prometheus的配置文件里手动写要抓取的目标地址。比如要监控node-exporter就得写scrape_configs: - job_name: node-exporter static_configs: - targets: [192.168.1.10:9100]问题马上就来了如果你有10台节点每台节点都要写一个targets。更麻烦的是如果某个Pod动态扩容出一个新副本你根本没法提前预知它的IP和端口。静态配置在这种动态环境里完全跟不上节奏。Prometheus Operator解决这个问题的核心思路是把targets是谁从静态配置改成了动态发现。你不再告诉Prometheus去抓这个IP而是告诉它抓所有满足这个标签选择器的Service背后的Pod。这个声明式对象就是ServiceMonitor。5.2 从ServiceMonitor到Pod的完整链路我以监控node-exporter为例把这条链路完整走一遍你就彻底明白了。首先kube-prometheus-stack会创建一个如下所示的ServiceMonitor对象apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: kube-prometheus-stack-node-exporter namespace: monitoring spec: selector: matchLabels: app.kubernetes.io/name: node-exporter endpoints: - port: http-metrics interval: 30s这个ServiceMonitor声明了两个关键信息一是selector筛选出标签为app.kubernetes.io/name: node-exporter的Service二是通过哪个端口去抓取指标。然后被选中的Service对象长这样apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/name: node-exporter spec: ports: - name: http-metrics port: 9100 targetPort: 9100 selector: app.kubernetes.io/name: node-exporter注意看这里的关联逻辑ServiceMonitor通过标签选中ServiceService通过selector选中Pod。Prometheus Operator内部的prometheus-reloader组件监听这些CRD的变化一旦发现新的ServiceMonitor就自动把对应的抓取任务加入Prometheus配置。5.3 验证自动发现是否正常部署完成后我怎么确认自动发现有正常工作方法很简单打开Prometheus UI页面点击Status菜单下的Targets你会看到一系列抓取目标包括node-exporter、kubelet、kube-apiserver等。每个target如果显示UP说明抓取正常。我见过不少人安装完成后发现targets都是DOWN尤其是kubelet相关的targets。这里有个细节要特别注意kubelet的metrics有两个端口10250是kubelet主端口10255是只读端口。新版K8s默认关闭了10255但kube-prometheus-stack的kubelet ServiceMonitor默认是通过10250去抓的这个端口需要认证所以如果你的配置里没有给Prometheus配置好认证信息kubelet的targets大概率是DOWN。kube-prometheus-stack默认会处理好这个问题但如果你用的是自己手写的配置文件就得检查一下bearer_token_file或者tls_config有没有配对。这个我在第6章里也有提到。6. 单节点部署踩过的坑和处理记录6.1 master节点污点导致组件调度不上去这是我装完K8s准备装Prometheus时遇到的第一个问题。kubeadm init完成之后master节点默认带了一个node-role.kubernetes.io/control-plane:NoSchedule的污点。意思很明确不让普通Pod调度到这个节点上。单节点环境当然只有一个节点如果不去掉这个污点所有需要调度的工作负载全部会卡在Pending状态。执行下面的命令移除污点kubectl taint nodes --all node-role.kubernetes.io/control-plane-这里的-号表示移除污点。执行完之后再用kubectl get pods -n monitoring查看之前Pending的Pod应该会陆续变成Running。如果仍然Pending再看看是不是下一节说的资源问题。6.2 Prometheus容器频繁OOM默认安装的kube-prometheus-stack给Prometheus设置的资源limits比较高但requests相对也高。在单节点4C8G的机器上Prometheus加上Grafana、Alertmanager、各exporter内存峰值能达到3GB以上。如果你同时在跑业务容器机器内存紧张的时候Prometheus容器会频繁被OOM Kill然后在CrashLoopBackOff和Running之间反复横跳。我一开始没在意直到发现Prometheus的Pod每隔几十分钟重启一次才意识到问题。排查方法是先看错误日志再用kubectl describe pod查看Last State能看到OOMKilled: true的标记。解决思路有两个一是调低Prometheus的采集频率把默认的全局采集间隔从15s改成30s或者60s二是限制Prometheus的内存使用不让它超过2GB。我当时直接把Prometheus的resources.limits.memory设置成了2Gi同时在values.yaml里调整采集间隔和保留时间把retention从默认的10天降到7天。改完之后内存占用稳定多了。6.3 kubelet指标抓取报错另一个让我排查了很久的问题是Prometheus UI里kubelet这个job的targets一直显示DOWN。错误信息一般是server returned HTTP status 401 Unauthorized。这个问题的根源在前面说过新版K8s的kubelet只监听10250端口而10250是带鉴权的。kube-prometheus-stack实际会自动创建一个叫kubelet的ServiceMonitor并且给Prometheus配置好对应的凭证。但为什么我的还会401呢后来我发现问题出在我手贱改了Prometheus的additionalScrapeConfigs在里面重新定义了一个抓取kubelet的任务覆盖了默认配置。把自定义配置删掉重新reload之后恢复正常。如果你也改了自定义配置建议检查一下是否有authorization: { type: Bearer, credentialsFile: /var/run/secrets/kubernetes.io/serviceaccount/token }这一段缺了这段就会401。6.4 开了持久化但没有StorageClassPod一直Pending这是我后来决定给Prometheus配置持久化存储时踩的坑。我在values.yaml里给Prometheus配了storageSpec但没有提前创建StorageClass结果Prometheus Pod一直卡在Pendingkubectl describe pod显示0/1 nodes are available: 1 node(s) had volume node affinity conflict。解决方法是先安装local-path-provisionerkubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml安装完成后确认default StorageClasskubectl get storageclass看到local-path后再去把values.yaml里的storageClassName改成local-path然后执行helm upgrade更新。之后Prometheus的PVC会自动创建Pod也能正常调度了。7. 告警规则和日常工作建议7.1 默认告警规则里哪些建议关掉kube-prometheus-stack装完自带了一套PrometheusRule资源里面有几百条告警规则。这些规则是按多节点生产集群的标准写的放到单节点环境里很多规则永远在触发或者根本没有意义。比如和etcd相关的告警单节点只有一个etcd实例etcdInsufficientMembers这个告警规则默认是(count(up{jobetcd}) 3)单节点永远满足条件会一直告警。还有和集群HA相关的规则比如KubeAPILatencyHigh、KubeControllerManagerDown这些在单节点环境触发阈值和意义都不同。我的做法是查看默认的告警规则把和单节点场景不匹配的规则注释掉或者直接不加载。你可以执行kubectl -n monitoring get prometheusrules找到etcd相关的规则编辑后删掉不需要的部分。宁可少一些告警也不要让告警系统每天都发一堆无关紧要的消息否则你会对告警麻木真正重要的告警反而被忽略了。7.2 我在单节点上最看重的三个指标基于这段实践单节点环境我最关心的三个指标是节点磁盘使用率、Prometheus自身内存占用、Pod重启次数。磁盘使用率用node-exporter的node_filesystem_avail_bytes查询超过80%就要注意了。Prometheus自身内存用process_resident_memory_bytes{jobprometheus}看如果长期接近limits就要考虑降采样频率。Pod重启次数用kube_pod_container_status_restarts_total做告警单节点环境Pod反复重启往往是资源不足或者配置出错的信号。这三个指标分别对应了单节点最容易触发的三类故障磁盘打满、监控系统自身崩掉、业务容器不稳定。把这三个盯住单节点K8s的日常巡检就基本覆盖到位了。7.3 给单节点折腾者的一条核心建议最后一条建议我想对同样在单节点上折腾K8s的人说生产环境如果是小规模业务单节点K8s完全可行但务必要给监控系统留足资源并且在另一个维度做一份兜底方案。什么叫兜底就是Prometheus本身挂了之后你还能收到告警比如在云服务器控制台上设置磁盘、CPU的告警或者接入一个独立的探活服务。这不算过度设计而是单节点架构下必须接受的一个现实监控系统和你监控的对象共享同一台物理机注定不是完全独立的。想清楚这一点你才能理解为什么单节点部署里告警的第一道防线往往不是Prometheus而是云平台或者外部探针。我的实际经验是把Prometheus当作精细化的排查工具把云平台基础告警当作保底两者配合才是单节点方案里最稳妥的姿势。