一次常规的 TLS 1.3 升级,可能不会让应用进程崩溃,也不会触发内部服务告警,却足以让外部流量彻底绕开一个健康区域。
问题出在系统边缘:Route 53 健康检查无法再正确探测目标,CDN 因此停止向该区域路由流量。与此同时,区域内的实例、负载均衡器和业务仪表盘仍然显示正常。组件都“活着”,用户却无法通过预期路径访问它们。
这正是高可用与韧性的区别:高可用关注冗余是否存在,韧性则关注系统遇到意外失效时,能否发现、理解并恢复服务。
一个区域健康,为什么仍然接不到流量
典型的多区域架构会包含几层不同的判断:
- 应用判断自身是否健康;
- 负载均衡器判断后端实例是否可用;
- DNS 或全局流量管理器判断区域是否可用;
- CDN 根据源站状态和路由策略决定把请求送到哪里;
- 客户端最终通过 DNS、TLS 和 HTTP 完成访问。
内部仪表盘通常覆盖前两层。事故却可能发生在后三层。
TLS 升级尤其容易暴露这种错位。应用团队看到的是:
- 服务进程正常;
- CPU、内存和错误率正常;
- 区域负载均衡器仍能处理请求;
- 内部探针返回 200。
但外部健康检查关心的是另一份契约:它能否解析域名、建立 TCP 连接、协商双方都支持的 TLS 版本和密码套件、验证证书,并在规定时间内获得预期的 HTTP 响应。任何一步不兼容,控制平面都可能把健康区域标记为不可用。
因此,“应用健康”不能推出“用户路径健康”。真正需要验证的是完整链路:
客户端 -> DNS -> CDN -> 全局路由/健康检查 -> 区域入口 -> 应用
如果监控只从区域内部访问应用,它就绕过了最可能出问题的 DNS、CDN、证书和外部 TLS 协商环节。
高可用解决冗余,韧性解决失控
高可用设计通常可以回答这些问题:
- 是否有多个实例?
- 是否跨可用区部署?
- 是否存在第二个区域?
- 数据是否有副本?
- 流量能否自动切换?
这些能力很重要,但它们隐含了一个前提:负责切换的控制平面必须正确理解系统状态。
如果健康检查错误地判定一个区域失效,自动切流反而会扩大故障。备用区域即使存在,也可能因为容量不足、配置漂移或同类探针问题而无法接管。此时系统拥有冗余,却没有可靠的恢复路径。
韧性需要回答另一组问题:
- 谁能发现控制平面的错误判断?
- 能否区分“应用不可用”和“探针不兼容”?
- 自动切换是否有容量和持续时间边界?
- 如何安全地覆盖错误路由决策?
- 上一次人工演练是什么时候?
- 哪个团队对恢复动作负最终责任?
换句话说,HA 是架构属性,韧性是架构、观测、操作和组织责任共同形成的能力。
控制平面为什么容易成为盲区
控制平面通常不承载业务请求,却决定业务请求去哪里。DNS 健康检查、CDN 源站选择、证书自动化、服务发现和流量策略都属于这一类。
它们有三个常见风险。
1. 监控对象与决策对象不同
团队监控的是应用实例,但 CDN 依据 DNS 健康状态做决策。应用指标全绿,并不能解释 CDN 为什么停止发送请求。
至少应同时观察:
- 区域应用的实际健康状态;
- Route 53 等外部健康检查的状态;
- CDN 到各源站的请求量和失败率;
- 每个区域接收的流量比例;
- 公网 DNS 解析结果;
- 从公网建立 TLS 会话的结果。
一个很有价值的告警不是“服务错误率升高”,而是状态矛盾:
区域应用健康 = true
且
全局路由认为区域健康 = false
这种告警直接指向控制平面与数据平面之间的分歧。
2. 低频变更缺少端到端验证
TLS、证书、DNS 和 CDN 策略的变更频率通常低于应用发布,因此更容易被当成基础设施细节。实际上,它们会改变外部探针和客户端能否完成连接。
TLS 升级验收不能只运行一次 curl https://internal-endpoint/healthz。还应验证:
- 健康检查器支持的 TLS 能力;
- 证书链与主机名;
- SNI 行为;
- 公网入口和直接区域入口;
- IPv4 与 IPv6 路径;
- CDN 使用的源站协议策略;
- 变更后的真实流量分布。
3. 恢复能力会自然衰减
故障切换脚本可能仍在仓库里,但权限已经变化;文档中的联系人已经离职;备用区域的配额跟不上当前流量;手工覆盖命令从未在生产条件下验证。
恢复能力不是一次性交付物。没有明确负责人、定期演练和可验证指标,它就会随着系统变化逐渐失效。
可以这样实践:从外部验证 TLS 与区域入口
下面的脚本使用 Python 标准库,对多个区域域名分别尝试 TLS 1.2 和 TLS 1.3,然后请求 /healthz。它不等同于 Route 53 的实际探针,但适合作为独立合成监控或变更前检查,用于发现协议兼容性和证书问题。
运行前,把 HOSTS 改成可以从公网访问的区域入口。脚本假设健康接口允许 HEAD 请求,并且 TLS 1.2 与 TLS 1.3 都是当前兼容性要求;如果你的安全策略不同,请调整 versions 列表。
#!/usr/bin/env python3
import os
import socket
import ssl
import sys
hosts = [
item.strip()
for item in os.environ.get(
"HOSTS",
"origin-us.example.com,origin-eu.example.com",
).split(",")
if item.strip()
]
path = os.environ.get("HEALTH_PATH", "/healthz")
timeout = float(os.environ.get("TIMEOUT_SECONDS", "5"))
versions = [ssl.TLSVersion.TLSv1_2, ssl.TLSVersion.TLSv1_3]
failures = []
for host in hosts:
for version in versions:
label = version.name
try:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_default_certs()
context.minimum_version = version
context.maximum_version = version
with socket.create_connection((host, 443), timeout=timeout) as tcp:
with context.wrap_socket(tcp, server_hostname=host) as tls:
request = (
f"HEAD {path} HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Connection: close\r\n\r\n"
)
tls.sendall(request.encode("ascii"))
status_line = tls.recv(4096).split(b"\r\n", 1)[0]
status = status_line.decode("ascii", errors="replace")
if " 200 " not in status:
raise RuntimeError(f"unexpected response: {status}")
print(
f"OK host={host} requested={label} "
f"negotiated={tls.version()} status={status}"
)
except Exception as exc:
message = f"FAIL host={host} tls={label} error={exc}"
failures.append(message)
print(message, file=sys.stderr)
if failures:
sys.exit(1)
保存为 check_tls_health.py 后运行:
chmod +x check_tls_health.py
HOSTS="origin-us.example.com,origin-eu.example.com" \
HEALTH_PATH="/healthz" \
./check_tls_health.py
更重要的是,不要只在部署机器或云内网运行它。可以从独立云账号、外部监控服务或至少一个不共享生产网络路径的位置执行。否则,探针仍可能绕过公网 DNS、CDN 或边缘 TLS 配置。
如果使用 Route 53 健康检查,还可以把控制平面状态纳入排障脚本:
export HEALTH_CHECK_ID="replace-with-health-check-id"
aws route53 get-health-check \
--health-check-id "$HEALTH_CHECK_ID"
aws route53 get-health-check-status \
--health-check-id "$HEALTH_CHECK_ID"
这里要检查的不只是 HEALTHY 或 UNHEALTHY,还要核对探测端口、协议、路径、域名以及各检查节点返回的信息是否符合当前 TLS 和入口配置。
为 TLS 和路由变更增加恢复护栏
一次更稳妥的变更流程可以包含以下步骤:
- 记录兼容性契约:明确健康检查器、CDN、源站和客户端必须支持哪些 TLS 版本,而不是默认“升级后都会工作”。
- 同时测试区域入口和用户入口:区域入口证明应用可达,用户入口证明 DNS、CDN、TLS 与路由链路整体可达。
- 观察流量分布:变更后检查每个区域的请求量。某个区域错误率为零,可能只是因为它已经没有流量。
- 设置状态矛盾告警:当区域内部健康但外部探针失败,或 CDN 流量突然归零时立即告警。
- 准备可逆变更:保留经过验证的 TLS 配置回滚方式,而不是在事故中临时研究旧配置。
- 演练人工覆盖:确认值班人员知道如何暂停自动切换、恢复某个区域或调整流量权重。
- 限制覆盖时长:人工强制路由应有审批、到期时间和后续复盘,避免临时措施成为永久风险。
不应把“永久允许旧协议”当作韧性方案。兼容窗口可以帮助回滚和迁移,但最终仍应根据安全要求收敛。关键是先确认健康检查、CDN 和客户端都已具备新协议能力,再移除旧协议。
上线前应问的几个问题
在批准多区域、TLS、DNS 或 CDN 变更前,可以用这份简短清单做最后检查:
- 是否有从公网执行的端到端探针?
- 探针是否使用与真实用户相同的域名、SNI 和证书路径?
- 是否单独监控每个区域的流量,而不只是错误率?
- 能否看到全局健康检查的原始失败原因?
- 备用区域是否经过容量验证?
- 自动切流错误时,谁有权执行人工覆盖?
- 回滚命令是否在近期演练过?
- 谁对恢复时间和恢复流程负责?
多区域、自动故障转移和健康检查只能提供构建韧性的材料。真正的韧性来自端到端观测、可逆变更、定期演练和清晰所有权。系统最危险的状态并不总是全面宕机,而是每个局部仪表盘都显示正常,唯独用户到服务之间的路径已经断开。