Consul 服务发现提供注册端点和已经记录的健康观测,不能承诺每个返回端点都会成功响应调用方的下一次请求。实用的运维模型需要分开四件事:注册了什么、哪些检查与该实例有关、查询接受哪些状态,以及这些观测可能已经过去多久。仅仅成功解析到一个地址,并没有自动建立上述全部保证,也没有证明业务请求已经能够完成。

注册记录与健康状态是两回事

catalog 存储注册信息,健康端点再附上检查结果。不加过滤的 /v1/health/service/web 响应,可以包含检查为 critical 的实例。检查失败并不会自动抹去服务注册;显式注销、配置了持续 critical 后自动移除,属于不同的生命周期机制。ACL 也可能影响读者能看到哪些记录,所以查询里缺少实例,不能只归因于健康检查失败。

注册的检查通常从 critical 开始,除非显式提供初始状态。但这并不意味着每个注册服务都一定具有有意义的就绪检查。判断某个服务实例时,需要结合它自己的服务级检查,以及 serfHealth 等节点级检查。同一节点上,数据库服务的检查失败,不会自动让不相关的 Web 服务检查失败;节点级检查变为 critical,则会影响该节点上的服务。不要把“相关检查全部通过”误解成“这台机器上的所有服务检查都必须通过”。

检查结果也不只是布尔值。脚本退出码零表示 passing,一表示 warning,其他退出码表示 critical;HTTP 检查按重定向处理后的响应判断:2xx 为 passing,429 为 warning,其他最终响应表示失败。默认会跟随重定向,除非设置 disable_redirects。这些状态究竟证明了什么,仍取决于探测内容。TCP 握手成功或进程存在,并不证明应用能完成一次真实请求;检查实现必须与希望验证的能力对应。

DNS 并非总是只返回 passing

HTTP 健康端点的 passing=true 要求相关检查全部 passing。Consul DNS 服务查询的默认策略不同:warning 实例仍可符合候选条件,critical 实例则被排除。配置 dns_config.only_passing=true 后,warning 也会被排除。因此,一个 warning 实例出现在 DNS,却不在 passing-only API 结果里,不一定意味着检查失效,也可能只是两条路径明确采用了不同策略。

比较结果时,应保持服务、数据中心、适用时的命名空间,以及观察时间窗口一致。Consul 的 DNS 通常监听 8600 端口;如果没有配置相应转发,只向系统解析器查询,并不是直接检查 Consul。直接查询能够去掉这一中间环节,但仍然不会自动从调用方网络探测业务端点,也不能证明返回地址在所有网络路径上都可达。

新鲜度包含多个阶段

探测间隔、探测超时、连续失败阈值、agent 到 server 的传播、读取一致性,以及客户端缓存,都可能增加延迟。三十秒间隔不等于端到端三十秒内必定摘除。TTL 检查从最近一次更新计算到期;如果服务在 TTL 中途失败,并不一定还会获得完整的一段新 TTL。应观察真实状态转移,不能把一个配置数字当成所有故障路径的精确上界。

反熵负责修复 agent 本地状态与 catalog 的差异。文档中的周期随集群规模变化,而且是近似间隔;注册变更本身也会触发更新。如果同步持续失败,差异可以跨越多个周期,因此周期同步既不是唯一更新机制,也不是最长收敛时间的保证。网络不可达、权限错误或存储故障,不会因为下一分钟到来就必然消失。

HTTP 查询通常使用 default 一致性模式,DNS 通常允许 stale 读取。consistent HTTP 读取要求 leader 向多数派确认身份,会付出额外代价。然而,更强的 catalog 读取一致性,也无法发现尚未被任何检查观测到的应用故障。注册状态的一致性与真实业务的就绪程度,始终是不同问题,不能互相替代。

坏掉的样子

passing 探测可能检查了错误依赖、从不同网络发起,或者遗漏死锁。反过来,如果每个实例的就绪状态都绑定到同一个共享依赖,共同故障又可能一次摘除整个池。应围绕可用的请求能力和预期降级行为选择检查,同时保留客户端超时及有界恢复路径,不能把注册表当成消除所有请求失败的机制。

不要根据一张 API 与 DNS 不一致的截图就宣布检查没有运行。先检查 warning 策略、相关检查 ID、ACL 可见性、注销行为,以及缓存或陈旧观测。critical 实例继续出现在未过滤的健康响应里,本来就是正常现象,不是服务器忽略过滤条件的证据。只有先确认比较对象与策略,才有依据继续追查异常。

合成节点与服务健康决定两种筛选策略;失败后旧快照仍旧,注册记录仍保留。
Consul 健康筛选:机制与证据边界。

快速验证

把代码保存为 consul_filter.py,执行 python3 consul_filter.py。这个标准库实验使用合成记录,不启动 Consul agent,不发网络请求。须启用断言,不使用 -O 或 PYTHONOPTIMIZE。它验证筛选差异和陈旧快照风险,并非 Consul 集成测试;本轮没有重跑已有精确记录的夹具。

from copy import deepcopy

nodes = {"n1": "passing", "n2": "critical"}
services = [
    {"ID": "web-1", "Name": "web", "Node": "n1", "Status": "passing"},
    {"ID": "web-2", "Name": "web", "Node": "n1", "Status": "warning"},
    {"ID": "db-1", "Name": "db", "Node": "n1", "Status": "critical"},
    {"ID": "web-3", "Name": "web", "Node": "n2", "Status": "passing"},
]


def health_rows(name):
    # Synthetic snapshot: only the node and THIS service contribute checks.
    return [
        {"ID": item["ID"], "Checks": [nodes[item["Node"]], item["Status"]]}
        for item in services if item["Name"] == name
    ]


def selected(rows, allowed):
    return [row["ID"] for row in rows if all(s in allowed for s in row["Checks"])]


snapshot = health_rows("web")
assert [row["ID"] for row in snapshot] == ["web-1", "web-2", "web-3"]
assert selected(snapshot, {"passing"}) == ["web-1"]
assert selected(snapshot, {"passing", "warning"}) == ["web-1", "web-2"]
print("unfiltered: web-1 web-2 web-3; passing-only: web-1; default DNS policy: web-1 web-2")

previous = deepcopy(snapshot)
services[0]["Status"] = "critical"
current = health_rows("web")
assert selected(previous, {"passing"}) == ["web-1"]  # An old snapshot stays old.
assert selected(current, {"passing"}) == []
assert len(current) == 3  # Failing health is not deregistration.
assert selected(current, {"passing", "warning"}) == ["web-2"]
print("service failure changes selection, not registration; an old snapshot still selects web-1")

nodes["n1"] = "critical"
assert selected(health_rows("web"), {"passing", "warning"}) == []
print("node failure excludes both web instances on n1; db-1 never controlled their earlier health")

断言证明不相关的数据库健康不会控制 Web 选择,warning 与 critical 在两种策略下不同,而失败注册仍保留在未过滤视图中。更新当前状态,也不会改写之前复制的快照。最后将节点状态改为 critical,模型会把该节点上的两个 Web 实例都移出符合 DNS 策略的候选集合。这些结果是确定的集合运算,不是实际测得的服务故障摘除延迟。

部署验收仍需在配置的 warning 策略下,对照真实未过滤与 passing-only 健康响应,以及直接 DNS 查询。分别记录检查状态转换和客户端不再使用实例的时刻。本次没有执行这项集成;离线模型没有提供探测时间测量、DNS 报文证据或可用性保证。把模型的适用范围写清楚,才能避免“断言都通过”掩盖尚未验证的运行链路。

来源