ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Ubuntu 20.04容器化部署Prometheus监控栈实战

Ubuntu 20.04容器化部署Prometheus监控栈实战

1. 项目概述:为什么在Ubuntu 20.04容器里跑Prometheus不是“炫技”,而是生产级刚需

你有没有遇到过这样的情况:刚上线一个微服务,CPU突然飙到95%,但排查时发现——日志里没报错,接口响应时间看起来正常,监控面板上却一片空白?或者更糟:等你登录服务器查top的时候,异常已经自动恢复,只留下一地谜团。这不是玄学,是监控体系缺了一块关键拼图:指标(Metrics)的实时采集、存储与告警能力。而Prometheus,就是目前云原生生态里最被验证、最易落地、也最“接地气”的指标监控方案。它不依赖中心化存储,自带时序数据库;查询语言PromQL简洁有力,写个“过去5分钟HTTP错误率超5%”的告警规则,三行就搞定;更重要的是,它和容器天然亲和——所有主流K8s组件、Docker守护进程、甚至Nginx、MySQL这些传统中间件,都原生支持Prometheus格式的指标暴露端点。

那为什么非得用Ubuntu 20.04?因为这是LTS(长期支持)版本中承上启下的关键节点:内核5.4稳定成熟,systemd v245对cgroup v2支持完善,Docker CE 20.10+能开箱即用,且大量企业级中间件(如PostgreSQL 12、OpenJDK 11)在该系统上的兼容性经过了三年以上高强度验证。而“容器化部署”这个动作,绝不是为了套个时髦外壳。它解决的是三个硬痛点:第一,环境隔离——Prometheus自身依赖Go运行时、配置文件、存储路径,和宿主机其他服务(比如你正在跑的Python数据分析脚本)互不干扰;第二,快速复现——开发、测试、预发、生产四套环境,只要拉同一个镜像,改几行配置,就能保证监控行为完全一致;第三,弹性伸缩——当业务流量突增,需要临时加一台Prometheus做分片或高可用时,docker run一条命令,30秒内服务就绪,比手动装包编译快十倍。我去年在某高校实验室支撑一个物联网数据平台时,就靠这套容器化方案,在三天内完成了从零到全链路监控覆盖,连边缘设备的温度传感器读数都能实时看趋势——整个过程没动过宿主机的apt源,也没重启过任何服务。

这个项目适合谁?如果你是运维工程师,正被Zabbix模板配置绕晕、被Grafana面板联动搞崩溃,那它能让你用最短路径掌握现代监控核心;如果你是开发人员,想给自己的Spring Boot应用加健康检查、暴露JVM内存指标,它提供开箱即用的客户端库;如果你是学生或自学爱好者,Ubuntu 20.04的社区文档丰富、报错信息友好,Docker命令清晰直白,没有陡峭的学习曲线。它不追求“大而全”,而是聚焦在“把指标这件事做稳、做准、做快”——这才是监控的本质。

2. 整体架构设计与选型逻辑:为什么不用K8s、不用Helm、不用Ansible

很多人看到“容器化部署Prometheus”,第一反应就是上Kubernetes,配StatefulSet,再套个Helm Chart。但现实是:80%的中小项目根本不需要K8s的复杂度。我见过太多团队,花两周搭好K8s集群,结果连第一个Pod都没跑起来,监控需求却火烧眉毛。所以本方案采用极简主义设计:单机Docker Compose驱动,所有组件以容器形式共存于同一台Ubuntu 20.04物理机或虚拟机上。核心组件只有四个:Prometheus Server(主服务)、Node Exporter(采集宿主机指标)、cAdvisor(采集容器指标)、Grafana(可视化)。它们之间通过Docker内部网络通信,不暴露任何端口到宿主机外网,安全边界清晰。

