第 13 章 动态更新:从一次切换到可观察的发布¶
服务器启动成功,只完成了控制平面的第一半工作。第二半是持续变化:输入更新后重新计算资源,让代理收到、接受,并让真实请求表现出预期结果。本章以实验 02的蓝绿切换为主线,前置知识是 snapshot、版本和 ACK/NACK。目标是能走完一次发布,区分各阶段证据,并对失败选择正确的恢复方向。
实验故意把变化控制在一个清晰范围:LDS 中 listener_0 引用 RDS book_routes;blue_backend、green_backend 两个 Cluster 及其 EDS 地址始终存在;切换操作只改变 Route 选择哪个 Cluster。这样既能看到真正的 CDS、EDS、LDS、RDS,又能把“新后端尚未分发”与“路由切换失败”分开理解。blue、green 与 Envoy 共用网络命名空间,因此这里的 EDS 可以使用 127.0.0.1:8081 和 127.0.0.1:8082;这不是普通独立容器之间的网络默认行为。
13.1 先建立更新前的证据¶
进入实验目录启动,确认业务初态:
cd labs/02-xds
docker compose up --build -d
curl -fsS http://127.0.0.1:18080/
预期得到纯文本 blue。如果代理尚未就绪,先等其完成订阅并查看 Compose 日志。接着读取只绑定本机的 admin:
curl -fsS http://127.0.0.1:19901/config_dump
输出往往很长。先搜索 listener_0、book_routes、blue_backend、green_backend,再观察各资源类型的动态状态和版本。不要只凭“名字出现了”就判断它在生效:admin 中可能同时展示 active、warming、静态和被拒绝配置的相关信息,需要看对象位于哪一类列表以及错误状态。
此时可以画出最小事实链:浏览器请求到 Envoy 业务 Listener;Listener 的 HCM 使用 book_routes;该路由当前转发到 blue_backend;Cluster 从 EDS 获得 blue 服务地址;最终收到 blue。这条链会帮助你把后面的变化定位在某一环,而不是一旦出错就重启所有容器。
13.2 发起一次确定的切换¶
通过实验管理接口选择 green:
curl -fsS -X POST 'http://127.0.0.1:19000/switch?backend=green'
curl -fsS http://127.0.0.1:18080/
第一条请求向控制平面应用提出更新,不是直接修改 Envoy 的 admin 配置。应用生成新的 RouteConfiguration 和 snapshot,发布到 envoy-book 的缓存键;已有 RDS watch 被唤醒,服务器发送新版本。初始版本为 v1,第一次成功切换会返回类似 {"backend":"green","version":"v2"} 的 JSON。Envoy 接受之后,后续业务请求应返回 green。HTTP 200 只说明该接口约定的发布动作成功,不代表“所有代理已经 ACK”。
接口只接受 POST,其他方法返回 405;backend 只接受 blue 或 green,非法值返回 400,不发布也不推进版本。重复选择当前合法值仍会生成新版本。这让实验的变化容易观察,也会产生没有业务内容差异的发布;生产系统可根据内容摘要避免这类重复工作。GET /healthz 返回成功仅说明控制平面进程可以服务,不能用它替代代理就绪或配置确认检查。
切换接口返回和代理收敛之间可能存在短暂时间差。第一条之后立即发的业务请求若仍是 blue,需要结合日志和版本判断是否还在正常传播;若持续不变,就必须沿链条排查。不要用无限重试把一个确定的错误掩盖成“最终总会好”。在实际发布系统中,应当为各阶段设置合理截止时间并报告当前停在哪一步。
恢复初态使用同一个接口:
curl -fsS -X POST 'http://127.0.0.1:19000/switch?backend=blue'
curl -fsS http://127.0.0.1:18080/
这仍是一次新的发布,只是内容回到之前的业务选择。它不是向旧报文发送 ACK,也不是把 nonce 倒回去。版本应按应用的发布策略推进,或者稳定标识对应内容;nonce 仍由当前流上的响应生成。
13.3 把“发布成功”拆成证据链¶
一次修改至少经历输入已接收、资源已构造并校验、缓存已更新、响应已准备并发送、代理已 ACK、业务行为已验证几个阶段。每个阶段都回答不同问题,应该保留可关联的发布号和时间,而不是只有一条笼统的“成功”日志。
| 看到的证据 | 可以说明 | 还不能说明 |
|---|---|---|
| 管理接口返回成功 | 应用接受或完成其约定的发布动作 | 代理一定已接受 |
| GetSnapshot 能读到新版本 | 当前缓存已经更新 | 更新已到达全部连接 |
| 响应回调有新版本与 nonce | 服务器准备发送该响应 | 网络发送成功或客户端 ACK |
| 匹配 nonce 的 ACK | 客户端处理了对应配置更新 | 后端实际健康、全部请求成功 |
| admin 中新路由 active,请求返回 green | 本代理的新路径有实际效果 | 整个代理群都已收敛 |
这个表的价值在于故障时可以停在最早缺失的证据上。只有缓存更新、没有响应,检查 Node 分组键、订阅和版本;有响应、没有确认,检查连接及代理日志;ACK 存在、业务不通,检查 active/warming、路由匹配、地址和后端,而不是继续修改 nonce。
13.4 NACK 不是控制平面自动撤销¶
假设最新 RDS 被拒绝,代理通常继续使用这个类型最近成功的配置。可是 SnapshotCache 可能仍保存着被拒绝的新 snapshot。库不会因为一个 NACK 就自动推导业务正确版本,更不会替所有代理同步回滚。应用需要记录每个目标的确认状态,查明原因,再发布修复内容或明确的回滚内容。
这里要区分协议拒绝与业务错误。一个资源字段非法,可能触发 NACK;一个语法合法但指向错误后端的路由,则可能被 ACK,最后返回 503。路由引用缺失 Cluster 也不能一概保证触发 NACK,其行为与资源及校验设置有关。实验应先在隔离环境里构造确定的错误,再观察实际拒绝信息,不能拿一段猜测的错误字符串写死自动化判断。
NACK 的 error_detail.message 对人工定位很有用,但不是跨 Envoy 版本稳定的机器协议。自动化应围绕是否存在错误、类型、发布记录与关联 nonce 建立状态,再把原始消息作为诊断信息保留。含配置内容的错误也要避免把 Secret 直接写入日志。
13.5 并发更新与重启会改变问题形状¶
如果两个管理请求同时到来,慢请求可能基于旧输入构造快照,却比新请求更晚写入 cache,造成旧配置覆盖新配置。简单实验可用互斥锁串行完成“修改期望状态、生成版本、构造校验、发布”这一段;复杂系统则可用队列、代次号和 compare-and-swap 等方式拒绝过时代次。只给计数器加锁,未必保护了整个发布顺序。
进程重启后也不能让同一个版本名代表另一份内容。实验的版本是进程内的 v1、v2 等,重启回到 v1,这是为了把样例保持简单,不能直接作为生产版本方案。代理可能保留旧配置并报告已有版本,如果新服务恰巧复用旧版本,某些更新就不容易被识别。持久发布号、唯一启动前缀或稳定内容摘要都是可考虑的策略,需要结合多副本一致性选择,而不是随意拼接当前时间。
实验控制平面的内存状态不是持久配置数据库,重建容器或重启进程后应按 README 说明重新确认初态。生产系统需要从持久的期望状态重建 cache,确保新副本在接受客户端前已经准备好正确快照。cache 是加速和分发层,不应成为唯一不可恢复的业务真相。
13.6 从实验切换扩展到正式迁移¶
实验始终保留蓝绿后端,是一种很好的教学简化。真实迁移要新建 Cluster、改变协议或更换证书时,依赖变化更多。比较清晰的步骤是先增加新依赖,让目标代理获得它并验证可用,再修改引用,观察错误率与延迟,最后在旧引用消失且连接排空策略允许时清理旧资源。
“先增加、再切换、后清理”不是任何场景都能无条件保证零故障,还要考虑客户端重连、连接复用、上游长连接、资源 warming 以及多个代理的收敛差异。但它显式保留了过渡时期所需的两套依赖,通常比先删除再等待所有引用追上更易控制。ADS 的有序发送可以帮助这一过程,仍不能替代业务探测和逐组发布。
发布完成后,按照实验 README 停止容器;docker compose down 会移除这组实验容器和默认网络,工作区源码仍保留。它不是回滚生产配置的通用命令。把本地实验、代理运行状态和持久期望状态分清,才能把学到的方法迁移到真实系统。
13.7 练习与参考答案¶
/switch已返回成功,业务仍为 blue。列出至少三处应检查的位置。- 新路由已经 ACK,后端连接被防火墙拒绝,这属于 NACK 吗?
- 为什么实验在切换后仍保留旧 Cluster,而不马上删掉?
参考答案:第一题检查目标 Node/缓存键与版本、RDS 订阅及响应、关联 nonce 的确认、admin 活跃路由和实际后端;按证据链定位最早缺口。第二题不是,ACK 反映配置处理结果,网络连通性需要另行验证。第三题保留依赖有利于理解和回退,也减少引用切换期间找不到目标的风险;正式环境何时清理还要考虑代理群收敛与连接排空。
本章源码与实验¶
- 实验 02 的运行与切换说明:端口、命令和预期结果以此为准。
- SetSnapshot 与 watch 唤醒。
- SotW 请求处理与旧 nonce 检查、响应发送。
- Snapshot 版本与 Delta 内容摘要:不同更新模式的版本机制不能混用。