先立一个残酷的指标
备份做得好不好,不看有没有定时任务,也不看仓库多大。只看两件事:你真恢复过几次,每次花了多久。其余的,基本是安慰剂。
cp 和 rsync 的问题不在慢,在于它们只有一个版本。文件被误改、被加密、被 rm -rf 之后,第二天同步一跑,好版本就跟着没了。restic 解决的是这个问题:内容寻址、去重、加密、快照,天生带多个时点。
它到底怎么存东西
restic 把文件切成块,用内容的哈希当文件名。同样的块只存一份,所以一个月 30 个快照不会占 30 份空间——这是它和 tar 打包最本质的区别。仓库里所有内容都加密,密钥由仓库密码派生,云存储那边看到的只是一堆密文。
代价也很直接:密码丢了,数据就是随机字节。没有后门,没有找回流程。
第一份:本地仓库
install -m 700 -d /etc/restic
openssl rand -base64 32 > /etc/restic/pass
chmod 600 /etc/restic/pass
export RESTIC_REPOSITORY=/srv/backup/restic
export RESTIC_PASSWORD_FILE=/etc/restic/pass
restic init
密码至少放两个地方:密码管理器一份,离线一份(打印出来锁抽屉也算)。直接写在脚本里的密码,等于给加密开了个后门——脚本会被看、会被备份、会被 git 提交。
第二份:备什么,怎么备
排除清单先写,不然第一次 backup 就会把 Docker 的 overlay2 整个卷进来,几百 G 的镜像层对恢复毫无意义:
# /etc/restic/excludes.txt
*.tmp
*.log
**/node_modules
**/.cache
/srv/data/docker/overlay2
/srv/data/docker/containers
restic backup \
--exclude-file=/etc/restic/excludes.txt \
--exclude-caches \
--one-file-system \
--tag daily \
/srv/data /etc /root
三个参数值得单独说。--exclude-caches 会跳过带 CACHEDIR.TAG 的目录,浏览器缓存、构建缓存大多遵守这个约定,白捡的收益。--one-file-system 防止顺着一层挂载把 NFS、移动硬盘、甚至 /proc 一起卷进来。--tag 是给后面保留策略留的标记,别省。
数据库不能直接备
正在写的 SQLite 或 PostgreSQL 数据目录,拷出来大概率是坏的。不是 restic 的问题,是文件在被修改的同时被读取——页写了一半的时候恰好被扫到,快照里就是脏数据。
正确做法是先导出,再让导出文件进备份范围:
# SQLite:用 .backup,不要 cp 那个 .db
sqlite3 /srv/data/app.db ".backup '/srv/dump/app.db'"
# PostgreSQL:整个实例
docker exec -t pg pg_dumpall -U postgres | gzip > /srv/dump/pg-$(date +%F).sql.gz
数据库目录本身可以排除掉。这一步偷懒,会在恢复那天一次性还回来。
第三份:异地
3-2-1 的后两条是「两种介质、一份异地」。restic 支持 rclone 后端,等于把 S3、B2、WebDAV、另一台机器的 SFTP 都变成仓库:
# 异地仓库用另一个密码
restic -r rclone:b2-backup:my-restic init --password-file /etc/restic/pass-offsite
# 把本地快照复制过去(-r 是目标,--from-repo 是源)
restic -r rclone:b2-backup:my-restic copy \
--from-repo /srv/backup/restic \
--from-password-file /etc/restic/pass \
--password-file /etc/restic/pass-offsite
异地用不同密码是刻意的。万一本地主机被完全控制,攻击者拿到本地密码,也开不了异地那份——这条设计对勒索软件同样有效:加密软件够不到挂载都没挂的对象存储。
如果你连云存储都不想用,最土的方案也有效:一块移动硬盘,每月插上跑一次 restic copy,跑完拔掉。离线的副本,是唯一不怕线上凭据泄露的副本。
forget 和 prune 不是一回事
restic forget \
--keep-daily 7 --keep-weekly 4 --keep-monthly 12 \
--keep-within 3m \
--prune
forget 只是删掉快照引用,数据块还躺在仓库里;prune 才真正回收空间。两者分开是因为 prune 要重写索引,耗时且需要独占锁——放在同一条命令里方便,但要知道代价。
保留策略的经验值:日备一周、周备一月、月备一年。它覆盖的是两种真实场景——「三天后才发现删错了」和「半年后才发现数据本身有问题」。别把 --keep-last 当主力,它按快照个数算,你临时手动跑一次备份,整个时间窗口就被挤歪了。
验证才是重点
# 每周:校验结构,并抽 10% 的数据真读一遍
restic check --read-data-subset=10%
# 每月:真恢复到临时目录,和源对比
restic restore latest --target /tmp/verify --include /srv/data/photos
diff -r /srv/data/photos /tmp/verify/srv/data/photos | head
check 默认只校验元数据——索引对不对、包在不在。加 --read-data 才会真去读数据块,云存储的静默损坏只有这一步能发现。全量读贵得离谱,用 --read-data-subset 轮着抽,一个月把整个仓库过一遍就够。
恢复演练的价值,在于暴露那些「备份时压根没想到」的东西:属主、权限、符号链接、硬链接、SELinux 标签。这些在 check 里全是绿的,恢复出来才发现对不上。
挂上定时器
用 systemd timer 而不是 cron。理由很实在:日志自动进 journald、失败有 systemctl status 可查、Persistent=true 能在关机错过后补跑一次。
# /etc/systemd/system/restic-backup.service
[Unit]
Description=restic backup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Environment=RESTIC_REPOSITORY=/srv/backup/restic
Environment=RESTIC_PASSWORD_FILE=/etc/restic/pass
ExecStart=/usr/local/bin/restic-backup.sh
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Daily restic backup
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer
RandomizedDelaySec 别省。一堆机器在同一秒往对象存储推数据,限流和账单都会教你做人。IOSchedulingClass=idle 让备份不去和业务抢磁盘。
几个真会踩的坑
- 锁。prune 和 backup 撞车会报
repository is already locked。别条件反射地restic unlock,先确认是不是真有进程在跑——强删锁可能让仓库停在半残状态。 - 缓存目录。默认在
~/.cache/restic。用 root 跑,几周就能在 /root 下堆出好几个 G。把RESTIC_CACHE_DIR指到数据盘。 - 边写边备。备份耗时超过业务写入周期时,快照内部会不一致。大目录要么短暂停写,要么按子目录拆成几次跑。
- 只备数据不备配置。compose 文件、Caddyfile、证书、systemd unit、crontab,全加起来不到 1MB。恢复时缺了它们,数据回来了,服务起不来。
备份的终点是恢复,不是完成率
写完这些,最容易发生的事是:定时器跑起来了,日志一片绿,然后一年没人再碰。这跟没做区别不大——只是多花了点磁盘钱,换来一种「我备份了」的心安。
所以最后一件事,比前面所有配置都重要:把恢复演练写进日历,一季度一次,计时。第一次做大概率会卡住,卡住的地方就是真出事时你会卡住的地方。趁现在数据完好,把那些坑先踩一遍,成本低得多。










暂无评论内容