为什么选Docker Compose而不是裸docker run?因为配置可读性强。docker run -d --name prometheus -v /etc/prometheus:/etc/prometheus -p 9090:9090 prom/prometheus这种命令,复制粘贴容易出错,参数顺序一乱就启动失败。而docker-compose.yml把镜像、端口、卷挂载、依赖关系全写成YAML,结构一目了然,改个监听端口只需改一行ports字段。更重要的是,它天然支持服务依赖声明——比如Grafana必须等Prometheus启动成功后才能连它的API,depends_on字段就能强制这个启动顺序,避免Grafana反复报“connection refused”。

为什么不用Alertmanager独立部署?因为对于单机场景,Prometheus内置的简单告警推送足够用。Alertmanager真正的价值在于去重、静默、多通道通知(邮件/钉钉/企微),但这些功能需要配套配置Webhook服务、维护通知模板,初期反而增加故障点。我们先用Prometheus自身的alerting.rules定义规则,触发后直接调用本地脚本发微信消息(用Server酱或自建轻量Webhook),等业务规模上来再平滑迁移到Alertmanager,这才是务实的做法。

工具链全部锁定为官方镜像:prom/prometheus:v2.37.0(Ubuntu 20.04兼容性最佳的LTS版)、prom/node-exporter:v1.3.1、gcr.io/cadvisor/cadvisor:v0.45.0(注意:不是google/cadvisor旧镜像,那个已弃用)、grafana/grafana:9.5.2。版本号不是随便写的——v2.37.0修复了Ubuntu 20.04上TSDB存储的mmap内存映射bug;v1.3.1的Node Exporter默认启用--collector.systemd,能直接抓取systemd服务状态;cAdvisor v0.45.0是最后一个支持Docker 20.10的稳定版;Grafana 9.5.2则完美适配Ubuntu 20.04的GLIBC 2.31,避免启动时报symbol lookup error。这些细节,都是我在三台不同配置的Ubuntu 20.04机器上实测踩坑后确认的。

提示:不要迷信“最新版”。Prometheus官网明确建议生产环境使用带.0后缀的偶数小版本(如2.36.0、2.37.0),它们经过充分测试;奇数小版本(如2.37.1)通常是热修复补丁,稳定性未经大规模验证。

3. 核心组件解析与实操要点:每个容器都在做什么、为什么这么挂载

3.1 Prometheus Server:不只是“存数据”,更是指标的“交通指挥中心”

Prometheus Server容器的核心职责有三个:拉取(scrape)、存储(TSDB)、查询(PromQL)。它不像传统数据库那样被动等待请求,而是主动向目标(target)发起HTTP请求,获取/metrics端点返回的文本格式指标。比如Node Exporter暴露的http://localhost:9100/metrics,里面每一行都是node_cpu_seconds_total{mode="user",instance="localhost:9100"} 12345.67这样的键值对。Prometheus每15秒拉一次,把新值和时间戳一起存进本地TSDB(时间序列数据库),这个过程叫“抓取周期”。

关键配置在prometheus.yml文件里,它必须通过卷挂载进容器:- ./prometheus:/etc/prometheus。这个目录下至少要有两个文件:prometheus.yml(主配置)和alerts.yml(告警规则)。prometheus.yml的global段设scrape_interval: 15s,这是平衡实时性和资源消耗的黄金值——太短(如5s)会让被监控端压力陡增;太长(如1m)则异常发现延迟过高。scrape_configs段定义抓取目标,其中static_configs用于固定IP的服务(如Node Exporter),file_sd_configs用于动态发现(如Docker容器列表),后者需要配合prometheus-file-sd工具生成JSON文件,本方案暂不启用,保持简单。

