5.8.7 发布与回滚自动化脚本
发布与回滚自动化脚本#
目标与适用场景#
- 目标:标准化发布流程、降低人为失误、支持快速回滚与可追溯。
- 适用:单机部署、滚动发布、蓝绿/灰度发布、配置与二进制更新。
发布与回滚原理草图#
目录规范与变量约定#
/opt/app/releases/存放版本目录/opt/app/current指向当前版本/opt/app/shared/存放持久化配置与日志- 关键变量:
APP_NAME、APP_HOME、RELEASES_DIR、CURRENT_LINK、BACKUP_DIR
环境准备与示例服务安装#
示例以 systemd 服务为例,先准备目录与配置:
sudo useradd -r -s /sbin/nologin demo || true
sudo mkdir -p /opt/demo/{releases,shared/config,backup,logs}
sudo chown -R demo:demo /opt/demo
示例 systemd 单元文件(/etc/systemd/system/demo.service):
[Unit]
Description=Demo Service
After=network.target
[Service]
User=demo
Group=demo
WorkingDirectory=/opt/demo/current
ExecStart=/opt/demo/current/bin/demo --config /opt/demo/shared/config/app.yaml
Restart=on-failure
Environment=APP_ENV=prod
[Install]
WantedBy=multi-user.target
启用服务:
sudo systemctl daemon-reload
sudo systemctl enable demo.service
发布流程设计与关键命令解释#
- 前置检查:
df -h检查磁盘、ss -lntp检查端口、id检查权限。 - 获取版本:从制品库/镜像仓库拉取,
sha256sum校验。 - 备份现网:
tar -czf备份当前版本目录或配置。 - 部署更新:解压到新版本目录,配置使用共享目录软链。
- 健康验证:HTTP 探针、进程状态、日志关键字检查。
- 记录发布:写入发布记录文件,记录版本、时间、操作人。
关键命令解释示例:
# 解压制品到新版本目录
tar -xzf demo-1.2.3.tar.gz -C /opt/demo/releases/20240101010101
# 软链接切换到新版本(原子替换)
ln -sfn /opt/demo/releases/20240101010101 /opt/demo/current
# 健康检查:HTTP 200 为成功
curl -fsS http://127.0.0.1:8080/healthz
样例脚本:发布脚本(deploy.sh)#
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="demo"
APP_HOME="/opt/${APP_NAME}"
RELEASES_DIR="${APP_HOME}/releases"
CURRENT_LINK="${APP_HOME}/current"
SHARED_DIR="${APP_HOME}/shared"
BACKUP_DIR="${APP_HOME}/backup"
PACKAGE="$1" # 传入制品包路径
RELEASE_ID="$(date +%Y%m%d%H%M%S)"
HEALTH_URL="http://127.0.0.1:8080/healthz"
LOG_FILE="${APP_HOME}/logs/deploy.log"
log() { echo "$(date '+%F %T') $*" | tee -a "$LOG_FILE"; }
precheck() {
[[ -f "$PACKAGE" ]] || { log "Package not found"; exit 1; }
mkdir -p "$RELEASES_DIR" "$SHARED_DIR/config" "$BACKUP_DIR" "${APP_HOME}/logs"
df -h "$APP_HOME" | awk 'NR==2{print "Disk:",$5}' | tee -a "$LOG_FILE"
ss -lntp | grep -q ":8080" || log "Port 8080 not in LISTEN (may start after deploy)"
}
verify_package() {
if [[ -f "${PACKAGE}.sha256" ]]; then
sha256sum -c "${PACKAGE}.sha256"
else
log "No checksum file, skip verify"
fi
}
backup_current() {
if [[ -L "$CURRENT_LINK" ]]; then
local cur_ver
cur_ver="$(readlink -f "$CURRENT_LINK")"
tar -czf "${BACKUP_DIR}/backup_${RELEASE_ID}.tar.gz" -C "$cur_ver" .
log "Backup created: ${BACKUP_DIR}/backup_${RELEASE_ID}.tar.gz"
fi
}
deploy_release() {
local target="${RELEASES_DIR}/${RELEASE_ID}"
mkdir -p "$target"
tar -xzf "$PACKAGE" -C "$target"
ln -sfn "$SHARED_DIR/config" "$target/config"
log "Release unpacked: $target"
}
switch_current() {
ln -sfn "${RELEASES_DIR}/${RELEASE_ID}" "$CURRENT_LINK"
log "Switched current -> ${RELEASE_ID}"
}
restart_service() {
systemctl restart "${APP_NAME}.service"
systemctl is-active --quiet "${APP_NAME}.service" || { log "Service failed"; exit 1; }
}
health_check() {
local try=0
local max=5
until curl -fsS "$HEALTH_URL" >/dev/null; do
try=$((try+1))
[[ $try -ge $max ]] && return 1
sleep 2
done
}
record_release() {
echo "${RELEASE_ID},$(date '+%F %T'),$(whoami),deploy" >> "${APP_HOME}/release.log"
}
rollback_latest() {
local last_release
last_release="$(ls -1 "${RELEASES_DIR}" | sort | tail -n 2 | head -n 1)"
[[ -n "$last_release" ]] || { log "No previous release"; exit 1; }
ln -sfn "${RELEASES_DIR}/${last_release}" "$CURRENT_LINK"
systemctl restart "${APP_NAME}.service"
log "Rollback to ${last_release}"
}
main() {
precheck
verify_package
backup_current
deploy_release
switch_current
restart_service
if health_check; then
record_release
log "Deploy success: ${RELEASE_ID}"
else
log "Health check failed, rollback"
rollback_latest
exit 1
fi
}
main "$@"
样例脚本:回滚脚本(rollback.sh)#
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="demo"
APP_HOME="/opt/${APP_NAME}"
RELEASES_DIR="${APP_HOME}/releases"
CURRENT_LINK="${APP_HOME}/current"
TARGET_VERSION="${1:-}"
if [[ -z "$TARGET_VERSION" ]]; then
echo "Usage: $0 <release_id>"
exit 1
fi
target="${RELEASES_DIR}/${TARGET_VERSION}"
[[ -d "$target" ]] || { echo "Target release not found: $target"; exit 1; }
ln -sfn "$target" "$CURRENT_LINK"
systemctl restart "${APP_NAME}.service"
systemctl is-active --quiet "${APP_NAME}.service" || { echo "Service not active"; exit 1; }
echo "Rollback to ${TARGET_VERSION} success"
健康检查与回滚联动(示例)#
# 模拟发布后检查
curl -fsS http://127.0.0.1:8080/healthz && echo "OK" || echo "FAIL"
# 若失败,执行回滚
./rollback.sh 20231231235959
排错清单与常用命令#
- 服务无法启动:
systemctl status demo.service -l、journalctl -u demo.service -n 200 - 端口未监听:
ss -lntp | grep 8080 - 配置丢失:检查
/opt/demo/shared/config是否存在 - 软链错误:
readlink -f /opt/demo/current
练习#
- 将制品包改名为
demo-1.0.0.tar.gz,补充对应.sha256,验证校验失败与成功的输出差异。 - 手动篡改
healthz返回码,观察自动回滚触发。 - 删除
/opt/demo/shared/config,重新发布,修复脚本使其自动创建配置目录。
安全与可靠性#
- 使用非 root 账户执行,必要时通过
sudo授权重启服务。 - 版本包签名校验与校验和验证。
- 幂等性:重复执行不破坏现有发布,软链切换原子操作。