
一台放在客厅角落的小主机,两块硬盘做了镜像,主人以为这就是备份。直到有一次电源烧了,两块盘一起停转,镜像完好,数据也跟着一起没了。镜像只防盘坏,不防机器坏、房间坏、手滑。
镜像解决的是盘坏,不解决机器坏、房间坏、手滑。真正的备份必须满足三个条件:有第二份、在别处、能还原。这份手册用 restic 加 rclone 做一套异地加密备份,全部配置给完整值,版本写明,坑也一并写上。
一、先说清楚这套东西的边界
restic 是一个内容寻址的备份程序:它把文件切成块、算哈希、只上传没见过的块,所以第二次备份通常只有首次的百分之几。仓库整体加密,密钥来自一个密码文件,云端只有密文。它不做实时同步,也不做双活,它只负责「某一时刻的状态可以完整取回」这件事。
本手册环境:Debian 12 或 Ubuntu 24.04,restic 0.17.3,rclone v1.68.1,systemd 255。版本不同命令参数会有差异,尤其是 restic 0.16 之前没有 –from-repo,升级前先看 changelog。
二、安装与远端配置
先装两个二进制,再从官方源取最新版,不要用发行版仓库里那个偏旧的 restic。这不是洁癖:0.16 之前没有 –from-repo,跨仓库迁移会直接卡住。
apt install -y rclone curl bzip2
cd /tmp
curl -LO https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_linux_amd64.bz2
bunzip2 restic_0.17.3_linux_amd64.bz2
install -m 0755 restic_0.17.3_linux_amd64 /usr/local/bin/restic
restic version
rclone 的远端配置在 root 的家目录下,路径是 /root/.config/rclone/rclone.conf。用交互式向导配一次即可。这个文件里是明文密钥,权限必须是 600。配完把这一段抄下来留档:
[b2]
type = b2
account = 你的_account_id
key = 你的_application_key
hard_delete = true
[s3backup]
type = s3
provider = Other
access_key_id = 你的_key_id
secret_access_key = 你的_secret
endpoint = https://s3.us-east-005.backblazeb2.com
region = us-east-005
密码单独放一个文件,权限 600,内容就是一行明文密码。这个文件丢了,仓库就打不开,任何人也打不开。把密码文件另存一份到手机或纸质记录里,这一步没有替代方案。
install -d -m 700 /etc/restic
printf '一串足够长的随机密码\n' > /etc/restic/password
chmod 600 /etc/restic/password
export RESTIC_REPOSITORY=rclone:s3backup:fhsj-restic
export RESTIC_PASSWORD_FILE=/etc/restic/password
restic init
init 只在第一次执行,成功后仓库里会多出 config、data、index、keys 四个目录。如果这一步报 403,九成是 rclone 的 bucket 名写错或密钥没有写权限,跟 restic 无关。
三、先做一次手动全量,看清备份集
不要一上来就写脚本。先手动跑一次,看着输出把那几个排除项调对。下面的路径按一台常见的小型服务器写,包含 /etc、/home、/srv、/var/www、/opt。第一次手动跑的目的是看清输出,不是完成备份。
restic backup /etc /home /root /srv /var/www /opt \
--one-file-system \
--exclude-caches \
--exclude /home/*/.cache \
--exclude /var/lib/docker \
--exclude /var/log/journal \
--exclude '*.tmp' \
--tag manual \
--verbose
–one-file-system 是必须的,否则它会顺着挂载点走进 /proc、/sys、/dev,然后死在某个不响应的设备文件上。不要备份 /var/lib/docker 目录本身,容器数据要在容器层做导出,直接拷目录得到的是一堆无法还原的层。
跑完之后看两样东西:新增块的量,和快照列表。这两个数字决定了后面所有策略参数该定多大。
restic snapshots --compact
restic stats --mode raw-data
第一次全量按数据量算,通常 1 GB 数据在 10 Mbps 上行下要十几分钟。第二次如果没有大改动,新增应该只有几十 MB。如果第二次的新增量接近第一次,说明有程序在持续写大文件,先把它找出来再谈备份策略。
四、systemd 服务与定时器
用 systemd 而不是 cron,因为这里需要失败可见、可以手动触发、能加资源限制。两个单元文件,放在 /etc/systemd/system 下。cron 也能跑,但它不会告诉你上次失败在哪一步。
[Unit]
Description=restic backup to remote
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Environment=RESTIC_REPOSITORY=rclone:s3backup:fhsj-restic
Environment=RESTIC_PASSWORD_FILE=/etc/restic/password
Environment=RCLONE_CONFIG=/root/.config/rclone/rclone.conf
Environment=RESTIC_CACHE_DIR=/var/cache/restic
ExecStart=/usr/local/bin/backup.sh
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=6h
[Unit]
Description=Run restic backup nightly
[Timer]
OnCalendar=*-*-* 03:20:00
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true 是关键:机器在 03:20 关着,开机后会补跑一次。没有这一行,一次关机就等于丢一天,而且你不会收到任何提示。
真正干活的 backup.sh,只做三件事:备份、按策略清理、抽样校验。设置 -eu,任何一步失败整次退出并留下非零返回码。三条命令的顺序不能换:先备份,再清理,最后校验。
#!/bin/sh
set -eu
/usr/local/bin/backup-inner.sh
/usr/local/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
/usr/local/bin/restic check --read-data-subset=5%
五、保留策略:一张表说清删什么
| 参数 | 保留数量 | 覆盖时间 | 用途 |
|---|---|---|---|
| –keep-daily 7 | 7 个 | 最近一周 | 误删、误改,最常见 |
| –keep-weekly 4 | 4 个 | 最近一个月 | 一周前才发现的问题 |
| –keep-monthly 6 | 6 个 | 半年 | 缓慢损坏、勒索软件 |
| –keep-last 3 | 3 个 | 最近三次 | 防止当天连续失败无快照 |
forget 只删索引,不释放空间;prune 才真正重写数据文件。prune 会读一遍仓库,在远端执行时既慢又占流量。把 forget 和 prune 放在同一条命令里,让它们一次完成,别分两次跑。
六、四个必然踩到的坑
- rclone 后端默认并发过高,小内存机器会被拖死。在仓库环境变量里加 RCLONE_B2_CONNECTIONS=4 和 RCLONE_B2_UPLOAD_CONCUTS=2,再把 –limit-upload 设成上行的七成。
- systemd 下找不到 rclone.conf。服务以 root 跑,Home 是 /root,但如果用 User= 指定了别的账号,rclone 会去那个账号的家目录找。显式给 RCLONE_CONFIG 路径,别赌默认值。
- 缓存目录把根分区撑满。restic 默认缓存在 ~/.cache/restic,一台 1 TB 数据的机器能吃掉几十 GB。把它挪到有空间的盘,或者用 –no-cache 换更慢的索引重建。
- prune 与 backup 撞锁。两次运行时间重叠时,后到的那次会报 repository is already locked。加 ExecStartPre 检查锁,或者干脆把备份频率调到远高于运行时长。
还有一个不算坑但一定会遇到的:第一次 prune 会跑很久,几小时都有可能。不要以为它卡死了就去 kill,中断的 prune 会留下未清理的数据文件,占着空间但不算进快照。
七、验收:不还原一次就不算有备份
按顺序做下面五步,全部通过才算这套东西能用。第三步是重点,跳过它的人最多。验收的标准不是「跑通了」,是「取回来了」。
- systemctl start restic-backup.service,看 systemctl status 是 active (exited),不是 failed。
- restic snapshots 能看到刚才那条,时间、标签、路径数都对得上。
- 挑一个目录做局部还原:restic restore latest –target /tmp/restore-test –include /etc/nginx,然后 diff 一次。
- restic check 输出 no errors were found。
- 把网线拔掉再跑一次,确认失败会写进 journalctl,而不是安静地跳过。
第五步常被忽略,但它决定了你多久能发现问题。备份系统最危险的失败不是报错,是静默成功——定时器还在跑,日志一片绿,仓库其实三天没写入。
八、监控:怎么知道它昨天失败了
备份任务失败时,最常见的情况是没有人在看。systemd 会把失败写进 journal,但 journal 不会主动喊你。没有通知的失败,等于没有失败过。所以最后一步是给自己留一个能被看见的信号:让服务在失败时发一封邮件,或者往一个你每天都会看的文件里写一行。
最省事的做法是再加一个单元,用 OnFailure= 挂到备份服务上。这一行是整套配置里唯一主动找你的东西:
[Unit]
Description=restic backup failed notice
[Service]
Type=oneshot
ExecStart=/usr/bin/mail -s restic-failed root
在 restic-backup.service 的 [Unit] 段里加一行 OnFailure=restic-fail-notice.service,整条链路就闭合了。通知的内容不重要,重要的是「没有消息」本身要能被理解为「成功」。
更稳的做法是反过来:每周日让一个脚本检查最近一次快照的时间,超过 48 小时就报警。这两种监控的差别是,前者相信失败会报错,后者不相信。
写在最后
这套配置加起来不到一百行,真正花时间的不是写配置,是决定哪些目录不该进去、保留多久、以及每隔一段时间亲手还原一次。前两件事可以一次定下来,第三件事必须反复做。前两件事是配置,第三件事是习惯,而习惯没法一次配好。
备份的价值不在备份那一刻,在还原那一刻。而还原这件事,只有在你真的做过一次之后,才知道自己备份的究竟是数据,还是一堆看起来像数据的文件。没有还原过的备份,只是一个关于安全的假设。
你上一次从备份里把文件捞回来,是什么时候?捞回来的是完整的那一份吗?

评论 (0)