Shell脚本如何实现一键回滚策略

wen 实用脚本 26

本文目录导读:

Shell脚本如何实现一键回滚策略

  1. 通用思路
  2. 场景一:文件/目录级回滚(基于备份)
  3. 场景二:应用版本回滚(基于软链接)
  4. 通用安全措施
  5. 执行示例
  6. 增强建议

下面给出一个通用的一键回滚 Shell 脚本模板,以及两种常见场景:文件/目录回滚应用版本回滚(基于备份/软链接),可以根据实际部署方式选择或组合使用。

通用思路

  • 备份阶段:在更新前,将当前版本或配置文件备份到指定目录(带时间戳)。
  • 回滚阶段:检测备份目录,提供列表供选择,恢复对应备份。
  • 一键触发:通过 ./rollback.sh 执行,自动完成。

场景一:文件/目录级回滚(基于备份)

适用于更新了配置、静态文件等场景。

脚本示例:rollback_files.sh

#!/bin/bash
# 配置
TARGET_DIR="/var/www/html"          # 待回滚的目标目录
BACKUP_ROOT="/backups/html"         # 备份存放根目录
ROLLBACK_LOG="/var/log/rollback.log"
# 颜色输出
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
log() {
    echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$ROLLBACK_LOG"
}
error_exit() {
    log "${RED}错误:$1${NC}"
    exit 1
}
# 检查备份目录是否存在
if [ ! -d "$BACKUP_ROOT" ]; then
    error_exit "备份目录 $BACKUP_ROOT 不存在"
