一、先聊聊边缘监控这事

边缘节点的监控一直是让人头大的事。尤其当你手里只有一台内存1GB的小盒子,还得让它跑业务、跑网络,再塞一个Prometheus进去,动不动就内存耗尽。更要命的是边缘网络不稳定,节点经常睡大觉,监控数据说断就断。

我这次是在OpenYurt集群里搞监控。OpenYurt这个方案很聪明,它把Kubernetes的能力延伸到边缘,但默认的监控方案在边缘场景下有点水土不服。这篇文章就记录我怎么一点一点把Prometheus在OpenYurt边缘节点上“养”活的,希望给同样在边缘挣扎的朋友一点参考。

1.1 为什么边缘节点监控让人头疼

首先,边缘节点资源太少。普通机房服务器32GB内存起步,边缘设备可能就512MB,连安装agent都费劲。Prometheus本身是内存大户,一启动就吃掉几百MB,稍微拉点指标就卡死。

其次,网络不稳定。边缘节点经常离线,或者网络抖动严重。中心机房那套“定期拉取”的方式,在边缘完全行不通——你还没拉到数据,节点已经掉线了。

第三,运维困难。边缘节点分散在各地,没法像机房一样随时登录。配置错了,改一下要跑好几个小时。所以必须设计成高度自动化的方案。

1.2 本文技术栈和整体思路

为了不让大家看乱,本文所有示例统一使用“Shell 脚本 + kubectl”这套技术栈。也就是说,所有配置我都用 heredoc 写进Shell脚本里,再用kubectl打到集群里去,这样既能看到YAML配置,又能直接执行,一举两得。

整体思路是:把Prometheus做成轻量级,能省则省;数据优先推送到中心,不靠边缘节点保存;采集目标要能跟着节点池走,别把中心节点也卷进来。

二、OpenYurt和Prometheus的基本搭档

2.1 OpenYurt是什么

OpenYurt是阿里开源的一个边缘计算平台,它基于Kubernetes,但是做了很多边缘场景的增强。核心思想是“中心管控,边缘自治”,也就是中心坏了,边缘还能自己跑。它把节点分成一个个节点池,每个池子可以有不同的调度策略。

对监控来说,OpenYurt带来的好处是:我们可以按照节点池来配置采集范围,而不是死板地按集群全局配置。同时,OpenYurt提供了边缘Pod的“单元化”部署,让监控组件可以就近部署。

但问题是,OpenYurt默认的架构里,边缘节点的kubelet仍然连接中心的kube-apiserver,一旦断网,很多状态就同步不了。Prometheus如果去抓kubelet的metrics接口,很可能因为网络问题而失败。

2.2 Prometheus常规部署在边缘的坑

常规部署,就是把Prometheus当成普通Pod,放在中心集群里,让它去抓所有节点的指标。看上去没问题,但在边缘环境下,坑一个接一个。

第一个坑:抓取超时。中心到边缘的网络延迟,动不动就几百毫秒,Prometheus默认scrape_timeout是10秒,经常超时,数据采不到。

第二个坑:资源浪费。每个节点的kubelet都暴露了10250端口的metrics,Prometheus要同时采集所有节点,在中心机器上产生大量连接和goroutine,内存直接飙红。

第三个坑:数据存储。边缘数据应该就近处理,而不是全部回传中心,不然带宽根本扛不住。

所以,单纯的“中心抓取”模式必须改。

三、资源受限下的适配优化实战

3.1 第一步:压缩Prometheus的资源占用

要限制Prometheus的内存,最直接的办法是减少采集量,然后限制container的硬上限。我们可以在Prometheus的启动参数里调整,也可以在配置里改采集频率。

先看一个基础的配置文件。技术栈:Shell 脚本(kubectl + ConfigMap)。

# 技术栈:Shell 脚本
# 示例:一个精简过的Prometheus配置文件,适合边缘小内存节点

cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 60s          # 采集间隔拉长到60秒,原来默认15秒,大幅减少连接和内存
      scrape_timeout: 15s           # 超时放宽到15秒,因为边缘网络慢
      evaluation_interval: 60s      # 告警规则评估也慢一点,省CPU

    scrape_configs:
      # 只采必需指标,去掉大量无关的target
      - job_name: 'kubelet'
        metrics_path: /metrics
        scheme: https
        tls_config:
          insecure_skip_verify: true
        static_configs:
          - targets: ['127.0.0.1:10250']   # 注意:这里是示意,实际要用节点地址
        relabel_configs:
          - source_labels: [__address__]
            target_label: instance
            regex: '(.*):.*'
            replacement: '${1}'
EOF

看到没,scrape_interval 从15秒改到60秒,一下子就少了四分之三的请求。内存占用也随之降下来。如果你有100个节点,原来每个节点每分钟抓4次,现在抓1次,效果非常明显。

然后,我们给Prometheus设置Pod资源上限。技术栈:Shell 脚本。

# 技术栈:Shell 脚本
# 示例:给Prometheus加资源限制,防止它把边缘节点内存吃光

cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.50.0
          imagePullPolicy: IfNotPresent
          args:
            - --config.file=/etc/prometheus/prometheus.yml
            - --storage.tsdb.retention.time=12h     # 只保留12小时数据,别占太多磁盘
            - --storage.tsdb.retention.size=2GB     # 磁盘上限2GB,防止数据无限膨胀
            - --storage.tsdb.min-block-duration=30m # 少量数据尽快落盘,防止内存累积
            - --storage.tsdb.max-block-duration=1h
            - --web.enable-lifecycle
          resources:
            requests:
              memory: "256Mi"      # 请求256MB,给调度器做参考
              cpu: "100m"
            limits:
              memory: "512Mi"      # 硬限制512MB,超出就重启,总比OOM杀进程强
              cpu: "500m"
          volumeMounts:
            - name: config
              mountPath: /etc/prometheus
            - name: data
              mountPath: /prometheus
      volumes:
        - name: config
          configMap:
            name: prometheus-config
        - name: data
          emptyDir: {}             # 边缘环境建议用本地磁盘,别用网络存储
EOF

注意--storage.tsdb.min-block-duration这个参数很关键。Prometheus默认会先在内存里攒数据,攒够2小时才写盘。边缘设备内存小,等不到2小时就爆了。把最小block时间改成30分钟,数据能更快落盘,内存压力小很多。

3.2 第二步:用Remote Write把数据传到中心

边缘节点上的Prometheus只负责本地采集,不需要承担长期存储。我们可以用Remote Write功能,把数据推送到中心机房的Prometheus或者对象存储。这样边缘节点就算挂掉,数据也已经送出去了,不会丢。

这里有一个重要配置。技术栈:Shell 脚本。

# 技术栈:Shell 脚本
# 示例:配置Remote Write,边缘Prometheus把数据推送到中心集群

cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-remote-write
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 60s
      scrape_timeout: 15s

    remote_write:
      - url: "http://prometheus-central.example.com:9090/api/v1/write"
        queue_config:
          capacity: 2500            # 队列长度,别太大,防止内存涨
          max_shards: 4             # 并发分片数,边缘网络带宽有限,4个够了
          min_shards: 1
          max_samples_per_send: 500 # 每次发送的样本数,减少HTTP包大小
          batch_send_deadline: 10s
        # 如果中心需要认证,可以加basic_auth
        basic_auth:
          username: "edge"
          password: "edgepass"

    scrape_configs:
      - job_name: 'node-exporter'
        static_configs:
          - targets: ['localhost:9100']
EOF

注意看max_shardsmax_samples_per_send。默认值在中心机房没问题,但在边缘,网络慢,分片太多反而容易堆积内存。保守设置,宁可多等几秒,也不能把Prometheus压垮。

另外,Remote Write是异步的,它会把数据先放到内存队列里。如果中心一直连不上,队列会越积越长。所以一定要给Prometheus设置资源限制,否则一个断网就能把内存吃光。

3.3 第三步:针对OpenYurt节点池的采集配置

OpenYurt的节点池很厉害,我们可以按池子来配置采集目标。比如,有一个节点池叫“beijing”,里面跑的是摄像头识别服务,我们只需要监控这些节点的CPU、内存、磁盘就够了,没必要采集整个集群。

在Prometheus里,我们可以用kubernetes_sd_configs来做服务发现,然后通过__meta_kubernetes_node_label来筛选节点池。技术栈:Shell 脚本。

# 技术栈:Shell 脚本
# 示例:基于OpenYurt节点池标签的采集配置

cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-node-pool
  namespace: monitoring
data:
  prometheus.yml: |
    scrape_configs:
      - job_name: 'node-exporter-opensource'
        kubernetes_sd_configs:
          - role: node
        relabel_configs:
          # 只保留属于某个节点池的节点,这里用标签 openyurt.io/pool 来匹配
          - source_labels: [__meta_kubernetes_node_label_openyurt_io_pool]
            regex: 'beijing'
            action: keep
          # 替换metrics路径,让Prometheus从node-exporter抓数据
          - source_labels: [__meta_kubernetes_node_name]
            target_label: __address__
            replacement: '${1}.svc.cluster.local:9100'
EOF

注意kubernetes_sd_configs需要Prometheus有对应的RBAC权限,能够读取节点信息。如果你的Prometheus运行在边缘节点上,还要保证它能连上kube-apiserver。

如果不想用服务发现,也可以用file_sd_configs,直接配置一个目标文件。但那个文件只能写死节点IP,节点池扩容时更新麻烦。所以我更推荐用服务发现,配合OpenYurt的节点池标签,动态调整,非常方便。

四、踩坑记录与排查过程

4.1 坑一:节点掉线后数据断流

一开始,我部署好后,发现只要节点一离线,Prometheus就疯狂重试连接,每次重试都要等好几个超时周期,日志里全是context deadline exceeded。这其实不是数据断流,是Prometheus在等连接超时。节点离线本来就不该一直重试。

解决办法是把scrape_interval调大,同时把scrape_timeout缩短。比如,采集间隔60秒,超时10秒,这样即使节点离线,也只在每个周期内重试一次,不会持续占资源。另外,可以打开honor_timestamps: true,让数据带上原始时间戳,这样节点恢复后,数据能正确按时间排序。

