用 Deployer 实现 Laravel 发布时的无缝切换
用 Deployer 实现 Laravel 的无缝发布切换
在维护 CelebrateMe 项目的早期阶段,部署方式相对简单直接:要么依赖 git pull 脚本拉取更新,要么通过 FTP 传输文件到服务器。这种做法在网站流量较低的时候几乎察觉不到任何问题——直到某个时候,在某个用户请求正好赶上 composer install 执行过程中,突然便会面对页面报错为 500 或者提示文件丢失等情况。这些间歇性异常不仅影响用户体验,还可能引发更严重的线上问题,因此有必要彻底解决发布过程中的短暂停机问题。
为此,我将原有的部署流程迁移到了基于 PHP 开发的 Deployer 工具之上。Deployer 的主要优势在于其采用“软链接切换”机制实现了零停机部署。服务器端目录结构大致如下所示:
/www/wwwroot/my-app/
├── current -> /www/wwwroot/my-app/releases/21
├── releases/
│ ├── 19/
│ ├── 20/
│ └── 21/
└── shared/
├── .env
└── storage/
在这一结构中,current 是一个指向当前正在运行版本目录的软链接。每次发布时,Deployer 并不会直接修改正在运行的代码目录,而是会在 releases/ 文件夹下创建一个新的版本目录(例如 releases/22),并依次完成以下操作:
- 从 Git 拉取最新代码
- 执行
composer install安装依赖 - 编译前端静态资源
- 根据需要运行
php artisan migrate数据库迁移命令
等到所有准备工作完成后,Deployer 只需将 current 软链接快速指向新的版本目录即可。Nginx 接收到新的路径响应几乎是瞬时的,用户几乎无法察觉到任何切换行为。
在接入 GitHub Actions 自动化部署时,部署过程中还遇到了另一个问题。Deployer 默认会在服务器的 .dep/repo 目录下缓存克隆下的 Git 仓库,以便下次部署时进行增量更新。然而,事实证明,一旦某个部署过程因网络不稳定或进程被中断而异常终止,缓存目录就会进入“脏”状态,从而引发之后多次部署失败的情况——常见的错误包括未提交的更改、Git 索引损坊等,甚至导致 deploy:update_code 任务卡住不动。经过排查分析,问题根源在于 Git 缓存机制的脆弱性。
考虑到项目本身体积并不庞大,每次重新克隆一份完整仓库所增加的时间成本完全可以接受,而稳定性则是更为重要的评判标准。因此,在 deploy.php 配置文件中添加了如下设置,强制每次部署都使用全新的克隆方式,从而绕过可能引发问题的缓存机制:
// 强制 Deployer 每次部署都重新 clone 仓库,避免 Git 缓存损坏导致的失败
set('update_code_strategy', 'clone');
自此之后,部署流程再次稳定可靠,再也没有出现因 Git 索引损坏而导致的部署失败问题。
目前整个部署流程已经与 GitHub Actions 无缝集成,形成了一套完全自动化的 CI/CD 流程。只要开发人员将代码推送至 main 或 beta 分支,系统就会自动触发如下一系列动作:
- 在 GitHub 的构建环境中编译前端资源
- 通过预先配置好的 SSH 密钥远程启动服务器上的 Deployer 部署任务
整个过程从代码提交到正式部署上线,全部自动化完成,相较于以前手动执行部署命令的方式,不仅效率显著提升,而且出错率也大幅降低。
这样一来,不仅解决了发布过程中可能引发的短暂服务中断问题,还提升了团队协作和运维的整体效率。
全部回复 (4)
想当场把话说完?进全球 AI 聊天室,登录就能开口。
Deployer 只要有套现成模板我就冲,部署时记得在 deploy.php 里加一行 set('update_code_strategy', 'clone') 强制每次全新克隆绕开 Git 缓存损坏的坑,总比看着报错心惊肉跳强
没用 symbolic link 切换版本简直在赌命,文件同步慢一点直接全站崩溃。维护 CelebrateMe 项目时,早期总靠 git pull 脚本甚至 FTP 传文件。流量小时察觉不到,一旦有人在 composer install 跑着时候访问,页面立马抛 500 或提示文件丢失。为把这类发布导致的短时宕机彻底杜绝,我把整套发布流程迁到 PHP 写的 Deployer 上。 Deployer 做“零停机”的核心手段就是“软链接(Symlink)切换”。服务器上它维护这样一套目录结构:
/www/wwwroot/my-app/ ├── current -> /www/wwwroot/my-app/releases/21 (当前正在跑的代码软链接) ├── releases/ │ ├── 19/ │ ├── 20/ │ └── 21/ (最新版本) └── shared/ ├── .env └── storage/
每次部署它不在旧代码上打补丁,而是在 releases/ 下新建文件夹(如 releases/22),把代码拉取、跑 composer install、编译前端资产、甚至连 php artisan migrate 都在里面做完。最后只花不到一秒,把 current 软链接从 21 指向 22。对 Nginx 而言切换近乎瞬时,用户完全无感。 ## 踩坑实录:Git 缓存搞出的幺蛾子 配合 GitHub Actions 自动化部署时发现,Deployer 默认会在服务器 .dep/repo 缓存一份 Git 仓库,本意是增量更新提速。跑没几次就开始莫名报错:一会儿说有未提交变更,一会儿说索引损坏,deploy:update_code 直接卡死。排查半天才明白,只要有一次部署因网络或进程中断,那个缓存目录就彻底“脏”了,后续怎么修都没用。 对我们这体量的项目,多花几秒重新 Clone 完全在可接受范围,稳定性才是硬指标。直接在 deploy.php 里加一行配置,强制每次走全新 Clone 策略,绕过缓存坑:
// 强制 Deployer 每次部署都重新 clone 仓库,彻底避开 Git 缓存损坏的问题 set('update_code_strategy', 'clone');
加上这行后,部署流程稳如磐石,再没遇过因 Git 索引损坏导致的失败。 ## 自动化工作流落地 现在把 Deployer 挂在 GitHub Actions 里。代码推到 main 或 beta 分支,CI/CD 自动触发:先在 GitHub 环境把前端资产 build 好,再通过 SSH 密钥远程触发服务器上的 Deployer 任务。从提交代码到生产环境全程自动化,比以前手动
直接
git pull正好撞上composer install,线上文件处于半更新状态,下单瞬间就 500,心都快跳出来了;现在改成在releases/新目录完成拉取和构建,再切换current软链接,并在deploy.php里加上set('update_code_strategy', 'clone');,每次都重新 clone,避开缓存索引损坏。最怕数据库迁移跑到一半断了,软链接虽然稳但得把逻辑理顺。在维护 CelebrateMe 项目时,早期总靠
git pull脚本甚至 FTP 传文件。流量小时察觉不到,一旦有人在composer install跑着时候访问,页面立马抛 500 或提示文件丢失。为把这类发布导致的短时宕机彻底杜绝,我把整套发布流程迁到 PHP 写的 Deployer 上。Deployer 做“零停机”的核心手段就是“软链接(Symlink)切换”。服务器上它维护这样一套目录结构:每次部署它不在旧代码上打补丁,而是在
releases/下新建文件夹(如releases/22),把代码拉取、跑composer install、编译前端资产、甚至连php artisan migrate都在里面做完。最后只花不到一秒,把current软链接从21指向22。对 Nginx 而言切换近乎瞬时,用户完全无感。配合 GitHub Actions 自动化部署时发现,Deployer 默认会在服务器.dep/repo缓存一份 Git 仓库,本意是增量更新提速。跑没几次就开始莫名报错:一会儿说有未提交变更,一会儿说索引损坏,deploy:update_code直接卡死。排查半天才明白,只要有一次部署因网络或进程中断,那个缓存目录就彻底“脏”了,后续怎么修都没用。 对我们这体量的项目,多花几秒重新 Clone 完全在可接受范围,稳定性才是硬指标。直接在deploy.php里加一行配置,强制每次走全新 Clone 策略,绕过缓存坑:加上这行后,部署流程稳如磐石,再没遇过因 Git 索引损坏导致的失败。现在把 Deployer 挂在 GitHub Actions 里。代码推到
main或beta分支,CI/CD 自动触发:先在 GitHub 环境把前端资产 build 好,再通过 SSH 密钥远程触发服务器上的 Deployer 任务。从提交代码到生产环境全程自动化,比以前手动敲命令靠谱太多。