Capistrano 回滚策略

解读

在国内 PHP 面试中,Capistrano 常被视作“一键部署”的代名词,但面试官真正想考察的是:

  1. 你是否理解“发布”与“回滚”在 Linux 文件系统层面的原子操作;
  2. 能否在零停机、高并发、CDN 缓存、队列任务等真实场景下,把回滚时间压到分钟级甚至秒级;
  3. 是否具备“可重复、可审计、可灰度”的工程化思维,而不仅是“敲一行 cap production deploy:rollback”。

知识点

  1. Capistrano 目录结构:releases、current、shared 三层语义及 ln -sfn 原子切换原理。
  2. 版本保留策略:keep_releases 与磁盘容量、Inode 消耗的权衡;国内云主机常配小容量高效云盘,需定时清理。
  3. 回滚触发条件:上线后 5 min 内错误率飙升、Sentry 告警、CAT 曲线突刺、支付掉单等典型指标。
  4. 回滚深度:单步(默认)与多步(rollback -s 3)的差异;如何结合 Git tag 做“指定版本回滚”。
  5. 数据兼容性:PHP 业务常伴随数据库结构变更(Laravel migration),回滚代码时必须评估“逆向迁移”风险;国内 DBA 流程要求先提工单,回滚窗口只有 30 min。
  6. OPcache/Swoole 常驻内存:代码回滚后需 reload php-fpm 或 restart Swoole,否则旧字节码仍在内存;Capistrano 的 :restart 任务需与 systemd 脚本联动。
  7. 静态资源缓存:CDN 缓存 30 min,回滚后必须按文件名 hash 做“资源指纹”,否则样式错乱;国内常用阿里云 CDN、腾讯云 ECDN,支持目录刷新 API,Capistrano 插件 capistrano-aliyun-cdn 可集成。
  8. 队列任务:Laravel Horizon、ThinkPHP QPS 队列,回滚后需 kill 旧版本 worker,防止新旧逻辑混跑;Capistrano 钩子 :publishing 阶段插入 supervisorctl restart。
  9. 灰度回滚:国内大厂普遍采用“SET 化 + 流量标签”方案,Capistrano 原生不支持,可借助 Nginx+Lua 按照 uid 尾号灰度 10% 流量,回滚时先切流量再切代码。
  10. 审计与复盘:回滚结束后需在 1 h 内输出“上线/回滚报告”,包括 Git diff、SQL 变更、告警截图,供技术委员会评审;Capistrano 可打开 airbrussh 日志格式,方便直接贴图。

答案

“我们在电商大促场景下把 Capistrano 回滚拆成三段:
第一段,预检:部署前把 keep_releases 设为 10,确保至少 3 个稳定版本可回滚;migration 采用‘双写兼容’策略,新增字段 nullable,删除字段先 deprecated,保证回滚不会破坏数据。
第二段,上线:利用 Capistrano 的 :publishing 钩子,在代码切到 current 之后、重启 php-fpm 之前,调用阿里云 CDN 刷新 API,把 /static/dist/* 全部预热;同时通过 supervisorctl 优雅重启 Laravel Queue Worker,确保新旧任务不串台。
第三段,回滚:监控基于 Prometheus + Grafana,5 min 内错误率 >1% 或订单支付成功率下降 0.3% 即自动触发钉钉机器人,运维一键执行 cap production deploy:rollback。此时 Capistrano 把 current 软链指向上一个 release,自动 reload php-fpm,并回滚数据库到兼容状态;若需回滚两步,加 -s 参数即可。整个回滚过程 30 s 内完成,用户无感知。
回滚结束后,我们在 JumpServer 上拉取 airbrussh 日志,连同 Sentry 错误趋势截图一起贴到 Confluence,30 min 内完成复盘,24 h 内提交修复补丁并走灰度重新上线。”

拓展思考

  1. 如果下一次上线包含“不可逆”migration(如拆分订单表),Capistrano 原生回滚已无法倒退,如何设计“蓝绿发布”或“影子表”方案?
  2. 在 Kubernetes 逐渐普及的国内云原生背景下,Capistrano 的 SSH 批量模式显得笨重,你是否考虑用 GitLab CI + Helm 做灰度,保留 Capistrano 仅做遗留 VM 的维护?
  3. 回滚后仍需保留故障现场,如何利用 Capistrano 的 :rollback 钩子自动把当前 release 目录打包成 tar.zst 并上传到 OSS,供后续 gdb 跟踪 core dump?