为什么要备份?

2019年,我硬盘挂了,五年来的照片、文档、代码,全没了。那种感觉,就像世界塌了一样。

从那之后,我发誓再也不让这种事发生。现在我有3份备份:本地NAS、云盘、异地服务器。即使哪天硬盘炸了,我也能淡定地换一个继续用。


备份策略:3-2-1原则

3份数据:原始数据+2份备份
2种介质:硬盘+云盘(SSD+HDD也行)
1个异地:至少一份备份在不同地方

简单说:你的电脑 → 本地NAS → 云盘


实战1:文件备份脚本

需求

  • 每天凌晨3点备份 /home/user/documents
  • 保留最近7天的备份
  • 压缩节省空间

编写脚本

创建 /root/backup/backup.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

# ===== 配置 =====
SOURCE_DIR="/home/user/documents" # 要备份的目录
BACKUP_DIR="/backup/daily" # 备份存放目录
DAYS_TO_KEEP=7 # 保留天数
DATE=$(date +%Y%m%d_%H%M%S) # 当前时间
BACKUP_FILE="backup_${DATE}.tar.gz" # 备份文件名

# ===== 创建备份目录 =====
mkdir -p "$BACKUP_DIR"

# 压缩备份
echo "$(date) 开始备份 $SOURCE_DIR..."
tar -czf "${BACKUP_DIR}/${BACKUP_FILE}" \
--exclude='*.log' \
--exclude='*.tmp' \
--exclude='node_modules' \
"$SOURCE_DIR"

# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "$(date) 备份成功: ${BACKUP_DIR}/${BACKUP_FILE}"
else
echo "$(date) 备份失败!" >&2
exit 1
fi

# ===== 删除旧备份 =====
echo "$(date) 删除${DAYS_TO_KEEP}天前的备份..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$DAYS_TO_KEEP -delete

# ===== 统计信息 =====
BACKUP_SIZE=$(du -sh "${BACKUP_DIR}/${BACKUP_FILE}" | cut -f1)
echo "$(date) 备份大小: $BACKUP_SIZE"

echo "$(date) 备份完成"

添加执行权限:

1
chmod +x /root/backup/backup.sh

定时运行

1
2
3
4
crontab -e

# 添加这行(每天凌晨3点)
0 3 * * * /root/backup/backup.sh >> /var/log/backup.log 2>&1

手动测试

1
2
3
4
5
# 运行备份
/root/backup/backup.sh

# 查看备份
ls -lh /backup/daily/

实战2:MySQL数据库备份

编写脚本

创建 /root/backup/backup_mysql.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

# ===== 配置 =====
DB_USER="root"
DB_PASS="YourPassword"
DB_NAME="wordpressdb" # 要备份的数据库名,空则备份所有
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="mysql_${DATE}.sql.gz"

# ===== 创建目录 =====
mkdir -p "$BACKUP_DIR"

# ===== 备份单个数据库 =====
if [ -n "$DB_NAME" ]; then
echo "$(date) 备份数据库: $DB_NAME"
mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" \
| gzip > "${BACKUP_DIR}/${BACKUP_FILE}"
else
# 备份所有数据库
echo "$(date) 备份所有数据库"
mysqldump -u"$DB_USER" -p"$DB_PASS" --all-databases \
| gzip > "${BACKUP_DIR}/${BACKUP_FILE}"
fi

# ===== 检查备份 =====
if [ $? -eq 0 ]; then
echo "$(date) 备份成功: ${BACKUP_DIR}/${BACKUP_FILE}"
else
echo "$(date) 备份失败!" >&2
exit 1
fi

# ===== 删除7天前的备份 =====
find "$BACKUP_DIR" -name "mysql_*.sql.gz" -mtime +7 -delete

echo "$(date) 备份完成"

权限:

1
chmod +x /root/backup/backup_mysql.sh

定时:

1
2
3
4
crontab -e

# 每天凌晨2点备份MySQL
0 2 * * * /root/backup/backup_mysql.sh >> /var/log/backup_mysql.log 2>&1

