最近在继续给 A Tour of Go 多语言项目增加意大利语站点时,Production 阶段又遇到了一个比较典型的问题。
站点本身已经部署成功,源站也正常,Cloudflare DNS 同样没有问题,但到了公网验收阶段,却会因为网络瞬态失败导致整个首次 Production 流程中断。
一开始我以为,只要再增加一些重试就可以解决。
实际排查下来,最后做的调整更彻底:
不再让完整公网验收流量经过我的本地电脑和跨境 SOCKS 链路,而是直接把公网验收放到 zgocloud 上执行。
调整之后,105 个 sitemap URL、CDN Cache、HTML Identity、Socket Boundary 等公网检查全部稳定通过。
一、问题不是部署失败,而是公网验收失败
A Tour of Go 多语言站点的首次 Production,并不是“服务启动了就算成功”。
正式流程里还需要继续检查:
- release identity
- remote/source identity
- 关键公网路由
- HTML identity
- sitemap 以及其中的全部 URL
- socket boundary
- CDN Cache
- 浏览器自动验收
- HUMAN visual gate
这次出现问题的时候,前面的 release 和源站检查其实都已经通过了。
例如:
[verify-production] remote identity: PASS
[verify-production] source routes: 7/7 PASS也就是说,已经有证据证明:
当前 release 正确,源站服务也是正常的。
但是进入公网请求后,同一个 Cloudflare URL 连续出现 15 秒超时:
curl: (28) Operation timed out after 15002 milliseconds with 0 bytes received
curl: (28) Operation timed out after 15002 milliseconds with 0 bytes received
curl: (28) Operation timed out after 15002 milliseconds with 0 bytes received最终:
[首次生产] FAILED
stage: public-machine
expected: command exit 0
actual: exit 1
这张日志很重要。
如果只看到:
first-production FAILED很容易下意识认为应该重新部署、重新检查 Nginx,甚至重新处理 Cloudflare DNS。
但真实 evidence 并不支持这些操作。
失败发生在:
stage: public-machine而不是 deploy、source health 或 DNS 阶段。
这意味着正确方向应该是继续检查公网验收链路,而不是破坏已经正常工作的 Production 现场。
二、原来的公网验收链路有点绕
这个项目的 Production 源站位于中国大陆,而 Cloudflare 控制面以及部分公网访问需要经过跨境网络。
此前为了稳定访问 Cloudflare,我已经使用 zgocloud 作为海外出口,并通过 SSH、SOCKS 等方式处理相关流量。
但旧的公网验收链路,本质上仍然类似:
本地电脑
↓
跨境连接
↓
zgocloud
↓
Cloudflare
↓
Production Origin这里有一个比较明显的问题。
真正需要验证的是:
zgocloud → Cloudflare → Production但大量公网验收流量还必须先经过:
本地电脑 → zgocloud这一段额外的跨境链路。
对于偶尔访问一个页面,这种差异可能并不明显。
但是 Production verifier 并不是只请求一次首页。
当前 A Tour of Go Production sitemap 中一共有:
105 URLs除了 sitemap 之外,还要继续检查:
public routes
HTML identity
socket boundary
CDN cache这意味着公网验收会产生大量连续请求。
只要其中某一段跨境链路存在一定概率的抖动,随着请求数量增加,失败的概率也会明显放大。
当时除了 curl 28 timeout 之外,我还遇到过:
curl: (97) Failed to receive SOCKS response, proxy closed connection单独看某一次失败,也许只是偶发现象。
但对于正式 Production acceptance 来说,不能简单忽略。
三、不能为了让 Production 通过而无限重试
看到网络瞬态错误以后,一个很自然的想法就是增加 retry。
例如:
失败
→ retry
→ 还失败
→ 再 retry但正式验收不能变成“只要多试几次,总有一次能通过”。
否则自动化验收的可信度反而会下降。
因此这次继续明确了几个原则。
1. 瞬态网络错误允许 bounded retry
对于明确可能属于瞬态网络问题的情况,正式 verifier 可以进行有限次数的 bounded retry。
例如当前 verify-production.sh 会针对部分:
curl exit 6
curl exit 7
curl exit 16
curl exit 28
curl exit 35
curl exit 97以及 Cloudflare 的部分:
HTTP 522
HTTP 525执行有限次数重试。
当前 verify-production.sh 中,单个公网 HTTP 逻辑请求最多尝试 3 次。
不过这里需要特别说明:
3 次并不是整个项目所有检查统一使用的数字。
不同阶段根据检查对象不同,会有自己的 bounded retry 或 readiness 规则。
例如某些 Nginx readiness、Playground Origin endpoint check 使用的是不同的尝试次数。
真正需要统一的不是“所有地方都重试几次”,而是:
重试必须有明确边界。
2. 重试耗尽以后必须 fail closed
无论具体阶段允许多少次尝试,只要正式的 bounded retry 已经耗尽,就必须:
FAILED不能继续假设网络“其实应该是好的”。
也不能为了让首次 Production 顺利结束而跳过失败检查。
3. sitemap 检查失败后直接停止
这一点对当前项目尤其重要。
sitemap 有:
105 URLs假设检查到第 20 个 URL 时,bounded retry 已经全部失败,那么后面的 URL 实际上就还没有得到验证。
此时继续请求第 21、22……105 个 URL,并不能改变前面已经失败的事实。
因此现在的正式逻辑是:
某个 sitemap URL 在 bounded retry 后仍然失败,就立即 fail closed。
只有真正完整验证全部 URL 后,才允许输出:
sitemap: 105/105 PASS这也是我越来越重视的一点:
自动化验收的目标不是提高 PASS 率,而是让 PASS 本身可信。
四、真正的调整:把公网验收直接放到 zgocloud
继续分析之后,我觉得与其不断增强本地 SOCKS 链路,不如先重新思考一个更基础的问题:
Production 公网验收到底应该在哪里运行?
既然正式公网流量最终需要从海外访问 Cloudflare,那么更直接的路径其实就是:
zgocloud
↓
Cloudflare
↓
Production Origin因此现在的 Production verifier 改成了:
本地维护机
│
│ SSH
▼
zgocloud
│
│ 直接执行公网 HTTP 验收
▼
Cloudflare
▼
Production Origin也就是现在日志中会明确看到:
[verify-production] public network runner: zgocloud (direct)这里的重点不是“换了一个代理”。
而是:
把真正的公网 HTTP acceptance 放到了更加合适的网络位置执行。
与此同时,并不是所有检查都搬到了 zgocloud。
本地仍然负责正式 release identity,远端 Production source identity 也仍然按原有正式路径检查。
也就是说,调整的是:
public HTTP acceptance 的执行位置而不是降低或者绕过:
release identity
source identity
Production authority五、为什么这比继续优化 SOCKS 更简单
理论上,也可以继续优化原来的链路:
本地
→ SOCKS
→ zgocloud
→ Cloudflare例如继续做:
- 更多 connection retry
- 更复杂的 tunnel 恢复
- 自动重新建立 SOCKS
- 多出口切换
- 动态线路评分
- 不同时间段自动选路
但是对于当前项目来说,这些方案会越来越复杂。
而真正的需求其实很简单:
我需要一个相对稳定的海外网络位置,验证 Production 经 Cloudflare 对公网呈现的真实状态。
既然 zgocloud 本身已经存在,那么:
直接在 zgocloud 执行公网 verifier明显比:
先从本地跨境连接到 zgocloud
再让大量请求经过这条 tunnel更直接。
这也是这次最终没有继续做复杂网络工程的原因。
六、改造后的真实 Production 结果
调整完成后,我继续使用同一份正式 it-IT release。
没有重新 publish,也没有重新 deploy。
再次进入正式 Production 验收后,输出变成:
[verify-production] public network runner: zgocloud (direct)
[verify-production] public routes: 7/7 PASS
[verify-production] html identity: PASS
sitemap URLs: 105/105
host mismatch: 0
HTTP failure: 0
[verify-production] sitemap: 105/105 PASS
[verify-production] socket boundary: PASS
[verify-production] CDN /: HIT -> HIT -> HIT PASS
[verify-production] CDN /tour/welcome/1: HIT -> HIT -> HIT PASS
PRODUCTION MACHINE ACCEPTANCE: PASS
[首次生产] 公网验收:PASS(31.1s)
这张结果和前面的失败日志形成了非常直接的对比。
旧方案:
source routes: PASS
↓
公网请求 curl 28
↓
public-machine FAILED新方案:
zgocloud (direct)
↓
public routes: 7/7 PASS
↓
sitemap: 105/105 PASS
↓
socket boundary: PASS
↓
CDN: PASS
↓
PRODUCTION MACHINE ACCEPTANCE: PASS七、网络失败以后,不应该随便重新部署
这次还有一个很实用的收获。
以前遇到:
first-production FAILED很容易产生一种冲动:
是不是重新 deploy 一次比较保险?但这其实取决于失败发生在哪一个 stage。
例如这次:
source routes: PASS
remote identity: PASSProduction 服务本身已经存在完整证据证明是正常的。
真正失败的是:
public-machine而且错误是:
curl 28
curl 97那么正确处理应该是:
保留当前 release
保留 Production 现场
↓
解决或等待网络条件恢复
↓
正式 resume而不是:
重新 publish
重新 upload
重新 deploy
重新修改 DNS否则不仅浪费时间,还可能把一个单纯的网络问题变成新的部署变量。
现在 first-production 已经支持正式 resume,并会重新检查关键 identity。
所以后续再遇到类似问题,我会优先根据:
stage
+
真实 evidence决定回流路径,而不是看到 FAILED 就从头再来。
八、首次 Production 尽量避开晚间跨境网络高峰
这次问题发生的时间也让我增加了一条偏运维习惯的规则。
如果没有紧急需求:
首次 Production、公网验收、DNS/CDN 激活等操作,尽量避开北京时间晚间的跨境网络高峰。
这里我并不是要证明:
某个具体时间段一定会出现丢包也没有必要为此建立复杂的长期网络监控。
只是从实际维护成本来看:
如果:
上午执行和:
晚上执行业务上没有区别,那么就没有必要主动选择一个更容易引入跨境网络变量的时间段。
尤其首次 Production 本身已经包含:
DNS
Cloudflare
origin
shared assets
public acceptance
browser acceptance变量已经足够多。
能少一个不确定因素,就少一个。
九、以后新增 locale 直接复用
现在 A Tour of Go 多语言项目已经有多门 Production locale。
后续新增语言时,我不打算再次为每一门语言重新设计网络方案。
当前正式路径直接复用:
Production deploy
↓
direct-origin acceptance
↓
Cloudflare DNS
↓
minimal public readiness
↓
zgocloud direct public machine acceptance
↓
browser acceptance
↓
HUMAN visual gate
↓
first-production finalize
↓
production_state=live网络瞬态问题:
bounded retry重试耗尽:
fail closedProduction 本身没有变化:
保留现场
稍后正式 resume而不是重新部署。
对于后续 locale 来说,这一部分已经可以直接作为 Production 基线复用。
十、总结
这次一开始看到的,其实只是几个:
curl: (28)以及后来出现的:
curl: (97)如果只针对错误码本身处理,很容易最后变成:
再多重试几次
再把 timeout 调大一些但真正值得解决的问题其实是:
为什么公网验收一定要经过这条更长、更容易波动的网络路径?
最终,我没有继续堆叠复杂的代理、自动选路或者多出口监控,而是选择了更简单的办法:
需要验证 Cloudflare 公网行为
↓
就在 zgocloud 直接验证同时继续坚持:
bounded retry
+
fail closed
+
完整 identity 验证这次也让我重新确认了一件事:
Production acceptance 本身也是 Production 架构的一部分。
站点部署在哪里是一回事。
验收从哪里执行,又是另一回事。
如果验收链路本身不稳定,即使 Production 完全正常,也可能不断得到 false failure。
与其不断提高 retry 次数,不如先确保:
验收发生在一个合理、稳定、与真实验证目标一致的位置。
对目前这个项目来说,zgocloud direct 已经足够解决这个问题,而且比继续维护复杂的跨境 SOCKS 公网验收链路更简单。
A Tour of Go 多语言翻译项目
本系列完整记录 A Tour of Go 多语言翻译项目从架构设计、整页翻译、结构保护、自动校验,到生产发布与后续维护的实际开发过程。
项目入口:
✅ 简体中文:A Tour of Go 简体中文版
✅ 日语:A Tour of Go 日语版
✅ 德语:A Tour of Go 德语版
✅ 法语:A Tour of Go 法语版
✅ 项目源码:GitHub:shuijingwan/go-tour-i18n
当前已上线简体中文、日语、德语和法语版本。项目属于非官方社区多语言翻译项目,与 Go 官方无隶属或授权关系。

