Prometheus生产案例:基于Consul 大规模服务发现监控部署
1. 文档概述
1.1 适用范围
本文针对物理机/虚拟机环境下的 Linux 主机监控 + 通用服务发现场景编写,适配 100 ~ 10000 台服务器的生产环境落地。Kubernetes 场景由 Consul 控制面注入 Sidecar 代理,不适用本文的独立 Client 部署模式。
1.2 软件版本基准
所有配置与机制说明基于以下版本编写,生产部署请以对应版本官方文档为最终依据:
- Consul 1.20.x(社区稳定版)
- Prometheus 2.53.x LTS
- Node Exporter 1.8.2
- 操作系统:Ubuntu 22.04 / 24.04 LTS
1.3 分层架构
整套体系自下而上分为 5 层,各层职责严格分离,避免功能重叠与架构混乱:
业务 / 运维使用层
│
Grafana / Alertmanager
│
Prometheus 监控层
│
Consul 服务发现层
│
Consul Client + Exporter
│
Linux 主机
1.4 核心结论
Consul 是服务目录与健康状态的唯一事实源,负责「谁存在、是否可用」;Prometheus 是指标体系的事实源,负责「采集什么、存多久、如何查询告警」。
二者为互补关系,不存在功能替代。
1.5 选型依据
选择 Consul 作为服务发现组件的核心原因:
- 原生支持 Agent-Local 健康检查,可自动剔除故障实例
- 支持服务元数据标签,便于 Prometheus 过滤与分组聚合
- 分布式架构,Client 节点资源占用低(单实例内存 < 100MB)
- 同时支持 HTTP API 与 DNS 两种查询方式,生态适配广泛
- 经过大规模生产环境验证,稳定性与成熟度高
2. 整体架构设计
2.1 完整拓扑
┌──────────────────────┐
│ Grafana │
│ Dashboard / Explore │
└──────────┬───────────┘
│
┌────────────────▼────────────────┐
│ Prometheus A / B │
│ 独立 TSDB / PromQL / Rules │
└────────────────┬────────────────┘
│
Consul Service Discovery
│
┌──────────────────────▼──────────────────────┐
│ Consul Server Cluster │
│ │
│ Server-01 Server-02 Server-03 │
│ Raft + Catalog + ACL │
└──────────────────────┬───────────────────────┘
│
LAN Gossip / Agent Communication
│
┌────────────────────────────┼────────────────────────────┐
│ │ │
┌───────▼────────┐ ┌────────▼───────┐ ┌────────▼───────┐
│ Consul Client │ │ Consul Client │ │ Consul Client │
│ Node Exporter │ │ Node Exporter │ │ Node Exporter │
│ Other Exporter │ │ Other Exporter │ │ Other Exporter │
└───────┬────────┘ └────────┬───────┘ └────────┬───────┘
│ │ │
Linux Host Linux Host Linux Host
│ │ │
└────────────────────────────┼────────────────────────────┘
│
1000+ Hosts
Prometheus A/B
│
├──────────────► Alertmanager Cluster ─────► 企业微信 / Email / Webhook
│
└──────────────► Long-term Storage(按需)
Thanos / Mimir / VictoriaMetrics
2.2 核心设计原则:控制流与数据流分离
这是本架构的核心设计思想,避免控制面成为数据瓶颈:
- 控制流:服务注册、健康状态、成员关系通过 Consul Agent → Gossip → Server 同步,仅传递状态信息。
- 数据流:Prometheus 直接拉取 Exporter 指标,不经过 Consul 转发,指标流量不经过控制面。
2.2.1 服务注册链路(控制流)
Linux 主机 → Consul Client → Service Register → Health Check → Consul Catalog
2.2.2 指标采集链路(数据流)
Linux → Node Exporter :9100 → Prometheus → TSDB → Grafana
↑
Consul SD 提供目标列表
2.2.3 故障传导链路
Exporter 故障 → Consul Health = critical → Prometheus SD 过滤 → Scrape 异常 → 告警规则触发 → Alertmanager → 通知
2.3 组件核心职责
| 组件 | 核心职责 |
|---|---|
| Consul Server | Raft 一致性、服务目录、集群状态、ACL 等控制面能力 |
| Consul Client | 每台业务机上的 Agent,本地服务注册、健康检查、成员通信 |
| Node Exporter | 暴露 Linux 主机操作系统与硬件指标 |
| Prometheus | 指标拉取、TSDB 存储、PromQL 查询、Recording Rule、告警规则计算 |
| Alertmanager | 告警路由、分组、抑制、静默、多渠道通知 |
| Grafana | 指标可视化与交互式查询 |
| Thanos/Mimir/VictoriaMetrics 等 | 按规模提供长期存储、全局查询、高可用写入能力 |
| CMDB | 资产事实与生命周期管理,作为自动化的数据源 |
2.4 组件职责边界
Consul 不承担
- 指标存储与计算
- 可视化面板
- 日志采集与检索
- 全量资产管理
- PromQL 查询
Prometheus 不承担
- 服务生命周期管理
- 节点故障检测与成员管理
- 资产注册与元数据管理
三者定位
CMDB = 资产属性的真相(这台机器是什么、归谁管、生命周期)
Consul = 服务运行的真相(现在有哪些实例、健康状态如何)
Prometheus = 运行指标的真相(这些实例产生了什么指标、性能如何)
三者互补,不能互相替代。
3. 核心原理
3.1 Consul 架构原理
3.1.1 Server 与 Client
Server
配置 server = true,是集群的控制面,仅部署奇数台(3/5台)。
核心职责:
- Raft 一致性选举与数据持久化
- 维护全局服务目录(Catalog)
- ACL 权限控制
- 处理跨节点的查询请求
- 维护 Leader/Follower 角色
生产原则:Server 仅作为控制面运行,禁止在 Server 节点注册业务服务、运行业务 Exporter。
Client
配置 server = false,部署在每台业务机上,是集群的数据面。
核心职责:
- 本地服务注册与注销
- 本地执行健康检查
- 参与 LAN Gossip,同步集群状态
- 承接本机业务的 API 查询请求
- 将本地状态同步到 Server 集群
关键特性:Client 不参与 Raft 选举,不存储一致性数据,本身无状态,单节点故障不影响全局集群。
3.1.2 Raft 一致性与法定人数
Raft 是 Consul Server 之间的强一致性协议,仅在 Server 节点间运行,用于保证集群数据一致。
- 3 节点集群:法定人数(Quorum)= 2,最多容忍 1 台节点故障
- 5 节点集群:法定人数 = 3,最多容忍 2 台节点故障
重要结论:3 节点集群只能同时挂 1 台;挂 2 台即失去法定人数,集群无法写入,也无法选举新 Leader。
3.1.3 Gossip 协议
基于 Serf 实现,分为 LAN Gossip(同数据中心所有节点)和 WAN Gossip(跨数据中心 Server)。
核心作用:
- 节点自动发现与加入
- 秒级节点故障检测
- 成员状态全网扩散
- 事件广播
生产强制要求:开启 Gossip 加密,防止恶意节点加入集群。
3.1.4 Agent-Local 健康检查模型
这是 Consul 与多数集中式注册中心的本质区别:
所有健康检查由注册服务的本地 Agent 执行,Server 不做远程探测。
例如本机部署了 Consul Client + Node Exporter,HTTP 健康检查由本地 Client 定时调用目标地址,仅将最终健康状态同步到集群。
核心价值:
- 检测结果最接近业务真实状态,无中间网络链路干扰
- 探测压力分布式分摊,大规模场景下 Server 无探测压力
- 避免集中式探测的单点瓶颈
3.1.5 Client 节点的生产必要性
千节点规模下,Client 不是冗余的转发代理,而是架构的核心支柱:
- 可靠的健康检查:没有 Client 则无本地健康检测能力,直连 Server 注册的服务,进程退出后集群无法感知,会产生大量僵尸实例。
- 节点级故障自动收敛:Client 是集群正式成员,节点宕机/断网通过 Gossip 秒级感知,自动摘除所有关联服务;远程注册无法实现该能力。
- 保护 Server 控制面:所有业务侧的注册、查询请求都在本机消化,Server 仅处理一致性写入,大幅降低控制面压力。
- 本地状态缓存:Client 持有全量服务目录副本,查询亚毫秒级响应;Server 短暂故障时,本地缓存可降级提供服务发现能力。
- 收敛安全边界:Client 默认可绑定 127.0.0.1,高权限 Token 无需扩散到业务机,大幅缩小攻击面。
- 运维解耦:服务注册配置随业务生命周期管理,升级变更不影响 Server 集群稳定性。
可省略 Client 的场景仅包括:10 台以内测试环境、纯静态服务目录且无需健康检查、容器编排平台已有原生服务发现机制。
千节点 Linux 生产环境,禁止将直连 Server API 注册作为标准方案。
3.2 Prometheus HA 与容量原理
3.2.1 原生 Prometheus 无主备机制
原生 Prometheus 是单实例架构,不存在内置的主备复制、自动切换机制。
- 双实例部署为冗余模式:A/B 两台独立抓取相同目标、各自维护独立的 TSDB 和 WAL,互不复制数据。
- 查询侧需通过 Grafana 双数据源、或接入 Thanos Query 等统一查询层实现高可用。
- 告警侧依赖 Alertmanager 集群去重,避免双实例重复发送告警。
说明:VictoriaMetrics、Thanos 等衍生方案提供集群化写入能力,不属于原生 Prometheus 范畴。
3.2.2 双实例 HA 方案
推荐拓扑:
Consul Catalog
│
┌───────────┴───────────┐
│ │
Prometheus A Prometheus B
│ │
└───────────┬───────────┘
│
Alertmanager 集群
设计要点:
- A/B 实例部署在不同故障域(不同物理机、不同机架)
- 使用完全一致的 scrape、rule 配置
- 独立存储,互不依赖
- 告警统一发送到 Alertmanager 集群,由后者完成去重、分组、路由
3.2.3 容量规划核心逻辑
“1000 台服务器需要多大配置”不是通用命题。真正决定 Prometheus 容量的核心因素:
- Target 总数量
- 单 Target 的活跃时序数(Active Series)
- 抓取间隔(scrape_interval)
- 标签基数(Cardinality)
- Recording Rule / Alert Rule 数量与复杂度
- 查询并发与面板复杂度
- 数据保留时长
- 磁盘 IOPS 与吞吐量
结论:不存在“单台 Prometheus 最多支持 X 台服务器”的通用标准,所有规格都需要基于实际场景压测确认。
3.2.4 主机监控场景容量估算(经验参考)
以下为默认配置下的经验值,仅用于前期估算,生产必须以实际压测为准:
- 单台 Node Exporter 默认 Collector:约 200~300 个活跃时序
- 开启 systemd/processes 等高开销 Collector:时序数增加 30%~100%
- 1000 台主机默认配置:约 20~30 万活跃时序
- 15s 抓取间隔下,100 万时序约对应 1.5 万 samples/sec
规格参考(15s 抓取间隔)
| 活跃时序规模 | CPU | 内存 | 30天保留磁盘占用估算 |
|---|---|---|---|
| 50 万 | 8核 | 16GB | 1.5~2.25 TB |
| 200 万 | 16核 | 64GB | 6~9 TB |
核心观测指标
prometheus_tsdb_head_series # 当前活跃时序数
rate(prometheus_tsdb_head_samples_appended_total[5m]) # 每秒写入样本数
process_resident_memory_bytes # 进程内存占用
rate(prometheus_tsdb_compactions_failed_total[5m]) # 压缩失败次数
4. 网络与系统前置准备
4.1 端口规划
4.1.1 Consul 端口
| 端口 | 协议 | 角色 | 用途 | 开放范围 |
|---|---|---|---|---|
| 8300 | TCP | Server | Raft 一致性同步 / Server RPC | Server 节点之间 |
| 8301 | TCP+UDP | Server+Client | LAN Gossip 成员发现与心跳 | 所有集群内节点 |
| 8302 | TCP+UDP | Server | WAN 跨数据中心 Gossip | 跨 DC 时使用,单 DC 可关闭 |
| 8500 | TCP | Server+Client | HTTP API / Web UI | Server: 仅运维/监控网段;Client: 仅 127.0.0.1 |
| 8501 | TCP | Server | HTTPS API | 按需开启 TLS 后使用 |
| 8600 | TCP+UDP | Server+Client | DNS 服务发现 | 按需开放 |
⚠️ 重要:8301 必须同时放通 TCP 和 UDP,仅放通 TCP 会导致 Gossip 通信异常,节点频繁离线。
4.1.2 其他组件端口
| 组件 | 端口 | 开放范围 |
|---|---|---|
| Node Exporter | 9100 | 仅 Prometheus 网段 |
| Prometheus | 9090 | 仅运维/监控网段 |
| Alertmanager | 9093 | 仅 Prometheus 网段 + 运维网段 |
| Grafana | 3000 | 运维/业务访问网段 |
4.2 防火墙原则
生产环境禁止通过关闭防火墙解决网络问题。
正确做法:
- 防火墙保持开启状态
- 按最小权限原则,仅开放必要端口,限制来源网段
- 管理面(8500、9090、3000)严禁 0.0.0.0/0 开放,严禁直接暴露公网
- 数据面(9100)仅允许 Prometheus 所在网段访问
4.3 系统基础配置
4.3.1 时间同步
所有节点必须配置时间同步,Raft 协议对时钟偏差敏感,节点间时间差建议控制在 1s 以内,过大可能导致选举异常、日志时序混乱。
apt update && apt install -y chrony curl wget unzip jq vim
systemctl enable --now chronyd
chronyc tracking
timedatectl set-timezone Asia/Shanghai
核心要求:时间同步正常 + 时区统一 + 日志时间可关联。
4.3.2 系统参数优化
- Client 节点:
nofile = 65536可满足需求 - Server 节点:可根据规模上调至 655360
- 所有参数调整需基于实际压测,并非数值越大越好
cat >> /etc/sysctl.conf << 'EOF'
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 1024 65535
fs.file-max = 655360
vm.max_map_count = 262144
EOF
sysctl -p
4.3.3 磁盘与故障域要求
Consul Server
- 必须使用 SSD 磁盘,Raft 日志对 fsync 延迟极度敏感,机械盘会导致频繁 Leader 选举
- 建议 IOPS ≥ 3000,写入延迟 < 10ms
- 3 台 Server 必须分布在不同故障域(不同物理机、不同机架、不同交换机),避免单点故障导致全集群失效
Prometheus
- 优先 SSD,关注持续写入吞吐量与存储空间
- 双实例部署在不同故障域,避免同时故障
5. Consul Server 集群部署
5.1 节点规划
- 1000~3000 台主机规模:3 台 Server
- 5000 台以上或多业务线:5 台 Server
- Server 必须专用,不承载业务服务
5.2 目录规范(遵循 FHS 标准)
/etc/consul.d/
├── consul.hcl # 主配置文件
├── services/ # 服务注册配置目录(Server 节点不建议存放业务服务)
└── certs/ # TLS 证书目录
/var/lib/consul/ # 数据目录(对应 data_dir)
日志统一通过 systemd 管理,使用 journalctl -u consul 查看。
5.3 安装二进制
cd /tmp
# 版本请替换为企业内部验证通过的版本
wget https://releases.hashicorp.com/consul/1.20.0/consul_1.20.0_linux_amd64.zip
unzip consul_1.20.0_linux_amd64.zip
install -m 0755 consul /usr/local/bin/consul
consul version
# 创建系统用户
useradd --system --home /var/lib/consul --shell /usr/sbin/nologin consul
mkdir -p /etc/consul.d/services /var/lib/consul
chown -R consul:consul /etc/consul.d /var/lib/consul
5.4 Server 配置文件
路径:/etc/consul.d/consul.hcl,每个节点修改 node_name、bind_addr、advertise_addr。
# 数据中心名称,同一集群所有节点必须一致
datacenter = "dc1"
# 数据存储目录
data_dir = "/var/lib/consul"
# 服务注册配置目录
config_dir = "/etc/consul.d/services"
# 日志级别
log_level = "INFO"
# 节点名称,集群内唯一
node_name = "consul-server-01"
# 集群内部通信绑定地址,填写本机内网IP,不可写 0.0.0.0
bind_addr = "10.0.0.71"
advertise_addr = "10.0.0.71"
# HTTP API 监听地址
client_addr = "0.0.0.0"
# 开启 Server 模式
server = true
# 集群期望 Server 节点数,3节点集群设为 3,所有节点必须一致
bootstrap_expect = 3
# 集群成员发现列表,填写所有 Server 节点地址
retry_join = ["10.0.0.71:8301", "10.0.0.72:8301", "10.0.0.73:8301"]
# Web UI 配置(Consul 1.18+ ui 字段已废弃)
ui_config {
enabled = true
}
# Raft 性能参数
performance {
raft_multiplier = 1
}
# 节点离线超过 7 天禁止启动,防止脏数据污染集群;0 表示关闭校验
server_rejoin_age_max = "168h"
# 端口配置
ports {
http = 8500
https = -1
dns = 8600
grpc = -1
serf_lan = 8301
serf_wan = 8302
server = 8300
}
5.5 systemd 服务文件
路径:/etc/systemd/system/consul.service
[Unit]
Description=Consul Server Agent
After=network-online.target
Wants=network-online.target
[Service]
User=consul
Group=consul
Type=simple
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=655360
[Install]
WantedBy=multi-user.target
5.6 集群启动与验收
- 依次启动 3 个节点
systemctl daemon-reload
systemctl enable --now consul
- 验收标准(全部通过才算集群健康)
# 1. 进程状态正常
systemctl status consul
# 2. 所有节点状态为 alive
consul members
# 3. Raft 集群正常,1 个 Leader,2 个 Follower
consul operator raft list-peers
# 4. 确认 Leader 信息
consul info | grep leader
注意:HTTP API 可访问 ≠ Raft 集群健康,必须验证 raft list-peers。
6. Consul Client 节点部署
说明:Consul Client 和 Consul Server 使用同一个二进制程序,只是配置文件里
server=false就变成客户端,不需要单独安装客户端版本。
安装参考Consul Server
6.1 Client 配置文件
路径:/etc/consul.d/consul.hcl
datacenter = "dc1"
data_dir = "/data/consul" # 注意权限 chown -R consul:consul
log_level = "WARN"
# 节点名称,全局唯一,建议使用主机名
node_name = "prometheus"
bind_addr = "10.0.0.42"
advertise_addr = "10.0.0.42"
# 安全加固:仅本机可调用 API
client_addr = "127.0.0.1"
# 关闭 Server 模式,纯客户端
server = false
# 集群 Server 节点列表
retry_join = ["10.0.0.71:8301", "10.0.0.72:8301", "10.0.0.73:8301"]
# 优雅退出时主动通知集群
leave_on_terminate = true
# 重启后自动重连集群
rejoin_after_leave = true
6.2 systemd 服务文件
路径:/etc/systemd/system/consul.service
[Unit]
[Unit]
Description=Consul Client Agent
After=network-online.target
Wants=network-online.target
[Service]
User=consul
Group=consul
Type=simple
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d -config-dir=/etc/consul.d/services
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
6.3 加入集群与验证
systemctl daemon-reload
systemctl enable --now consul
# 验证节点已加入集群,状态为 alive
[root@Ans-prometheus /etc/consul.d]# consul members
Node Address Status Type Build Protocol DC Partition Segment
consul1 10.0.0.71:8301 alive server 2.0.0 2 dc1 default <all>
consul2 10.0.0.72:8301 alive server 2.0.0 2 dc1 default <all>
consul3 10.0.0.73:8301 alive server 2.0.0 2 dc1 default <all>
prometheus 10.0.0.42:8301 alive client 2.0.0 2 dc1 default <default>
6.4 生命周期说明
systemctl stop consul会触发优雅退出,Client 主动发送 leave 消息,集群实时收敛状态- 异常断电、强制终止等场景不会触发 leave,依赖 Gossip 协议检测节点故障
- 节点异常离线后,默认
reconnect_timeout(72h)超时后,自动从 Catalog 中清理
7. Node Exporter 部署与服务注册
7.1 Node Exporter 部署
cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar zxf node_exporter-1.8.2.linux-amd64.tar.gz
install -m 0755 node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
useradd --system --no-create-home --shell /usr/sbin/nologin node_exporter
服务文件 /etc/systemd/system/node_exporter.service:
[Unit]
Description=Prometheus Node Exporter
After=network-online.target
Wants=network-online.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9100 \
--collector.systemd
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
启动服务:
systemctl daemon-reload
systemctl enable --now node_exporter
7.2 验证
必须分两步验证,二者不等价:
# 1. 本机验证,确认 Exporter 正常运行
curl http://127.0.0.1:9100/metrics
# 2. 在 Prometheus 节点执行,确认网络链路可达
curl http://10.0.10.20:9100/metrics
7.3 生产参数原则
- 默认 Collector 优先:默认开启的采集器已覆盖绝大多数主机指标,无需全开
- 高开销采集器按需开启:
systemd、processes、interrupts等会显著增加时序数,确认业务需要再开启 - 安全加固:生产环境建议通过
--web.config.file配置 Basic Auth 或 TLS,避免未授权访问指标 - 高危采集器管控:
textfile采集器可自定义指标,需严格管控写入权限,防止恶意注入高基数指标
7.4 服务注册配置
- 配置文件方式,生产推荐
服务定义放入/etc/consul.d/services/目录,支持.hcl或.json格式。
示例:/etc/consul.d/services/node_exporter.hcl
service {
id = "node-exporter-10.0.10.20"
name = "node-exporter"
address = "10.0.10.20"
port = 9100
meta = {
environment = "prod"
team = "ops"
os = "ubuntu2204"
}
check {
id = "node-exporter-http"
name = "node-exporter metrics check"
http = "http://10.0.10.20:9100/metrics"
interval = "15s"
timeout = "5s"
# 持续故障 24 小时后自动注销服务
deregister_critical_service_after = "24h"
}
}
生效与验证:
# 校验配置语法
consul validate /etc/consul.d
# 热加载配置
consul reload
# 验证服务注册
# 列出集群中所有注册的服务名称
[root@Ans-prometheus /etc/consul.d/services]# consul catalog services
consul
node-exporter
# 查询 node-exporter 服务对应的所有实例节点
[root@Ans-prometheus /etc/consul.d/services]# consul catalog nodes -service=node-exporter
Node ID Address DC
consul2 402f1e7d 10.0.0.72 dc1
prometheus de77a92c 10.0.0.42 dc1
- 手动注册
curl -X PUT \
-d '{
"id": "node-exporter-10.0.0.71",
"name": "node-exporter",
"address": "10.0.0.71",
"port": 9100,
"meta": {
"environment": "prod",
"team": "ops",
"os": "ubuntu2204"
},
"checks": [
{
"http": "http://10.0.0.71:9100/metrics",
"interval": "15s",
"timeout": "5s",
"deregister_critical_service_after": "24h"
}
]
}' \
http://127.0.0.1:8500/v1/agent/service/register
| 字段 | 含义 | 说明 |
|---|---|---|
| id | 服务实例唯一 ID | 实例级别全局唯一;重复相同 id 注册会直接覆盖旧实例;推荐规范:node-exporter-10.0.0.71;注销接口也依靠该 id 定位实例。 |
| name | 服务名称 | 服务分组标识,同一服务下全部实例name保持一致;Prometheus consul_sd_configs.services 匹配的就是该字段,用于筛选一类服务实例。 |
| address | 服务实例 IP | 业务服务真实监听 IP;Consul 服务目录对外暴露的实例地址,Prometheus 服务发现读取该地址作为采集目标。 |
| port | 服务端口 | 业务服务监听端口,和 address 组合构成实例访问地址。 |
| meta | 实例元数据 | key‑value 键值对;会生成__meta_consul_service_metadata_ |
| tags | 服务标签数组 | 字符串数组;仅用于 Consul API/UI 过滤;仅产出单个合并元标签__meta_consul_tags,不会被 labelmap 自动拆分;exporter 场景一般不使用。 |
| checks | 健康检查配置 | 数组,支持配置多条健康检查;由接收注册请求的本地 Consul Agent 执行探测,Server 不直接做检查。 |
| checks[].id | 检查项 ID | 单条检查的唯一标识,可选;建议填写,便于区分多条不同检查。 |
| checks[].name | 检查项名称 | 可读描述名称,Consul UI 展示使用,纯展示用途。 |
| checks[].http | HTTP 检查地址 | Agent 周期性 GET 请求;返回 2xx‑3xx 判定为passing,其余状态为critical。 |
| checks[].interval | 检查间隔 | 健康检查执行周期,例15s;不要设置过小,避免大量实例带来 Agent CPU 压力。 |
| checks[].timeout | 检查超时时间 | HTTP 探测超时阈值,超时直接判定检查失败,应小于 interval。 |
| checks[].deregister_critical_service_after | 故障自动注销时间 | 实例持续 critical 状态达到该时长,Agent 自动注销该服务;生产建议配置24h,规避主机临时抖动产生无效注册残留。 |
- 手动注销
和上面的注册命令一一对应,通过服务 ID(Service ID)注销,命令如下:curl -X PUT \ http://127.0.0.1:8500/v1/agent/service/deregister/node-exporter-10.0.0.71- 命令拆解
-
接口路径:/v1/agent/service/deregister/{service_id}
路径最后一段就是注册时 id 字段的值,必须和注册时完全一致(大小写敏感)
示例中注册 ID 为 node-exporter-10.0.0.71,因此注销路径对应写该值 -
请求方法:PUT(Consul Agent 接口统一要求)
-
作用范围:Agent 级别接口,在哪个节点的 Agent 上注册的,就要在哪个节点上注销;跨节点无法直接注销其他节点的服务
-
- 命令拆解
7.5 注册方式对比
| 方式 | 推荐场景 | 优点 | 缺点 |
|---|---|---|---|
| 本地配置文件 | 固定基础设施、常驻 Exporter | 持久化稳定、随 Agent 自动注册、可审计 | 静态,修改需 reload |
| 本地 HTTP API | 动态应用、自动化发布 | 实时生效、无需重启 Agent | 需配套注销逻辑,否则遗留僵尸服务 |
| CLI 命令 | 测试调试、排障 | 操作简单 | 不适合大规模生产 |
| 远程 Server API | 仅临时测试 | 无需部署 Client | 无本地健康检查、无法自动感知故障、不推荐生产 |
7.6 Service ID 设计规范
- 必须全局唯一,推荐格式:
服务名-IP或服务名-主机名 - 同一 Agent 上 Service ID 重复会导致后注册的覆盖先注册的
- 禁止只用服务名作为 ID,否则多实例会互相覆盖
7.7 健康检查机制说明
常用检查类型
| 类型 | 适用场景 | 判断标准 |
|---|---|---|
| HTTP 检查 | Web 服务、Exporter | 返回 2xx~3xx 状态码为健康 |
| TCP 检查 | 数据库、中间件端口 | 端口可建立连接为健康 |
| TTL 检查 | 核心业务进程 | 服务主动上报心跳,超时未报为异常 |
| Script 检查 | 自定义业务逻辑 | 脚本退出码 0 为健康(慎用,有安全风险) |
Node Exporter 场景优先使用 HTTP
/metrics检查,比 TCP 检查更准确。
自动注销机制
deregister_critical_service_after 机制说明:
- 由注册该服务的本地 Agent 维护,并非 Server 全局处理
- 服务持续处于 critical 状态超过阈值,且 Agent 在线时,自动注销服务
- 如果 Agent 本身离线,该机制不生效,依赖节点故障的 Gossip 收敛
- 阈值不建议设置过短(如 < 10min),避免网络抖动导致服务频繁被摘除
节点故障与服务故障的区别
| 场景 | Consul Node 状态 | 服务健康状态 | 处理机制 |
|---|---|---|---|
| Exporter 挂了,主机正常 | alive | critical | 健康检查检测,超阈值自动注销服务 |
| 主机断电/断网 | failed | 随节点标记不可用 | Gossip 检测节点故障,超时后自动清理节点 |
| 主机正常下线 | left | 随节点注销 | Agent 主动 leave,状态实时收敛 |
8. Prometheus Consul 服务发现集成
8.1 SD 工作原理
完整链路:
Consul Catalog + Health → Prometheus Consul SD → Meta Labels → Relabel → 最终 Target → Scrape
Prometheus 定期从 Consul 获取服务列表与健康状态,通过 Relabel 规则过滤生成最终抓取目标,全程无需重启 Prometheus。
8.2 生产级配置示例
以下提供两套生产可用的配置方案,均基于 Consul 分布式架构原则设计,可根据实际规模与运维成本选型。
8.2.1 本机 Consul Client 方案(生产标准推荐)
8.2.1.1 设计思路与角色边界
本方案是千节点以上生产环境的标准落地方式,核心思想是将 Consul 数据面前置到 Prometheus 本机:
- 三台 Consul Server 仍独立部署,作为集群控制面,保持专用、无业务负载
- 在 Prometheus 所在服务器上,额外部署一个 纯 Client 模式 的 Consul 进程(
server = false),作为集群的数据面节点加入集群 - 该 Client 节点通过 Gossip 协议与三台 Server 同步全量服务目录与健康状态,在本地维护完整缓存
- Prometheus 仅需访问本机
127.0.0.1:8500即可获取服务列表,无需直连任何 Server
高可用原理:Client 自动感知集群存活节点,只要 Server 集群仍满足法定人数(3 节点集群挂 1 台仍正常),本地数据就能持续同步,Prometheus 侧完全无感知,真正实现服务发现层高可用。
⚠️ 注意:这不是「把其中一台 Server 和 Prometheus 混部」。Client 是无状态的数据面角色,资源占用极低(<100MB 内存),不参与 Raft 选举,故障不影响集群一致性;与 Server 混部的方案完全不同,也不推荐 Server 与 Prometheus 混部。
8.2.1.2 前置条件
Prometheus 所在机器已部署 Consul Client 并成功加入集群,Client API 仅监听 127.0.0.1。
8.2.1.3 配套 Consul Client 配置
路径:/etc/consul.d/consul.hcl
datacenter = "dc1"
data_dir = "/var/lib/consul"
config_dir = "/etc/consul.d/services"
node_name = "prometheus-sd-client"
bind_addr = "Prometheus本机内网IP"
advertise_addr = "Prometheus本机内网IP"
client_addr = "127.0.0.1"
# 核心:纯客户端模式,不参与选举
server = false
# 三台 Server 地址,用于加入集群
retry_join = ["10.0.0.71:8301", "10.0.0.72:8301", "10.0.0.73:8301"]
leave_on_terminate = true
rejoin_after_leave = true
log_level = "WARN"
启动并验证:
systemctl enable --now consul
# 正常应看到 3 台 Server + 本机 Client,状态均为 alive
consul members
8.2.1.4 Prometheus 配置
scrape_configs:
- job_name: "node-exporter"
# 抓取全局配置
scrape_interval: 15s # 抓取间隔,主机监控推荐 15s
scrape_timeout: 10s # 抓取超时,预留充足冗余
metrics_path: "/metrics" # 显式声明指标路径,避免歧义
# Consul 服务发现配置
consul_sd_configs:
- server: "127.0.0.1:8500" # 仅对接本机 Consul Client,无单点故障
datacenter: "dc1" # 必须与集群实际 DC 名称严格一致
refresh_interval: "30s" # 服务列表刷新间隔,平衡实时性与压力
services: ["node-exporter"] # 源头仅拉取指定服务,减少无效开销
allow_stale: true # 允许非 Leader 返回数据,降低控制面压力
# 开启 ACL 时使用,凭证文件需严格限制权限
# token_file: "/etc/prometheus/secrets/consul-token"
# 标签重写规则
relabel_configs:
# 1. 核心过滤:仅保留健康状态为 passing 的实例,故障自动摘流
- source_labels: [__meta_consul_health]
regex: "passing"
action: keep
# 2. 拼接最终抓取地址:服务IP + 服务端口
- source_labels: [__meta_consul_service_address, __meta_consul_service_port]
separator: ":"
target_label: __address__
# 3. 映射业务标签:节点名称、数据中心、环境元数据
- source_labels: [__meta_consul_node]
target_label: node
- source_labels: [__meta_consul_datacenter]
target_label: datacenter
- source_labels: [__meta_consul_service_metadata_environment]
target_label: environment
8.2.1.5 方案核心优势
- 真正的高可用:基于 Consul 原生分布式协议,集群只要不失去法定人数,服务发现就不受影响,远优于多地址轮询
- 查询性能最优:全量服务目录本地缓存,亚毫秒级返回,无跨网络请求开销
- 保护控制面:Prometheus 不直接请求 Server,大幅降低控制面读压力
- 安全边界清晰:Server 的 8500 端口无需对 Prometheus 网段开放,仅需开放 8301 Gossip 端口,攻击面更小
- 配置简洁:Prometheus 侧仅需一行本地地址,无需重复配置多台 Server
8.2.2 多 Server 直连冗余方案(无额外进程)
8.2.2.1 适用场景
中小规模场景,不想额外部署 Consul Client 进程,通过配置多台 Server 地址实现服务发现层的基本冗余。
Prometheus 会并行从所有 Server 拉取服务列表,基于实例标签自动去重;单台 Server 故障时,其余节点仍可正常返回目标。
8.2.2.2 Prometheus 配置
scrape_configs:
- job_name: "node-exporter"
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: "/metrics"
consul_sd_configs:
# 第 1 台 Server
- server: "10.0.0.71:8500"
datacenter: "dc1"
refresh_interval: "30s"
services: ["node-exporter"]
allow_stale: true
# 第 2 台 Server
- server: "10.0.0.72:8500"
datacenter: "dc1"
refresh_interval: "30s"
services: ["node-exporter"]
allow_stale: true
# 第 3 台 Server
- server: "10.0.0.73:8500"
datacenter: "dc1"
refresh_interval: "30s"
services: ["node-exporter"]
allow_stale: true
relabel_configs:
- source_labels: [__meta_consul_health]
regex: "passing"
action: keep
- source_labels: [__meta_consul_service_address, __meta_consul_service_port]
separator: ":"
target_label: __address__
- source_labels: [__meta_consul_node]
target_label: node
- source_labels: [__meta_consul_datacenter]
target_label: datacenter
- source_labels: [__meta_consul_service_metadata_environment]
target_label: environment
8.2.2.3 注意事项
- 三台配置的
services、datacenter必须完全一致,否则失去冗余意义 - 会产生重复查询请求,对 Server 有轻微额外压力
- 仅适合中小规模场景,千节点以上强烈推荐方案一
8.2.3 关键参数详解
| 参数 | 作用与生产建议 |
|---|---|
server |
对接的 Consul Agent 地址,单条配置仅支持单个 host:port,不支持数组;多实例冗余需写多条 consul_sd_configs |
datacenter |
必须与 Consul 集群实际数据中心名称严格一致;不一致会返回空结果且无明确报错,是常见隐形坑 |
services |
从源头过滤服务,仅拉取指定名称的服务,大幅减少无效数据传输与后续 Relabel 开销;支持多服务数组;不支持通配符 |
refresh_interval |
服务列表刷新间隔,默认 60s;主机监控场景 30s 足够,过频会增加 Consul 压力 |
allow_stale |
允许非 Leader 节点返回读请求,大幅降低 Leader 压力,同时提升多节点可用性;服务发现场景对秒级一致性无要求,强烈建议开启 |
token_file |
开启 ACL 时使用,建议用文件存储凭证,禁止明文写入配置;需严格限制文件权限为 600 |
8.2.4 Relabel 规则设计说明
- 健康状态过滤:
action: keep仅保留passing状态的实例,是 Consul SD 最核心的规则,实现「故障实例自动摘流」的核心能力 - 抓取地址拼接:使用
service_address + service_port生成最终抓取地址,确保抓取的是服务真实端口,而非 Consul 节点默认端口 - 业务标签映射:将 Consul 中的节点、元数据映射为 Prometheus 永久标签,用于后续聚合、过滤、告警路由
- 标签设计原则:保留默认
instance = IP:Port,额外新增node标签存储主机名;禁止用主机名覆盖instance,避免丢失精确定位信息
8.2.5 常见配置陷阱
- 服务名不匹配:
services填写的名称必须与 Consul 注册的service.name完全一致(大小写敏感),否则发现不到任何目标 - datacenter 不一致:Consul 默认值为
dc1,若自定义了 DC 名称此处必须同步修改;该错误无明确报错,排查难度高 - 健康过滤导致无目标:90% 的「Consul 正常但 Prometheus 无 Target」问题,都是服务健康状态非 passing 被规则过滤;排查优先查看 Prometheus UI → Status → Service Discovery 中的 Dropped Targets
- 单地址单点故障:仅配置一台 Server 地址时,该节点故障会导致服务发现完全停滞,生产环境必须做高可用
- 明文 Token 风险:禁止在配置文件中明文写入 ACL Token,必须使用
token_file并严格限制文件权限与访问范围
8.2.3 验证
# 查看 Consul 中服务实例数
[root@consul2 ~]# curl -s http://10.0.0.71:8500/v1/catalog/service/node-exporter | jq length
2
[root@consul2 ~]# curl -s http://10.0.0.71:8500/v1/catalog/service/node-exporter | jq '.[].Node'
"consul2"
"prometheus"
# 查看 Prometheus 生效的目标数
[root@consul2 ~]# curl -s http://10.0.0.42:9090/api/v1/targets | jq '.data.activeTargets | length'
3
[root@consul2 ~]# curl -s http://10.0.0.42:9090/api/v1/targets | jq -r '.data.activeTargets[].labels.instance'
10.0.0.72:9100
10.0.0.42:9100
localhost:9090
9. 标签治理与数据规范
9.1 推荐标签体系
适合聚合、过滤、告警路由的维度标签:
environment=prod
region=cn-east-1
az=az01
team=ops
os=ubuntu2204
service=node-exporter
9.2 高基数陷阱
禁止作为 Label 的内容
- request_id、user_id、订单号、UUID
- 时间戳、随机字符串
- 自由文本内容
核心原则
Label 是用于聚合的维度,不是日志字段。高基数标签会导致时序数爆炸,撑爆 TSDB。
错误示例:
http_requests_total{request_id="a7f8..."} # 完全不可聚合,基数爆炸
正确方向:
http_requests_total{method="GET", route="/api/orders", status="200"}
9.3 系统边界
推荐数据流:
CMDB(资产事实)→ 自动化平台 → Consul 注册(服务发现事实)→ Prometheus SD → 指标(运行事实)
三者各司其职,不要用 Consul 存储全量资产信息,也不要用 Prometheus 做资产管理。
10. 自动化批量部署
10.1 项目目录规范
monitoring/
├── inventory/
│ ├── production
│ └── staging
├── group_vars/
├── roles/
│ ├── consul_server/
│ ├── consul_client/
│ ├── node_exporter/
│ └── prometheus/
├── templates/
└── site.yml
10.2 Role 职责划分
| Role | 负责内容 |
|---|---|
| consul_server | Server 安装、配置、systemd、TLS、ACL、验证 |
| consul_client | Client 安装、配置、systemd、Gossip、Agent Token |
| node_exporter | Exporter 安装、systemd、Consul 服务注册配置 |
| prometheus | Prometheus 部署、配置、规则、SD 配置 |
10.3 幂等性要求
重复执行 Playbook 不能产生重复用户、重复配置、重复服务注册。
- 全部使用 Ansible 官方幂等模块
- 配置文件使用 template 管理
- 服务注册配置随 Role 统一管理
10.4 分批发布原则
禁止一次性推送 1000 台节点,避免打爆 Server 集群。
推荐批次:10 → 50 → 100 → 500 → 全量
每批次观察指标:
- Server CPU、内存、磁盘 IO
- Gossip 状态、Raft 延迟
- Catalog 增长速度
- Prometheus Target 与时序增长
# 参考
- name: Deploy Consul Client + Node Exporter
hosts: consul_clients
gather_facts: yes
vars:
consul_encrypt_key: "<Gossip 加密密钥>"
consul_agent_token: "<Client Agent Token>"
tasks:
- name: Create directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /data/consul/data
- /data/consul/conf.d
- /data/node_exporter
- name: Install Consul binary
unarchive:
src: https://releases.hashicorp.com/consul/1.20.0/consul_1.20.0_linux_amd64.zip
dest: /usr/local/bin/
remote_src: yes
creates: /usr/local/bin/consul
- name: Create consul user
user:
name: consul
shell: /sbin/nologin
create_home: no
- name: Set consul dir permissions
file:
path: /data/consul
owner: consul
group: consul
recurse: yes
- name: Generate client config
copy:
content: |
datacenter = "dc1"
data_dir = "/data/consul/data"
config_dir = "/data/consul/conf.d"
node_name = "{{ ansible_hostname }}"
bind_addr = "{{ ansible_default_ipv4.address }}"
advertise_addr = "{{ ansible_default_ipv4.address }}"
client_addr = "127.0.0.1"
server = false
retry_join = ["10.0.0.71", "10.0.0.72", "10.0.0.73"]
log_level = "WARN"
leave_on_terminate = true
rejoin_after_leave = true
encrypt = "{{ consul_encrypt_key }}"
acl {
enabled = true
default_policy = "deny"
tokens {
agent = "{{ consul_agent_token }}"
}
}
dest: /data/consul/consul.hcl
- name: Deploy consul systemd service
copy:
content: |
[Unit]
Description=Consul Client Agent
After=network-online.target
Wants=network-online.target
[Service]
User=consul
Group=consul
Type=simple
ExecStart=/usr/local/bin/consul agent -config-file=/data/consul/consul.hcl
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=655360
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/consul.service
- name: Install Node Exporter
unarchive:
src: https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
dest: /data/node_exporter/
remote_src: yes
extra_opts: [--strip-components=1]
creates: /data/node_exporter/node_exporter
- name: Deploy node_exporter service
copy:
content: |
[Unit]
Description=Prometheus Node Exporter
After=network-online.target
[Service]
Type=simple
ExecStart=/data/node_exporter/node_exporter --web.listen-address=:9100 --collector.systemd
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/node_exporter.service
- name: Register node_exporter to Consul
copy:
content: |
{
"service": {
"id": "node_exporter-{{ ansible_default_ipv4.address }}",
"name": "node_exporter",
"address": "{{ ansible_default_ipv4.address }}",
"port": 9100,
"checks": [
{
"http": "http://127.0.0.1:9100/metrics",
"interval": "15s",
"timeout": "5s",
"deregister_critical_service_after": "72h"
}
]
}
}
dest: /data/consul/conf.d/node_exporter.json
- name: Start all services
systemd:
name: "{{ item }}"
daemon_reload: yes
state: started
enabled: yes
loop:
- consul
- node_exporter
11. 全生命周期管理
11.1 新增服务器标准流程
1. OS 初始化与安全加固
2. 时间同步与基础配置
3. 部署 Consul Client
4. 加入 Consul 集群,验证节点状态
5. 部署 Node Exporter / 业务 Exporter
6. 配置服务注册与健康检查
7. 验证健康状态为 passing
8. Prometheus SD 自动发现 Target
9. 验证指标采集正常、up == 1
10. Grafana 可查询、告警正常生效
11.2 正常下线流程
1. 业务流量摘除
2. 注销服务注册(consul services deregister / API 注销)
3. 停止 Exporter
4. 执行 consul leave 优雅退出 Client
5. 停止 consul 服务
6. 服务器下电/回收
7. 验证节点从 Consul 成员列表消失、Prometheus Target 消失
11.3 节点替换
- 新旧节点主机名、IP 均变化时,确保清理旧节点的 Node ID、Service ID
- 禁止只修改 IP 不清理旧数据,避免产生残留条目
12.4 批量扩容
从 1000 台扩容到 3000 台时,同步观察:
- Consul:Catalog 规模、Gossip 延迟、Server 资源、Raft 延迟
- Prometheus:Target 数量、Active Series、Samples/sec、WAL/TSDB 状态、查询延迟
- 瓶颈通常先出现在 Prometheus 侧,而非 Consul 侧
12. 自监控与告警体系
12.1 Consul 自监控关键指标
- 集群是否有 Leader
- Raft 对等节点数量是否符合预期
- Raft 提交延迟
- 节点离线数量
- 健康检查 critical 数量
- ACL 错误次数
- Server 磁盘使用率
12.2 Prometheus 自监控关键指标
- Target Up 率
- Scrape 失败率
- Active Series 增长率
- Samples 写入速率
- WAL 写入失败、TSDB 压缩失败
- 规则评估延迟
- Remote Write 延迟与失败率
- 磁盘空间使用率
12.3 最低告警集合(必配)
- Consul 无集群 Leader
- Consul Server 节点离线
- Consul Client 大量离线
- Node Exporter 实例故障
- Prometheus Scrape 失败率过高
- Prometheus TSDB / WAL 异常
- Active Series 突增(基数爆炸预警)
- 监控组件磁盘空间不足
- 规则评估异常
- Alertmanager 集群异常
原则:监控系统自身的故障告警,优先级高于业务告警。
13. 备份、升级与容灾
13.1 Snapshot 快照备份
社区版 Consul 无内置自动快照功能,需通过脚本定时执行。
备份命令
consul snapshot save /backup/consul-$(date +%Y%m%d-%H%M%S).snap
- 可在任意 Server 节点执行,建议在 Follower 节点执行,避免影响 Leader 性能
- 快照包含全量集群数据:KV、服务注册、ACL、会话、配置条目
- 备份文件属于敏感数据,需加密存储、严格控制访问权限
定时备份脚本
#!/bin/bash
BACKUP_DIR="/data/backup/consul"
mkdir -p $BACKUP_DIR
consul snapshot save ${BACKUP_DIR}/consul-$(date +%Y%m%d).snap
# 保留 30 天
find $BACKUP_DIR -name "*.snap" -mtime +30 -delete
加入 crontab 每日凌晨执行。
13.2 恢复演练
有备份 ≠ 能恢复,必须定期做恢复演练。
恢复流程:
搭建新集群(同版本) → 初始化单节点 → 执行 snapshot restore → 验证 Raft 状态 → 验证服务目录 → 验证 ACL → 接入 Prometheus 验证 SD
- 恢复操作会覆盖目标集群所有数据,严禁在正常运行的生产集群上直接执行
- 建议至少每季度做一次恢复演练,验证备份有效性
13.3 版本升级
生产升级流程:
- 阅读对应版本 Release Notes,确认兼容性与废弃变更
- 测试环境完整验证功能、配置兼容性
- 生产集群执行 Snapshot 备份
- 先升级 Follower 节点,滚动重启,验证正常
- 最后升级 Leader 节点(会触发一次重新选举)
- 全量验证集群状态、服务注册、Prometheus SD
- 观察 24 小时无异常后完成升级
禁止通过 apt upgrade 无脑升级,Consul 大版本常有配置不兼容变更。
14. 企业级安全加固
参考架构:
Consul Datacenter
│
┌────────────────┴────────────────┐
│ │
Control Plane Data Plane
│ │
3/5 Server Agents 1000+ Client Agents
│ │
├── Raft ├── Service
├── ACL ├── Health Check
├── Catalog └── Workload
└── CA / Config
14.1 ACL 访问控制
14.1.1 什么是 ACL
ACL(Access Control List,访问控制列表)是 Consul 内置的权限管理系统。它通过 Token(令牌) 和 Policy(策略) 两个核心概念,实现对集群资源的细粒度访问控制。
- Token:一串唯一的密钥(形如
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),代表一个身份。当客户端向 Consul 发起 API 请求或执行 CLI 命令时,需要携带 Token 来证明自己的身份。 - Policy:一组权限规则的集合,定义了“允许对哪些资源执行哪些操作”。Policy 可以绑定到一个或多个 Token 上,Token 的最终权限是其关联的所有 Policy 的并集。
Consul 的资源按前缀划分,主要包括:
| 资源前缀 | 说明 |
|---|---|
agent |
本地 Agent 的操作,如注册/注销服务、更新健康检查 |
node |
节点信息,如节点注册、注销、健康状态更新 |
service |
服务目录信息,如服务注册、查询、注销 |
key |
KV 存储中的键值 |
session |
会话管理 |
acl |
ACL 自身的策略和 Token 管理 |
operator |
集群运维操作,如 Raft 快照、节点强制离线 |
ACL 的核心目标是 最小权限原则:每个 Token 只拥有完成其职责所必需的最小权限,避免权限过大造成安全风险。
14.1.2 核心目标
- 默认拒绝所有未授权操作:开启 ACL 后,
default_policy = "deny"表示任何未携带有效 Token 的请求都会被拒绝。这类似于“默认关上门”,只有持有钥匙的人才能进入。 - 按最小权限原则分配 Token:不为图省事给所有组件同一个高权限 Token,而是为不同角色创建专用 Token。
- 高权限 Management Token 仅管理员持有:Bootstrap 生成的最高权限 Token(相当于 root)只能由管理员保存在安全位置,严禁写入任何业务节点的配置文件。
- Agent 仅配置 Agent Token:所有 Consul Agent(Server 和 Client)的配置中只使用 Agent Token,该 Token 仅允许操作本节点的服务和节点信息,不能修改全局策略或其他节点。
- 监控组件仅授予只读 Token:Prometheus 等监控系统只需要查询服务目录,因此授予只读权限,避免监控系统被攻破后成为攻击入口。
14.1.3 完整实施步骤
以下步骤假设集群当前未启用 ACL,所有节点处于正常运行状态。
步骤 1:备份集群数据
ACL 的启用属于高风险操作,一旦配置错误可能导致集群不可用。因此,务必先执行快照备份。
consul snapshot save /data/consul/backup/pre-acl-$(date +%Y%m%d).snap
备份文件应妥善保存,确保可恢复。
步骤 2:所有 Server 节点开启 ACL(平滑过渡方案)
直接在所有节点上启用 default_policy = "deny" 会导致所有未携带 Token 的请求立即被拒绝,可能造成集群功能中断(例如 Client 尚未配置 Token)。因此,推荐采用 “先 allow 后 deny” 的滚动收紧策略。
第一阶段: 在所有 Server 节点的配置文件 /data/consul/consul.hcl 中添加以下内容:
acl = {
enabled = true
default_policy = "allow" # 先保持宽松,保证现有功能不受影响
enable_token_persistence = true
}
enabled = true:开启 ACL 功能。default_policy = "allow":暂时允许所有未携带 Token 的请求,确保现有业务不受影响。enable_token_persistence = true:将 Token 信息持久化到磁盘,避免重启丢失。
然后滚动重启所有 Server 节点:
systemctl restart consul
确保每个节点重启后集群正常(consul members 显示所有节点 alive,Raft 有 Leader)。
步骤 3:Bootstrap 生成最高权限 Token
在任一 Server 节点执行:
consul acl bootstrap
此命令仅在集群首次启用 ACL 时有效,会生成一个全局管理 Token(Management Token),拥有不受限的权限。返回内容类似:
{
"ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"AccessorID": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"SecretID": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
"Description": "Bootstrap Token (Global Management)"
}
务必妥善保存 SecretID。这是集群最高权限 Token,一旦丢失无法找回,只能重建集群并从备份恢复。
步骤 4:创建 Agent 专用 Policy 和 Token
Agent Token 供 Consul Server 和 Client 节点自身使用,权限应严格限制在“管理本节点的节点信息和本节点注册的服务”,不得包含全局管理权限。
创建 Policy 文件 /data/consul/acl/agent-policy.hcl:
agent_prefix "" {
policy = "write"
}
node_prefix "" {
policy = "write"
}
service_prefix "" {
policy = "write"
}
agent_prefix "":允许操作本地 Agent 的 API(如注册服务、更新健康检查)。node_prefix "":允许注册、更新本节点信息。service_prefix "":允许注册、注销本节点提供的服务。
注意:此 Policy 权限较大(写权限覆盖所有 service 前缀),但配合 Agent Token 的绑定机制,实际只能操作本节点 Agent 所管理的服务,因为 Agent 会将请求限制在本地节点。仍建议为每个节点创建独立 Token,避免一个 Token 泄露影响所有节点。
将 Policy 写入 Consul(需使用 Bootstrap Token 授权):
consul acl policy create \
-name "agent-policy" \
-description "Minimal policy for Consul agents" \
-rules @/data/consul/acl/agent-policy.hcl \
-token "<Bootstrap Token SecretID>"
为每个节点创建独立 Token(推荐):
# 为 consul-01 节点创建 token
consul acl token create \
-description "Agent token for consul-01" \
-policy-name agent-policy \
-token "<Bootstrap Token SecretID>"
记录返回的 SecretID,稍后配置到对应节点的 acl.tokens.agent 字段。
步骤 5:创建 Prometheus 只读 Policy 和 Token
Prometheus 仅需要查询服务目录来发现监控目标,因此授予只读权限即可。
创建只读策略文件 /data/consul/acl/read-only-policy.hcl:
node_prefix "" {
policy = "read"
}
service_prefix "" {
policy = "read"
}
创建 Policy 和 Token:
consul acl policy create \
-name "read-only" \
-description "Read-only policy for monitoring" \
-rules @/data/consul/acl/read-only-policy.hcl \
-token "<Bootstrap Token SecretID>"
consul acl token create \
-description "Prometheus read token" \
-policy-name read-only \
-token "<Bootstrap Token SecretID>"
步骤 6:配置 Agent Token 并收紧默认策略
- 将所有 Server 和 Client 节点的配置文件
/data/consul/consul.hcl中的 ACL 部分修改为:
acl = {
enabled = true
default_policy = "deny" # 收紧为默认拒绝
enable_token_persistence = true
tokens = {
agent = "<对应节点的 Agent Token SecretID>"
}
}
- 按“先 Server 后 Client”的顺序滚动重启所有节点:
systemctl restart consul
重启后,所有未携带 Token 的请求将被拒绝,而配置了 Agent Token 的节点能够正常注册服务、上报健康状态。
- 验证集群状态:
consul members
consul operator raft list-peers
确保所有节点均处于 alive 状态,Raft 有稳定的 Leader。
步骤 7:验证权限
- 无 Token 访问应被拒绝:
curl -s http://10.0.0.71:8500/v1/catalog/services
# 预期返回 403 Forbidden
- 使用只读 Token 查询应成功:
curl -s -H "X-Consul-Token: <read-only-token>" http://10.0.0.71:8500/v1/catalog/services
# 预期返回服务列表
- 使用 Agent Token 尝试创建全局策略应被拒绝:
consul acl policy create -name test -rules 'node_prefix "" { policy = "write" }' -token "<agent-token>"
# 预期返回 Permission denied
14.1.4 ACL 常见故障与处理
| 故障现象 | 可能原因 | 解决方案 |
|---|---|---|
Agent 启动后日志报 ACL not found |
Agent Token 配置错误或未创建 | 检查 token 是否正确,重新创建 token 并配置 |
consul members 显示节点状态异常 |
Agent Token 缺少 node_prefix write 权限 |
修正 agent-policy,添加相应权限 |
| Prometheus 目标发现为空 | 只读 Token 无效或权限不足 | 检查 token,确保 service_prefix read 和 node_prefix read |
| 忘记 Bootstrap Token | 管理 Token 丢失 | 无法找回,只能重建集群并从备份恢复(如有快照) |
14.2 Gossip 加密
14.2.1 目的
Consul 使用基于 Serf 的 Gossip 协议进行节点成员管理和故障检测。Gossip 通信默认是明文的,同一网络中的恶意节点可以:
- 伪造成员加入集群,窃取服务目录或进行拒绝服务攻击。
- 嗅探通信内容,获取敏感信息。
通过 Gossip 加密,使用对称密钥对 Gossip 通信进行加密,只有持有相同密钥的节点才能加入集群并正常通信,有效防止未授权节点接入。
14.2.2 配置步骤
生成密钥
在任意节点执行一次:
consul keygen
输出形如 a1b2c3d4-... 的 32 字节密钥(Base64 编码)。该密钥必须安全保存,并分发到所有节点。
所有节点配置
在 /data/consul/consul.hcl 中添加:
encrypt = "生成的密钥字符串"
encrypt_verify_incoming = true
encrypt_verify_outgoing = true
encrypt:设置对称加密密钥。encrypt_verify_incoming:要求所有传入的 Gossip 消息必须经过加密验证,否则拒绝。encrypt_verify_outgoing:要求所有发出的 Gossip 消息必须加密并携带验证信息。
注意:
encrypt_verify_incoming/outgoing需要 Consul 1.8 及以上版本,请确保所有节点版本一致。
滚动上线
禁止一次性全量修改,否则节点间因密钥不一致导致 Gossip 通信中断,集群成员关系混乱。
推荐分两个阶段:
- 阶段一:所有节点添加
encrypt密钥,但先设置encrypt_verify_incoming = false和encrypt_verify_outgoing = false,滚动重启。此时通信已加密,但尚未强制验证。 - 阶段二:待全部节点稳定运行后,再开启
encrypt_verify_incoming = true和encrypt_verify_outgoing = true,滚动重启。
14.2.3 验证
consul info | grep -A 5 "serf_lan"
# 确认 encryption 字段为 true
也可以检查日志,确认没有 Gossip 解密失败的错误。
14.3 TLS 加密
14.3.1 适用链路
生产环境建议对以下链路启用 TLS:
- Prometheus → Consul Server API:保护服务发现查询的数据机密性和完整性,防止中间人攻击。
- Consul Server 之间的 RPC:保护 Raft 一致性协议传输,防止数据篡改和窃听。
- Consul Agent 之间的通信(可选但推荐):保护 Client 与 Server 之间的 RPC。
14.3.2 证书准备
使用自建 CA 或 HashiCorp Vault 签发证书。证书必须包含以下 SAN(Subject Alternative Name):
- 节点的主机名
- 节点的 IP 地址
server.dc1.consul(用于 Server 间通信的统一名称)localhost(可选,便于本地调试)
如果使用自建 CA,需在所有节点上信任该 CA 证书。
14.3.3 配置示例
在 /data/consul/consul.hcl 中添加:
verify_incoming = true
verify_outgoing = true
verify_server_hostname = true
ca_file = "/data/consul/tls/consul-agent-ca.pem"
cert_file = "/data/consul/tls/dc1-server-consul-01.pem"
key_file = "/data/consul/tls/dc1-server-consul-01-key.pem"
auto_encrypt {
allow_tls = true
}
verify_incoming:验证所有传入连接的客户端证书。verify_outgoing:验证所有出站连接的服务端证书。verify_server_hostname:验证服务器证书的主机名是否匹配。ca_file:CA 证书路径。cert_file/key_file:节点自己的证书和私钥路径。auto_encrypt.allow_tls:允许 Client 通过auto_encrypt自动获取客户端证书(Server 需开启此选项)。
对于 Client 节点,可使用 auto_encrypt 自动获取客户端证书,减少证书分发工作量。Client 配置中需包含:
auto_encrypt {
tls = true
}
14.3.4 常见故障
| 故障现象 | 原因 | 处理 |
|---|---|---|
证书验证失败:certificate signed by unknown authority |
CA 证书未正确配置 | 确保所有节点 ca_file 指向同一 CA |
x509: cannot validate certificate for IP ... |
证书 SAN 不包含该 IP | 重新签发证书,添加正确 SAN |
| 证书过期 | 未建立轮换机制 | 使用 Vault 自动轮换或设置告警提前续期 |
14.4 密钥管理最佳实践
14.4.1 敏感信息范围
- Gossip 加密密钥
- ACL Token(Bootstrap、Agent、只读等)
- TLS 私钥
- 云厂商访问凭证(如使用 Vault)
14.4.2 管理原则
- 禁止明文存储:所有 Token、密钥不得写入 Git、Ansible 明文变量或文档中。
- 使用加密存储:
- Ansible Vault:适用于 Ansible 自动化部署场景,加密变量文件。
- HashiCorp Vault:统一秘密管理,支持动态生成 Consul Token 和证书轮换。
- 云 KMS:如 AWS KMS、阿里云 KMS,用于加密本地配置文件。
- 最小化分发:高权限 Management Token 仅存储在管理员的安全介质中(如密码管理器),禁止分发到业务节点。
- 定期轮换:建立密钥轮换机制,尤其是 TLS 证书和 Agent Token。
- 审计与追踪:记录 Token 的创建、使用、删除操作,定期审计权限。
14.4.3 Ansible Vault 示例
# 加密变量文件
ansible-vault encrypt secrets.yml
# 在 Playbook 中引用
vars_files:
- secrets.yml
secrets.yml 内容(加密后):
consul_gossip_key: "a1b2c3d4-..."
consul_agent_token: "xxxxxxxx-..."
prometheus_read_token: "yyyyyyyy-..."
运行时提供 Vault 密码:
ansible-playbook deploy.yml --vault-password-file .vault_pass
14.5 安全加固检查清单
| 检查项 | 要求 | 验证命令 |
|---|---|---|
| Gossip 加密 | 所有节点 encrypt 一致,验证开启 |
consul info | grep encryption |
| ACL 启用 | default_policy=deny,所有操作需 Token |
无 Token 请求 API 返回 403 |
| Agent Token 最小权限 | 无 acl 写权限 |
尝试创建策略应被拒绝 |
| Prometheus 只读 Token | 仅能查询服务目录 | 尝试修改服务应被拒绝 |
| TLS 配置 | API 和 RPC 使用 HTTPS | curl https://... 成功,curl http://... 拒绝 |
| 敏感信息加密存储 | 无明文密钥出现在配置文件中 | 检查 Git 仓库和服务器文件 |
14.6 总结
企业级安全加固是 Consul 生产部署的 必选项 而非可选项。通过 ACL 最小权限、Gossip 加密、TLS 传输加密 和 严格的密钥管理,可有效防止未授权访问、数据泄露和中间人攻击。实施时务必遵循滚动升级、先备份后操作、先验证后收紧的原则,确保业务连续性。安全无小事,任何一个薄弱环节都可能成为攻击突破口,因此上述每一项措施都应严格落实并定期审计。
15. 企业级故障排查手册
1 故障定位总链路
遇到 Grafana 无数据,按从下到上的顺序排查,禁止上来就重启所有服务:
Linux 主机 → Node Exporter → Consul Client → 服务注册 → 健康状态 → Consul Catalog → Prometheus SD → Target → PromQL → Grafana
2 Server 启动失败:server_rejoin_age_max
现象:
服务启动立即退出,8500 端口无监听
典型日志:
startup error: error="refusing to rejoin cluster because server has been offline for more than the configured server_rejoin_age_max (168h0m0s)"
根因:节点离线超过配置阈值(默认 7 天),本地 Raft 数据过期,集群保护机制禁止启动,防止脏数据污染。
解决方案:
- 场景 A:集群其他节点正常,仅单节点故障
- 停止 consul 服务
- 仅删除 Raft 数据,保留 node-id:
rm -rf /var/lib/consul/raft - 启动服务,节点以全新 Follower 身份从 Leader 同步全量数据
- 场景 B:整个集群全部宕机,无可用 Leader
- 停止所有节点
- 所有节点清空数据目录:
rm -rf /var/lib/consul/* - 重新启动集群,全新初始化
3 No Cluster Leader
现象:大量报错 No cluster leader,无法写入服务、无法注册。
分步排查:
- 检查所有 Server 节点进程是否存活
- 检查所有 Server 节点 8300 TCP、8301 TCP+UDP 是否互通
- 检查节点间时间差是否超过 1s
- 检查磁盘是否打满、IO 是否阻塞
- 检查所有节点
bootstrap_expect配置是否一致 - 检查是否有过半 Server 节点故障,失去法定人数
禁止上来就删除 data 目录,可能造成不可逆的数据丢失。
4 Node ID 冲突
典型日志:
Failed push/pull merge: Member 'xxx' has conflicting node ID
根因:虚拟机克隆、拷贝 data 目录,导致多台机器 node-id 文件相同。
解决方案:
- 停止冲突节点的 consul 服务
- 删除 node-id 文件:
rm -f /var/lib/consul/node-id - 启动服务,自动生成全新唯一 ID
- 验证
consul members状态正常
5 Leader 频繁选举
可能原因:
- Server 间网络延迟高、丢包
- Server 使用机械硬盘,Raft fsync 慢
- 节点 CPU/内存资源打满
- 时间同步异常
优化方向:
- Server 部署在同机房低延迟网络
- 必须使用 SSD 磁盘
- 排查资源瓶颈,不要只靠调大
raft_multiplier掩盖问题
6 Consul 正常但 Prometheus 无 Target
排查三步法:
- Consul 侧验证:服务是否存在?健康状态是否为 passing?
- SD 层验证:Prometheus UI → Service Discovery 中是否能发现该服务?是否被 Dropped?
- Relabel 验证:检查 keep/drop 规则,确认健康状态、标签匹配是否符合预期
90% 以上的该类问题,都是健康检查非 passing 被 relabel 过滤导致。
7 Target 存在但 Scrape 失败
- 查看 Prometheus UI → Targets 页的具体错误信息
- 在 Prometheus 节点手动 curl 目标地址,验证网络连通性
- 检查防火墙、安全组、目标端口是否监听
- 检查 Relabel 后的地址是否正确
- 检查是否有 TLS、认证配置不匹配
8 服务注册与健康检查故障
故障1:配置了服务注册文件,但 Consul 中看不到
排查顺序:
- 主配置是否声明了
config_dir - 文件后缀是否为
.json/.hcl - 配置语法是否正确:
consul validate 文件路径 - 是否执行了
consul reload
故障2:服务注册成功,但健康检查一直 critical
排查顺序:
- 在 Client 节点本地 curl 健康检查地址,验证可用性
- 检查端口、路径是否正确
- 检查本地防火墙是否放行
- 检查
address字段是否为 Client 可访问的真实地址
9 服务启动异常:
error="refusing to rejoin cluster because server has been offline for more than the configured server_rejoin_age_max (168h0m0s) - consider wiping your data dir
错误原因
-
server_rejoin_age_max默认值 168h(7天)。 -
Consul Server 离线超过 7 天(默认 168 小时)后重新启动时的保护机制。Consul 认为本地数据可能已经过期,拒绝自动重新加入集群,以防止脏数据污染整个 Raft 集群。
场景 1:其他 2 个 Server 正常,仅 consul1 故障
目的:让 consul1 放弃过期数据,以全新节点身份从 Leader 同步。
# 停止 consul1 上的服务
systemctl stop consul
# 备份数据目录(可选,防止误操作)
mv /data/consul/data /data/consul/data_backup_$(date +%F)
# 重新创建空数据目录
mkdir -p /data/consul/data
chown consul:consul /data/consul/data
# 启动 consul
systemctl start consul
# 验证
consul members
consul operator raft list-peers
注意:不需要删除 node-id 文件(在新创建的空目录中会自动生成),所以直接清空 data 目录即可。
场景 2:整个集群全部宕机,无 Leader
如果 3 个节点都处于离线超过 7 天状态,且没有可用 Leader,需要清空所有节点数据并重新初始化,否则集群无法选主。
# 在所有 Server 节点执行
systemctl stop consul
rm -rf /data/consul/data/*
systemctl start consul
# 验证
consul members
consul operator raft list-peers
后果:会丢失所有未备份的 Consul 状态(KV、服务目录、ACL 等)。如果有 snapshot,可以在集群重新建立后恢复。
场景 3:只有 1 个 Server,但配置了 bootstrap_expect=3
如果实际上你只打算运行单节点,需要修改配置:
bootstrap_expect = 1
然后清空数据重启:
systemctl stop consul
rm -rf /data/consul/data/*
# 修改配置 bootstrap_expect = 1
systemctl start consul
场景 4:希望保留本地数据(不推荐)
可以通过调大 server_rejoin_age_max 绕过检查,例如:
server_rejoin_age_max = "720h"
但这样做可能引入数据不一致,仅建议在测试环境或确定集群状态无变化时使用。生产环境优先采用场景 1 的方式从 Leader 同步。
预防措施
- 确保 Server 节点稳定运行,长时间停机前应执行
consul leave或提前备份 snapshot。 - 定期备份:
consul snapshot save backup.snap - 监控节点离线时长,及时处理。
附录 A:生产排障命令链
# 1. 确认服务是否在 Consul 目录中
consul catalog services | grep node-exporter
# 2. 确认服务健康状态
consul health service node-exporter
# 3. 确认节点成员状态
consul members
# 4. 确认 Raft 集群状态
consul operator raft list-peers
# 5. 本地验证 Exporter
curl -s http://10.0.10.20:9100/metrics | head
# 6. 验证 Prometheus Target 状态
curl -s http://prometheus:9090/api/v1/targets | jq '.data.activeTargets | length'
# 7. 验证指标是否存在
curl -G http://prometheus:9090/api/v1/query \
--data-urlencode 'query=up{job="node-exporter", instance="10.0.10.20:9100"}'
附录 B:排障优先级口诀
先看主机与网络
再看 Exporter
再看 Consul Client
再看服务注册
再看健康状态
再看 Catalog
再看 Prometheus SD
再看 Target
最后看 PromQL 与 Grafana
禁止上来就重启所有服务,必须逐层定位、最小范围修改。
附录 C:生产设计原则速记
- Consul Server = 控制面,Consul Client = 数据面 Agent
- 原生 Prometheus 双实例是冗余,不是 Master/Slave
- 健康检查只回答「是否应该被发现/采集」,不是「主机是否完全健康」
- 固定基础设施优先 Client + 配置文件注册
- 动态应用使用本地 Agent API 注册,必须配套注销
- CMDB、Consul、Prometheus 三者职责不能混淆
- 不能通过关闭防火墙解决生产网络问题
- 不能把高权限 Token 分发到上千台业务服务器
- 不能把 UUID、用户 ID 等高基数内容作为 Prometheus Label
- 生产级 = 正常运行 + 故障可感知 + 故障可恢复 + 灾备可验证

浙公网安备 33010602011771号