Consul 2.x 服务发现、KV 与 ACL 工具手册
服务注册成功,为什么 DNS 没有答案
开发者把 order-api 注册进 Consul,UI 里也能看到实例,但 dig order-api.service.consul 返回空答案。最常见的原因不是 DNS 端口坏了,而是健康检查已经失败:Catalog 保存“注册了什么”,Health 视图决定“哪些实例可被发现”,DNS 默认会过滤不健康实例。
Consul 是一个分布式控制面。每个节点可以运行 agent;client agent 承接本机服务注册、健康检查、DNS 和 API 请求,server agent 额外参与 Raft 共识并持久化 Catalog、KV、ACL 等状态。服务发现解决“当前哪些地址可用”,KV 适合少量配置与协调元数据。它不替代业务数据库,也不会仅凭注册信息完成应用流量代理。
在虚机或物理机网络中,Consul DNS 能让旧应用以较小改造接入服务发现;若应用需要阻塞查询、索引或详细健康元数据,可直接使用 HTTP API。Kubernetes 已有 Service 与 EndpointSlice 时,应先判断是否真的需要跨运行时发现、Consul 服务网格或统一目录,否则额外 agent 和控制面会增加维护成本。
先认清正在使用的发行轨道
Consul 发布目录当前同时保留 2.0.x、1.22.x 和 1.21.x 等轨道。2.0.2 是当前 2.0 修复版本;从 2.0 开始,版本号仍写成三段,但语义改为 IBM 的 Version-Modification-Fix:首段开启新的支持生命周期,中段增加功能,末段提供修复。1.21.x 是最后一个旧式 LTS 轨道,不应把“1.x”笼统等同于过期,也不应把 2.0 当普通语义化大版本直接跳升。
下面固定 hashicorp/consul:2.0.2。团队升级前应阅读目标轨道发布说明、升级路径和已知问题,再在隔离环境验证快照、ACL、agent 滚动和客户端行为。
Consul 有 Community Edition 与 Enterprise。Community Edition 提供服务发现、KV 和单集群等核心能力;Enterprise 的多运行时、多租户、额外韧性、FIPS、审计等能力受具体版本与授权约束。Consul 源码从 1.17.0 起采用 Business Source License 1.1,旧版本与部分 SDK、API 客户端可能使用不同许可;从 1.21 起 Enterprise 授权迁移到 IBM。引入、修改、再分发或购买支持时,应逐项核对目标制品的 LICENSE 与 IBM 条款,不能用“免费可下载”推导为传统 OSI 开源许可,也不能把 Enterprise 文档中的能力写进 CE 基线。
用持久单 server 跑通本地链路
consul agent -dev 会启动单 server、使用宽松默认值并把状态放在临时目录,适合几分钟的 CLI 演示。为了观察重启后的 Raft 与 Catalog 状态,开发联调更适合一个带数据卷的单 server。它仍然不是高可用集群。
services:
consul:
image: hashicorp/consul:2.0.2
container_name: te-consul
command:
- consul
- agent
- -server
- -ui
- -node=server-1
- -datacenter=dc-dev
- -bootstrap-expect=1
- -client=0.0.0.0
- -data-dir=/consul/data
ports:
- "127.0.0.1:8500:8500"
- "127.0.0.1:8600:8600/tcp"
- "127.0.0.1:8600:8600/udp"
volumes:
- consul-data:/consul/data
healthcheck:
test: ["CMD", "consul", "members"]
interval: 10s
timeout: 5s
retries: 20
volumes:
consul-data:docker compose up -d consul
docker compose ps consul
docker exec te-consul consul members
curl -sS http://127.0.0.1:8500/v1/status/leader
curl -sS http://127.0.0.1:8500/v1/operator/raft/configurationmembers 应显示 server-1 为 alive;leader 接口应返回非空地址;Raft 配置应显示一个 voter。UI 位于 http://127.0.0.1:8500/ui/。这里把 HTTP 和 DNS 只映射到宿主机回环,避免学习环境意外进入局域网。
本地 Compose 没有映射 8300/8301/8302,因为只有一个节点。组成真实集群时必须按职责开放:
| 端口 | 协议 | 职责 |
|---|---|---|
8300 | TCP | server 间 Raft/RPC,以及 client agent 到 server RPC |
8301 | TCP/UDP | 同一 datacenter 的 LAN Serf gossip,覆盖 client 与 server agent |
8302 | TCP/UDP | 跨 datacenter 的 WAN Serf gossip,仅 server 联邦需要 |
8500 | TCP | 默认 HTTP API、CLI 和 UI,默认启用 |
8501 | TCP | HTTPS API,默认未启用 |
8502 / 8503 | TCP | gRPC / gRPC TLS,主要服务数据面组件 |
8600 | TCP/UDP | Consul DNS |
注册服务,观察健康过滤
先启动一个可被 Consul 容器访问的测试 HTTP 服务。若业务服务运行在宿主机,Docker Desktop 可使用 host.docker.internal;Linux Engine 通常还需要 extra_hosts: ["host.docker.internal:host-gateway"]。下面的服务定义把实例 ID、地址和检查写清:
{
"ID": "te-order-dev-api-1",
"Name": "te-order-dev-api",
"Address": "host.docker.internal",
"Port": 18080,
"Tags": ["dev", "http"],
"Meta": {
"owner": "order-team",
"environment": "dev"
},
"Check": {
"ID": "te-order-dev-api-1-http",
"Name": "order api health",
"HTTP": "http://host.docker.internal:18080/actuator/health",
"Method": "GET",
"Interval": "10s",
"Timeout": "2s",
"DeregisterCriticalServiceAfter": "10m"
}
}curl -sS -X PUT \
--data @consul/service-order-dev.json \
http://127.0.0.1:8500/v1/agent/service/register
curl -sS 'http://127.0.0.1:8500/v1/health/service/te-order-dev-api?passing=true'
dig @127.0.0.1 -p 8600 te-order-dev-api.service.consul A
dig @127.0.0.1 -p 8600 _te-order-dev-api._tcp.service.consul SRV注册接口成功时通常返回空响应体和成功状态码。健康接口应出现 Service.ID 与状态为 passing 的 check;A 查询返回地址,SRV 查询还应携带端口。Address 必须从消费者网络可达,注册容器内 IP、错误宿主机名或 localhost 都会制造“发现成功、调用失败”的实例。
反向实验:让健康检查失败
停止测试 HTTP 服务,等待至少一个检查周期,再重复 ?passing=true 与 DNS 查询。预期 passing 结果和 DNS 答案都不再包含该实例,而不带过滤的 /v1/health/service/te-order-dev-api 仍能看到状态为 critical 的检查。这条证据链区分了 Catalog 注册、Health 状态和发现结果。
如果 DNS 仍返回实例,先确认查询的是同一 agent 与 datacenter,再检查 DNS 缓存 TTL、服务是否还有其他健康实例,以及 ACL 下 DNS 使用的 default/anonymous token。不要通过删除健康检查来“修复”发现;那只是取消了故障探测。
用 KV 验证索引与并发控制
KV 适合少量配置、开关和协调元数据。写入后读取原始值:
curl -sS -X PUT --data '100' \
http://127.0.0.1:8500/v1/kv/te/order/dev/limits/pending-orders
curl -sS \
'http://127.0.0.1:8500/v1/kv/te/order/dev/limits/pending-orders?raw=true'预期 PUT 返回 true,读取返回 100。再查看带元数据的 JSON:
curl -sS \
http://127.0.0.1:8500/v1/kv/te/order/dev/limits/pending-orders响应中的 CreateIndex、ModifyIndex 和 Base64 编码的 Value 来自 Raft 提交序列。需要防止覆盖他人修改时,用当前 ModifyIndex 做 CAS,而不是“先读后写”裸覆盖:
curl -sS -X PUT --data '120' \
'http://127.0.0.1:8500/v1/kv/te/order/dev/limits/pending-orders?cas=<modify-index>'正确索引应返回 true。把 cas 改成旧索引后应返回 false,值保持不变。这是一个可重复的反向实验:失败表示并发保护生效,不是服务异常。
Consul KV 单值大小与请求链路有边界,DNS 对大量实例也受协议响应大小限制。大 JSON、二进制制品、频繁业务事件、用户数据和高吞吐计数器会放大 Raft 日志、快照和 watch 压力,应进入对象存储、数据库、消息系统或专用配置平台。官方把 KV 标为 feature complete,架构设计不应押注它继续演化成通用数据库。
用 blocking query 关闭读取与监听之间的空窗
Consul 的 HTTP 变更通知不是独立的消息队列,而是带索引的长轮询。客户端先读取当前值并保存响应头 X-Consul-Index,下一次请求再把这个值放入 index;当索引前进或 wait 到期时,Consul 才返回。下面先取得当前索引:
curl -sS -D consul-kv.headers -o consul-kv.json \
'http://127.0.0.1:8500/v1/kv/te/order/dev/?recurse=true'
CONSUL_INDEX=$(awk 'BEGIN { IGNORECASE=1 } /^X-Consul-Index:/ { gsub("\\r", "", $2); print $2 }' consul-kv.headers)终端 A 从这个索引开始等待,终端 B 修改前面的配置键:
# 终端 A
curl -sS -D consul-watch.headers \
"http://127.0.0.1:8500/v1/kv/te/order/dev/?recurse=true&index=${CONSUL_INDEX}&wait=60s"
# 终端 B
curl -sS -X PUT --data '130' \
http://127.0.0.1:8500/v1/kv/te/order/dev/limits/pending-orders终端 A 应在修改后返回,新的 X-Consul-Index 应大于旧值。超时但没有变更时也会正常返回,客户端必须继续携带最近一次有效索引,而不是把超时当成故障或立刻高频重试。若 Consul 恢复、升级或更换 datacenter 后索引回退,客户端应接受较小索引、执行一次全量读取并重建本地状态,不能永久拿旧索引等待。
Session 把存活状态绑定到锁,但不能充当 fencing token
Session 把节点、健康检查与 KV 锁关联起来。未启用 ACL 的本地基线可直接执行下面的实验;启用 ACL 后,请求还必须携带应用 token,并使用后文 policy 中的 session "server-1" 权限。
创建两个 TTL Session,并让它们竞争同一把锁:
cat > consul/session-a.json <<'JSON'
{"Name":"te-order-dev-lock-a","Node":"server-1","TTL":"30s","LockDelay":"5s","Behavior":"release"}
JSON
cat > consul/session-b.json <<'JSON'
{"Name":"te-order-dev-lock-b","Node":"server-1","TTL":"30s","LockDelay":"5s","Behavior":"release"}
JSON
SESSION_A=$(curl -sS -X PUT --data @consul/session-a.json \
http://127.0.0.1:8500/v1/session/create | jq -r .ID)
SESSION_B=$(curl -sS -X PUT --data @consul/session-b.json \
http://127.0.0.1:8500/v1/session/create | jq -r .ID)
curl -sS -X PUT --data 'worker-a' \
"http://127.0.0.1:8500/v1/kv/te/order/dev/locks/reconcile?acquire=${SESSION_A}"
curl -sS -X PUT --data 'worker-b' \
"http://127.0.0.1:8500/v1/kv/te/order/dev/locks/reconcile?acquire=${SESSION_B}"第一次应返回 true,第二次应返回 false。读取该键时,Session 指向 A,LockIndex 在新的持有者成功获取锁时递增。应用必须周期性调用 PUT /v1/session/renew/${SESSION_A};停止续约后,Session 的失效并不保证恰好发生在 TTL 那一刻,官方说明强制过期最长可能接近两倍 TTL。Behavior=release 会释放锁而保留键值,delete 会删除键,二者都要通过 watch 或 blocking query 驱动重新竞选。
curl -sS -X PUT "http://127.0.0.1:8500/v1/session/destroy/${SESSION_A}"
sleep 5
curl -sS -X PUT --data 'worker-b' \
"http://127.0.0.1:8500/v1/kv/te/order/dev/locks/reconcile?acquire=${SESSION_B}"销毁 A 后仍可能受 LockDelay 保护;等待 5 秒后,B 应获取成功。Consul 锁只能证明“某个 Session 当前持有 KV 锁”,不能阻止已经暂停的旧进程继续操作数据库或外部系统。涉及支付、调度、主从切换等副作用时,必须把单调递增的 fencing token 带到受保护资源并由资源端拒绝旧 token;不能只依赖进程里的布尔值 isLeader。
开启 ACL 后做一次越权失败实验
共享环境不能沿用未认证的本地基线。ACL、agent mTLS 和 gossip encryption 保护的是不同链路:ACL 决定身份能做什么,mTLS 保护 RPC/API 通信与节点身份,gossip key 保护 8301/8302 成员消息。三者不能相互替代。
所有 agent 使用一致的 ACL 配置。新集群可从以下基线开始:
acl {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}集群启动后在一个 server 执行一次 consul acl bootstrap。输出中的 SecretID 关联 global-management,权限不受限,只能进入密钥系统并交给极少数 ACL 管理员。随后分别创建 server agent、client agent、DNS 查询和应用身份,不能让业务应用复用 bootstrap token。
应用 policy 可以限制到自己的 KV 前缀和服务:
key_prefix "te/order/dev/" {
policy = "write"
}
service "te-order-dev-api" {
policy = "write"
}
node_prefix "" {
policy = "read"
}
session "server-1" {
policy = "write"
}export CONSUL_HTTP_ADDR='http://127.0.0.1:8500'
export CONSUL_HTTP_TOKEN='<application-secret-id>'
consul kv put te/order/dev/verify ok
consul kv get te/order/dev/verify
consul kv put other/project/verify should-fail前两条应成功,最后一条应返回 permission denied。若越权写入成功,policy、token 绑定或实际 CONSUL_HTTP_TOKEN 有误。ACL token 的 SecretID 是凭证,AccessorID 用于管理定位;日志和工单可记录后者,不能记录前者。
为 DNS 准备独立只读 token,因为 DNS 请求本身通常没有位置携带调用者 token,agent 会使用 default token,缺失时退回 anonymous token。生产环境应把 anonymous 权限保持最小,并避免用过宽的 default token 让所有 DNS 查询继承管理能力。
TLS 必须同时验证加密、客户端身份和 server 名称
ACL 只处理授权,不会自动加密 8500、内部 RPC 或 gRPC。新 datacenter 应使用同一受控 CA 为各 agent 签发独立证书,并在 server 与 client agent 上显式开启双向验证。当前配置结构可使用:
ports {
http = -1
https = 8501
}
tls {
defaults {
ca_file = "/consul/config/certs/consul-agent-ca.pem"
cert_file = "/consul/config/certs/dc-dev-server-consul-0.pem"
key_file = "/consul/config/certs/dc-dev-server-consul-0-key.pem"
verify_incoming = true
verify_outgoing = true
}
internal_rpc {
verify_server_hostname = true
}
}verify_incoming 拒绝没有受信客户端证书的入站连接,verify_outgoing 校验对端证书链,verify_server_hostname 进一步要求 server 证书匹配 server.<datacenter>.<domain>。只配置 CA 而不打开 hostname 验证,受信但被攻陷的 client 证书仍可能冒充 server。私钥应由 Secret 挂载且仅运行用户可读,不能写入镜像、Compose 文件或仓库。
curl --cacert consul-agent-ca.pem \
--cert operator-consul-0.pem \
--key operator-consul-0-key.pem \
--resolve server.dc-dev.consul:8501:127.0.0.1 \
https://server.dc-dev.consul:8501/v1/status/leader
curl --cacert consul-agent-ca.pem \
--resolve server.dc-dev.consul:8501:127.0.0.1 \
https://server.dc-dev.consul:8501/v1/status/leader第一条应通过 TLS 并返回 leader,第二条应在握手阶段因缺少客户端证书而失败。随后还要从 client agent 验证 server RPC、从每个故障域验证证书 SAN,并在轮换演练中先分发新 CA/证书、确认新旧信任窗口,再撤销旧材料。已有明文集群不能一次把三个 verify_* 全部强切;应遵循官方迁移顺序分阶段开启,否则滚动过程会让新旧 agent 互相拒绝。
gossip 负责成员传播,Raft 负责权威顺序
LAN gossip 基于 Serf/memberlist,快速传播成员加入、离开和故障信息,并计算网络坐标;它追求扩散速度,不决定 KV 写入的权威顺序。server agent 组成 Raft peer set,leader 将 Catalog、KV、ACL 等状态变更追加为有序日志并复制到 quorum。client agent 不参加 Raft,因此增加 client 数不会直接扩大 peer set,但会增加 RPC、健康检查、DNS 和连接负载。
三 server 可容忍一个 voter 故障,五 server 可容忍两个;官方建议使用 3 或 5 个 voting server。偶数 voter 不提高可容忍故障数,却增加复制成本。server 应跨故障域放置,使用低延迟、稳定网络和持久磁盘;“同一宿主机三个容器”只能练习 join 与选举,不能提供真实容灾。
gossip encryption 对称密钥由所有 datacenter 成员共享。新集群先执行 consul keygen,将 encrypt 安全分发给所有 agent。已有未加密集群要按官方步骤先允许混合流量,再分阶段开启 outgoing 和 incoming 验证,不能一次强切导致成员互相拒绝。轮换使用 consul keyring -install/-list/-use/-remove,只有当新 key 在全部成员传播完成后才能移除旧 key。
Raft 对网络抖动和磁盘延迟敏感。盲目降低 performance.raft_multiplier 会加快选举也会提高资源与网络要求;应先用 leader 变更、提交延迟、磁盘 fsync 和网络丢包证据定位,再基于压测调整。
项目接入要让注册、发现和退出成对
项目至少外置这些坐标:
CONSUL_HTTP_ADDR=https://consul.service.example.com:8501
CONSUL_HTTP_TOKEN=<runtime-token>
CONSUL_DATACENTER=dc-dev
CONSUL_SERVICE_ID=te-order-dev-api-1
CONSUL_SERVICE_NAME=te-order-dev-api
CONSUL_KV_PREFIX=te/order/dev/应用启动日志可以打印地址、datacenter、service ID/name 和 KV prefix,但要屏蔽 token。注册由本机 agent、部署编排器或专用 registrator 持有生命周期;谁注册,谁就必须在优雅停止时注销,并为崩溃场景配置合理的健康检查和 DeregisterCriticalServiceAfter。这个值过短会在短暂故障时抹掉证据,过长会留下幽灵实例,应由故障恢复时间和实例替换策略决定。
调用方通过 DNS 时要支持多地址、超时、重试和连接重建;通过 HTTP API 时可使用 blocking query 的索引等待变化,避免高频轮询。长轮询超时、代理 idle timeout 与 Consul HTTP timeout 必须对齐,否则会出现稳定的周期性断连。
KV 消费者应记录最后应用的 ModifyIndex,新值解析失败时保留旧快照并告警。涉及多个 key 的一致变更不能假设普通 KV PUT 自动形成事务边界;需要原子语义时评估 Consul transaction API,复杂业务事务仍应回到数据库。
从故障证据选择恢复动作
| 现象 | 第一证据 | 常见原因 | 恢复动作 |
|---|---|---|---|
UI 可开,写请求报 No cluster leader | /v1/status/leader、Raft peers | quorum 丢失、server 网络或磁盘异常 | 恢复多数 voter,禁止并行清空 data-dir |
members 显示 failed/left | LAN/WAN members、8301/8302、gossip key | 防火墙、NAT 广告地址或 key 不一致 | 修网络与 advertise 地址,确认 keyring 后重新 join |
| 注册存在但 DNS 空 | health API、check 输出、DNS token | check critical 或 ACL 无 service:read/node:read | 修服务可达性或最小只读 policy,再查 DNS |
| 健康 passing 但调用失败 | 从消费者网络直连注册地址 | 注册了容器内 IP、错误主机名或端口 | 修 Address/Port,注销旧 service ID |
| KV 写入 permission denied | consul acl token read -self、policy | 漏 token、绑定错 policy 或 prefix 不匹配 | 发放正确的最小权限 token,不扩大 anonymous |
| 重启后出现旧服务或 KV | data-dir、Raft index、volume | 复用了旧持久数据 | 先确认是否应恢复;个人环境才可清卷重建 |
| DNS 只返回部分大规模实例 | UDP 截断、实例数量、响应大小 | DNS 协议限制与随机化返回 | 使用 HTTP API 分页/过滤,重新评估发现接口 |
遇到 quorum 故障时,强制移除 peer 或恢复快照都属于高风险操作。先保存 data-dir、快照、当前 peer 列表和日志,再按多数派状态制定恢复步骤。多个节点各自“修复”并重新组成不同集群会造成状态分叉。
快照要在隔离集群完成恢复验收
Consul 快照是 Raft 管理状态的原子时间点副本,包含 KV、Catalog、prepared query、Session 和 ACL。它也可能包含可恢复凭证,因此必须加密存储、限制读取、记录校验值和保留周期。Community Edition 可由受控调度任务执行 CLI;持续运行的 Snapshot Agent 属于 Enterprise 能力。
consul snapshot save consul-dc-dev.snap
consul snapshot inspect consul-dc-dev.snap
sha256sum consul-dc-dev.snap > consul-dc-dev.snap.sha256
sha256sum -c consul-dc-dev.snap.sha256默认一致性模式需要当前 leader 确认,因此丢失 quorum 后才临时想做快照通常已经太晚。-stale 可以降低 leader 压力,但恢复点可能落后,是否允许必须由 RPO 决定。恢复演练要创建一套无业务流量、具有稳定 leader 的新集群,并使用与生成快照相同的 Consul 版本:
consul operator raft list-peers
consul snapshot restore consul-dc-dev.snap
consul snapshot inspect consul-dc-dev.snap
consul catalog services
consul kv get -recurse te/order/dev/一次 restore 会由 Raft 传播到所有 server,不应在每个节点各执行一遍。验收至少核对 leader、peer、关键服务、KV、ACL policy/token 数量和 Session 清理策略;随后使用新发放的最小权限 token 做一次允许与拒绝验证。快照恢复不是单个配置项回滚工具,也不能跨未验证的版本边界直接覆盖正在运行的 data-dir。
清理、回滚和停用
实验结束先注销服务,再删除自己的 KV 前缀:
curl -sS -X PUT \
http://127.0.0.1:8500/v1/agent/service/deregister/te-order-dev-api-1
curl -sS -X DELETE \
'http://127.0.0.1:8500/v1/kv/te/order/dev/?recurse=true'
curl -sS http://127.0.0.1:8500/v1/health/service/te-order-dev-api
curl -sS 'http://127.0.0.1:8500/v1/kv/te/order/dev/?recurse=true'两次查询都应不再返回实验对象。共享环境执行递归删除前必须列出 prefix、复核 owner 并保存必要快照。个人本机确认卷内没有其他数据后,才可运行 docker compose down -v。
KV 回滚可以用上一个受控版本重新 PUT,并确认 ModifyIndex 前进且消费者应用新索引。集群版本回滚必须遵循目标版本支持的升级/降级路径;快照包含 Catalog、KV 和 ACL 等敏感控制面状态,备份、传输、恢复演练都要按高敏数据管理。
停用 Consul 时,先冻结新注册与 KV 写入,迁移消费者,再迁移注册者;观察旧 catalog、DNS 查询、blocking query 和 agent 成员逐步归零。随后撤销应用 token、agent token、DNS token 与管理 token,销毁 gossip key、证书、快照和持久卷。只关 server 不撤销凭证,会把旧身份带进下一套环境。
容量、成本和长期治理
容量基线至少包含 server 数与故障域、client agent 数、service/check 数、健康检查频率与耗时、Catalog/KV 写入率、Raft apply 与 leader 变化、blocking query 数、DNS QPS/截断、快照大小、磁盘 fsync、CPU/内存/GC、网络延迟和 ACL 拒绝率。阈值由 SLO、故障预算、版本和压测得到,不使用脱离负载的万能数字。
成本不只是 server 资源。每节点 agent、证书与 gossip key 分发、ACL token 生命周期、快照存储、跨区域网络、升级频率、值班能力和 Enterprise 许可都会形成长期成本。Community Edition 要保持在受维护轨道,官方升级建议意味着团队需要持续升级能力;选择旧 LTS、当前 2.0、Enterprise 或托管方案时,应把支持期限、迁移次数、人员能力和事故成本放在同一张决策表中。
每个 datacenter、service、check、KV prefix、ACL policy/token 和快照都要有 owner。变更评审记录镜像与二进制版本、配置 diff、Raft/ACL/证书影响、回滚入口和验证证据。持续多个演练周期后,leader 变化、失败检查、ACL 拒绝和存活对象数应回到稳定基线,不能随轮次单调增长。
上线评审可以用以下问题收尾:
发行轨道、补丁版本、支持策略和许可已按目标制品确认。server/client agent、Raft、LAN/WAN gossip、HTTP/HTTPS 与 DNS 的网络方向明确。服务注册地址从真实消费者网络可达,健康失败能从 DNS 中剔除。
KV 有前缀、owner、大小与更新频率约束,CAS 失败不会覆盖新值。bootstrap、agent、DNS、应用和运维 token 已拆分并可撤销轮换。agent mTLS、gossip encryption 与 ACL 都有部署和演练记录。
quorum 丢失、错误注册地址、健康失败、越权写入和清理都有预期证据。快照恢复、版本回滚、容量趋势、成本、owner 与退出路径进入长期台账。
