本文目录导读:

- 核心原则:脚本必须支持“可逆操作”
- 场景一:数据库脚本(最常见)
- 场景二:文件/配置修改脚本
- 场景三:自动化运维脚本(Ansible, SaltStack)
- 批量回滚的常见陷阱与注意事项
- 一个简单的“双脚本”模式(适合新手)
对于实用脚本的批量回滚,答案是肯定的,但这完全取决于脚本本身的设计是否支持幂等性、版本控制和异常处理。
“能否回滚”不在于“脚本”这个载体,而在于“脚本里写了什么”。
下面我将从几个常见的场景出发,说明如何实现以及需要注意的风险。
核心原则:脚本必须支持“可逆操作”
一个可回滚的脚本,通常需要包含两部分逻辑:
- 正向逻辑(部署/修改):执行主要任务。
- 逆向逻辑(回滚/恢复):撤销正向操作,恢复到执行前的状态。
数据库脚本(最常见)
这是最需要批量回滚能力的场景,如果一个 SQL 脚本批量修改了大量数据,通常依靠事务和备份表。
如何实现:
- 开启事务:在脚本开头使用
BEGIN transaction;,如果中间任何一步失败,执行ROLLBACK;即可。 - 备份旧数据:在修改前,将受影响的数据插入到一张备份表中(
table_backup_20231027)。 - 提供回滚文件:提供一个独立的
rollback.sql文件,内容就是反向操作。- 正向:
UPDATE users SET status = 1 WHERE id IN (1,2,3); - 回滚:
UPDATE users SET status = 0 WHERE id IN (1,2,3);- 注意:这种回滚要求你在执行正向脚本时,必须记录下修改了哪些ID。
- 正向:
批量回滚方式:
# 假设你有一个回滚脚本目录 mysql -h host -u user -p database < ./rollback/rollback_20231027.sql
或者,将回滚命令集成到同一个脚本中,通过参数控制:
# 假设脚本支持 --rollback 参数 ./deploy_database.sh --rollback
文件/配置修改脚本
对于修改配置文件、替换二进制文件等操作,回滚通常依赖版本控制(Git)或文件快照。
如何实现:
- 使用 Git 管理:脚本在修改前,先
git add .并git commit一个快照,回滚时直接git reset --hard <commit_hash>。 - 备份原文件:脚本在复制新文件之前,先将原文件重命名或移动到备份目录。
- 正向:
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d)然后替换为新配置。 - 回滚:
cp /etc/nginx/nginx.conf.bak.20231027 /etc/nginx/nginx.conf
- 正向:
批量回滚方式:
# 遍历所有备份文件,恢复原位置
for backup_file in $(find /etc/nginx/ -name "*.bak.20231027"); do
original_file=$(echo $backup_file | sed 's/.bak.[0-9]*//')
cp "$backup_file" "$original_file"
done
echo "批量回滚完成,请重启服务"
自动化运维脚本(Ansible, SaltStack)
如果你使用的不是裸 shell 脚本,而是像 Ansible、SaltStack 这样的工具,它们天生就支持幂等性和回滚。
如何实现:
- 幂等性:如果某个 task 执行失败,Ansible 会直接报错,不会继续执行,你可以配置
ignore_errors: no,并在遇到错误时触发meta: end_play。 - 快照回滚:最彻底的方法,在云环境中,脚本在执行前先调用 API 对目标虚拟机或数据库创建快照。
- 正向:部署新代码。
- 回滚:
aws ec2 create-snapshot --volume-id vol-xxxx-> 如果出错 ->aws ec2 restore-snapshot --volume-id vol-xxxx --snapshot-id snap-xxx
批量回滚方式(利用工具自身):
# Ansible 剧本如果执行失败,可以用之前的 tag 重新部署旧版本 ansible-playbook -i inventory deploy.yml --tags "rollback_v1.2"
批量回滚的常见陷阱与注意事项
-
非原子性:脚本是对多个目标(100台机器、100万行数据)执行的,如果第50台机器失败了,前49台已经成功,无法自动撤销,你需要:
- 方案A:从第50台开始停止,手动处理前49台(不如方案B好)。
- 方案B:使用蓝绿部署或金丝雀发布,先改一小部分,验证通过后全面部署,回滚时整体切换流量即可,无需逐台操作。
-
依赖关系:回滚脚本可能会引入新的依赖问题,正向脚本修改了 A 表和 B 表,回滚脚本需要同时恢复 A 和 B,但可能 B 表的数据已经被其他进程修改,导致回滚失败。
-
日志记录至关重要:脚本在修改任何资源前,必须输出日志,记录修改的对象、原始值和目标值,批量回滚时,这些日志是你知道“该取消什么”的唯一依据。
-
小心“硬删除”:如果正向脚本执行了
rm -rf或DROP TABLE,而你没有预先备份,回滚将变得非常困难(甚至不可能)。建议: 脚本中永远不要直接删除,而是先移动到回收站(mv /app/code /app/code_old_20231027),直到确认新版本稳定后再清理。
一个简单的“双脚本”模式(适合新手)
如果你只是写一个简单的 shell 脚本做批量操作,可以按如下结构设计:
# deploy.sh (部署脚本)
#!/bin/bash
ACTION=${1:-deploy} # deploy 或 rollback
if [ "$ACTION" == "deploy" ]; then
echo "执行部署..."
# 1. 备份
cp -r /app/config /app/config.backup.$(date +%Y%m%d)
# 2. 修改
echo "new config" > /app/config/app.conf
echo "备份完成,可回滚时使用:$0 rollback $(date +%Y%m%d)"
elif [ "$ACTION" == "rollback" ]; then
ROLLBACK_DATE=$2 # 20231027
if [ -z "$ROLLBACK_DATE" ]; then
echo "请指定回滚日期,$0 rollback 20231027"
exit 1
fi
echo "执行回滚到日期 $ROLLBACK_DATE ..."
cp -r /app/config.backup.$ROLLBACK_DATE /app/config
echo "回滚完成,请重启服务"
fi
批量回滚命令:
# 对100台服务器批量执行
for ip in $(cat server_list.txt); do
ssh root@$ip "/path/to/deploy.sh rollback 20231027"
done
| 脚本类型 | 能否批量回滚 | 推荐实现方式 |
|---|---|---|
| shell脚本 | 可以,但需谨慎 | 备份原文件 + 编写对应的回滚脚本 |
| SQL脚本 | 可以,必须用事务 | BEGIN + ROLLBACK(单次)或 rollback.sql(批量) |
| Ansible/Playbook | 天生支持 | 利用幂等性、meta: end_play、tags 回滚 |
| Kubernetes (kubectl) | 天生支持 | kubectl rollout undo deployment/app |
| 云API脚本 | 可以,但开销大 | 执行前创建快照,失败后用快照恢复 |
核心建议:
- 如果可能,尽量使用成熟的工具(Ansible, K8s, 数据库迁移工具)来执行批量操作,它们内置了比裸脚本更好的回滚机制。
- 如果必须写裸脚本,务必在脚本开头增加
--dry-run参数(试运行),让它只打印“我会做什么”,而不实际执行,先在测试环境跑通回滚流程,再上生产。 - 永远不要在没有任何备份或快照的情况下,对一个重要系统执行不可逆的批量脚本操作。