fi
# 列出可用备份(按最新排序)
echo "================= 可用备份列表 ================="
backups=($(ls -1t "$BACKUP_ROOT"))
if [ ${#backups[@]} -eq 0 ]; then
    error_exit "无可用备份"
fi
for i in "${!backups[@]}"; do
    backup_path="$BACKUP_ROOT/${backups[$i]}"
    backup_size=$(du -sh "$backup_path" 2>/dev/null | awk '{print $1}')
    echo "  [$((i+1))] ${backups[$i]}  (大小: ${backup_size:-未知})"
done
echo -e "\n请输入要回滚的编号 (1-${#backups[@]}),或输入 q 退出:"
read -r choice
if [[ "$choice" == "q" || "$choice" == "Q" ]]; then
    log "用户取消回滚"
    exit 0
fi
# 验证输入
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#backups[@]}" ]; then
    error_exit "无效选择"
fi
# 计算数组下标(从0开始)
idx=$((choice-1))
selected_backup="${backups[$idx]}"
restore_source="$BACKUP_ROOT/$selected_backup"
log "开始回滚 → 目标: $TARGET_DIR, 备份源: $restore_source"
# 确认
echo -e "${RED}警告:将覆盖 $TARGET_DIR 当前内容!${NC}"
echo -n "确认执行回滚?(yes/no): "
read -r confirm
if [ "$confirm" != "yes" ]; then
    log "用户取消回滚"
    exit 0
fi
# 2.1 方法A:直接覆盖(适合文件较多时用 rsync)
log "正在回滚文件..."
rsync -a --delete "$restore_source/" "$TARGET_DIR/" 2>&1 | tee -a "$ROLLBACK_LOG"
if [ $? -ne 0 ]; then
    error_exit "文件回滚失败"
fi
# 2.2 方法B(可选):先清空再复制
# rm -rf "$TARGET_DIR"/* && cp -ar "$restore_source"/* "$TARGET_DIR/"
log "${GREEN}回滚完成:从 $selected_backup 恢复到 $TARGET_DIR${NC}"

使用前需要:

  1. 提前在 ${BACKUP_ROOT} 下存放备份,20250307_120000_backup
  2. 更新脚本前先执行一次备份,类似:
    cp -a /var/www/html /backups/html/$(date +%Y%m%d_%H%M%S)_backup

场景二:应用版本回滚(基于软链接)

适用于使用 当前版本符号链接 切换的项目(如 Java jar、Node 应用)。

目录结构示例

/opt/myapp/
├── releases/
│   ├── v1.0.0/
│   ├── v1.1.0/
│   └── v1.2.0/
└── current -> releases/v1.2.0/

脚本示例:rollback_app.sh

#!/bin/bash
APP_NAME="myapp"
APP_HOME="/opt/$APP_NAME"
RELEASES_DIR="$APP_HOME/releases"
CURRENT_LINK="$APP_HOME/current"
SERVICE_NAME="myapp.service"
LOG_FILE="/var/log/rollback_app.log"
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
error_exit() {
    log "ERROR: $1"
    exit 1
}
# 检查 releases 目录
if [ ! -d "$RELEASES_DIR" ]; then
    error_exit "版本目录 $RELEASES_DIR 不存在"
fi
# 获取所有版本并按时间倒序
versions=($(ls -1t "$RELEASES_DIR"))
if [ ${#versions[@]} -eq 0 ]; then
    error_exit "无可用版本用于回滚"
fi
echo "================= 可回滚版本列表 ================="
echo "当前版本: $(readlink "$CURRENT_LINK" 2>/dev/null || echo '无')"
echo "------------------------------------------------"
for i in "${!versions[@]}"; do
    ver_dir="$RELEASES_DIR/${versions[$i]}"
    # 检查版本目录是否有效
    [ -d "$ver_dir" ] || continue
    deploy_time=$(stat -c "%y" "$ver_dir" | cut -d'.' -f1)
    # 标记当前版本
    current_flag=""
    if [ "$(readlink -f "$CURRENT_LINK")" = "$(readlink -f "$ver_dir")" ]; then
        current_flag=" ← 当前"
    fi
    echo "  [$((i+1))] ${versions[$i]}  (部署于: $deploy_time)$current_flag"
done
echo -e "\n请输入要回滚到的版本编号 (1-${#versions[@]}):"
read -r choice
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#versions[@]}" ]; then
    error_exit "无效选择"
fi
idx=$((choice-1))
target_version="${versions[$idx]}"
target_dir="$RELEASES_DIR/$target_version"
if [ ! -d "$target_dir" ]; then
    error_exit "目标版本目录 $target_dir 不存在"
fi
# 确认
echo -e "将切换到版本:$target_version"
echo -n "确认回滚?(yes/no): "
read -r confirm
if [ "$confirm" != "yes" ]; then
    log "用户取消回滚"
    exit 0
fi
# 停止服务(可选)
log "停止服务 $SERVICE_NAME ..."
systemctl stop "$SERVICE_NAME" 2>/dev/null || echo "无systemctl,跳过停止"
# 切换软链接
log "切换软链接: $CURRENT_LINK -> $target_dir"
rm -f "$CURRENT_LINK"
ln -s "$target_dir" "$CURRENT_LINK"
if [ $? -ne 0 ]; then
    error_exit "软链接切换失败"
fi
# 启动服务
log "启动服务 $SERVICE_NAME ..."
systemctl start "$SERVICE_NAME" 2>/dev/null || \
    (echo "跳过 systemctl, 尝试手动启动..." && \
     cd "$target_dir" && nohup ./start.sh > /dev/null 2>&1 &)
# 简单健康检查(等待3秒)
sleep 3
if pgrep -f "$APP_NAME" > /dev/null 2>&1; then
    log "${GREEN}回滚成功:已切换到 $target_version${NC}"
else
    log "${RED}警告:服务可能未正常启动,请手动检查${NC}"
fi

使用前准备:

  • 每次发布新版本时,将文件放入 releases/v1.x.x/ 目录。
  • 更新 current 软链接指向新版本。
  • 脚本支持 systemd 或自定义启动。

通用安全措施

# 在脚本开头建议添加:
set -euo pipefail   # 任何错误立即退出
# 或
set -e              # 遇到错误退出

执行示例

# 赋予执行权限
chmod +x rollback_files.sh
# 执行回滚
sudo ./rollback_files.sh

输出示例:

================= 可用备份列表 =================
  [1] 20250307_120000_backup  (大小: 45M)
  [2] 20250306_080000_backup  (大小: 44M)
请输入要回滚的编号 (1-2),或输入 q 退出:
1
警告:将覆盖 /var/www/html 当前内容!
确认执行回滚?(yes/no): yes
[2025-03-07 14:20:00] 正在回滚文件...
[2025-03-07 14:20:05] 回滚完成:从 20250307_120000_backup 恢复到 /var/www/html

增强建议

  • 日志回滚:保留最近N次回滚日志。
  • 邮件/钉钉通知:回滚完成后可以通知团队。
  • 回滚前快照:回滚前先对当前错误版本做一次备份(防止丢失)。
  • 并发锁flock 防止同时执行。

如果有具体部署环境(Kubernetes、Docker、Ansible等),回滚策略会不同,可以进一步补充。

抱歉,评论功能暂时关闭!