本质是并发写冲突,需通过resource_group串行化部署作业,或在ssh脚本中加文件锁,长期应采用容器化、cdn、蓝绿部署等架构优化方案。

这个问题本质是并发写冲突,不是数据库事务意义上的“死锁”,而是多个流水线作业同时操作同一台目标服务器的相同路径(比如覆盖文件、重启服务、执行部署脚本),导致状态不一致、文件损坏或服务异常。解决的关键在于**串行化关键部署动作**,而非依赖系统级锁机制。
明确部署临界区范围
先识别哪些操作绝对不能并行执行:
- 向同一目录拷贝/覆盖静态资源(如
cp -r dist/* /var/www/app/) - 重启同一进程(如
systemctl restart myapp.service) - 执行数据库迁移脚本(
flyway migrate或liquibase update) - 修改共享配置文件(如
/etc/nginx/conf.d/app.conf)
这些操作必须被约束为“同一时间仅一个作业能进入”。
用 GitLab CI 的 resource_group 实现轻量级串行
GitLab 原生支持基于资源组的互斥调度,无需额外工具或脚本。在 .gitlab-ci.yml 中为部署作业添加 resource_group:
deploy_to_prod:
stage: deploy
script:
- ./scripts/deploy.sh
resource_group: "prod-deployment" # 同名组内作业自动排队
only:
- main
只要所有指向同一生产节点的部署作业都使用相同的 resource_group 名称(如 "prod-server-01"),GitLab Runner 就会确保它们顺序执行,后触发的作业自动等待前一个完成。
服务端加文件锁(适用于自托管 Runner 或 SSH 部署)
如果使用 SSH 直连服务器部署,可在部署脚本中加入简单文件锁逻辑:
- 部署开始前:尝试创建带进程ID的锁文件(如
/tmp/deploy.lock),失败则循环等待 - 部署结束后:主动删除锁文件
- 超时强制清理:加个 10 分钟超时,避免锁残留阻塞后续流程
示例片段(Bash):
LOCK_FILE="/tmp/deploy.lock" PID_FILE="/tmp/deploy.pid" <h1>等待获取锁</h1><p>while [ -f "$LOCK_FILE" ]; do if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then sleep 5 else rm -f "$LOCK_FILE" "$PID_FILE" fi done</p><p>echo $$ > "$PID_FILE" touch "$LOCK_FILE"</p><h1>执行实际部署命令</h1><p>cp -r dist/* /var/www/app/ systemctl restart myapp</p><h1>清理</h1><p>rm -f "$LOCK_FILE" "$PID_FILE" </p>
避免锁问题的架构优化
长期来看,比加锁更根本的解法是减少对共享节点的强依赖:
- 改用容器化部署:每次部署生成新镜像,通过 Kubernetes 或 Docker Compose 滚动更新,天然隔离
- 静态资源走 CDN 或对象存储:前端资源上传至 OSS/S3,Nginx 只做反向代理,不参与文件写入
- 蓝绿部署或金丝雀发布:新版本部署到独立实例组,流量切换由负载均衡器控制,旧组保持只读
这类设计让“多流水线同时生效”不再构成风险,反而成为加速发布的手段。