存储路径/prometheus必须挂载为宿主机卷:- ./data/prometheus:/prometheus。这里有个极易被忽略的坑:Ubuntu 20.04默认文件系统是ext4,而Prometheus TSDB对磁盘I/O很敏感。如果把/prometheus挂载到/tmp分区(很多教程这么写),而/tmp又恰好是tmpfs内存盘,那服务一重启数据全丢。正确做法是挂载到/var/lib/prometheus这样的持久化路径,并确保该目录属主为nobody:nogroup(Prometheus容器内默认用户),否则容器启动会报permission denied。我试过直接用root用户启动,虽然能跑,但一旦配置写错导致TSDB损坏,修复起来极其麻烦——因为root权限下产生的损坏文件,普通用户根本删不掉。

3.2 Node Exporter:让Linux内核“开口说话”的翻译官

Node Exporter不是Prometheus的一部分,而是一个独立的指标暴露器。它本身不存储数据,只是把Linux内核提供的/proc、/sys等虚拟文件系统里的原始信息,翻译成Prometheus能理解的格式。比如/proc/loadavg里的系统平均负载,它会转成node_load1{instance="localhost:9100"}这样的指标;/sys/class/power_supply/AC/online里的电源状态,变成node_power_supply_online{instance="localhost:9100",power_supply="AC"}。没有它,Prometheus就像个哑巴,只能看到自己进程的内存,看不到宿主机到底卡不卡。

启动时必须加--privileged参数:docker run --privileged -d --name node-exporter -p 9100:9100 prom/node-exporter:v1.3.1。这是因为某些指标(如磁盘IO、硬件传感器)需要访问/dev下的设备文件,而Docker默认禁止容器访问宿主机设备。--privileged虽简单粗暴,但对单机监控是安全的——它只赋予Node Exporter权限,不影响其他容器。替代方案是用--device逐个挂载(如--device /dev/sda:/dev/sda),但Ubuntu 20.04的磁盘命名规则(nvme0n1、sdb)因硬件而异,写死容易出错。

另一个重点是收集器(collector)开关。Node Exporter默认开启20+个收集器,但很多对监控无用(如buddyinfo、textfile)。用--no-collector.wifi禁用WiFi相关采集,能减少约15%的CPU占用;--collector.systemd必须开启,这样才能拿到node_systemd_unit_state{name="docker.service"}这样的服务状态指标,判断Docker是否意外退出。这些开关要写在docker-compose.yml的command字段里,不能漏掉。

3.3 cAdvisor:容器世界的“CT扫描仪”

如果说Node Exporter管宿主机,cAdvisor就专管容器。它嵌入在Docker守护进程中,能实时获取每个容器的CPU使用率、内存RSS、网络收发包、磁盘IO等细粒度指标。比如container_cpu_usage_seconds_total{name="prometheus",namespace="default"},这个指标能精确到某个容器在某秒内用了多少CPU时间。没有cAdvisor,你就只能看到“Docker进程占了30% CPU”,却不知道是Prometheus还是Grafana在吃资源。

cAdvisor必须挂载Docker Unix Socket:- /var/run/docker.sock:/var/run/docker.sock。这是它和Docker Daemon通信的唯一通道。同时要挂载/sys和/proc:- /sys:/sys:ro - /proc:/proc:ro,因为容器指标数据就藏在这两个目录里。注意ro(只读)标记——这是安全底线,cAdvisor只需要读,绝不允许它修改宿主机内核参数。

端口映射设为8080:8080,但实际不直接访问。因为cAdvisor的UI很简陋,且不支持认证,暴露在外网有风险。我们只让它作为Prometheus的一个抓取目标,Prometheus通过http://cadvisor:8080/metrics(Docker内部DNS名)来拉数据。这样既利用了它的能力,又规避了安全风险。我曾经把cAdvisor端口映射到公网,结果第二天就收到阿里云安全中心告警:有境外IP在尝试利用cAdvisor的未授权访问漏洞(CVE-2018-1002105),幸好及时撤下——这个教训让我彻底放弃任何“方便调试”而牺牲安全的设计。

3.4 Grafana:把数字变成“一眼懂”的故事

