1. 为什么我最后还是用 Docker 跑了 Prometheus监控系统的选型这件事圈子里讨论过很多轮了。市面上的方案不少Zabbix、Open-Falcon、Nightingale各有各的忠实用户。但如果你问我个人项目或者中小团队起步最省心的组合是什么我的答案一直是 Prometheus。别急着争论我先把话说清楚不是 Prometheus 完美而是它在“采集、存储、告警、可视化”这条链路上足够标准生态足够大社区里踩坑的人够多你遇到问题基本都能找到答案。但 Prometheus 本身部署起来有个让人头疼的地方它依赖一堆组件。光一个 Prometheus server 还不够通常你得配 node_exporter 采集主机指标配 Grafana 做可视化后面可能还要加 Alertmanager 处理告警。每一样单独装一遍环境依赖、版本兼容、配置文件路径、systemd 服务管理这些琐碎的事情会消耗掉你大量本来应该用在业务上的精力。尤其当你手上有好几台机器要监控的时候重复部署这件事会让人很崩溃。我后来把 Prometheus 全家桶都塞进 Docker 里跑这个过程里踩过不少坑也理顺了很多思路。用容器跑监控系统最大的好处不是“装起来快”这么简单而是你把整个监控环境变成了一段可以版本化管理、可以随时推倒重建的代码。配置文件挂载在宿主机上容器本身是无状态的坏了删掉重新起一个就是不存在“这台机器被我搞坏了不敢动”这种心理负担。而且 Docker 的方式天然契合 Prometheus 的抓取模型。Prometheus 是拉模式它会主动去每个 exporter 的 HTTP 端点抓数据。当你用 Docker 管理 exporter 时每个 exporter 都是独立的小容器IP 可能会变但没关系只要你在 docker run 的时候指定好端口映射或者干脆用 docker compose 把整个监控栈串在一个自定义网络里服务发现这件事就变得非常优雅。这篇文章我打算从头到尾记录一遍我实际的操作过程包括目录规划、配置文件怎么写、容器怎么起、告警怎么接以及我遇到过的一些奇怪问题。如果你正准备搭一套自己的监控系统或者已经在裸机上跑 Prometheus 但想迁移到容器环境这篇应该能帮你省下不少时间。2. 先想清楚要监控什么再决定怎么部署很多人上来就急着执行 docker run结果跑到一半发现目录挂载错了、配置文件写错了、exporter 忘了起然后开始怀疑是不是 Docker 方案有毛病。其实问题不在 Docker在于你根本没想清楚自己到底要监控什么。2.1 明确监控对象和采集目标在动手之前先花十分钟问自己三个问题第一监控的对象是什么。如果是监控一台 Linux 服务器本身你需要 node_exporter它采集 CPU、内存、磁盘、网络、文件系统这些基础指标。如果是监控 Docker 容器本身的运行状态——比如容器CPU占用、内存限制、网络收发——那需要 cadvisor它专门采集容器级别的指标。如果你要监控的是某个应用比如 MySQL、Redis、Nginx那就各自有对应的 exporter比如 mysqld_exporter、redis_exporter、nginx_exporter。第二数据要存多久。Prometheus 默认只保留 15 天的数据如果你觉得不够需要在启动参数里改 --storage.tsdb.retention.time。我个人的习惯是个人项目存 30 天团队项目存 90 天再长的历史直接接对象存储或者 Thanos但那是后话。第三告警要不要配。如果你只是想在浏览器上看着舒服那 Grafana 配个面板就够了。但如果你希望系统出问题时能第一时间收到通知那 Alertmanager 是必须的。它支持邮件、企业微信、钉钉、Slack 这些渠道。把这三个问题想清楚你的部署方案基本就定型了。我自己常用的组合是Prometheus server node_exporter cadvisor Grafana Alertmanager也就是一台机器上监控宿主机资源和容器运行状态数据都能看到异常能告警面板还算漂亮。2.2 选择 docker run 还是 docker compose这个问题我纠结过一阵子。对于只有 Prometheus server 一个容器的情况docker run 完全够用一条命令就起来了没什么好说的。但当你需要同时管理 5 个以上容器的时候docker run 的劣势就非常明显了——你得自己记住端口映射关系自己维护网络自己保证启动顺序。docker compose 的价值在于它把整个监控栈的编排逻辑写在一个 YAML 文件里所有服务共享一个自定义网络服务名就是主机名互相访问不需要查 IP。比如 Prometheus 要抓 node_exporter 的指标直接写 node-exporter:9100 就行不用关心这台机器实际分配的 IP 是多少。另外docker compose 对配置文件的维护也更友好。你可以把 prometheus.yml、alertmanager.yml 这些配置文件放在同一个目录下compose 文件里用相对路径挂载整个目录可以扔进 Git 仓库做版本管理。环境出问题了git checkout 回到上一个可用版本就行。所以我给你的建议是单机单容器用 docker run多组件监控体系直接用 docker compose。没有中间选项不要想着用 docker run 一条条敲敲到第五个命令你一定会后悔。2.3 目录规划把配置和数据分离部署之前先把宿主机上的目录结构定好。我踩过一次坑是图省事把配置文件直接放在数据目录里后来想清理数据重建的时候配置文件也被一起删掉了。所以目录一定要分离配置归配置数据归数据。我自己常用的目录结构是这样的/opt/monitor ├── docker-compose.yml ├── prometheus │ ├── prometheus.yml │ └── alert_rules.yml ├── alertmanager │ └── alertmanager.yml ├── grafana │ ├── grafana.ini │ └── provisioning └── data ├── prometheus ├── alertmanager └── grafana从这套结构能看出来所有配置文件放在各自的子目录下好维护数据统一放在 data 下面需要备份或者清理的时候直接对 data 目录操作就行了不用小心翼翼地区分哪个文件重要哪个不重要。3. 核心配置文件拆解prometheus.yml 和告警规则配置文件是这次部署里最核心的部分也是决定这套监控系统能不能正常工作的关键环节。我见过不少人部署成功后发现页面上一个数据都没有百分之七八十都是配置里出了问题。3.1 prometheus.yml 的关键字段prometheus.yml 的结构主要由 global、alerting、rule_files、scrape_configs 四部分组成。我直接贴一份我自己在用的配置然后逐段解释global: scrape_interval: 15s evaluation_interval: 15s external_labels: monitor: my-monitor alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093 rule_files: - /etc/prometheus/alert_rules.yml scrape_configs: - job_name: prometheus static_configs: - targets: [localhost:9090] - job_name: node-exporter static_configs: - targets: - node-exporter:9100 - cadvisor:9100 - job_name: cadvisor static_configs: - targets: - cadvisor:8080global 段里scrape_interval 表示 Prometheus 每隔多久去抓一次指标默认是 15 秒。这个参数看着不起眼但直接影响你的数据精度和存储开销。如果业务对实时性要求高可以调到 5 秒但相应的数据量会变成三倍。对于一般服务器监控来说 15 秒绰绰有余。evaluation_interval 表示告警规则多久评估一次同样设 15 秒就行。external_labels 是给所有指标打上额外的标签当你后续对接 Thanos 或者多套 Prometheus 时这个标签就用来区分数据来源。rule_files 指向告警规则文件。这里注意路径是容器内的路径不是宿主机路径。因为配置文件挂载进容器后Prometheus 进程看到的就是容器内的路径后面讲 docker compose 的时候我会再强调一遍这个细节。scrape_configs 部分就是真正定义要抓什么的地方。每个 job 代表一类采集任务targets 是采集目标的地址列表。这里有一个很关键的点在 docker compose 网络里服务名就是可解析的主机名。所以在 compose 文件里我给服务起的名字是 node-exporter那么配置里就写 node-exporter:9100。这个机制让配置不用关心容器实际的 IP只要服务名和端口对得上就一定能抓到数据。3.2 告警规则文件怎么写才不容易炸alert_rules.yml 用来定义告警规则。我见过有人一开始就写了一堆规则结果上线第一天告警轰炸第二天就把告警全部静音了。告警规则要克制刚开始宁可少配也不要贪多。一份比较稳妥的入门规则文件长这样groups: - name: host-rules rules: - alert: HostHighCPU expr: 100 - (avg(rate(node_cpu_seconds_total{modeidle}[5m])) * 100) 85 for: 5m labels: severity: warning annotations: summary: Host CPU high description: CPU usage has been above 85% for 5 minutes. - alert: HostDiskAlmostFull expr: (1 - (node_filesystem_free_bytes{mountpoint/} / node_filesystem_size_bytes{mountpoint/})) * 100 85 for: 10m labels: severity: warning annotations: summary: Disk almost full description: Disk usage on root mount is above 85%.写告警规则的三个要点第一表达式里尽量用 rate 函数而不是直接拿计数器本身。比如 node_cpu_seconds_total 是一个累计值你直接查它看不出当前 CPU 使用率必须通过 rate 计算单位时间内的增量。第二for 子句一定要用。它的含义是“这个条件持续多久才触发告警”设置成 5m 能滤掉 CPU 瞬时飙升的系统抖动避免告警疲劳。没有 for 的告警规则基本等于给自己找麻烦。第三labels 里的 severity 字段建议从第一天就规范起来。后面接 Alertmanager 做路由分发时会按照 severity 决定走哪些通知渠道。前期不规范后期全得返工。3.3 Alertmanager 的通知配置Alertmanager 的作用是接收 Prometheus 推送过来的告警事件然后按路由规则决定发给谁。它的配置文件风格和 prometheus.yml 完全不同用 route 和 receiver 两个核心概念。先看一份最简配置route: group_by: [alertname] group_wait: 10s group_interval: 10s repeat_interval: 1h receiver: email-alert receivers: - name: email-alert email_configs: - to: opsexample.com from: monitorexample.com smarthost: smtp.example.com:465 auth_username: monitorexample.com auth_password: your-passwordgroup_by 表示告警按什么关键字分组同一个组内的告警会合并成一条通知否则一个突发故障触发多条告警你的邮箱会被瞬间塞满。repeat_interval 是重复通知的间隔。假设某台机器宕机了告警不会只发一次而是每隔一段时间重新提醒一次。设成 1h 比较合适太短是折磨太长容易把故障忘掉。你如果不想配邮件也可以直接用 Webhook 方式把告警推到钉钉或者企业微信的机器人地址。我后来实际部署时用的是 Webhook 方式因为邮件配置麻烦而且容易进垃圾箱。但无论哪种方式Alertmanager 的 route 和 receiver 逻辑是一样的先把这套逻辑搞懂后面接任何渠道都只是改 receiver 的配置而已。4. docker-compose 编排实战从零搭起整套监控栈配置文件理顺了下面才是真正动 docker compose 的时候。这里我会把每一步参数为什么这么写说明白避免你看着别人抄了一段 YAML 却没搞懂含义。4.1 docker-compose.yml 完整解析先贴出我实际在用的 docker-compose.ymlversion: 3.8 networks: monitor-net: driver: bridge volumes: prometheus-data: grafana-data: services: prometheus: image: prom/prometheus:v2.53.0 container_name: prometheus restart: always volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml:ro - prometheus-data:/prometheus command: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.path/prometheus - --storage.tsdb.retention.time30d ports: - 9090:9090 networks: - monitor-net node-exporter: image: prom/node-exporter:v1.8.2 container_name: node-exporter restart: always pid: host volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - --path.procfs/host/proc - --path.sysfs/host/sys - --path.rootfs/rootfs ports: - 9100:9100 networks: - monitor-net cadvisor: image: gcr.io/cadvisor/cadvisor:v0.49.1 container_name: cadvisor restart: always volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro - /dev/disk/:/dev/disk:ro ports: - 8080:8080 networks: - monitor-net grafana: image: grafana/grafana:11.0.0 container_name: grafana restart: always volumes: - grafana-data:/var/lib/grafana ports: - 3000:3000 networks: - monitor-net environment: - GF_SECURITY_ADMIN_USERadmin - GF_SECURITY_ADMIN_PASSWORDadmin123 alertmanager: image: prom/alertmanager:v0.27.0 container_name: alertmanager restart: always volumes: - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - alertmanager-data:/alertmanager command: - --config.file/etc/alertmanager/alertmanager.yml ports: - 9093:9093 networks: - monitor-net逐段拆开讲。network 部分我建了一个独立的 bridge 网络 monitor-net所有服务都挂在这个网络上。这样它们之间就能用服务名互相访问Prometheus 配置里的 alertmanager:9093 就是靠这个网络解析的。强烈建议不要跟宿主机已有的容器混用网络隔离能避免很多端口冲突和服务名解析的意外。volume 部分我用了两种方式。一种是把宿主机目录挂载进容器用于配置文件另一种是命名卷用于数据持久化。配置文件类型挂载做了 :ro 只读限制防止容器里误修改挂载的配置文件。数据卷用命名卷的好处是数据由 Docker 管理备份、迁移都方便而且容器删除重建后数据还在。prometheus 服务里command 覆盖了默认启动参数。--storage.tsdb.path/prometheus 指定数据存放在容器内的 /prometheus正好对应挂载的命名卷 prometheus-data。--storage.tsdb.retention.time30d 把数据保留时间从默认的 15 天延长到 30 天。这一步提醒一下我建议在 compose 文件里哪怕用默认值也显式写出来因为这些参数直接决定运维策略别人看你的 compose 时不用再翻文档猜。node-exporter 这一段有几个容易被忽略的细节。pid: host 让 node-exporter 能共享宿主机的 PID 命名空间这样才能看到宿主机上所有进程的资源占用。然后挂载了宿主机的 /proc、/sys 和根目录并在 command 里指定对应的 path 参数。cadvisor 需要挂载的目录更多因为这些目录是它采集 Docker 容器运行时信息的来源。如果这几个目录缺了cadvisor 启动会报错或者在页面上能看到容器列表但拿不到具体的某项数据。grafana 的环境变量只设了管理员账号和密码。初次部署先用默认的 admin/admin123 进去登录后第一件事就是改密码这个建议我会在后面再强调一遍。4.2 数据持久化为什么要用命名卷数据持久化这部分是一个很容易被忽视的坑。我见过有人直接把宿主机目录挂载给 Prometheus 的数据目录看起来没问题但时间一长就会发现两个问题。第一是文件权限问题。容器内的进程通常以普通用户运行而宿主机目录的所有者未必匹配。如果挂载目录的属主是 root容器内进程可能没权限写入装好的 Prometheus 启动一两秒后就会因为 TSDB 无法写入而退出。你用 docker logs 能看到一堆权限报错。第二是 IO 性能问题。TSDB 的写入模式是高频率的随机写直接落在宿主机磁盘上如果用的是不适合小文件高并发读写的文件系统性能会有明显劣化。命名卷在 Docker 默认配置下由 Docker 管理虽然底层还是宿主机文件系统但它的挂载方式和性能表现更可控一点而且在 Docker Desktop 这类环境里命名卷是唯一可靠的选择。如果你确实需要把数据落在宿主机某个目录上比如想直接拿备份脚本去 copy那就注意挂载到 ./data/prometheus 这种专门建好的目录并在部署前设置好属主mkdir -p /opt/monitor/data/prometheus chown 65534:65534 /opt/monitor/data/prometheus65534 是 nobody 用户的 UIDPrometheus 官方镜像默认以这个用户运行属主不对一定会遇到写入权限的问题。4.3 启动顺序与依赖管理compose 文件里我没有写 depends_on因为监控系统这几个服务之间其实没有硬性的强启动顺序。Prometheus 不会因为 Alertmanager 还没起就跑不起来它顶多是在告警通知临时不可用的时候重试。不过我这里想分享一个经验生产环境里还是建议加 depends_on不是为了解决启动顺序问题而是为了让日志输出更干净避免容器同时启动时端口还没就绪别的服务已经在尝试连接。grafana: depends_on: - prometheus严格来说depends_on 只控制启动顺序不保证依赖的服务已经完全就绪。有追求的话可以配置 healthcheck 加 condition: service_healthy但对这套监控栈来说这个复杂度没有必要。监控系统的组件相对独立告警连接失败会自动重试不会造成数据丢失。执行启动docker compose up -d等所有容器都进入 running 状态再用 docker compose ps 看一眼启动情况。如果某些容器反复重启先看日志docker logs prometheus --tail 100 docker logs node-exporter --tail 1005. 实操中躲不过去的坑与排查思路运行起来只是开始后面才是真正考验耐心的阶段。这里我把实操中遇到的问题和排查方法完整记录一下基本覆盖了大多数部署初期的故障类型。5.1 机器负载曲线异常的排查Prometheus 界面打开节点监控一栏里能看到 CPU 和内存曲线。如果你发现 node-exporter 抓到的 Load Average 总是异常偏高但你在宿主机上用 top 看负载其实很低那大概率是容器把 /proc 的挂载搞错了。node-exporter 默认读取容器内的 /proc 文件而不是宿主机的。容器内的 /proc 反映的是容器自身看到的进程和资源状态。因为容器内有自己的 PID 命名空间所以你让它去读容器内部的 /proc/stat它计算出来的 CPU 使用率当然只跟容器内的活动有关而容器内其实只有一个 node-exporter 进程指标自然不正常。解决办法就是我前面配置里的那些参数。把宿主机的 /proc、/sys 挂载进容器然后在 command 里明确指定 --path.procfs 和 --path.sysfs告诉 node-exporter 去读宿主机真实的数据源。这里我把排查步骤完整列一下你去操作的时候对着查就行首先到 Prometheus 的 Targets 页面确认 node-exporter 的采集状态是 UP。点进去查看采集到的样本如果指标能查到但数值奇怪那就检查容器的挂载命令确认 /proc 和 /sys 是否来自宿主机。然后直接进容器内部验证一下docker exec -it node-exporter sh cat /proc/loadavg在容器里看到的 /proc/loadavg 应该和宿主机上 cat /proc/loadavg 的输出一致如果你原来安装过 node_exporter 二进制直接对比也行。如果数值不一致那就是挂载配置或者 path 参数写错了。再用docker inspect node-exporter看一下 Mounts 部分确认源路径确实指向了 /proc、/sys 和 /而不是容器内部的路径。5.2 Targets 页面里出现 DOWN 的排查Prometheus 的 Targets 页面是排障的起点。如果你能看到某个 job 下有 target 的状态是 DOWN那说明 Prometheus 确实解析到了配置里的地址但连不上。这时候别去怀疑 Prometheus 本身直接按层排查。第一步确认目标容器是否正常启动。用 docker ps 看状态反复重启或者 Exited 的容器先解决它自己的问题。第二步确认端口是否映射正确。比如 node-exporter 的容器端口是 9100宿主机端口也指定了 9100那 Prometheus 在容器内部访问 node-exporter:9100 应该没问题。我遇到过一次比较隐蔽的情况是 exporter 容器启动时端口被占用Docker 没有报错但容器内部的服务监听的不是预期的那个端口。用 docker logs 看一下 exporter 的启动日志能看到“Listening on”之类的输出确认监听的端口和你配置的 targets 保持一致。第三步检查容器网络。prometheus 容器和 exporter 容器必须处于同一个自定义网络内才能通过服务名访问。如果你把某个 exporter 容器放在了默认网络里Prometheus 这边指定服务名解析不到target 就会一直 DOWN。最简单的验证方法是在 prometheus 容器里执行docker exec -it prometheus sh ping node-exporter能 ping 通说明网络没问题ping 不通检查 compose 文件里两个服务的 networks 配置是否一致。还有一种情况不太常见但是很容易让人懵Prometheus 的容器网络配置没问题target 也是 UP但页面上看不到任何指标。这种问题十有八九是采集路径不对。默认的 exporter 指标路径是 /metrics但如果某些自定义 exporter 的路径改成别的地方了你就必须在 scrape_configs 里用 metrics_path 指定- job_name: custom-app metrics_path: /custom/metrics static_configs: - targets: [custom-app:8080]5.3 容器重启后数据丢失的问题这个问题在测试环境尤其常见。Prometheus 里配置好了数据和面板重启容器后发现一切回到解放前啥都没了。排查思路很简单看你 compose 文件里 Prometheus 的 volumes 是怎么写的。如果只写了配置文件挂载没写数据卷挂载那数据当然存在容器可写层里容器一删就没了。Docker 官方甚至有个很形象的表述容器可写层与容器同生命周期容器没了这层数据也跟着蒸发。正确做法就是用命名卷或宿主机目录把 Prometheus 的 /prometheus 目录持久化出来。这里我要特别强调的是测试环境凑合过去就算了生产环境一定把数据卷挂在显眼的位置并且定期备份。备份的命令也顺带分享一下其实就是在 Prometheus 不停机的情况下对数据卷做一次快照式的复制docker run --rm -v prometheus-data:/prometheus -v /opt/backup:/backup alpine tar czf /backup/prometheus-data.tar.gz /prometheus这个命令把 prometheus-data 卷打包成 tar 文件放到宿主机 /opt/backup 下。恢复的时候反过来解包就行。Prometheus 的 TSDB 在运行中会产生很多临时文件直接 cp 会有文件不一致的风险但打包工具层面用 tar 是可以接受的我自己实际还原测试是能正常启动的。5.4 告警通知收不到的问题数据都正常采集了告警规则也生效了但就是收不到通知这个问题的排查路径相对固定。先在 Prometheus 的 Alerts 页面看告警状态。如果状态是 pending说明条件满足了但还没有持续够 for 字段指定的时间。等够时间它会变成 firing这时候如果 pending 状态卡住不动优先检查 for 的时间是否设得太长。如果状态已经是 firing但通知没发出去那问题出在 Alertmanager 这一侧。看 Alertmanager 的日志docker logs alertmanager --tail 50日志里如果出现 Notification failed 或者 SMTP 相关的错误那就检查 smarthost、auth 账户这些配置。我在配置邮箱告警时踩过一次坑SMTP 端口写成了 25但服务器要求使用 SSL 加密的 465 端口导致一直发不出来。后来换成 465问题就解决了。如果你是走 Webhook 方式调第三方机器人同样先看 Alertmanager 的日志。一般出问题就是 Webhook 地址配置错了或者接收方接口要求 POST 的 JSON 格式跟默认不兼容。这个情况下可以在 Alertmanager 配置里自定义发送模板但刚上手阶段先用默认模板跑通链路再考虑定制。6. Grafana 可视化与数据源接入Prometheus 本身的 UI 只适合临时看一眼数据和排查问题真正做日常监控看板必须接 Grafana。它跟 Prometheus 搭配可以说是绝配因为它原生支持 PromQL 查询不需要额外写数据库中间层。6.1 添加 Prometheus 数据源Grafana 启动后浏览器打开 http://服务器IP:3000用你设置的 admin 账号登录。第一次登录会要求改密码这一步别跳过监控系统如果被人拿到控制权比你业务系统被入侵还麻烦。数据源添加的路径是 Configuration - Data Sources - Add data source找到 Prometheus 那一项URL 一栏填什么要看你的部署方式。如果你是 docker compose 部署Grafana 和 Prometheus 在同一个自定义网络里那直接填 http://prometheus:9090 就行。注意这里一定是服务名而不是 localhost。这个错误太常见了——在 Grafana 容器内部localhost 指的是 Grafana 容器自己Prometheus 当然不在它肚子里。如果你 Grafana 是通过别的方式部署的比如宿主机直接装的二进制Prometheus 在容器里那填 http://localhost:9090 反而才对因为 Grafana 进程访问的是宿主机的网络栈。填错的人往往没想清楚网络访问路径取决于进程运行的上下文而不是你想当然的直觉。保存后点击“Save Test”如果显示 Successfully queried the Prometheus API说明数据源连通了。6.2 导入现成监控面板自己从零画面板当然可以但刚开始没必要。Grafana 社区有个很庞大的面板市场直接在面板列表里输入 ID 就能导入。node-exporter 的经典面板 ID 是 1860cadvisor 的经典面板 ID 是 14282。你输入 ID 后Grafana 会要求选择数据源选你刚添加的 Prometheus 就行。导入后如果发现没有任何数据大概率是面板里的查询语句用了旧版本的指标名。node-exporter 在新版本升级过一批指标命名规则比如原来的 node_cpu 变成了 node_cpu_seconds_total。这种版本差异问题在面板市场里很常见不用纠结去面板设置里把对应的查询改一改就恢复了。这个坑几乎每个人都会踩一次我已经习以为常。真正自己从零创建面板时要注意一个核心概念PromQL 查询语句和普通的 SQL 的思维方式不一样PromQL 是围绕时间序列和标签展开的。你要查某个时间序列过去 5 分钟的平均 CPU 使用率表达式是100 - (avg(rate(node_cpu_seconds_total{modeidle}[5m])) * 100)这段拆开看也不难理解node_cpu_seconds_total 是累计值modeidle 过滤出空闲状态的项rate[5m]算这 5 分钟每秒的变化率avg 把多个 CPU 核取平均再用 100 减掉得到的就是整体 CPU 使用率。7. 从实验环境到生产可用还要做哪些事整套系统能在 Docker 里跑通和它能稳定地持续运行其实差着一整个“生产化”的距离。这一节我把从实验到生产之间必须做的几件事讲透每一件都来自实际教训。7.1 资源配额一定要显式声明Docker 容器的资源限制默认是不限制的。Prometheus 被设计成尽全力采集和存储如果你的 scrape_interval 非常激进而且 targets 很多它能把宿主机内存吃满。这种情况在裸机部署时也会发生但容器部署会让人更放松警惕因为容器看起来太像“小盒子”了。所以在 compose 文件里我建议给每个组件都加上资源限制。这里给一组可以直接参考的初始值prometheus: deploy: resources: limits: memory: 2G reservations: memory: 512M根据你目标的规模按经验放大但一定要设 limits。不设的话监控系统把自己监控的对象搞挂了这种事故说出来都是笑话。还有一个接近实战的细节存储 Retention 时长不能只看需求也要看磁盘容量。一个简单的估算方法单台机器 15 秒采集间隔Prometheus 每天的存储大约在 1-2GB具体取决于指标数量和标签基数。如果磁盘 100GB你设了 90 天 retention结果发现到第 40 天磁盘就满了那是因为标签基数太大。TSDB 的压缩率会受到基数影响基数指数级膨胀时存储量也会跟着膨胀好几个数量级。7.2 日志别让 Docker 一直吞着所有容器默认把日志写到 Docker 的 json-file 驱动里。长时间运行后日志文件会越来越大尤其是 Prometheus 和 Alertmanager 这种会把每次抓取异常、通知失败都写进日志的组件几个月不清理宿主机磁盘上会出现几十 GB 的日志文件。两个方案一个是 Docker 层面限制日志大小logging: driver: json-file options: max-size: 20m max-file: 5另一个是把日志接到专门的日志收集链路比如 Loki 或者别的主流日志系统。监控系统的日志本身也是需要观察的对象这个链路搭建好之后你排查问题会更顺手。7.3 定期验证告警链路的可用性这是我在实际运维中体会很深的一点。告警系统部署完成后如果不定期验证大概率第一次真正触发告警是你凌晨被电话叫起来那说明系统真的挂了。常规做法是配置一条“心跳”告警。选一个几乎不会出问题但确实需要持续运行的指标比如 Prometheus 自身是不是活着。node_exporter 的 up 指标天然适合这个用途。在告警规则里加一条- alert: ExporterDown expr: up 0 for: 1m这条规则会在任何采集目标离线时触发。再配合 Alertmanager 的通知链路你可以每个月主动把某个 exporter 容器停掉测试能不能收到通知。我在实际运维中发现一个月不测一次链路里总会出点幺蛾子——要么是通知渠道的密钥过期要么是 Alertmanager 的配置在升级后不兼容。7.4 镜像版本锁定与升级策略compose 文件里我使用的镜像都带了具体版本号比如 prom/prometheus:v2.53.0。这不是随便选的而是刻意避开 latest 标签。latest 的最大问题在于它不是一个稳定的标识同一天内你 docker compose pull 两次可能拿到两个不同的镜像。生产环境的监控系统最忌讳的就是不可控的变化。但锁定版本也意味着你要主动安排升级节奏。Prometheus 和 Grafana 都是迭代特别快的项目每两个月就会发布新版本。我建议每三个月安排一次升级先备份数据卷然后在测试环境把整套 compose 起一遍确认没有兼容性问题再上生产。有一点特别提醒Prometheus 的数据格式跨大版本升级时可能会有不兼容的风险升级前一定先看官方升级文档。连续性要求高的系统还要检查 TSDB 能不能被新版本正常读取。备份没做好之前任何升级都不要碰。8. 几个我觉得很受用的习惯这套系统我前前后后重新部署过很多次每次都有一些新的体会。过程中慢慢形成了一些小习惯写在这里供你参考。第一docker compose 文件里每个服务都加 container_name。这个看起来是小事但排查问题时你 docker logs 后面跟的名字是固定的不用每次 docker ps 去查容器 ID。尤其是你有多个环境时A 环境和 B 环境的容器名做个区分能避免很多误操作。第二配置文件的改动一定要经过 docker compose config 校验再 up。compose 支持这种语法校验能提前发现 YAML 格式错误、缩进不对这类低级问题不用反复重启容器试错。第三把配置目录整个纳入 Git 管理。配置文件的变更历史、回滚能力、多环境复用这些价值在出事故的时候才会真正体现出来。平时看起来多了一步 git commit但关键时刻能救命。第四每次部署完顺手验证一遍采集链路。Prometheus 的 Targets 页面所有 target 都是 UPAlerts 页面规则都在正常加载Grafana 面板上能刷出曲线。这三件事加在一起不超过两分钟但能把绝大部分隐患在刚上线时就暴露掉。这套 Docker 化的 Prometheus 监控栈我现在用得很顺手也帮身边的开发者搭过好几套。如果你按上面的步骤来应该半天之内就能从零到一跑起来一套能看的监控系统。后面再根据自己的业务体量和需求去调优过程会顺得多。