4.2 坑二:TSDB碎片爆炸

第二件让我头疼的事,是Prometheus的TSDB文件碎片越来越多。边缘节点断电频繁,TSDB的block文件容易损坏。后来我查了文档,发现需要用tsdb命令来清理。如果你不想手动操作,可以在启动参数里加上--storage.tsdb.retention.size,限制总大小。当超过限制时,Prometheus会自动删除最老的block,而不是让碎片堆积。

另外,--storage.tsdb.wal-compression这个参数默认是启用的,但如果你用的版本比较老,可能没有开。WAL压缩能显著减少磁盘占用。建议显式地加上这个参数。先看看当前TSDB的block情况。技术栈:Shell 脚本。

# 技术栈:Shell 脚本
# 示例:查看TSDB中block的占用情况,判断碎片程度
kubectl -n monitoring exec deploy/prometheus -- ls -lh /prometheus/tsdb/

如果发现block数量特别多,并且单个block文件很小,就说明碎片化了。优化方案就是给Prometheus加上--storage.tsdb.min-block-duration=30m--storage.tsdb.max-block-duration=1h,具体参数可以参考前文3.1节中的完整Deployment示例。另外,--storage.tsdb.retention.size=2GB一定要加上,这样当总数据量超过2GB时,旧block会被自动清理,整个目录就不会无限膨胀。

4.3 坑三:Docker指标采集权限问题

边缘节点上用Docker跑容器,想从/metrics采集Docker监控指标,结果报权限错误。这是因为Docker的socket文件权限是root:root,Prometheus进程不是root用户就访问不了。

解决办法有两种:一是把Prometheus的securityContext改成privileged,但这样不安全;二是挂载Docker socket时,把宿主机的用户组映射进去。我推荐第二种,更安全。但在快速验证时,为了方便,我直接用了root。技术栈:Shell 脚本。

# 技术栈:Shell 脚本
# 示例:通过hostPath挂载Docker socket,并配置securityContext
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
spec:
  template:
    spec:
      securityContext:
        runAsUser: 0          # 用root运行,方便读socket(示例,生产环境慎用)
      containers:
        - name: prometheus
          image: prom/prometheus:v2.50.0
          volumeMounts:
            - name: docker-sock
              mountPath: /var/run/docker.sock
      volumes:
        - name: docker-sock
          hostPath:
            path: /var/run/docker.sock
EOF

注意,用root运行有风险,如果你有更好的办法,比如给Prometheus挂一个专用的组,或者用sidecar把socket代理成http接口,那就更好了。我这个是拿来在测试环境快速验证的。

五、应用场景与优缺点分析

5.1 适合场景

这套优化方案特别适合以下几种情况:

  • 边缘节点内存小于1GB,资源紧张,装不下完整版Prometheus。
  • 边缘节点数量多,并且分散在多个地区,网络质量参差不齐。
  • 要求既有本地缓存,又能把数据汇总到中心做统一分析。
  • 已经在用OpenYurt做边缘集群管理,希望监控组件完全融入节点池调度。

5.2 技术优缺点

好处很明显:省内存,因为采集频率降了,还限制了存储;数据不丢,因为Remote Write能把数据推出去;配置灵活,能按节点池采集。

缺点也有:采集频率降低,意味着监控的粒度变粗,有些瞬时尖峰可能捕捉不到;Remote Write依赖网络,如果中心服务不可用,本地缓存压力会增大;Prometheus单机架构在高可用方面有短板,边缘节点上如果Prometheus挂掉,这一节点就失去监控。

另外,边缘节点如果长时间离线,本地Prometheus的Remote Write队列会堆积,内存和磁盘都可能被撑爆。要解决这个,需要调整queue_config,并且定期检查队列长度。

六、注意事项与总结

6.1 注意事项

第一,别把Prometheus的所有组件都堆在同一个边缘节点上。如果你既要采集,又要告警,还要远程写,那就需要多个进程,建议拆开,或者用轻量级的agent模式。

第二,OpenYurt的节点池标签命名有规则,一定要检查你的openyurt.io/pool标签确实存在。可以用kubectl get nodes --show-labels查一下。

第三,远程写入的中心端点必须稳定,最好前面加一组负载均衡,否则中心挂了,边缘数据全部积压。

第四,一定要设置好storage.tsdb.retention.size,否则磁盘慢慢被填满,节点会挂。

第五,升级Prometheus版本前,先看看TSDB格式有没有变化。边缘节点没法快速回滚,出问题很麻烦。

6.2 总结

把Prometheus弄到OpenYurt边缘节点上,核心就八个字:少采、快传、限存、自治。少采,是降低采集频率和指标数量;快传,是用Remote Write把数据及时推走;限存,是限制内存和磁盘占用;自治,是让边缘Prometheus在网络断开时也能独立工作。

我这一路踩了不少坑,但最终跑通的时候,看着只有256MB的节点上安稳运行的Prometheus,心里还是很舒坦的。资源受限不是绝路,只要好好调一调,Prometheus照样能在边缘发光发热。希望这篇文章能让你少走几步弯路。