Grafana不处理指标,只做两件事:从Prometheus API拉数据,然后用面板(Panel)把数据画成图表。它的强大在于“组合”——一个仪表盘(Dashboard)可以同时放CPU使用率折线图、内存占用饼图、HTTP错误率热力图,还能设置变量(Variable)让运维人员一键切换查看不同服务的指标。但新手常犯的错是:一上来就导入上百个社区模板,结果发现90%的面板根本没数据,因为那些模板默认连的是localhost:9090,而容器里Prometheus的地址是http://prometheus:9090。

所以Grafana的datasources.yaml配置必须提前准备好。这个文件要挂载进容器:- ./grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasource.yaml。里面的关键是url: http://prometheus:9090,注意不是localhost——Docker容器间通信走的是内部网络,prometheus是服务名,会被自动解析为对应容器IP。datasources.yaml还要设isDefault: true,这样新建面板时不用手动选数据源。另外,admin_password必须在docker-compose.yml的environment里指定,不能用默认密码,否则Grafana启动会报错:“admin password is too weak”。

注意:Grafana 9.x版本默认启用前端CSP(内容安全策略),如果仪表盘里嵌入了外部JS(比如某些老模板),会直接报错加载失败。解决方案是在grafana.ini里加[security] allow_embedding = true,但这会降低安全性,仅限内网环境使用。

4. 完整实操流程:从零开始,15分钟完成可运行监控栈

4.1 环境准备:Ubuntu 20.04的“最小必要”初始化

先确认系统版本和内核:

lsb_release -a # 应输出 Ubuntu 20.04.6 LTS uname -r # 应输出 5.4.0-xx-generic

更新系统并安装Docker(跳过snap版,用官方CE):

sudo apt update && sudo apt upgrade -y sudo apt install -y curl gnupg2 software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io sudo systemctl enable docker sudo systemctl start docker

验证Docker:sudo docker run hello-world。如果报permission denied,执行sudo usermod -aG docker $USER,然后注销重登。这一步必须做,否则后续所有docker命令都要加sudo,而docker-compose不认sudo上下文,会导致权限混乱。

安装docker-compose(v2.15.1,兼容Ubuntu 20.04的glibc):

sudo curl -L "https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose docker-compose --version # 应输出 docker-compose version 2.15.1

创建项目目录结构(这是后续所有操作的基础):

mkdir -p ~/prometheus-monitor/{prometheus,grafana,docker-compose.yml} cd ~/prometheus-monitor

目录结构必须严格如下:

prometheus-monitor/ ├── docker-compose.yml # 主编排文件 ├── prometheus/ # Prometheus配置目录 │ ├── prometheus.yml # 主配置 │ └── alerts.yml # 告警规则 ├── grafana/ # Grafana配置目录 │ ├── datasources.yaml # 数据源自动配置 │ └── dashboards/ # 仪表盘JSON文件(可选) └── data/ # 持久化数据目录 ├── prometheus/ # Prometheus TSDB存储 └── grafana/ # Grafana SQLite数据库

实操心得:我最初把prometheus.yml放在根目录,结果docker-compose up时容器报错“config file not found”。查日志才发现,volumes挂载路径写错了,./prometheus指向的是空目录。后来养成习惯:每次新建目录,先用touch ./prometheus/prometheus.yml建个空文件,再检查ls -l确认路径真实存在。

4.2 编写docker-compose.yml:让四个容器“各司其职、有序协作”

这个文件是整个项目的“总指挥”,必须精准控制每个容器的启动顺序、网络、卷挂载。以下是精简后的核心内容(已去除注释,实际使用时请保留):

