4、手动部署流程:从代码到生产环境的手动步骤
说实话,现在CI/CD遍地都是,但手动部署这个手艺不能丢。我在老东家接手过一个金融系统,生产环境根本不能用自动流水线,全靠人工一步一步来。你想想看,如果连手动部署都搞不明白,自动化工具出了问题你连修都无从下手。
这一章我就把手动部署的标准流程掰开揉碎讲清楚。每个步骤我都会结合自己的踩坑经历,帮你避开那些让人抓狂的雷区。
4.1 前置检查:代码就绪与依赖确认
嗯,这一步很多人会跳过。我刚开始带项目时也这样,结果有一次打包后发现少了个配置文件,只能临时改,搞得很狼狈。
我个人习惯在部署前走一遍这个检查清单:
| 检查项 | 具体内容 | 我的经验 |
|---|---|---|
| 代码版本 | 确认git分支/tag与预期一致 | 打tag时写清楚版本号+日期,比如v2.1.0-20250415 |
| 依赖包 | pom.xml或package.json是否锁定版本 | 锁定版本能避免“昨天还能跑,今天炸了”的灵异事件 |
| 环境变量 | dev/staging/prod的配置差异 | 我曾把测试环境数据库地址带到生产,幸好回滚快 |
| 数据库迁移 | 是否有DDL/DML脚本需要先执行 | 生产库迁移一定要备份,最好先在预发环境跑一遍 |
| 权限检查 | 部署账号是否有目标目录写权限 | “Permission denied”这个错我至少见过二十次 |
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/
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/
du -sh /opt/myapp 看看大小。
4.4 停止服务与部署
这一步要小心翼翼。我见过的坑大多出在“停止服务”和“替换文件”之间的窗口期。
标准操作流程:
- 优雅停服:不要直接kill -9,要发SIGTERM让进程处理完当前请求。
- 确认进程已退出:用
ps aux | grep java或者systemctl status看一眼。 - 替换文件:解压新包到目标目录,注意备份旧的配置文件(如果配置有变化)。
- 修改权限:确保新文件属于正确的用户和组。
# 优雅停服(以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 验证与清理
部署完成并确认正常后,还需要做几件收尾工作:
- 功能冒烟测试:关键接口、登录、核心业务流程跑一遍。
- 资源监控:CPU、内存、磁盘IO是否正常,有没有内存泄漏迹象。
- 清理临时文件:删掉
/tmp/下的安装包和备份(保留至少一个回滚版本到下一轮部署)。 - 更新文档与日志:记录部署时间、版本号、变更内容、操作人,方便日后排查。
说实话,清理这一步很多人不做。直到有一次,服务器 /tmp 目录满了,导致后续部署失败,我才重视起来。
下面这张图概括了手动部署的完整流程,从代码到生产环境,每个环节都有对应的操作和检查点。