边缘节点上的监控经常给人一种错觉:Prometheus 明明就装在这台机器上,可抓指标还是会超时。原因在于 Prometheus 的“眼睛”并不完全在本地——它要去云端的 Kubernetes apiserver 问“有哪些 Pod 需要抓”,云边网络一抖动,这只眼睛就容易“瞎”。今天我们直接来拆解一个实用方案:让 Prometheus 通过 YurtHub 先请求本机代理,再让 YurtHub 去问云端,断网时用本地缓存兜底,再给 remote write 加上缓冲队列,避免监控写入把自己卡死。
这个方案的技术栈统一是:OpenYurt + Prometheus + YurtHub。下面所有代码都基于这套技术栈。
一、边缘监控为什么会超时
1.1 云边网络不是隔壁邻居
在数据中心里,Prometheus 和 Kubernetes apiserver 就在同一个局域网,请求很快。但边缘节点通常通过 4G、专线或者 Wi-Fi 与云端通信,网络延迟高、丢包多、抖动频繁。你让 Prometheus 在每次抓取前都去云端拉一遍 Pod 列表,它不超时才怪。
更麻烦的是,不是所有服务发现都需要“实时到云端”。边缘场景下,节点上跑着哪些 Pod,这个信息本地应该有一份。可惜默认配置下,Prometheus 的 Kubernetes 服务发现(简称 SD)会直接请求云端 apiserver,网络一断,SD 请求先超时,后面抓取全部停摆。
1.2 超时的真正原因是服务发现
先看一个典型的错误配置。很多边缘节点上的 Prometheus 是这样写的:
# prometheus.yml(错误示例:SD 直连云端)
# 技术栈:OpenYurt + Prometheus + YurtHub
scrape_configs:
- job_name: "edge-pods"
kubernetes_sd_configs:
- api_server: "https://10.0.0.10:6443" # 云端 apiserver,跨公网
role: pod
bearer_token_file: /etc/prometheus/token
这个配置放在中心机房没问题,放在边缘节点就问题很大。每次 Prometheus 做服务发现,都要先走到云端,再返回一堆 Pod 信息和端口。只要云边网络断了几秒钟,服务发现就会失败,Prometheus 自然抓不到指标,日志里全是“context deadline exceeded”或者“connection refused”。
这里顺便说一个小知识:Kubernetes 服务发现的本质就是 Prometheus 从 apiserver 拉取 Pod、Endpoints、Service 等资源对象列表。role: pod 表示它要的是 Pod 信息,拿到之后还要通过 relabel 规则生成采集目标。这个列表必须是可用的,否则后面的抓取无从谈起。边缘场景最需要的不是“每次都要最新”,而是“断网时也要有一份能用的”。
二、YurtHub 做本地代理的思路
2.1 YurtHub 在边缘节点上扮演的角色
YurtHub 是 OpenYurt 里的一个边缘节点组件。你可以把它理解成边缘节点上的“门卫”。所有需要访问云端 apiserver 的请求,都可以先到 YurtHub 那里报到,由 YurtHub 转发给云端;如果云边网络断了,YurtHub 就用本地缓存代替云端回答。
它做这些事情:
- 监听本机 IP 端口,接收 Prometheus、kubelet 等组件的请求。
- 把请求转发到云端 apiserver。
- 把 GET 请求的结果缓存到本地磁盘。
- 断网时从缓存里返回旧数据,让请求不失败。
有了它,Prometheus 的服务发现就不再直接暴露给公网了,而是访问 127.0.0.1 上的 YurtHub。
2.2 把 Prometheus 的请求先交到 YurtHub 手里
下面是一个简化版的 YurtHub DaemonSet,重点是看参数。真实环境中的证书挂载、资源限制需要按 OpenYurt 官方要求配置。
# yurt-hub.yaml(简化示例,重点看代理和缓存参数)
# 技术栈:OpenYurt + Prometheus + YurtHub
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: yurt-hub
namespace: kube-system
spec:
selector:
matchLabels:
app: yurt-hub
template:
metadata:
labels:
app: yurt-hub
spec:
hostNetwork: true # 用宿主机网络,方便 Prometheus 访问 127.0.0.1
nodeSelector:
node-role.kubernetes.io/edge: "true"
containers:
- name: yurt-hub
image: openyurt/yurthub:v1.5.0
command: ["/usr/local/bin/yurthub"]
args:
- --bind-address=127.0.0.1
- --proxy-port=10267 # YurtHub 本地代理端口
- --kube-apiserver-address=https://10.0.0.10:6443 # 改成你的云端 apiserver
- --cache-dir=/var/lib/yurthub/cache
- --enable-resource-cache=true # 开启资源缓存
- --cache-strategy=ttl # 按 TTL 管理缓存
- --cache-ttl=15m # 缓存保留 15 分钟
volumeMounts:
- name: cache
mountPath: /var/lib/yurthub/cache
- name: kubeconfig
mountPath: /etc/kubernetes
volumes:
- name: cache
hostPath:
path: /var/lib/yurthub/cache
- name: kubeconfig
hostPath:
path: /etc/kubernetes
部署完 YurtHub 后,Prometheus 的配置立刻可以改成下面这样:
# prometheus.yml(正确示例:SD 走 YurtHub 本地代理)
# 技术栈:OpenYurt + Prometheus + YurtHub
scrape_configs:
- job_name: "edge-pods"
kubernetes_sd_configs:
- api_server: "https://127.0.0.1:10267" # YurtHub 本地端口,不再跨云边网络
role: pod
tls_config:
insecure_skip_verify: true # 边缘自签证书,测试期先跳过校验
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
这样 Prometheus 每次做服务发现,都只访问本机。YurtHub 在背后和云端进行通信,并对响应进行缓存。云边网络好,数据是最新的;云边网络差,至少请求不会挂在公网上。
三、断网时自动走缓存降级
3.1 YurtHub 缓存什么
YurtHub 主要缓存 Kubernetes 资源对象,比如 Pod 列表、Endpoints、ConfigMap、Secret 等。对于 Prometheus 来说,最有用的就是 Pod 和 Endpoints 缓存。因为有了它们,Prometheus 就能生成抓取目标。
缓存是“最近一次成功的结果”。云端 apiserver 有新的变化时,YurtHub 会趁机更新;如果没机会更新,就用旧数据兜底。这在边缘场景中其实是友好的:Pod 的 IP 和端口短期内不会大变,缓存里的目标仍然是有效的。
3.2 缓存降级流程
可以模拟一下断网时的效果。在边缘节点上手动请求 YurtHub,看看它能不能返回 Pod 列表:
# 技术栈:OpenYurt + Prometheus + YurtHub
# 在边缘节点上模拟:请求 YurtHub 获取 Pod 列表
curl --max-time 3 \
-k \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
https://127.0.0.1:10267/api/v1/pods \
-w "\nHTTP状态码: %{http_code}\n"
如果之前已经成功请求过一次,那么即使现在云端已经断了,YurtHub 仍然会返回一个 200 和一个 JSON 列表。Prometheus 从里面看到 Pod 的 IP,接着就去本节点或者局域网内抓取 exporter 的指标端口。因为抓取目标就在身边,所以不会像之前那样超时。
这就是缓存降级的意义:监控流量并没有停,只是“降级”到了本地缓存。对大多数边缘监控场景来说,本地指标仍然可用,比完全中断强得多。
四、配置缓冲队列避免监控写入阻塞
4.1 remote write 为什么会被网络拖死
Prometheus 采集到指标后,一般要通过 remote write 发送到中心存储,比如 Thanos、VictoriaMetrics 或者自定义监控平台。如果中心存储和边缘节点之间的网络不稳定,remote write 会频繁重试。
Prometheus 默认的 remote write 队列容量不大,网络一卡,队列很快占满。占满之后新样本就会被丢弃。更严重的是,如果所有分片都在等待网络超时,写路径很容易进入“阻塞”状态,让人感觉 Prometheus 卡死了。
4.2 用 queue_config 给本地 Prometheus 加缓冲
Prometheus 给 remote write 提供了 queue_config,这就是我们要说的“缓冲队列”。通过调大容量、控制分片数和重试间隔,可以让边缘节点在断网时先把数据存起来,等网络恢复后再慢慢发送。
下面是一段带详细注释的配置:
# prometheus.yml 远程写片段
# 技术栈:OpenYurt + Prometheus + YurtHub
remote_write:
- url: "http://<中心存储地址>:9091/api/v1/write"
queue_config:
capacity: 10000 # 队列能容纳的样本数,默认 2500,边缘场景调大
max_shards: 20 # 最大分片数,分片越多,并发发送能力越强
min_shards: 1 # 最少分片,网络不好时不会疯狂占用资源
max_samples_per_send: 500
batch_send_deadline: 5s
min_backoff: 1s # 重试前最少等 1 秒
max_backoff: 120s # 重试前最多等 120 秒,避免频繁骚扰中心存储
retry_on_http_429: true # 中心存储限流时,自动退避重试
这段配置解决的是“写入阻塞”问题。采集线程把数据放进队列后,可以立刻去抓下一批;发送由后台分片负责。网络断开时,队列会暂存数据;网络恢复后,积压的数据会继续发送,不会反过来阻塞采集。
需要说清楚一点:YurtHub 缓存的是 Kubernetes 资源对象,不是任意指标数据。也就是说,断网时 YurtHub 负责让 Prometheus“有目标可抓”,remote write 队列负责让指标数据“有地方暂存”。两者配合才是一个完整的断网兜底方案。
五、完整配置示例
下面给出一份可以直接参考的完整 Prometheus 配置。注意把 <edge-node-name> 和 <中心存储地址> 换成你自己的环境。
# prometheus.yml(边缘节点完整版)
# 技术栈:OpenYurt + Prometheus + YurtHub
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["127.0.0.1:9090"]
# 小区里的常见方案:同节点抓 node exporter,完全不依赖云边网络
- job_name: "node-exporter"
static_configs:
- targets: ["127.0.0.1:9100"]
# 通过 YurtHub 做 Kubernetes 服务发现
- job_name: "edge-pods"
kubernetes_sd_configs:
- api_server: "https://127.0.0.1:10267" # 走 YurtHub 本地代理
role: pod
tls_config:
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_pod_node_name]
regex: "<edge-node-name>" # 改成你的边缘节点名
action: keep
remote_write:
- url: "http://<中心存储地址>:9091/api/v1/write"
queue_config:
capacity: 10000
max_shards: 20
min_shards: 1
max_samples_per_send: 500
batch_send_deadline: 5s
min_backoff: 1s
max_backoff: 120s
retry_on_http_429: true
这份配置里,Prometheus 抓取的是本地 Prometheus、本地 node-exporter,以及通过 YurtHub 发现的属于本节点的 Pod。云边网络断开时,YurtHub 的缓存让服务发现继续可用,remote write 的队列让数据不至于立刻丢失。网络恢复后,监控系统自动回到正常状态。
六、应用场景、技术优缺点与注意事项
6.1 适合的应用场景
这套方案特别适合边缘计算场景,比如工厂车间、门店、变电站、车载节点等。这些地方的网络条件不稳定,但监控又不能停。只要你需要部署 Prometheus 在边缘节点上,并且要通过 Kubernetes 服务发现来发现 Pod,YurtHub 本地代理就是一个合适的选择。
6.2 技术优缺点
优点是明显的:
- 服务发现不再依赖云边网络,抓取不再频繁超时。
- 断网时能利用缓存降级,监控数据链路上的“目标发现”部分不会崩。
- remote write 队列可以缓冲数据,避免写入阻塞导致整个 Prometheus 卡住。
- 架构上把云边流量集中到 YurtHub,便于统一控制。
缺点也要认识清楚:
- YurtHub 并不是远程写数据的缓存器,它缓存的是 Kubernetes 资源对象,不能指望它把指标数据都存下来。
- 缓存是“上一次成功结果”,如果边缘应用发生了非常大的调度变化,断网期间缓存可能与实际情况有偏差。
- 队列容量有限,如果断网时间太长,仍然可能丢数据。需要结合网络状况合理设置 capacity 和重试参数。
6.3 注意事项
- 部署 YurtHub 时要注意版本和 cluser 的 kube-apiserver 版本兼容性。
- 开启缓存后,注意本地磁盘空间。缓存目录不要写在容易被清理的临时目录中。
- Prometheus 的 token 权限要控制好,至少保证能读取 Pod 和 Endpoints。
- remote write 的队列不要盲目调大,容量太大可能导致内存占用过高,反而不稳定。
- 如果边缘节点内存有限,建议优先用 Prometheus Agent 模式运行,只采集和发送,不做规则计算和告警。
七、总结
边缘节点上的 Prometheus 抓取超时,根子往往在“服务发现依赖云端”这一环。让 Prometheus 先访问 YurtHub,由 YurtHub 代理云端 apiserver,并开启资源缓存,就能在断网时自动降级到本地缓存,保证目标列表不丢。与此同时,remote write 的 queue_config 承担了“缓冲队列”的角色,让网络恢复后的数据能慢慢追投。这套组合拳虽然不能做到绝对不丢数据,但已经能让边缘监控在恶劣网络下保持可用,不再动不动就超时卡死。
评论
围绕“边缘节点上Prometheus抓取指标总超时,如何通过YurtHub做本地代理,让监控流量在断网时自动走缓存降级,并配置缓冲队列避免监控写入阻塞”参与讨论