1. 先把要解决的问题说清楚:容器监控到底在盯什么
1.1 为什么 docker stats 解决不了长期监控
最近把手上几台跑着 Docker 的服务器整体梳理了一遍,最大的感触是:大家习惯了把容器跑起来,却很少认真回答一个问题——这些容器现在到底什么状态?打开docker stats看了一眼,CPU 不高,内存还剩不少,然后关掉。但这种状态持续不了一周:凌晨三点某个容器悄悄被 OOM 杀掉,重启了几轮才活过来,等早上发现时,现场早就没了。
docker 容器监控这件事,我以前也觉得“就是个 dashboard”,直到真靠它定位过两次线上故障之后,才明白它要解决的根本不是“好看”,而是“有证据、能复盘、能提前发现”。
先聊docker stats。它是 Docker 引擎自带的命令,能实时拉取每个容器的 CPU 百分比、内存占用和限额、网络 I/O、块 I/O、PIDS 数量。作为排查问题的第一现场工具,它非常好用,一条命令就能看到当前状态。但作为监控手段,它的短板是致命的:
- 没有历史数据。关掉终端,曲线就消失了,第二天想复盘昨晚发生了什么,完全无从下手。
- 没有聚合能力。几十个容器一行一行刷屏,想对比哪个容器长期吃内存,靠肉眼根本看不出来。
- 没有告警。容器挂了、内存快满了、疯狂重启,它不会主动通知你,只会等你上班打开终端才发现。
- 每次只能看到当前实例。容器 ID 一变,之前的“状态印象”就作废了。
所以我的结论是:docker stats负责“现在时”,监控系统负责“过去时和未来时”。真正要落地 docker 容器监控,至少要让数据能留存、能查询、能画趋势、能在异常时叫醒你。
1.2 容器和虚拟机的监控逻辑差在哪
做过传统虚拟机监控的人,很容易把老思路直接套到容器上——在每台机器里装一个 Agent,采集 CPU、内存、磁盘,然后上报。这套逻辑对虚拟机成立,但对容器经常失效。
原因有几个:
容器是短暂的。一个容器可能今天还叫 web-01,明天因为镜像更新就换了一个全新的容器 ID,甚至名字带上了随机后缀。用“机器”的视角去监控它,你会发现监控目标一直在“变化”。
容器内部不适合随便装 Agent。很多官方镜像都是精简版,连 shell 都没有,装 Agent、改配置都不现实。就算装进去,容器重启一次就全没了。监控采集点放在容器外面、放在宿主机层面,才是正路。
容器的资源是 cgroup 控制的。CPU、内存、网络、磁盘 I/O 的统计数据,都存在于宿主机/sys/fs/cgroup下面,而不是容器内部的/proc。换句话说,监控容器资源,本质上是在监控“宿主机上的一组 cgroup 目录”。
这就解释了为什么容器监控的工具链必须支持“从外面读 cgroup 数据”。你看docker stats的实现,其实也是 Docker 引擎替你读了 cgroup 和/proc之后换算展示的。我们要做的,无非是把这套数据持续采集、存储、可视化。
1.3 一套合格监控至少要覆盖的指标
我按重要性把要盯的指标分了五类,这也是我后来做告警规则的骨架:
| 指标类别 | 核心指标 | 为什么要盯 |
|---|---|---|
| 资源使用 | CPU 使用率、CPU 限流次数、内存 working set、网络收发速率、磁盘 I/O | 判断容器是否接近资源上限,提前扩容或调参 |
| 状态与可用性 | 容器是否存活、健康检查结果、重启次数、OOM 事件 | 发现服务悄悄挂了、反复重启的异常容器 |
| 配额与限额 | memory limit、CPU quota、重启策略 | 确认限额设置是否合理,避免被打满后被内核杀死 |
| 业务探活 | 端口连通性、HTTP 状态码、关键接口响应时间 | 资源都正常但业务不可用的“假活”情况 |
| 日志与事件 | stdout/stderr 日志、Docker Event 事件流 | 事后复盘 OOM、重启、异常退出的直接证据 |
很多人做监控,只做了第一类“资源使用”,觉得 CPU 不高就万事大吉。但实际故障里,我遇到最多的是“容器一直重启”“端口还开着但不响应”“内存限额设太小被 OOM”。这些信息,单纯看 CPU 曲线根本发现不了。
所以在你动手搭监控之前,先在心里过一遍这五个维度。后面选工具、配告警,都是围绕这五类指标展开的。
2. 方案选型的取舍:为什么最终落在 cAdvisor + Prometheus + Grafana 这个组合上
2.1 先盘点一下市面上的主流做法
我早期接触过几种方案,各有各的适用场景,这里直接说结论。
第一种,自己写脚本周期调 Docker API,把结果写进文件或数据库。好处是零依赖,坏处是数据不连续、格式不统一,而且每次容器一多,脚本就开始变脆。我身边有同事用 Python 脚本跑了几个月,最后发现数据缺了一大段,原因是某次磁盘写满后脚本静默退出了。自己造轮子可以,但做监控,稳定性比炫技重要得多。
第二种,直接用开源监控 Agent。比如Netdata,安装简单,装完自带漂亮实时面板,CPU、内存、网络、磁盘全都能看,还能看 Docker 容器的资源占用。它适合单机、想要开箱即用、不想折腾的人。但如果你有四五台服务器,想集中到一个大盘看,想用自己的规则告警,Netdata 的集中管理和告警配置就没那么顺手了。
第三种,也是我最终选的方向:cAdvisor 负责采集,Prometheus 负责存储和计算,Grafana 负责展示和告警触达,Alertmanager 负责消息分发。这套东西拆开看每个组件都只干一件事,但组合起来非常完整。cAdvisor 是 Google 开源的容器资源采集器,专门从 cgroup 读数据,并暴露 Prometheus 格式的/metrics接口;Prometheus 是业界标准的时序数据库;Grafana 是最常见的可视化看板。三者都是 Docker 生态里用得最多、踩坑资料最全的工具。
我还考虑过 Telegraf + InfluxDB 的 TICK 栈,功能上也是完整的。但它的查询语言、社区模板、二次开发资料整体不如 Prometheus 生态丰富。既然要写进博文分享,我默认推荐大家走 Prometheus 这条路,遇到问题搜解决方案也最容易。
2.2 这套组合的架构与数据流
先看整体数据流,理解了它,后面配置文件基本不会看不懂:
容器(cgroup 数据) ↓ 采集 cAdvisor(暴露 /metrics) ↓ 拉取 Prometheus(按 scrape_interval 抓取并存储) ↓ 查询 Grafana 看板 Alertmanager 告警 ↓ 通知 邮件 / Webhook 机器人cAdvisor 跑在宿主机上,通常也以容器方式部署。它读取每个容器的 CPU、内存、网络、磁盘等 cgroup 指标,同时在 8080 端口暴露一个/metrics接口,里面全都是 Prometheus 格式的文本数据。
Prometheus 按照你设置的抓取频率(比如每 15 秒)去访问 cAdvisor 的/metrics,把指标存进自己的 TSDB 时序库。注意它的工作方式是“拉取”,不是“推送”,所以 cAdvisor 只要活着、端口开着,Prometheus 就不需要知道容器内部发生了什么,耦合度很低。
Grafana 连上 Prometheus 数据源之后,可以用现成面板模板把曲线画出来。告警规则写在 Prometheus 的规则文件里,一旦表达式触发,Prometheus 把告警推给 Alertmanager,由 Alertmanager 负责去重、分组、限流,再发到你的邮箱或者 webhook 机器人。
这套架构的好处是每个环节都能替换。前端想换,换个看板工具就行;告警想换渠道,改 Alertmanager 配 webhook 就行;想加节点,再部署一个 cAdvisor,在 Prometheus 的 scrape 配置里加一行 target 就行。
2.3 方案怎么选:机器少、规模小、预算不同的情况怎么办
这套架构虽然好,但如果你的需求只是“在自用的两台小服务器上看看容器状态”,我会劝你别一上来就全家桶。Prometheus + Grafana 这套东西本身也要占资源,至少在监控容器资源之外,你还得管 Prometheus 的存储和 Grafana 的升级。
我的建议分三档:
- 一台到两台机器,只想快速看状态:用
docker stats加健康检查就够了,再配一个Uptime Kuma做端口探活,页面简洁,告警也好配。 - 三到五台机器,想要统一看板:上 cAdvisor + Prometheus + Grafana 全家桶,这是性价比最高的一档。资源占用可控,cAdvisor 用官方镜像,Prometheus 给个 10GB 的 retention 盘,足够了。
- 业务规模再大、有专门运维资源:可以考虑接入 Loki 做日志聚合,加 node_exporter 监控宿主机,甚至给业务核心容器埋自有埋点,这一步就到可观测性了。
真实项目里最怕的是“需求两行,方案一本书”。监控工具是为问题服务的,不要为了上全家桶而上全家桶。下面所有实操,我都按第二档来写,因为它最具普适性,也最容易作为起点扩展。
3. 一套直接能抄的部署实操:从 compose 文件到告警落地
3.1 部署前的四个前置确认
在写 docker-compose.yml 之前,有四个事情必须确认,否则后面会反复踩坑。
第一,确认 Docker 引擎和 Docker Compose 的版本。Compose 插件如果太老,version字段和部分参数可能不兼容。我建议用支持 Compose v2 的版本,下面的文件里不写version字段也可以,直接用新版模式。
第二,确认 cAdvisor 容器需要挂载哪些目录。这是最容易出错的地方。标准做法是挂载宿主机根目录、/var/run(Docker socket)、/sys(cgroup 和系统信息)、/var/lib/docker(容器存储目录)、/dev/disk(磁盘信息)。这些是只读挂载,cAdvisor 需要读它们才能获取完整的指标数据。挂少了会出现“容器列表不全”“磁盘指标丢失”之类的问题。
第三,谨慎挂载 Docker socket。这是双刃剑。cAdvisor 要访问 Docker API 才能获取容器元数据(名字、镜像、标签),所以需要读/var/run/docker.sock。但拿到 socket 就相当于拿到了宿主机的控制权,如果你把 cAdvisor 的 8080 端口暴露到公网,风险非常大。我的做法是:监听在内部监控网络,不映射 8080 到公网,或者至少加一层反向代理和认证。
第四,确认宿主机的 cgroup 版本。现在新系统很多默认 cgroup v2,老版本 cAdvisor(0.43 以前)在 v2 上会有“内存指标读不出来”“启动直接 panic”这类问题。直接用比较新的 cAdvisor 镜像基本能规避,但如果你用的是发行版自带的旧镜像,一定要升级。
提示:部署监控组件本身之前,先在宿主机执行
docker info,查看 “Cgroup Version” 这一行,记录是 v1 还是 v2,后面排障会用到。
3.2 监控栈的完整部署文件
我习惯把监控部署在专门的目录/opt/monitor,用一个 compose 文件管理所有组件。下面的文件是完整可用的,你直接抄,按需改端口和密码。
先看目录结构:
/opt/monitor/ ├── docker-compose.yml ├── prometheus/ │ ├── prometheus.yml │ └── rules/ │ └── container.yml └── alertmanager/ └── alertmanager.yml接着是docker-compose.yml:
networks: monitor: driver: bridge services: prometheus: image: prom/prometheus:v2.51.0 container_name: prometheus restart: unless-stopped volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./prometheus/rules:/etc/prometheus/rules:ro - prometheus-data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--storage.tsdb.retention.time=30d' - '--storage.tsdb.retention.size=10GB' ports: - '9090:9090' networks: - monitor cadvisor: image: gcr.io/cadvisor/cadvisor:v0.49.1 container_name: cadvisor restart: unless-stopped volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro - /dev/disk/:/dev/disk:ro command: - '--housekeeping_interval=10s' - '--docker_only=true' ports: - '18080:8080' networks: - monitor grafana: image: grafana/grafana:10.4.0 container_name: grafana restart: unless-stopped environment: - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=your_password volumes: - grafana-data:/var/lib/grafana ports: - '3000:3000' networks: - monitor alertmanager: image: prom/alertmanager:v0.27.0 container_name: alertmanager restart: unless-stopped volumes: - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro ports: - '9093:9093' networks: - monitor volumes: prometheus-data: grafana-data:几个细节说明一下:
cAdvisor 的端口我没有照抄默认的 8080,而是映射成了宿主机的18080:8080。8080 太容易和其他服务冲突,换高位端口省心。Grafana 默认端口 3000 我喜欢保留,因为看板访问频率高,记端口成本越低越好。
--housekeeping_interval=10s是 cAdvisor 采集 cgroup 数据的周期。默认是 1 秒一次,太频繁,在容器很多时会明显增加宿主机负载;改成 10 秒,对监控精度影响很小,资源开销小很多。--docker_only=true可以过滤掉非 Docker 创建的 cgroup,避免指标太多太杂。
Prometheus 的--storage.tsdb.retention.time=30d表示保留 30 天数据,--storage.tsdb.retention.size=10GB是存储上限,两个都写,先到哪个算哪个。这样数据虽然会滚动删除,但至少盘不会被打爆。
然后是prometheus/prometheus.yml:
global: scrape_interval: 15s evaluation_interval: 15s rule_files: - /etc/prometheus/rules/container.yml scrape_configs: - job_name: 'cadvisor' metrics_path: /metrics static_configs: - targets: ['cadvisor:8080'] labels: env: prod这里有个关键点:Prometheus 和 cAdvisor 在同一个 compose 内部网络monitor里,所以 target 直接写服务名cadvisor:8080就行,不需要写宿主机 IP。如果你把 cAdvisor 部署在别的主机上,就把这里改成IP:18080。
scrape_interval我设置的 15 秒,对容器监控来说足够。想更快看到变化可以改 5 秒,但存储量会翻三倍,对大多数场景没必要。
rule_files指向告警规则目录,下面会写具体规则。
最后是alertmanager/alertmanager.yml:
global: smtp_smarthost: 'smtp.你的邮箱服务商:465' smtp_from: 'monitor@example.com' smtp_auth_username: 'monitor@example.com' smtp_auth_password: '你的授权码' route: group_by: ['alertname', 'name'] group_wait: 30s group_interval: 5m repeat_interval: 6h receiver: email receivers: - name: email email_configs: - to: 'ops@example.com'这套配置我故意写得精简。group_by按告警名字和容器名分组,避免同一容器多个指标同时告警刷屏;group_wait: 30s是等半分钟收集同类告警再发,避免网络抖动引发一堆邮件;repeat_interval: 6h意味着同一告警如果一直不恢复,至少隔 6 小时才重复提醒一次,防止半夜被连续轰炸。
重要:邮箱发信最好用“授权码”而不是登录密码,你的
smtp_from和smtp_auth_username通常是同一个邮箱地址。
3.3 Grafana 看板导入与关键面板的解读思路
部署起来之后,浏览器访问http://服务器IP:3000,用 admin 和刚才设置的密码登录。第一次登录会要求改密码,改掉之后进入首页。
在 Grafana 里先添加 Prometheus 数据源。路径是 “Connections” → “Data sources” → “Add data source”,选 Prometheus,URL 填http://prometheus:9090,保存。因为 Grafana 和 Prometheus 也在同一个monitor网络里,这个地址是通的。
之后去 “Dashboards” → “Import”,输入一个面板 ID 导入现成模板。最常用的是 14282 这个 Docker 监控面板,我身边不少同事第一次搭就用它;另一个是 893,叫“Docker and system monitoring”,会把宿主机监控也带上,前提是你后续加了 node_exporter。
面板模板导入后,你会发现里面通常有这几类核心面板,我建议至少搞懂它们的含义:
- CPU 使用率面板:最常用的指标是
rate(container_cpu_usage_seconds_total{name!=""}[5m]),单位是“每秒使用的 CPU 秒数”,1.0 表示占满 1 个核心,乘 100 就是百分数。面板上如果加了container_spec_cpu_quota和container_spec_cpu_period的除法,含义会变成“相对 CPU 限额的百分比”,两种表达都成立,看你自己习惯。 - 内存面板:重点关注
container_memory_working_set_bytes,而不是container_memory_usage_bytes。usage包含文件缓存,很大一部分可以回收;working_set才是当前真正“被占用且不能轻易释放”的内存,更接近实际内存压力。配合container_spec_memory_limit_bytes算百分比,能看出距离 OOM 还有多远。 - 网络面板:
container_network_receive_bytes_total和container_network_transmit_bytes_total,因为有interface这个标签,通常会带出 eth0、veth 一堆网卡。建议查询时加上interface="eth0"过滤,或聚合时按容器名 sum,否则趋势曲线会特别乱。 - 磁盘 I/O 面板:
container_fs_reads_bytes_total和container_fs_writes_bytes_total的速率。注意容器看到的磁盘写入,有很大一部分是镜像层和容器层的叠加,读操作经常打到镜像缓存上,所以这个值不能完全等价于物理磁盘 I/O,更多用来发现“某容器突然大量写盘”这类异常。
我导入模板后一般不会直接用,而是先对照上面的指标映射关系,把模板里明显的指标名错误修一下。很多模板是面向老版本 cAdvisor 写的,指标名稍有出入,这时就到 Prometheus 的 “Status” → “Targets” 页面,确认 target 状态是 UP,然后在查询页http://服务器IP:9090里直接测试指标是否存在,再回 Grafana 改查询。
3.4 告警规则怎么配才有价值
看板只是把数据变成图,真正让监控“有用”的是告警。我见过不少人搭完看板就结束了,结果某天磁盘满、容器被 OOM,还是一点感知都没有。
告警规则写在prometheus/rules/container.yml里,Prometheus 每隔evaluation_interval(我设的 15 秒)会算一次这些规则,触发后发给 Alertmanager。
下面是我现役的一套精简规则,指标含义和触发逻辑都写清楚了:
groups: - name: container_runtime rules: - alert: TargetDown expr: up{job="cadvisor"} == 0 for: 2m labels: severity: critical annotations: summary: "监控采集目标 {{ $labels.instance }} 不可达" - alert: ContainerHighCpu expr: sum by (name) (rate(container_cpu_usage_seconds_total{name!=""}[5m])) * 100 > 80 for: 10m labels: severity: warning annotations: summary: "容器 {{ $labels.name }} CPU 使用率超过 80% 持续 10 分钟" - alert: ContainerMemoryNearLimit expr: container_memory_working_set_bytes{name!=""} / clamp_min(container_spec_memory_limit_bytes{name!=""}, 1) > 0.9 for: 5m labels: severity: warning annotations: summary: "容器 {{ $labels.name }} 内存使用率超过限额 90%" - alert: ContainerRestarting expr: changes(container_start_time_seconds{name!=""}[10m]) > 0 for: 0m labels: severity: warning annotations: summary: "容器 {{ $labels.name }} 在 10 分钟内发生过重启"规则设计上有几个经验:
for是“持续时间”,必须加。比如 CPU 瞬时飙到 90% 可能只是启动瞬间,持续 10 分钟才是真问题。没有for的告警,一天能触发几十次,很快就被忽略了。
内存规则的clamp_min(..., 1)是为了防止container_spec_memory_limit_bytes为 0 时出现除零错误。如果一个容器没有设置内存限额,它的 limit 数值通常是宿主机总内存,这个告警基本不会触发,这是合理的——没限额就不用担心“接近限额”。
重启检测用的是changes(container_start_time_seconds[10m]) > 0。container_start_time_seconds是容器的启动时间戳,容器一旦重启,这个值会变化,10 分钟内变化过就说明异常重启。配合 Docker Event 里的 OOM 事件,基本能定位大部分“容器反复挂”的现场。
告警触达可以先用邮件,后改成企业微信、飞书、钉钉群里常见的机器人 webhook。Alertmanager 的webhook_configs很简单,把 URL 配好就行。我个人建议一步到位接入办公群群机器人,不然邮件很容易被忽略,告警的价值就大打折扣。
3.5 别忘了日志和事件这一层
Prometheus 这类时序监控擅长数值曲线,但“为什么这个容器挂了”这种问题,曲线回答不了,得有日志和事件。
最简单的起步方案是给 Docker 配置日志轮转,避免日志文件无限增长把磁盘写爆。在/etc/docker/daemon.json里加上:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }改完重启 Docker,新容器默认每个日志文件最多 10MB、最多保留 3 个。日志轮转是监控链条里必须做的一环,磁盘被日志撑爆的故障,我见过太多次了。
事件方面,Docker 自己提供docker events命令,可以实时输出容器创建、销毁、异常退出、OOM 等事件。比如:
docker events --filter 'type=container' --filter 'event=die' --filter 'event=oom'我自己写过一个很小的定时脚本,把docker events的输出接个 webhook 推到群里,只要某个容器 OOM 或者异常退出,第一时间就能看到“哪个容器挂了、为什么挂”。这比等告警再查日志快得多。
4. 上线后踩过的坑:常见故障与排查思路
4.1 容器一重启,面板上就少了一台机器
这是我最初遇到的最奇怪的问题。明明容器还在跑,Grafana 面板上却找不到它的曲线了。后来才意识到:cAdvisor 给每条时序数据打的标签里包含id(容器 ID),容器一重启,ID 变了,新容器不会自动复用旧曲线上的标签,所以面板看起来就像“少了一台机器”。
解决办法有两个方向。一个是看板变量尽量用name这个标签来选容器,因为容器名相对稳定。另一个是把 compose 项目的标签用上,比如container_label_com_docker_compose_project,这样哪怕某个服务的容器名带随机后缀,也能按项目名聚合。
另外,cAdvisor 启动时加--store_container_labels=false可以明显降低时序数据的基数,因为它不再把容器的一堆自定义元数据标签全存下来,只保留name这类很有限的标签。代价是你会失去 compose 项目标签、镜像标签这些维度。我的经验是:如果监控只按容器名看曲线、告警也要按容器名分组,那这个参数值得开;如果后面要做“按项目聚合”“按服务分组”,就不要开。
注意:容器监控的时序数据天然有高基数问题。每个容器的 ID、每次重启都会引入新的标签组合,数据量会随时间缓慢膨胀。定期清掉已经停用很久的容器,或者在 Prometheus 查询时用
container_last_seen低于某个时间阈值过滤掉僵尸容器,都是控制存储量的实用手段。
4.2 CPU 和内存的数值总是对不上
上线之后,有人对比过docker stats和 Grafana 面板,发现数值对不上,就怀疑监控不准。这种事我一开始也纠结过,后来搞清楚了原因。
docker stats的 CPU 百分比是“当前所有核心的总使用率再除以核心数”,比如 8 核机器上某个容器用了 1 核,它显示约 12.5%。而 Grafana 模板里的rate(container_cpu_usage_seconds_total[5m])算出来的是“每秒消耗的 CPU 秒数”,1 表示占满一个物理核心。两者正确理解之后,其实能互相印证,只是单位不一样。
内存更典型。container_memory_usage_bytes这个指标会把 page cache 算进去,一个刚启动、读了不少文件的 Java 应用,usage 可能显示好几个 GB,但其实大部分是缓存,可以被回收。docker stats里显示的内存也是这个含义。感性的理解就是:usage 是“占了多少”,working_set 是“真正压着多少”。要做内存告警、要评估 OOM 风险,我建议一律用 working_set 和 limit 的比例,而不是 usage。
还有一个容易忽略的点:容器内看到的 CPU 数和宿主机核数不一定一致。有些容器通过--cpuset-cpus绑定了部分核,或者 quota 限制了 CPU 配额,这时光看绝对 CPU 时间很难判断“它是否已到瓶颈”。所以告警规则里我才特意示范了container_spec_cpu_quota的角色——它是 cgroup 里配置的 CPU 配额,用来做“相对限额的占比”才合理。
4.3 cgroup v2 带来的内存指标失踪
前年我在一台新采购的机器上部署 cAdvisor,启动倒是正常,但 Prometheus 里container_memory_usage_bytes全是零,有些甚至直接查不到。查了一圈才发现,那台系统的 cgroup 已经是 v2,而当时的 cAdvisor 版本对 v2 支持不完整。
这个问题现在好解决:升级 cAdvisor 镜像到新版本(v0.47 以上对 cgroup v2 支持已经成熟),部署前先用docker info确认宿主机是 v1 还是 v2,再选对应版本的镜像。如果你用的是发行版软件仓库里的老镜像,这个坑尤其要小心。
还有一类 cgroup v2 的衍生问题:挂载路径和权限不对导致 cAdvisor 启动时打印大量错误,或者container_network_*指标时有时无。排查思路是一致的——看 cAdvisor 容器日志,如果出现关于 cgroup、permission denied 的内容,优先检查它的挂载参数是否正确、/sys是否有只读挂载、SELinux/AppArmor 是否拦截。
4.4 磁盘被 Prometheus 悄悄吃掉了
监控系统自己把系统盘写满,这是运维圈最著名的黑色幽默之一。Prometheus 的 TSDB 会一直写数据,不做控制的话,几个月就能吃下几十 GB。
我给的 compose 文件里已经加了--storage.tsdb.retention.time=30d和--storage.tsdb.retention.size=10GB。这两个参数双保险,时间上限和大小上限谁先到,Prometheus 就先清理谁。实际业务量大的时候,我最担心的是标签基数膨胀导致样本量激增,这时即使有 10GB 上限,也可能几天就写满,然后频繁删除数据。
快速估算存储量可以套一个简单公式:样本数 ≈ 序列数 × 每天抓取次数。假设 2000 个时序、15 秒抓一次,一天约 1150 万条样本,Prometheus 实际压缩后大约 20 到 50MB 一天,30 天大约 1 到 2GB。如果抓取间隔改成 5 秒,或者你的容器数很多导致标签组合膨胀,这个数字直接翻几倍。所以 10GB 的 retention 对中小规模来说是够用的,但一定要养成定期看盘的习惯。
宿主机磁盘监控本身也可以顺手接入。在 compose 文件里加一个 node_exporter,挂载宿主机根目录和/proc、/sys,然后 Prometheus 加一个 scrape target。这样node_filesystem_avail_bytes、node_filesystem_size_bytes就能覆盖宿主机磁盘的告警,监控体系才算闭环。
4.5 告警风暴:一次 Docker 重启,手机震一晚上
告警风暴是移动时代的新折磨。某次我调整 Docker 重启策略,几十个容器同时重启,Prometheus 一口气触发了上百条“容器重启”告警,Alertmanager 又把没分组的邮件发了一整晚。
这种问题的根治方案,其实在 Alertmanager 的配置里。group_by: ['alertname', 'name']把同类告警合并成一条;group_wait: 30s让短暂抖动先被吸收;repeat_interval: 6h限制重复提醒频率。这三个参数配好之后,告警量能减少百分之九十以上。
还有一条经验:不要在告警规则里堆“无差别监控”。比如所有容器都加一条“网络速率高”的告警,业务不同、基准不同,必然产生大量误报。我的做法是:全局只保留通用规则(TargetDown、容器重启、内存接近限额),业务特定的告警按服务单独加,阈值参考历史曲线确定。
5. 阶段一跑通之后:日常维护与后续演进
5.1 我每个月会固定做的几件事
监控系统跑通不是终点,它也需要维护。我给自己定了个周期,每月做一次,二十分钟以内能搞定。
第一,看 Prometheus 存储和磁盘情况,确认 retention 没有被意外改掉,确认 cAdvisor 和 Prometheus 的容器都在稳定运行。第二,打开 Grafana,快速浏览一遍所有容器过去 7 天的 CPU 和内存曲线,重点找那些长期徘徊在 80% 以上的容器,要么加资源限制,要么提扩容申请。第三,翻一遍最近一周的告警记录,看哪些告警重复率最高,如果某个告警从来没人处理过,就该考虑是不是阈值设太低,或者直接删掉。
这步很像“复盘”。监控数据的最大价值不是让你实时盯着,而是月底回头看时能回答“这个月哪几次异常、什么原因、指标上有没有前兆”。有证据,就能优化;没证据,就只能拍脑袋。
5.2 从容器监控延伸到业务可观测性
等容器层面的资源监控稳定了,你会发现还有一个盲区:容器的资源都正常,但业务就是不好用。这就要从“容器监控”往“业务可观测性”走了。
最简单的补法是加探活。比如Uptime Kuma或者blackbox_exporter,定期探测容器暴露的端口或 HTTP 接口,返回probe_success指标。它能解决“端口开着但服务假死”这类容器监控看不见的问题。我自己的经验是:先保证“容器能对外提供服务”,再谈高深的应用链路追踪。
日志这块,如果机器数量多了,可以引Loki来统一收集日志,配合 Grafana 一个入口看指标、看日志。如果只是想偶尔看看实时输出,Dozzle这种轻量日志工具比折腾 Loki 便宜得多。
再往后,就是给核心服务注入应用层指标。用 Prometheus 客户端库在代码里暴露/metrics,比如请求数、延迟、错误率。这一步需要开发配合,但对真实业务来说,它的价值远高于再画十个资源面板。
5.3 一点个人体会
整套跑下来,我最深的体会是:监控不是有了工具就完事,而是你愿不愿意把“复盘”变成习惯。docker stats 能给的是现在时,Prometheus 这套组合给的是连续的、可回溯的证据链。刚开始你可能觉得搭它麻烦,但踩过两次“凌晨三点被 OOM 干掉、早上还不知道原因”的坑之后,你会感谢昨天多花了一小时配告警的自己。
顺着这套基础,后面要加的东西其实都有清晰的路径:要宿主机状态,加 node_exporter;要业务探活,加探活器;要看日志,接 Loki;要更精细的告警,多写几条业务规则。容器监控这件事没有终点,只有一步步逼近“问题发生前就能感知”的状态。