4、手动部署流程:从代码到生产环境的手动步骤

说实话,现在CI/CD遍地都是,但手动部署这个手艺不能丢。我在老东家接手过一个金融系统,生产环境根本不能用自动流水线,全靠人工一步一步来。你想想看,如果连手动部署都搞不明白,自动化工具出了问题你连修都无从下手。

这一章我就把手动部署的标准流程掰开揉碎讲清楚。每个步骤我都会结合自己的踩坑经历,帮你避开那些让人抓狂的雷区。

4.1 前置检查:代码就绪与依赖确认

嗯,这一步很多人会跳过。我刚开始带项目时也这样,结果有一次打包后发现少了个配置文件,只能临时改,搞得很狼狈。

我个人习惯在部署前走一遍这个检查清单:

检查项 具体内容 我的经验
代码版本 确认git分支/tag与预期一致 打tag时写清楚版本号+日期,比如v2.1.0-20250415
依赖包 pom.xml或package.json是否锁定版本 锁定版本能避免“昨天还能跑,今天炸了”的灵异事件
环境变量 dev/staging/prod的配置差异 我曾把测试环境数据库地址带到生产,幸好回滚快
数据库迁移 是否有DDL/DML脚本需要先执行 生产库迁移一定要备份,最好先在预发环境跑一遍
权限检查 部署账号是否有目标目录写权限 “Permission denied”这个错我至少见过二十次
⚠️ 警告: 千万别在生产环境裸奔。前置检查里最容易被忽略的是“目标服务器磁盘空间”。有一次我部署大包,服务器还剩200M,结果mv到一半报错了,服务直接挂掉。从那以后我每次部署前都会先跑 df -h。

4.2 构建与打包

手动部署不代表手动编译。我一般用Maven或npm在开发机上打好包,然后传到生产服务器。

这里有个细节:构建命令要和生产环境对齐。比如Java项目,开发机用JDK 11,生产是JDK 8,打出来的包可能不兼容。我建议直接用生产环境的JDK版本在CI机上打包,如果没有CI机,就在生产环境旁边搭个构建沙箱。

构建示例:

# Maven 构建(跳过测试以节省时间,但前提是测试已提前跑过)
mvn clean package -DskipTests -Pprod

# npm 构建
npm install --production
npm run build