实战3:上传到云盘(百度云/阿里云/腾讯云)

以阿里云OSS为例:

安装阿里云CLI

1
2
3
4
5
6
# Ubuntu
apt install -y ossutil

# 配置
ossutil config
# 按提示输入Endpoint、AccessKeyID、AccessKeySecret

修改备份脚本

在文件备份脚本的最后添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ===== 上传到云盘 =====
OSS_BUCKET="your-bucket-name"
OSS_PATH="backups/"

echo "$(date) 上传到阿里云OSS..."
ossutil cp "${BACKUP_DIR}/${BACKUP_FILE}" \
"oss://${OSS_BUCKET}/${OSS_PATH}${BACKUP_FILE}" -f

if [ $? -eq 0 ]; then
echo "$(date) 上传成功"
else
echo "$(date) 上传失败" >&2
fi

实战4:异地备份(SCP到另一台服务器)

配置SSH免密登录

1
2
3
4
5
# 在备份服务器上生成密钥
ssh-keygen -t rsa -b 4096

# 复制公钥到目标服务器
ssh-copy-id user@remote-server.com

修改脚本

在备份脚本添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ===== 拷贝到异地服务器 =====
REMOTE_USER="user"
REMOTE_HOST="remote-server.com"
REMOTE_DIR="/backup/backup-local"

echo "$(date) 拷贝到异地服务器..."
scp "${BACKUP_DIR}/${BACKUP_FILE}" \
"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"

if [ $? -eq 0 ]; then
echo "$(date) 异地备份成功"
else
echo "$(date) 异地备份失败" >&2
fi

实战5:Docker容器备份

备份所有容器

创建 /root/backup/backup_docker.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

BACKUP_DIR="/backup/docker"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

# ===== 备份Docker卷 =====
echo "$(date) 备份Docker数据卷..."
docker volume ls -q | while read volume; do
echo "备份卷: $volume"
docker run --rm -v "$volume":/data -v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/volume_${volume}_${DATE}.tar.gz" /data
done

# ===== 导出镜像(可选)=====
# echo "$(date) 导出Docker镜像..."
# docker save -o "$BACKUP_DIR/images_${DATE}.tar" nginx mysql wordpress

# ===== 删除30天前的备份 =====
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete

echo "$(date) Docker备份完成"

实战6:完整备份系统(全量镜像)

创建系统快照

谨慎使用,这是”核武器”级别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

DATE=$(date +%Y%m%d)
BACKUP_FILE="/backup/system_${DATE}.img"

# ===== 检查磁盘空间 =====
AVAILABLE=$(df -BG / | awk 'NR==2{print $4}' | tr -d 'G')
if [ "$AVAILABLE" -lt 20 ]; then
echo "磁盘空间不足(剩余${AVAILABLE}G),需要至少20G" >&2
exit 1
fi

# ===== 创建系统镜像 =====
echo "$(date) 开始备份整个系统(这可能需要很长时间)..."
dd if=/dev/sda of="$BACKUP_FILE" bs=4M status=progress

# 压缩
gzip "$BACKUP_FILE"

echo "$(date) 系统备份完成: ${BACKUP_FILE}.gz"

警告

  • 需要至少20G空闲空间
  • 不要在系统运行时做全量备份
  • 建议在维护窗口(凌晨)执行

脚本调试技巧

调试模式

在脚本开头添加:

1
#!/bin/bash -x    # -x显示执行的每条命令

日志记录

1
2
3
4
5
6
7
8
9
10
11
LOG_FILE="/var/log/backup.log"

# 所有输出都记录到日志
exec > >(tee -a "$LOG_FILE") 2>&1

# 或用函数记录
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

log "开始备份..."

错误处理

1
2
3
4
set -e  # 任何命令失败就退出

# 或
trap 'echo "备份失败!"' ERR

邮件通知

备份完成后发邮件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装mailutils
apt install -y mailutils

