2024 年 12 月 16 日,Railway 的一次常规配置变更意外演变为全网入站流量阻断事故。工程师在清理历史遗留的静态 IP 封禁列表时,将空数组传入 Terraform,导致 GCP 防火墙规则从 "阻断特定 IP" 变成 "阻断所有 IP"。这场持续约 10 分钟的事故,以及 Railway 长期以来与 GCP 的摩擦,最终推动其构建了一套从 DNS 秒级切换到跨云负载均衡的韧性架构。
Terraform 配置陷阱:空数组与 nil 的语义鸿沟
事故的直接诱因是一个看似无害的代码变更。Railway 过去维护着静态和动态两套 IP 封禁机制,随着 DDoS 防护能力的提升,他们决定完全迁移到动态方案,并清理遗留的静态封禁列表。
在 Terraform 配置中,工程师将 source_ranges 设置为空数组 [],意图表示 "没有 IP 需要封禁"。然而 GCP Terraform Provider 的实现逻辑是:当 source_ranges 未提供或为空时,默认将规则应用于所有 IP。由于 Terraform 使用 Go 语言编写,在 Go 的类型系统中,空数组与 nil 没有本质区别,这一语义差异导致防火墙规则从 "阻断空列表" 被解释为 "阻断一切"。
这一陷阱的隐蔽性在于,它违背了开发者的直觉预期。在大多数编程语境中,空集合意味着 "无操作",但 GCP 的防火墙规则语义恰恰相反 —— 未指定源范围即表示 "所有源"。Railway 在事后审计中发现,类似的宽松规则还存在于其他遗留配置中,这促使他们建立了更严格的防火墙规则审查机制。
实时流量迁移:DNS 秒级切换的工程参数
面对全网阻断,Railway 的应急响应遵循 "先回滚,后调查" 的原则。从变更合并到完全恢复,整个过程用时约 12 分钟,其中关键的时间消耗在于两个环节:告警通知路由到错误的值班人员,以及 Terraform 在回滚时执行了不必要的 plan 阶段。
这次事故催生了 Railway 对流量迁移能力的重新设计。在理想的多云架构中,当某一云提供商出现网络层阻断时,应当能够在秒级完成流量切换。实现这一目标需要以下工程参数:
DNS TTL 设置:将关键域名的 TTL 设置为 30-60 秒,确保 DNS 变更能够在全球范围内快速传播。过短的 TTL 会增加 DNS 服务器负载,但在故障场景下这是可接受的权衡。
健康检查频率:负载均衡器的健康检查间隔应设置为 5-10 秒,连续失败 2-3 次即判定为不健康。这能够在 15-30 秒内检测到后端故障并触发流量切换。
多区域部署:在每个地理区域至少部署两个云提供商的节点,确保单一云故障不会导致该区域服务中断。
Railway 在事故后优化了告警路由逻辑,将配置变更相关的告警直接发送给提交变更的工程师,而非轮值的值班人员。同时,他们在 Terraform 回滚流程中增加了 "紧急模式",跳过 plan 阶段直接执行 apply,将回滚时间从 3 分钟压缩到 30 秒以内。
多云架构设计:从单云依赖到多活隔离
Railway 与 GCP 的摩擦并非始于这次事故。2023 年 12 月,他们经历了一次更为严重的事件:GCP 的虚拟化层在资源压力下触发 softlock,导致机器无法自动故障转移。当时,Railway 的工程师在串口日志中发现了 kvm_wait 和 __pv_queued_spin_lock_slowpath 等内核级错误,这些错误与 GCP 的嵌套虚拟化实现相关。
更早之前,GCP 的 Artifact Registry 还曾无故将 Railway 的配额限制降至零,导致镜像分发严重延迟。面对这一系列问题,Railway 决定构建自主可控的基础设施:
自有网络栈:基于 eBPF 和 IPv6 Wireguard 构建了覆盖全平台的私有网络,替代了 GCP 的 VPC 网络。这一架构不仅解决了 GCP 的网络抖动问题,还为跨云通信提供了统一的抽象层。
裸金属部署:在每个区域部署自有裸金属服务器,通过 BGP 协议的 L3 网络 fabric 实现交换机级别的故障隔离。这与依赖单一云提供商的虚拟化层相比,提供了更高的可预测性和可控性。
多云负载均衡:在保留 GCP 支持的同时,引入 AWS 作为备选提供商,并在多个数据中心之间实现负载分担。这种设计使得单一云提供商的阻断不会导致服务中断,而是触发自动的流量迁移。
工程实践清单:可落地的参数与监控点
基于 Railway 的事故复盘和架构演进,以下是构建云阻断韧性系统的实践清单:
配置管理:
- 对 Terraform 等基础设施即代码工具进行空值语义审计,特别关注数组、列表类型的默认值行为
- 建立防火墙规则的白名单审查机制,禁止无源范围限制的阻断规则
- 实施配置变更的自动化测试,包括 dry-run 和 impact analysis
应急响应:
- 建立 "变更者告警" 机制,配置变更触发的告警直接通知提交者
- 优化回滚流程,支持跳过 plan 阶段的紧急回滚模式
- 定义明确的 RTO(恢复时间目标)和 RPO(恢复点目标),并定期进行演练
监控与可观测性:
- 在串口控制台、内核日志等低层级采集点设置监控,捕获虚拟化层的异常信号
- 建立跨云的健康检查矩阵,确保每个区域至少有两个独立的健康检查源
- 监控 DNS 传播延迟,确保 TTL 变更能够按预期生效
架构设计:
- 实施至少双云提供商策略,避免单一云锁定
- 在每个区域部署异构基础设施(裸金属 + 多云虚拟机)
- 设计无状态服务架构,确保实例可以在不同云之间无缝迁移
Railway 的经历表明,云提供商的阻断可能来自账号层面、网络层面或虚拟化层,而构建韧性系统的关键在于假设故障必然发生,并为此设计自动化的故障隔离和流量迁移机制。
资料来源
- Railway, "Incident Report: December 16th, 2024", blog.railway.com
- Railway, "Not Everything Is Google's Fault (Just Most Things)", blog.railway.com, 2023-12-01
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。