本文目录导读:

在Shell脚本中实现安全回滚策略的核心是事务性操作和状态管理,以下是一个完整的多级回滚框架:
基础回滚结构
#!/bin/bash
set -euo pipefail # 严格模式
# 日志与状态文件
LOG_FILE="/var/log/deploy.log"
STATE_FILE="/tmp/deploy_state"
ROLLBACK_DIR="/tmp/rollback_backup"
# 初始化回滚环境
init_rollback() {
mkdir -p "$ROLLBACK_DIR"
echo "0" > "$STATE_FILE" # 0=初始, 1=进行中, 2=完成
}
# 记录回滚操作
record_operation() {
local operation="$1"
local backup_file="$2"
echo "$operation:$backup_file" >> "$ROLLBACK_DIR/manifest.txt"
}
# 执行回滚
rollback() {
echo "[ROLLBACK] 开始回滚..."
local status=$(cat "$STATE_FILE")
if [[ "$status" == "0" ]]; then
echo "[ROLLBACK] 无操作可回滚"
return 0
fi
# 反向读取manifest.txt执行回滚
tac "$ROLLBACK_DIR/manifest.txt" | while IFS=':' read -r op backup; do
case "$op" in
"backup_file")
cp "$backup" "${backup%.bak}"
echo "[ROLLBACK] 恢复文件: ${backup%.bak}"
;;
"backup_dir")
rm -rf "${backup%.bak}"
cp -r "$backup" "${backup%.bak}"
echo "[ROLLBACK] 恢复目录: ${backup%.bak}"
;;
"execute_cmd")
eval "$backup" # 逆操作命令
echo "[ROLLBACK] 执行逆操作: $backup"
;;
esac
done
echo "0" > "$STATE_FILE"
echo "[ROLLBACK] 回滚完成"
}
带检查点的安全部署
#!/bin/bash
# 检查点管理
CHECKPOINT_DIR="/tmp/checkpoints"
init_checkpoints() {
rm -rf "$CHECKPOINT_DIR"
mkdir -p "$CHECKPOINT_DIR"
}
save_checkpoint() {
local name="$1"
local timestamp=$(date +%s)
# 保存需要恢复的信息
cat > "$CHECKPOINT_DIR/${name}.ckpt" <<EOF
timestamp=$timestamp
description=$2
EOF
# 记录当前文件系统状态
find /var/www -type f -newer "$CHECKPOINT_DIR/start" \
> "$CHECKPOINT_DIR/${name}_files.txt" 2>/dev/null || true
}
restore_checkpoint() {
local name="$1"
if [[ ! -f "$CHECKPOINT_DIR/${name}.ckpt" ]]; then
echo "错误: 检查点 $name 不存在"
return 1
fi
# 恢复文件
while IFS= read -r file; do
if [[ -f "${file}.bak" ]]; then
cp "${file}.bak" "$file"
echo "恢复: $file"
fi
done < "$CHECKPOINT_DIR/${name}_files.txt"
# 执行自定义恢复脚本
if [[ -f "$CHECKPOINT_DIR/${name}_restore.sh" ]]; then
bash "$CHECKPOINT_DIR/${name}_restore.sh"
fi
}
# 示例部署函数
deploy_application() {
init_checkpoints
# 阶段1: 备份当前状态
echo "阶段1: 备份..."
cp -r /var/www/app /var/www/app.bak
save_checkpoint "backup" "备份原有应用"
# 阶段2: 停止服务
echo "阶段2: 停止服务..."
systemctl stop nginx
save_checkpoint "stop_nginx" "停止Nginx"
# 阶段3: 更新代码
echo "阶段3: 更新代码..."
cd /var/www/app
git pull origin main || {
echo "更新失败,触发回滚"
restore_checkpoint "stop_nginx"
restore_checkpoint "backup"
systemctl start nginx
exit 1
}
save_checkpoint "update_code" "更新代码"
# 阶段4: 数据库迁移
echo "阶段4: 数据库迁移..."
php artisan migrate --force || {
echo "数据库迁移失败,触发回滚"
restore_checkpoint "update_code"
restore_checkpoint "stop_nginx"
restore_checkpoint "backup"
systemctl start nginx
exit 1
}
save_checkpoint "migrate" "数据库迁移"
# 阶段5: 启动服务并验证
echo "阶段5: 启动服务..."
systemctl start nginx
# 健康检查
for i in {1..10}; do
if curl -f http://localhost/health; then
echo "部署成功!"
save_checkpoint "success" "部署成功"
return 0
fi
sleep 3
done
# 健康检查失败
echo "健康检查失败,自动回滚"
restore_checkpoint "migrate"
restore_checkpoint "update_code"
restore_checkpoint "stop_nginx"
restore_checkpoint "backup"
systemctl start nginx
return 1
}
高级回滚:版本化目录结构
#!/bin/bash
# 版本化部署目录
DEPLOY_DIR="/opt/app"
VERSIONS_DIR="${DEPLOY_DIR}/versions"
CURRENT_LINK="${DEPLOY_DIR}/current"
ROLLBACK_COUNT=5 # 保留5个历史版本
# 创建新版本
create_version() {
local version="v$(date +%Y%m%d_%H%M%S)"
local target="${VERSIONS_DIR}/${version}"
mkdir -p "$target"
echo "$version" > "${target}/.version"
# 复制当前版本
if [[ -L "$CURRENT_LINK" ]]; then
cp -r "$(readlink -f $CURRENT_LINK)/." "$target/"
fi
echo "$version"
}
# 部署新版本
deploy_version() {
local version="$1"
local target="${VERSIONS_DIR}/${version}"
# 原子切换
ln -sfn "$target" "${CURRENT_LINK}.tmp"
mv -T "${CURRENT_LINK}.tmp" "$CURRENT_LINK"
# 清理旧版本
cleanup_old_versions
}
# 回滚到指定版本
rollback_to_version() {
local version="$1"
local target="${VERSIONS_DIR}/${version}"
if [[ ! -d "$target" ]]; then
echo "版本 $version 不存在"
return 1
fi
# 原子切换
ln -sfn "$target" "${CURRENT_LINK}.tmp"
mv -T "${CURRENT_LINK}.tmp" "$CURRENT_LINK"
echo "已回滚到版本: $version"
}
# 清理旧版本
cleanup_old_versions() {
local versions=($(ls -1t "$VERSIONS_DIR" 2>/dev/null))
local count=${#versions[@]}
if (( count > ROLLBACK_COUNT )); then
local remove_count=$(( count - ROLLBACK_COUNT ))
for (( i=ROLLBACK_COUNT; i<count; i++ )); do
rm -rf "${VERSIONS_DIR}/${versions[i]}"
echo "删除旧版本: ${versions[i]}"
done
fi
}
# 完整部署流程
safe_deploy() {
echo "=== 开始安全部署 ==="
# 1. 创建新版本
local version=$(create_version)
echo "创建版本: $version"
# 2. 部署代码到新版本目录
cd "${VERSIONS_DIR}/${version}"
git clone --depth=1 https://github.com/your/repo.git . || {
echo "代码下载失败"
rm -rf "${VERSIONS_DIR}/${version}"
exit 1
}
# 3. 运行测试
if ! make test; then
echo "测试失败,自动清理"
rm -rf "${VERSIONS_DIR}/${version}"
exit 1
fi
# 4. 创建配置文件备份
if [[ -L "$CURRENT_LINK" ]]; then
cp "$(readlink -f $CURRENT_LINK)/.env" "${VERSIONS_DIR}/${version}/.env"
fi
# 5. 执行原子切换
deploy_version "$version"
# 6. 健康检查
sleep 5
if curl -f http://localhost; then
echo "部署成功!当前版本: $version"
else
echo "健康检查失败,启动回滚..."
local prev_version=$(ls -1t "$VERSIONS_DIR" | sed -n '2p')
if [[ -n "$prev_version" ]]; then
rollback_to_version "$prev_version"
echo "已回滚到版本: $prev_version"
fi
fi
echo "=== 部署完成 ==="
}
信号处理和异常退出
#!/bin/bash
# 全局回滚标志
ROLLBACK_REQUIRED=false
CURRENT_STEP=""
# 信号处理
cleanup() {
echo ""
echo "收到中断信号!"
if $ROLLBACK_REQUIRED; then
echo "执行紧急回滚..."
rollback_to_last_checkpoint
fi
exit 1
}
trap cleanup SIGINT SIGTERM SIGHUP
# 步骤执行器
run_step() {
local step_name="$1"
local step_function="$2"
CURRENT_STEP="$step_name"
echo "[步骤] $step_name"
# 执行前保存状态
save_step_state "$step_name"
# 执行步骤
if ! $step_function; then
echo "[错误] 步骤 $step_name 失败"
ROLLBACK_REQUIRED=true
rollback_to_step "$step_name"
return 1
fi
echo "[完成] $step_name"
return 0
}
# 主部署流程
main() {
# 初始化
init_rollback_environment
ROLLBACK_REQUIRED=true
# 执行各步骤
run_step "备份数据库" backup_database || exit 1
run_step "更新代码" update_code || exit 1
run_step "安装依赖" install_dependencies || exit 1
run_step "数据库迁移" run_migrations || exit 1
run_step "清理缓存" clear_cache || exit 1
# 全部成功
ROLLBACK_REQUIRED=false
echo "部署成功!"
}
实用回滚工具函数
#!/bin/bash
# 文件级别回滚函数
backup_file() {
local file="$1"
if [[ -f "$file" ]]; then
cp "$file" "${file}.rollback.$(date +%s)"
echo "备份: $file"
fi
}
restore_file() {
local file="$1"
local backup=$(ls -t "${file}.rollback."* 2>/dev/null | head -1)
if [[ -n "$backup" ]]; then
cp "$backup" "$file"
echo "恢复: $file"
fi
}
# 数据库回滚
backup_database() {
local db_name="$1"
local backup_file="/tmp/${db_name}_$(date +%s).sql"
mysqldump "$db_name" > "$backup_file"
echo "$backup_file"
}
restore_database() {
local backup_file="$1"
local db_name="$2"
mysql "$db_name" < "$backup_file"
echo "数据库恢复完成"
}
# 配置管理
save_config() {
local config_file="$1"
if [[ -f "$config_file" ]]; then
cp "$config_file" "${config_file}.prev"
fi
}
restore_config() {
local config_file="$1"
if [[ -f "${config_file}.prev" ]]; then
cp "${config_file}.prev" "$config_file"
rm -f "${config_file}.prev"
fi
}
使用建议
- 始终备份原始状态:在执行任何修改前创建完整备份
- 使用原子操作:通过符号链接切换实现零停机部署
- 保留足够的历史:至少保留3-5个历史版本
- 自动验证:部署后自动进行健康检查
- 日志记录:详细记录所有操作和回滚过程
- 信号处理:处理中断信号,保证异常时也能回滚
这个框架可以根据具体需求调整,关键是保持操作的可逆性和原子性。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播