# 发送邮件
mail -s "备份完成" your@example.com << EOF
备份完成!

文件: ${BACKUP_FILE}
大小: $BACKUP_SIZE
时间: $(date)

详见日志: /var/log/backup.log
EOF

配合cron:

1
0 3 * * * /root/backup/backup.sh && echo "备份成功" | mail -s "备份通知" you@example.com

恢复测试(重要!)

备份后不测试,等于没备份。

测试文件恢复

1
2
3
4
5
# 解压备份
tar -xzf /backup/daily/backup_20240213_030001.tar.gz -C /tmp/test

# 检查文件
ls -la /tmp/test/home/user/documents

测试数据库恢复

1
2
3
4
5
6
# 导入数据库
gunzip < /backup/mysql/mysql_20240213_030001.sql.gz | \
mysql -uroot -p wordpressdb

# 检查数据
mysql -uroot -p wordpressdb -e "SHOW TABLES;"

定期演练(每月一次)

  • 选一个周末
  • 恢复备份到测试服务器
  • 验证数据完整性
  • 记录恢复时间

备份监控

检查备份是否成功

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

# 检查最近24小时是否有新备份
LATEST=$(find /backup/daily -name "backup_*.tar.gz" -mtime -1 | tail -1)

if [ -z "$LATEST" ]; then
echo "$(date) 警告:最近24小时没有备份!" | \
mail -s "备份失败警告" your@example.com
else
echo "$(date) 备份正常:$LATEST"
fi

检查备份文件大小

1
2
3
4
5
6
7
8
# 备份文件太小可能出错了
LATEST_BACKUP=$(find /backup/daily -name "backup_*.tar.gz" -mtime -0 | tail -1)
SIZE=$(stat -c%s "$LATEST_BACKUP")

if [ "$SIZE" -lt 1024 ]; then
echo "$(date) 警告:备份文件过小($SIZE字节)" | \
mail -s "备份异常" your@example.com
fi

常见问题

Q: 备份太慢怎么办?
A:

  • --exclude 排除不需要的文件
  • 压缩级别调低(zip用 -1,tar已自动平衡)
  • 增量备份(rsync

Q: 磁盘满了,备份失败?
A:

  • 减少保留天数
  • 删除旧文件:find /backup -mtime +30 -delete
  • 扩容磁盘

Q: crontab没执行?
A:

  • 查看cron日志:tail -f /var/log/cron
  • 检查脚本权限:ls -l /root/backup/*.sh
  • 检查脚本路径:写绝对路径

Q: 如何增量备份?
A: 用rsync:

1
rsync -av --delete /source/ /backup/

完整备份系统架构

1
2
3
4
5
6
7
8
9
本地
├── /backup/daily/ # 文件备份
├── /backup/mysql/ # 数据库备份
├── /backup/docker/ # Docker备份
└── /backup/system/ # 系统镜像(可选)

异地(云端)
├── OSS对象存储 # 阿里云/腾讯云/百度云
└── 远程服务器 # SCP同步

总结

  • 3-2-1原则:3份数据,2种介质,1个异地
  • 文件备份:tar压缩,排除无用文件
  • 数据库备份:mysqldump导出,gzip压缩
  • 云盘备份:用OSS/CLI工具上传
  • 异地备份:SCP拷贝到远程服务器
  • Docker备份:卷和镜像都保留
  • 定时执行:crontab设定时间,0 3 * * * 每天凌晨3点
  • 日志记录:所有输出写日志,出问题好排查
  • 邮件通知:备份完成/失败都发邮件
  • 恢复测试:定期演练,别等到丢了才慌
  • 监控告警:检查备份是否成功,文件大小是否正常

最后一句:备份这事儿,要么做,要么不做,不存在”差不多”。多做一份,多一份安心。


恭喜!15篇教程全部完成!

从SSH连接到自动备份,你现在应该能独立配置一台Linux服务器了。有问题随时回来翻翻,多写写,多练练,很快就能成为运维大神!加油!💪