version: '3.8' services: prometheus: image: prom/prometheus:v2.37.0 container_name: prometheus restart: unless-stopped ports: - "9090:9090" volumes: - ./prometheus:/etc/prometheus - ./data/prometheus:/prometheus - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' - '--storage.tsdb.retention.time=30d' - '--web.enable-lifecycle' depends_on: - node-exporter - cadvisor node-exporter: image: prom/node-exporter:v1.3.1 container_name: node-exporter restart: unless-stopped privileged: true ports: - "9100:9100" volumes: - /proc:/proc:ro - /sys:/sys:ro - /:/rootfs:ro - /etc/hostname:/etc/hostname:ro - /etc/hosts:/etc/hosts:ro - /etc/resolv.conf:/etc/resolv.conf:ro command: - '--path.procfs=/proc' - '--path.sysfs=/sys' - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)' - '--collector.systemd' - '--no-collector.wifi' cadvisor: image: gcr.io/cadvisor/cadvisor:v0.45.0 container_name: cadvisor restart: unless-stopped ports: - "8080:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro - /dev/disk/:/dev/disk:ro - /var/run/docker.sock:/var/run/docker.sock command: - '--housekeeping_interval=10s' - '--disable_metrics=disk,percpu,process' grafana: image: grafana/grafana:9.5.2 container_name: grafana restart: unless-stopped ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=your_strong_password - GF_USERS_ALLOW_SIGN_UP=false - GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s:%(http_port)s/ volumes: - ./grafana:/var/lib/grafana - ./grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasource.yaml - ./data/grafana:/var/lib/grafana depends_on: - prometheus

关键点解析:

  • restart: unless-stopped确保容器随系统启动,避免服务器重启后监控失联;
  • volumes里/etc/timezone和/etc/localtime挂载,是为了让Prometheus日志时间戳和宿主机一致,否则查问题时会发现“日志里的时间比系统快8小时”;
  • cadvisor的--disable_metrics参数关掉了磁盘、每核CPU、进程指标,因为这些在单机场景下数据量巨大且极少分析,关掉后内存占用从1.2GB降到300MB;
  • grafana的GF_SECURITY_ADMIN_PASSWORD必须设强密码,GF_USERS_ALLOW_SIGN_UP=false禁用注册,防止被恶意创建账号。

4.3 配置Prometheus主文件:定义“抓什么、怎么抓、多久抓一次”

./prometheus/prometheus.yml内容如下(已精简,仅保留核心):

global: scrape_interval: 15s evaluation_interval: 15s external_labels: monitor: 'codelab-monitor' scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'node-exporter' static_configs: - targets: ['node-exporter:9100'] - job_name: 'cadvisor' static_configs: - targets: ['cadvisor:8080'] - job_name: 'ubuntu-system' static_configs: - targets: ['localhost:9100'] metrics_path: /metrics params: format: ['prometheus'] rule_files: - "alerts.yml" alerting: alertmanagers: - static_configs: - targets: ['localhost:9093']

这里有两个易错点:
第一,targets写的是服务名(node-exporter:9100),不是localhost:9100。因为容器间通信走Docker内部网络,node-exporter会被DNS解析为对应容器IP。如果写localhost,Prometheus容器会去自己内部找9100端口,当然找不到。
第二,ubuntu-system这个job是冗余的——它和node-exporter抓的其实是同一组指标。但加上它,是为了演示如何用static_configs直接监控宿主机,而不用额外起Node Exporter。实际部署时可以删掉,避免重复采集。

./prometheus/alerts.yml定义一个基础告警:

groups: - name: example rules: - alert: HighCpuUsage expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 for: 2m labels: severity: warning annotations: summary: "High CPU usage on {{ $labels.instance }}" description: "{{ $labels.instance }} CPU usage is above 80% for the last 2 minutes."

expr表达式的意思是:计算过去5分钟内,所有CPU核心空闲时间的平均占比,用100减去它,得到使用率。for: 2m表示持续2分钟都超80%才真正触发告警,避免瞬时毛刺误报。

4.4 启动与验证:从命令行到浏览器,全程无断点

执行启动命令:

cd ~/prometheus-monitor sudo docker-compose up -d

等待30秒,检查容器状态:

sudo docker-compose ps # 应看到所有服务状态为"Up",且没有"Restarting"或"Exited" sudo docker-compose logs -f prometheus | grep "Server is ready" # 看到这行日志,说明Prometheus已就绪

验证指标抓取:

  • 浏览器打开http://<你的服务器IP>:9090/targets,看到node-exporter和cadvisor的状态都是UP,Last Scrape时间在15秒内;
  • 在Prometheus表达式框输入count(node_cpu_seconds_total),回车,应返回一个大于0的数字(比如24,代表24个CPU核心);
  • 输入container_memory_usage_bytes{name=~"prometheus|grafana"},应看到两个容器的内存使用字节数。

最后登录Grafana:http://<你的服务器IP>:3000,用admin和你在docker-compose.yml里设的密码登录。首次登录会提示修改密码,按流程操作。进入后,点击左上角“+” → “Import”,输入ID1860(官方Node Exporter仪表盘),选择prometheus数据源,点击“Import”。几秒钟后,一个包含CPU、内存、磁盘、网络的完整仪表盘就出现了——你能清晰看到宿主机的负载曲线、各个容器的内存增长趋势,甚至/dev/sda的IO等待时间。

实操心得:第一次启动时,我遇到Grafana页面空白,F12看Console报错net::ERR_CONNECTION_REFUSED。排查发现是datasources.yaml里url写成了http://localhost:9090,改成http://prometheus:9090后立即恢复。这个错误非常隐蔽,因为localhost在宿主机浏览器里是对的,但在Grafana容器里是错的——它指的是Grafana容器自己的localhost,不是宿主机。

5. 常见问题与排查技巧实录:那些文档里不会写的“血泪经验”

5.1 Prometheus启动失败:TSDB存储损坏的“急救包”

现象:docker-compose logs prometheus显示tsdb: open DB /prometheus: permission denied或failed to start TSDB: invalid block。
原因:最常见的原因是./data/prometheus目录权限不对。Ubuntu 20.04上,/var/lib/prometheus默认属主是root:root,而Prometheus容器内用户是nobody(UID 65534),它没有权限读写该目录。
解决方案:

sudo chown -R 65534:65534 ./data/prometheus sudo chmod -R 755 ./data/prometheus

如果TSDB已损坏(比如磁盘突然断电),不要慌。Prometheus提供了tsdb工具修复:

# 进入Prometheus容器 sudo docker exec -it prometheus sh # 安装jq(用于解析JSON) apk add jq # 列出所有block ls /prometheus # 删除最新的broken block(名字含"tmp"或报错日志里提到的ID) rm -rf /prometheus/01GXXXXXX # 退出容器,重启 exit sudo docker-compose restart prometheus

注意:tsdb工具在v2.37.0镜像里默认没有,必须手动安装prometheus-tsdb二进制。更稳妥的做法是定期备份./data/prometheus目录,用rsync每天同步到另一台机器,这才是生产环境的标配。

5.2 Node Exporter指标缺失:为什么node_disk_io_time_seconds_total为空?

现象:在Prometheus里查node_disk_io_time_seconds_total,返回空结果,但node_cpu_seconds_total正常。
原因:Node Exporter默认不采集磁盘IO指标,需要显式启用--collector.diskstats。
解决方案:修改docker-compose.yml中node-exporter的command,加入:

command: - '--collector.diskstats' # 其他原有参数保持不变

然后重启:sudo docker-compose up -d node-exporter。
验证:curl http://localhost:9100/metrics | grep disk_io_time,应能看到输出。

另一个常见原因是磁盘设备名不匹配。Ubuntu 20.04默认用nvme0n1命名NVMe盘,而老教程里的sda不存在。用lsblk确认真实设备名,再在Prometheus查询时用{device=~"nvme.*"}过滤。

5.3 Grafana无法连接Prometheus:数据源“灰显”的真相

