5.8.7 发布与回滚自动化脚本

发布与回滚自动化脚本#

目标与适用场景#

  • 目标:标准化发布流程、降低人为失误、支持快速回滚与可追溯。
  • 适用:单机部署、滚动发布、蓝绿/灰度发布、配置与二进制更新。

发布与回滚原理草图#

文章图片

目录规范与变量约定#

  • /opt/app/releases/ 存放版本目录
  • /opt/app/current 指向当前版本
  • /opt/app/shared/ 存放持久化配置与日志
  • 关键变量:APP_NAMEAPP_HOMERELEASES_DIRCURRENT_LINKBACKUP_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

发布流程设计与关键命令解释#

  1. 前置检查:df -h 检查磁盘、ss -lntp 检查端口、id 检查权限。
  2. 获取版本:从制品库/镜像仓库拉取,sha256sum 校验。
  3. 备份现网:tar -czf 备份当前版本目录或配置。
  4. 部署更新:解压到新版本目录,配置使用共享目录软链。
  5. 健康验证:HTTP 探针、进程状态、日志关键字检查。
  6. 记录发布:写入发布记录文件,记录版本、时间、操作人。

关键命令解释示例:

# 解压制品到新版本目录
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 -ljournalctl -u demo.service -n 200
  • 端口未监听:ss -lntp | grep 8080
  • 配置丢失:检查 /opt/demo/shared/config 是否存在
  • 软链错误:readlink -f /opt/demo/current

练习#

  1. 将制品包改名为 demo-1.0.0.tar.gz,补充对应 .sha256,验证校验失败与成功的输出差异。
  2. 手动篡改 healthz 返回码,观察自动回滚触发。
  3. 删除 /opt/demo/shared/config,重新发布,修复脚本使其自动创建配置目录。

安全与可靠性#

  • 使用非 root 账户执行,必要时通过 sudo 授权重启服务。
  • 版本包签名校验与校验和验证。
  • 幂等性:重复执行不破坏现有发布,软链切换原子操作。