# 打包为tar(便于传输)
tar -czf app-v2.1.0.tar.gz target/*.jar config/ scripts/
💡 提示: 打包时把配置文件和启动脚本一起打进tar包里,这样在服务器上直接解压就能用,不用东拼西凑。我以前把配置文件单独上传,结果有一次漏传了,服务启动后一直报错找不到数据源。

4.3 传输与备份

把包传到服务器上,最常用的就是scp或rsync。我个人偏爱rsync,断点续传,还能保留文件属性。

先备份正在运行的老版本:

# 备份当前版本(假设应用目录在 /opt/myapp)
cp -r /opt/myapp /opt/myapp_backup_$(date +%Y%m%d_%H%M%S)

# 或者用 tar 打包备份(更省空间)
tar -czf /opt/backups/myapp_rollback_20250415.tar.gz /opt/myapp

然后上传新包:

# 本地执行:上传到服务器临时目录
rsync -avz -e ssh app-v2.1.0.tar.gz deployuser@prod-server:/tmp/
⚠️ 警告: 备份的时候注意磁盘空间!我曾在一个只剩3G的服务器上备份了2.5G的老版本,结果备份成功后剩下的空间不够解压新包,搞得进退两难。建议备份前先 du -sh /opt/myapp 看看大小。

4.4 停止服务与部署

这一步要小心翼翼。我见过的坑大多出在“停止服务”和“替换文件”之间的窗口期。

标准操作流程:

  1. 优雅停服:不要直接kill -9,要发SIGTERM让进程处理完当前请求。
  2. 确认进程已退出:用 ps aux | grep java 或者systemctl status看一眼。
  3. 替换文件:解压新包到目标目录,注意备份旧的配置文件(如果配置有变化)。
  4. 修改权限:确保新文件属于正确的用户和组。
# 优雅停服(以systemd为例)
sudo systemctl stop myapp

# 确认停止
sleep 3
if systemctl is-active myapp | grep -q "inactive"; then
    echo "服务已停止"
else
    echo "强制停止"
    sudo systemctl kill myapp
fi

# 解压新包到临时目录,再移动(避免覆盖一半出问题)
mkdir -p /tmp/myapp_new
tar -xzf /tmp/app-v2.1.0.tar.gz -C /tmp/myapp_new
mv /opt/myapp /opt/myapp_old   # 旧目录挪走,保留用于回滚
mv /tmp/myapp_new /opt/myapp

# 修正权限
sudo chown -R appuser:appgroup /opt/myapp
sudo chmod +x /opt/myapp/scripts/*.sh

🔄 关键技巧: 用“移动目录”的方式部署,而不是原地覆盖。这样如果新版本有问题,一秒就能把 myapp_old 移回来,回滚速度极快。我在电商大促前就靠这招成功规避了一次严重故障。

4.5 启动服务与健康检查

启动服务时别急着收工。我见过太多人启动完就走了,结果服务启动失败,日志里一堆错误,客户先发现。

我的检查三步法:

步骤 操作 预期结果
启动服务 sudo systemctl start myapp 启动成功,返回exit code 0
检查进程 ps aux | grep myapp 或 systemctl status myapp 进程运行中,状态显示 active (running)
功能验证 curl -I http://localhost:8080/health 返回 HTTP 200,响应时间正常
日志检查 tail -50 /var/log/myapp/startup.log 无 ERROR 或 Exception,正常打印启动完成

如果健康检查失败,别慌。先看日志,大概率是配置文件路径写错、数据库连不上、或者端口被占用。我曾经花了两小时找原因,最后发现是新版本把应用端口从8080改成了8081,而我还在用8080做curl。

4.6 回滚预案

手动部署最核心的保障就是回滚能力。记住一个原则:部署前准备好回滚,部署后至少保留一次回滚点。

我习惯在部署过程中保留三份快照:

  • 上一版本的应用目录(刚才移走的 myapp_old)
  • 上一版本的打包备份(app-v2.0.0.tar.gz)
  • 数据库快照(如果有结构变更,提前跑 mysqldump)

回滚命令很简单:

# 回滚应用
sudo systemctl stop myapp
mv /opt/myapp /opt/myapp_failed
mv /opt/myapp_old /opt/myapp
sudo systemctl start myapp

# 回滚数据库(如果有)
mysql -u root -p production_db < /opt/backups/db_20250415.sql
⚠️ 警告: 回滚数据库一定要谨慎!如果新版本有数据迁移,回滚老数据库可能导致数据不一致。我的经验是:如果在部署时同时变更了数据库,尽量不做回滚操作,而是打补丁修复,除非你能确认旧应用完全兼容新数据库结构。

4.7 验证与清理

部署完成并确认正常后,还需要做几件收尾工作:

  1. 功能冒烟测试:关键接口、登录、核心业务流程跑一遍。
  2. 资源监控:CPU、内存、磁盘IO是否正常,有没有内存泄漏迹象。
  3. 清理临时文件:删掉 /tmp/ 下的安装包和备份(保留至少一个回滚版本到下一轮部署)。
  4. 更新文档与日志:记录部署时间、版本号、变更内容、操作人,方便日后排查。

说实话,清理这一步很多人不做。直到有一次,服务器 /tmp 目录满了,导致后续部署失败,我才重视起来。

下面这张图概括了手动部署的完整流程,从代码到生产环境,每个环节都有对应的操作和检查点。

手动部署流程框架 代码仓库 (Git) 构建打包 (Maven/npm) 上传服务器 (scp/rsync) 备份旧版本 停止服务 (systemctl stop) 部署文件 (解压替换) 启动服务 + 健康检查 失败则回滚 ✅ 部署完成
📌 我的建议: 把这套手动流程写成脚本,下次部署时一个一个核对。我在团队里推行部署工单制度,把每一步做成Checklist,部署前必须逐项勾选。后来团队再也没出过因部署步骤遗漏导致的线上事故。