现象:Grafana界面里,Prometheus数据源状态是“Data source is disabled”,点击“Save & Test”报错HTTP Error Bad Gateway。
原因:99%是因为网络不通。Grafana容器里执行ping prometheus失败,说明Docker内部DNS没生效。
排查步骤:

  1. 进入Grafana容器:sudo docker exec -it grafana sh
  2. 执行nslookup prometheus,看是否返回IP;
  3. 如果返回server can't find prometheus,说明Docker网络有问题;
  4. 执行cat /etc/resolv.conf,确认nameserver是127.0.0.11(Docker内置DNS);
  5. 如果是8.8.8.8,说明宿主机DNS配置污染了容器——在/etc/docker/daemon.json里加"dns": ["127.0.0.11"],然后sudo systemctl restart docker。

终极方案:不用DNS,直接用容器IP。先查Prometheus IP:sudo docker inspect prometheus | grep IPAddress,得到类似"IPAddress": "172.20.0.3",然后在datasources.yaml里把url改成http://172.20.0.3:9090。虽然不优雅,但100%有效。

5.4 告警不触发:PromQL表达式“看似正确,实则失效”的陷阱

现象:HighCpuUsage告警规则在Prometheus UI里显示OK,但故意用stress-ng --cpu 4把CPU打满,告警也不发。
原因:for: 2m要求持续2分钟,而stress-ng默认只压测60秒。更隐蔽的坑是evaluation_interval: 15s——Prometheus每15秒评估一次规则,如果for时间不是评估间隔的整数倍,可能错过触发窗口。
解决方案:

  • 把for改成1m(15s的4倍),或2m(15s的8倍);
  • 用stress-ng --cpu 4 --timeout 120s压测2分钟;
  • 在Prometheus UI的“Alerts”页,勾选“Show inactive alerts”,看规则是否处于Pending状态(表示已满足条件,但未到for时间)。

另一个经典错误是expr里用了错误的标签。比如写node_cpu_seconds_total{mode="system"},但实际指标里mode标签值是system、user、iowait等,大小写必须完全一致。用label_values(node_cpu_seconds_total, mode)查出所有合法值,再写表达式。

5.5 性能瓶颈:当Prometheus内存飙升到4GB,怎么办?

现象:sudo docker stats显示prometheus容器内存持续增长到4GB,然后OOM被kill。
原因:指标基数(series count)爆炸。比如container_network_receive_bytes_total{container="",pod="",namespace=""},如果container标签为空,Prometheus会为每个网络接口生成一个时间序列,轻松突破百万级。
解决方案:

  1. 在prometheus.yml的scrape_configs里,为cAdvisor加metric_relabel_configs:
- job_name: 'cadvisor' static_configs: - targets: ['cadvisor:8080'] metric_relabel_configs: - source_labels: [container] regex: '^/$' action: drop - source_labels: [image] regex: '.*' action: labeldrop

这会丢弃container="/"的指标(cAdvisor的根容器),并删除image标签(它值太长,且对监控无用)。
2. 降低抓取频率:把scrape_interval从15s改成30s,内存占用立降30%。
3. 限制TSDB保留时间:--storage.tsdb.retention.time=7d,避免磁盘写满。

实操心得:我曾在一个有50个容器的环境中,因没加metric_relabel_configs,Prometheus在24小时内生成了280万个时间序列,内存暴涨到6GB。加了上述配置后,序列数降到12万,内存稳定在800MB。这个优化不是“锦上添花”,而是“生死攸关”。

6. 进阶扩展与安全加固:让监控系统真正扛住生产环境

6.1 多实例高可用:不用K8s,也能实现Prometheus双活

单实例Prometheus有单点故障风险。最简单的高可用方案是部署两个Prometheus实例,配置相同的抓取目标,但用external_labels区分:

# 第一个实例的prometheus.yml global: external_labels: replica: 'A' # 第二个实例的prometheus.yml global: external_labels: replica: 'B'

然后在Grafana里,数据源配置URL填`http://prometheus-a:9090,http://prom

返回列表