本文目录导读:

可靠、可重复、可回滚,一个好的发版脚本应该像流水线一样,将人为失误降到最低。
下面是一个通用的、生产级别的自动化发版脚本设计方案,涵盖了从代码检出到服务重启的全流程。
核心设计原则 (Design Principles)
- 幂等性:脚本多次执行,结果相同,比如第一次失败,修复后重跑不会造成冲突。
- 原子性:发版过程要么全成功,要么全失败,避免“半发布”状态。
- 最小影响:优先使用灰度发布或滚动更新,避免全量停服。
- 可观测性:每一步都有清晰的日志、耗时和状态输出。
- 安全性:敏感信息(密码、Token)从环境变量或密钥管理服务获取,不硬编码。
通用发版流程架构
一个标准的自动化发版脚本通常包含以下 5个阶段:
graph TD
A[开始] --> B{阶段1: 准备}
B --> C[阶段2: 构建]
C --> D[阶段3: 部署]
D --> E[阶段4: 验证]
E --> F[阶段5: 收尾]
F --> G[结束]
subgraph 准备
B1[代码Checkout] --> B2[版本号管理]
B2 --> B3[环境变量/配置注入]
end
subgraph 构建
C1[编译/打包 (如 mvn/gradle/npm/pip)] --> C2[单元测试/代码扫描]
C2 --> C3[构建产物归档 (如 tar.jar Docker Image)]
end
subgraph 部署
D1[备份当前版本] --> D2[传输新版本]
D2 --> D3[停止旧服务/容器]
D3 --> D4[启动新服务/容器]
end
subgraph 验证
E1[健康检查 (Health Check)] --> E2[接口冒烟测试 (Smoke Test)]
E2 --> E3[依赖服务连通性检查]
end
subgraph 收尾
F1[通知 (钉钉/飞书/邮件)] --> F2[清理旧备份]
F2 --> F3[更新发布记录]
end
脚本实现细节与示例 (Bash + Makefile)
我们将使用 Makefile 作为顶层调度器,配合 Bash 脚本 实现具体逻辑,这是 Linux 环境下最常见的组合。
目录结构设计
/opt/deploy/
├── deploy.sh # 主入口脚本
├── lib/
│ ├── utils.sh # 通用工具函数
│ ├── build.sh # 构建模块
│ └── deploy.sh # 部署模块
├── config/
│ ├── prod.env # 生产环境变量
│ └── test.env # 测试环境变量
└── logs/ # 发布日志
主入口脚本 deploy.sh
#!/bin/bash
set -euo pipefail # 关键:出错即停,未定义变量报错,管道错误捕获
# 加载配置
source "./config/${ENV:-prod}.env"
source "./lib/utils.sh"
# 全局变量
WORKSPACE=$(pwd)
RELEASE_ID=$(date +%Y%m%d%H%M%S) # 唯一ID,用于回滚
BACKUP_DIR="/data/backup/${PROJECT_NAME}/${RELEASE_ID}"
# 帮助信息
usage() {
echo "Usage: $0 {deploy|rollback|status} [version]"
exit 1
}
# 主函数
main() {
local action=${1:-deploy}
case $action in
deploy)
validate_env
log_info "开始发布 ${PROJECT_NAME} - ${RELEASE_ID}"
prepare
build
deploy_app
validate
notify
;;
rollback)
rollback_version=${2:-$(find /data/backup/${PROJECT_NAME} -maxdepth 1 -type d | tail -2 | head -1)}
rollback $rollback_version
;;
status)
health_check
;;
*)
usage
;;
esac
}
# 执行
main "$@"
各模块实现 lib/deploy.sh
#!/bin/bash
deploy_app() {
log_info "开始部署..."
# Step 1: 备份当前版本
mkdir -p $BACKUP_DIR
cp -r /data/app/${PROJECT_NAME} $BACKUP_DIR/current_bak
log_info "备份完成: $BACKUP_DIR"
# Step 2: 停服务 (优雅关闭)
systemctl stop ${PROJECT_NAME}.service || true
sleep 5
if systemctl is-active --quiet ${PROJECT_NAME}.service; then
log_error "服务无法停止,强制终止"
systemctl kill ${PROJECT_NAME}.service
fi
# Step 3: 更新文件
rsync -a --delete /tmp/build/${PROJECT_NAME}/ /data/app/${PROJECT_NAME}/
# Step 4: 更新配置 (从KMS/Consul获取,不硬编码)
update_config_from_vault
# Step 5: 启动服务
systemctl daemon-reload
systemctl start ${PROJECT_NAME}.service
log_info "部署完成,服务已启动"
}
rollback() {
local version=$1
log_info "开始回滚至: $version/current_bak"
systemctl stop ${PROJECT_NAME}.service || true
rsync -a --delete $version/current_bak/ /data/app/${PROJECT_NAME}/
systemctl start ${PROJECT_NAME}.service
log_info "回滚完成"
}
验证模块 lib/validate.sh
#!/bin/bash
validate() {
local retries=0
local max_retries=12 # 最大等待2分钟
local sleep_seconds=10
log_info "开始健康检查..."
# 等待端口监听
while [ $retries -lt $max_retries ]; do
if curl -sf http://127.0.0.1:${PORT}/health | grep -q 'OK'; then
log_success "健康检查通过"
break
fi
retries=$((retries+1))
sleep $sleep_seconds
done
if [ $retries -eq $max_retries ]; then
log_error "健康检查失败,启动自动回滚"
rollback
exit 1
fi
# 执行冒烟测试 (可选)
curl -s http://127.0.0.1:${PORT}/api/smoke && log_success "冒烟测试通过"
}
工具函数 lib/utils.sh
#!/bin/bash
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1" >&2
}
log_success() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] ✔ $1"
}
validate_env() {
if [[ -z "${PROJECT_NAME:-}" || -z "${PORT:-}" ]]; then
log_error "环境变量缺失,请检查 config 文件"
exit 1
fi
}
不同技术栈的差异适配
Java (Spring Boot)
- 构建:
mvn clean package -DskipTests或gradle build - 产物:
*.jar - 部署: 直接
java -jar app.jar或 Docker 化。 - 注意: 需配置 JVM 参数、GC 日志等。
Node.js (Express/Nest)
- 构建:
npm ci --only=production && npm run build - 产物: 编译后的
dist/文件夹 +node_modules - 部署:
pm2 startOrReload ecosystem.config.js - 注意: Node 版本管理 (nvm),避免包缺失。
Python (Flask/Django)
- 构建:
pip install -r requirements.txt -t /tmp/deps - 产物: Python 代码 + 依赖包
- 部署: 使用 UWSGI/Gunicorn + Nginx,重启 WSGI 进程。
- 注意: 虚拟环境隔离。
静态前端 (Vue/React)
- 构建:
npm run build - 产物:
dist/下的index.html+js/css - 部署: 直接覆盖 Nginx/CDN 的静态文件目录。
- 注意: 利用版本号或 hash 文件名避免缓存问题。
Docker 容器化
- 构建:
docker build -t myapp:$RELEASE_ID . - 部署:
docker stack deploy/kubectl apply -f deployment.yaml - 注意: Kubernetes 中使用
imagePullPolicy: Always或指定 tag。
高级特性与最佳实践
灰度/金丝雀发布
在 deploy.sh 中使用 if 逻辑:
if [[ "${ENV}" == "prod" && "${TRAFFIC}" == "10%" ]]; then
deploy_to_canary
sleep 300 # 观察5分钟
deploy_to_rest
fi
安全与配置管理
- 使用 Vault / AWS Secrets Manager 获取数据库密码。
- 使用 GitOps (ArgoCD/Flux) 管理 K8s YAML,避免 SSH 到服务器。
回滚机制
- 快照回滚:备份整个
/data/app/目录 + 数据库 Schema。 - 版本回滚:
./deploy.sh rollback [version tag]。 - 数据库回滚:执行对应的
Vxxx__rollback.sql脚本。
通知与告警
在 notify() 函数中集成 Webhook:
notify() {
curl -X POST -H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"发版成功: '$PROJECT_NAME' '$RELEASE_ID'"}}' \
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
}
CI/CD 集成
在 Jenkinsfile / GitLab CI 中调用脚本:
stages:
- build
- deploy
deploy-prod:
stage: deploy
script:
- scp deploy.sh user@prod-server:/opt/deploy/
- ssh user@prod-server "cd /opt/deploy && bash deploy.sh deploy"
only:
- tags
脚本设计检查清单
- [ ] 环境分离: 测试、预发、生产使用不同配置。
- [ ] 错误处理:
set -e,任何一步失败都中止并发送告警。 - [ ] 备份: 部署前必须备份当前可运行版本。
- [ ] 验证: 健康检查 + 冒烟测试,通过后才视为成功。
- [ ] 回滚: 一键回滚到上一版本,且支持指定历史版本。
- [ ] 审计: 每次发版记录日志到文件,包含开始时间、结束时间、版本号、操作人。
- [ ] 幂等性: 多次执行结果一致,不会产生副作用。
- [ ] 无锁: 避免同一时刻多人并发发版(可以通过 Jenkins 队列或 GitLab CI 锁)。
这个框架可以根据你的具体技术栈(Java、Node、Docker、K8s)调整构建和部署模块,但核心流程(准备->构建->部署->验证->收尾)是不变的。