代码能跑 ≠ 代码能上线。从 main 分支合到生产环境,中间这一段路才是事故的高发区。前面五天我们讲了 DEFINE、PLAN、BUILD、VERIFY、REVIEW,今天是六阶段流水线的最后一站:SHIP。它关心的是:怎么把代码安全、可逆、可观察地送到用户手里。
agent-skills 在 SHIP 阶段放了四个技能:ci-cd-and-automation、observability-and-instrumentation、shipping-and-launch、deprecation-and-migration。它们四个刚好对应上线的四件事——自动化流水线、埋点观测、上线动作本身、以及下线。
一句话定位
ci-cd-and-automation 把”每条规则都要人记”变成”每条规则流水线强制”;
observability-and-instrumentation 把”上线后才知道有没有问题”变成”上线那一刻就知道在哪看”;
shipping-and-launch 把”一键部署”变成”可逆、可观察、分阶段的上线流程”;
deprecation-and-migration 把”代码只会越写越多”变成”主动把不赚钱的代码清掉”。
四个技能串起来,就是一条完整的”上线 → 观测 → 演进 → 退役”链路。
ci-cd-and-automation:把检查前移
这个技能的核心原则只有两条,但都是反直觉的:
第一条叫 Shift Left。把检查尽量往前推,能在写代码那一秒发现的,就不要等到联调再发现。一个 lint 错误在编辑器里 5 秒能改掉,到 CI 里要 30 秒,到生产环境里可能就是一次回滚事故。
第二条叫 Faster is Safer。小批量、高频次发布反而更安全。这条原则来自 DORA(DevOps Research and Assessment)的多年研究:发布频率越高的团队,变更失败率越低,事故恢复时间越短。一次发布 30 个改动的版本,比一次发布 3 个改动的版本难回滚得多。
整条流水线画出来长这样:
flowchart LR
PR["PR 打开"] --> L["Lint
eslint / prettier"]
L --> T["Type Check
tsc --noEmit"]
T --> U["Unit Tests
jest / vitest"]
U --> B["Build
npm run build"]
B --> I["Integration
API / DB"]
I --> E["E2E
Playwright"]
E --> S["Security Audit
npm audit"]
S --> BS["Bundle Size
预算检查"]
BS --> R["Ready for Review"]
每一关都不可跳过。SKILL.md 里专门写了一行:
No gate can be skipped. If lint fails, fix lint — don’t disable the rule. If a test fails, fix the code — don’t skip the test.
意思是:CI 红了,唯一正确的处理方式是修代码,不是关规则、不是 skip 测试。任何”为了过 CI 所以我注释掉了这个检查”的做法,都是在给未来的事故埋种子。
我们本博客系统就有一个真实的例子——deploy.sh 这个脚本做的事情非常朴素:
1 |
|
它对应的就是”自动化部署”这四个字。每次发布前人肉 ssh 上来敲命令,不仅慢,还会让”发布当天是不是网络挂了”这种问题纠缠不清。脚本一上,同样的事变成幂等、可重试、可回放的。这就是 CI/CD 的最小闭环。
CI 跑挂了怎么办
技能里专门讲了一种反馈循环:当 CI 失败时,把报错粘回给 AI 让它修:
1 | CI fails |
这个模式把”AI 写代码”和”CI 守住质量”两件事串成了一个回路。AI 负责生成,CI 负责挑刺,CI 的反馈又喂回给 AI 让它修正。这条反馈链跑顺了,AI 编码才能真正进入”自校正”模式。
CI 优化优先级
流水线超过 10 分钟就会被人开始嫌弃。技能给了一份优化清单,按投入产出排序:
1 | Slow CI pipeline? |
最快的收益是前两条——缓存和并行。其它都是后话。
observability-and-instrumentation:代码不可观察 = 代码不可运维
这个技能开篇就一句值得背下来的话:
Code you can’t observe is code you can’t operate.
如果一个功能上线后没人看得见它在干嘛,那它和没上线没区别——只是多了几行占内存的代码。
埋点不是上线以后再补的,是和功能一起写的。这一点和测试是平级的:你会写完代码不写测试就提交吗?理论上不该,埋点也一样。
三件套:日志、指标、追踪
| 信号 | 回答的问题 | 成本特征 |
|---|---|---|
| 结构化日志 | “这个 case 具体发生了什么?” | 按事件数增长 |
| 指标 | “整体上多快、多频繁?” | 每条 series 成本固定,便宜 |
| 追踪 | “跨服务时间花在哪?” | 每请求一份,可采样 |
一句话记忆:metrics 告诉你有问题,traces 告诉你在哪,logs 告诉你为什么。
写日志时最常见的反模式是字符串拼接:
1 | // BAD:拼出来的字符串没法过滤、没法聚合 |
前者在生产里就是一坨文本,后者在 ELK、Loki、Datadog 里能直接按字段聚合。”支付失败率突增 3 倍,按错误码分组”这种查询,前者做不了,后者一行 SQL。
关联 ID 是必须的
没有 requestId,每一行日志都是孤儿。微服务里一个请求穿五六个服务,等出问题时你拿着 6 个服务的日志对时间戳——这是考古,不是调试。
1 | app.use((req, res, next) => { |
边界处生成或接受 ID,子 logger 挂上,所有下游调用都带上。看起来很基础,但 80% 的生产系统没做对。
告警要告”症状”不要告”原因”
这是一条非常容易踩坑的规则:
1 | SYMPTOM(值得叫醒人): CAUSE(放 dashboard,不叫人): |
CPU 85% 不一定意味着用户受害,但错误率 > 1% 一定意味着用户受害。前者叫醒人可能是误报,后者叫醒人几乎一定是真问题。
每条告警还要满足四个硬条件:
- 必须 actionable。如果处理方式是”忽略它,它自愈”,直接删告警。
- 必须带 runbook 链接。哪怕只有三行:这是啥、第一手查什么、谁接。
- 必须有阈值和持续时间,且这个阈值要能回溯到 SLO 或历史数据,不能拍脑袋。
- 只分两档:page(用户受影响,现在动)和 ticket(性能下降,本周内动)。三档以上就开始有人”反正先 ack 再说”了。
与 debugging 的区别
这个技能专门写了一行 NOT for:
NOT for: Diagnosing a failure happening right now — use the
debugging-and-error-recoveryskill (observability is what makes that skill fast next time).
区别很清晰:debugging 是当下救火,observability 是为下次救火铺路。两者不是替代关系,是前后关系。
shipping-and-launch:可逆、可观察、分阶段
CI 跑过了、可观测性也埋好了,接下来才是上线动作本身。shipping-and-launch 这个技能的核心是三件事:上线清单、灰度发布、回滚预案。
上线清单
清单很长,分六块:代码质量、安全、性能、可访问性、基础设施、文档。重点是这块不能省——很多团队上线靠的是”看着差不多就上”。技能里给的最小集是:
- 所有测试通过(unit / integration / e2e)
- 构建成功,无警告
- 无 TODO 注释遗留
- 无
console.log残留 - 错误处理覆盖预期失败模式
- 关键依赖审计无 high/critical 漏洞
- 健康检查端点存在并响应
看着啰嗦,但这是用事故换出来的。漏掉”健康检查端点”那一行,生产环境里 service 进程死了没人知道,直到用户报 bug。
分阶段灰度
灰度的核心是”小流量先验证,再逐步放大”。整个序列画出来:
sequenceDiagram
participant S as 调度系统
participant F as Feature Flag 服务
participant U1 as 1% 用户
participant U2 as 10% 用户
participant U3 as 50% 用户
participant U4 as 100% 用户
Note over S,F: 步骤 1: 代码部署,flag=OFF
S->>F: 设置 rollout = 1%
F->>U1: 命中 flag,启用新功能
Note over S,F: 监控 15-30 分钟
S->>F: 设置 rollout = 10%
F->>U2: 命中 flag,启用新功能
Note over S,F: 监控 1-2 小时
S->>F: 设置 rollout = 50%
F->>U3: 命中 flag,启用新功能
Note over S,F: 监控 4-8 小时
S->>F: 设置 rollout = 100%
F->>U4: 全部启用
每一步之间都要等数据说话,不能急着推。技能给了一份”什么时候可以往前推”的判断表:
| 指标 | 推进(绿) | 暂停排查(黄) | 回滚(红) |
|---|---|---|---|
| 错误率 | 与基线差距 < 10% | 高于基线 10%-100% | > 2 倍基线 |
| P95 延迟 | 与基线差距 < 20% | 高于基线 20%-50% | > 50% |
| 客户端 JS 错误 | 无新错误类型 | 新错误 < 0.1% 会话 | > 0.1% |
| 业务指标 | 中性或正向 | 下滑 < 5% | 下滑 > 5% |
这张表的好处是把”要不要回滚”从主观判断变成了对照表。开会时大家对着表看一眼,不用吵。
回滚策略
回滚是上线动作的一部分,不是补救措施。每一次上线前,回滚方案必须就位。
1 | ## Rollback Plan for [Feature/Release] |
三种回滚方式,按响应时间排序:
- Feature flag 关闭:秒级,最快。适合功能层面可分离的改动。
- 前一版本镜像/部署:分钟级。适合 flag 兜不住的情况。
- 数据库迁移回滚:最慢,需要预先测试好 down 路径。
技能里专门强调了一句很重的话:
Rolling back is responsible engineering. Shipping a broken feature is the failure.
回滚不是认怂,是负责任。硬撑着不回的代价是 P0 事故 + 用户损失 + 团队士气打击。
启动日监控清单
上线后第一个小时是黄金一小时。技能给的 checklist:
1 | 1. Check health endpoint returns 200 |
最后一条特别容易忘:dry-run 一次回滚。真到回滚的时候你才发现按钮点了没反应、镜像拉不下来、脚本权限不对——这就是事故叠加事故。
deprecation-and-migration:顺带说一下
这一篇不是 SHIP 阶段的主菜,但同属 SHIP 阶段,必须提一下。这个技能的核心论点只有一句:
Code is a liability, not an asset.
代码是负债,不是资产。每一行代码都有持续维护成本——bug 要修、依赖要升级、安全补丁要打、新人 onboarding 要看懂。”我们写了好多代码”从来不是成绩,”我们少维护多少代码”才是。
弃用策略
弃用分两档:
| 类型 | 何时用 | 机制 |
|---|---|---|
| Advisory(劝导式) | 迁移可选、旧系统稳定 | 警告、文档、引导 |
| Compulsory(强制式) | 安全风险、阻塞进展 | 硬截止日期 + 迁移工具 |
默认用 Advisory。只有当维护成本或风险已经超过迁移成本时,才用 Compulsory。而且 Compulsory 必须配迁移工具——你不能只发个”将于 X 月 X 日下线”,那叫通知,不叫弃用。
数据库迁移:Expand → Migrate → Contract
最危险的迁移是数据库 schema,因为数据是唯一不能靠”回滚部署”恢复的东西。规则是永远不要原地改列,分三步走:
1 | EXPAND ─────────→ MIGRATE ─────────→ CONTRACT |
举例:要把 name 改名为 full_name:
- Expand:加
full_name为 nullable,部署。旧代码无视它,没事。 - Migrate:应用双写
name和full_name,部署。 - Backfill:分批把
name → full_name复制过去,分批是为了不锁表。 - Switch reads:应用切到读
full_name,但仍双写,部署并观察。 - Contract:单独的、靠后的部署,停写
name然后删列。
每一步都是独立可部署、可回滚的。如果第 4 步出问题,回滚代码,full_name 还在被写入,没有数据丢失。
反合理化表里的金句
技能里这张表非常值得背:
| 借口 | 现实 |
|---|---|
| “它还能跑,干嘛删” | 能跑但没人维护的代码会堆积安全债和复杂度 |
| “也许以后会用到” | 以后要用就重建,”以防万一”的成本比重建还贵 |
| “改一列名就一行代码” | 灰度期间新老代码一起跑,一个会读到不存在的列 |
| “我们会写回滚脚本的” | 一个没有 down 路径的迁移是一个不能回滚的部署 |
最后一行是金句——“我们会写回滚脚本的”永远等于”我们不会写的”。
写在最后
SHIP 阶段看起来是”部署 → 验证 → 完事”,但它实际上是六阶段里最容易出事的。前面五个阶段做好了,SHIP 阶段只是收尾;前面五个阶段有缝,SHIP 阶段就是替它们兜底。
把今天四个技能串起来:
- ci-cd-and-automation 保证”上生产前”的门。
- observability-and-instrumentation 保证”上生产后”的眼。
- shipping-and-launch 保证”上”的姿势。
- deprecation-and-migration 保证”下”的姿势。
四件事缺一不可。一个团队如果只有 CI 没有可观测性,那是有门没眼;如果只有可观测性没有灰度,那是有眼没刹车。
明天开始我们换个方向:怎么把这一整套 agent-skills 装到 Claude Code / Cursor / Gemini / Codex 里。踩过的坑、安装顺序、配置技巧——一篇实操指南。
参考资料