跳转至

备份与文件传输工具

主要作者

@taoky

本文编写中

简介

备份是保障数据安全的重要手段。在理想情况下,备份应做到 3-2-1 原则:三份文件的副本,其中两份存储在不同的设备上,另一份远程存储。

一般来讲,管理员需要设置定时任务(使用 crontab 或者 systemd timer),在任务中调用备份数据的命令来实现备份。常见的方法包括:

  • 使用 tar 归档数据目录。
  • 使用 rsync 同步数据目录。
  • 使用数据库的 dump 工具(例如 mysqldump)备份数据库。
  • 使用 BtrfsZFS 或 LVM 提供的快照功能。Btrfs 与 ZFS 提供了 send/receive 功能以便网络传输。
  • 使用 rclone 同步到云存储(例如 OneDrive、S3 对象存储等)。
  • 使用诸如 DuplicityBorgBackup 等备份工具。
  • 特定平台可能有专用的备份工具,例如虚拟化平台 Proxmox VE 的 Proxmox Backup Server

单纯的快照与 RAID 都不是备份

快照是文件系统的特性,如果文件系统损坏,那么快照就都无法正常读取。而 RAID 只能在硬盘故障数量小于对应等级限制时才能保证数据完整性,无法防止在诸如误删除、自然灾害等情况下数据的丢失与损坏。

以下首先介绍设计备份时需要注意的问题,之后从上述方法列表引申开来,介绍一部分工具的使用方法与技巧。

备份的注意事项

除了简介中提到的 3-2-1 原则以外,备份的设计还需要关注版本管理数据一致性

版本管理的问题很容易理解:如果备份的版本只有一份的话,那么一旦数据被误删除,并且触发了备份,那么被删除的数据就没法用正常方法找回了。不过如果数据很大的话,存储每份版本就会需要大量的磁盘空间,因此在选型时需要考虑使用支持增量备份或去重的方案。

而数据一致性的问题在不少时候会被忽视。

崩溃一致性

很多时候,我们希望即使程序崩溃、系统断电,程序维护的数据内部状态仍然是一致的。这被称为崩溃一致性(crash consistency)。一般来讲,数据库都实现了崩溃一致性。

即使应用实现了崩溃一致性,如果备份工具直接复制对应的文件,那么得到的文件也有可能是不一致的——这个文件可能前半部分在 A 状态,后半部分在 B 状态。因此,备份数据库一般都使用数据库软件提供的 dump 工具导出 SQL,而不是直接用 rsync 等工具简单复制数据库对应的存储文件。

如果需要备份的文件有一致性的要求,并且不方便使用专用的工具,那么也可以使用文件系统的快照功能避免上述提到的问题。

最后,在有条件的情况下,需要验证备份的有效性,避免出现备份损坏或不完整的情况。

反面例子

2017 年,GitLab 由于操作失误,在错误的数据库主机上执行了命令导致数据丢失。尽管设置了各种备份,他们没能够第一时间有效恢复:

  • LVM 备份每天执行一次,上一次执行还是手动在 6 小时之前做的;备份的数据会加载到 staging 环境里面,但是 webhook 数据会被删除。
  • 常规备份每天执行一次,但是没人知道备份到了哪里;等到找到的时候发现备份结果只有几个字节。
  • PostgreSQL 的备份因为版本不兼容,实际上一直运行失败,啥都没备份。
  • Azure 的磁盘快照没有给数据库服务器开启。
  • S3 的备份也是空的。

于是最后,五重备份只留下了一个 6 小时前、没有 webhook 数据的备份。

Rsync

Rsync 是最常用的文件同步工具之一,支持本地复制和远程复制。相比于 cp(本地)和 scp(SSH 远程),rsync 可以做到增量复制,只复制文件中发生变化的部分,并且能够保留文件完整的元数据信息。Rsync 也是从镜像站点同步完整镜像的事实标准。

文件传输

最常用的命令:

rsync -avP --delete /path/to/source /path/to/destination

参数含义如下:

参数 说明
-a 归档模式,等同于 -rlptgoD
-r 递归复制目录
-l 保留符号链接
-p 保留文件权限
-t 保留修改时间(mtime)
-g 保留 group 信息
-o 保留 owner 信息
-D 保留设备文件与特殊文件
-v 详细输出
-P 保留传输一部分的文件且显示进度
--delete 删除目标目录中源目录没有的文件

以下提供一些参考示例。

/A 文件复制到 /tmp/A

rsync -avP /A /tmp/A
# 或者
rsync -avP /A /tmp/

/etc 文件夹复制为/复制到 /tmp/example。区别在 source 的结尾是否有 /,如果没有的话,源文件夹就会作为一个整体复制到目标文件夹内;否则如果有 /,源文件夹内的内容会被复制到目标文件夹内。

rsync -avP /etc /tmp/example1
# 此时 /etc/passwd 对应 /tmp/example1/etc/passwd
rsync -avP /etc/ /tmp/example2
# 此时 /etc/passwd 对应 /tmp/example2/passwd

作为 scp 的高效替代,rsync 也支持基于 SSH 的远程复制(远程服务器也需要安装 rsync)。可以使用 -z 开启压缩,以 CPU 为代价减小传输量。rsync 对增量复制的支持允许了「断点续传」的功能,在网络情况欠佳的时候尤其有用。

rsync -avPz user@remote:/path/to/source /path/to/destination
rsync -avPz /path/to/source user@remote:/path/to/destination

如果远程服务器需要特殊的 ssh 参数(例如端口),并且不想修改 ~/.ssh/config,可以使用 -e 参数:

rsync -avPz -e "ssh -p 2222" user@remote:/path/to/source /path/to/destination

镜像同步

Rsync 同时也可以作为服务端(daemon 模式)对外提供 rsync 服务,默认端口为 TCP 873。

Rsync over TLS

Rsync 默认为明文协议,不过其支持通过其他反向代理工具(例如 Nginx 的 stream 模块)实现 TLS 加密,默认端口为 874,需要使用 rsync-ssl 命令连接。

以下介绍与镜像同步(mirrors)相关的服务端与客户端配置。其他用途可视情况自行调整。

Rsync 服务端配置

Debian 默认的 rsync 的 systemd 服务依赖于 /etc/rsyncd.conf 文件,同时 /usr/share/doc/rsync/examples/rsyncd.conf 提供了一个参考范例。

一份用于镜像站点的 rsyncd.conf 可能如下所示:

motd file = /etc/rsyncd/rsyncd.motd
pid file = /var/run/rsyncd.pid
log file = /var/log/rsyncd.log
max verbosity = yes
transfer logging = yes
ignore nonreadable = yes
# 需要 adduser --system rsyncd
uid = rsyncd
gid = nogroup
use chroot = yes
dont compress = *
max connections = 50
refuse options = checksum
read only = true
timeout = 240
reverse lookup = no

其中比较重要的参数有:

  • 安全性相关:rsync daemon 会以 root 身份运行,在收到请求后会 fork 出子进程实际处理请求。uidgid 用于子进程降权,use chroot 用于限制访问范围,避免非预期的漏洞泄漏系统中的其他文件。由于镜像站点肯定不允许用户修改文件,因此 read only 应当设置为 true
  • 性能相关:max connections 用于限制总连接数,timeout 用于限制连接超时时间,防止被未响应的客户端长时间占用资源。dont compressrefuse options 用于禁用压缩(如果需要传输的文件大多已经是压缩过的)与检验和(会大量占用服务器 CPU)。reverse lookup 用于关闭反向 DNS 查询,避免 DNS 问题导致的连接延迟。

之后就是定义暴露的模块(module):

[repo1]
path = /path/to/repo1

[repo2]
path = /path/to/repo2

模块是 rsync URL 的第一层,例如 rsync://server/repo1/somedir/ 中,repo1 就是模块名。配置完成之后,可以使用 rsync://server/ 列出全部模块,rsync rsync://server/repo1 来确认,并且采用类似的命令同步某个模块的全部文件。

同时启用多个 rsync 服务

如果有需要多个 daemon 的需求(例如需要多个 rsync 服务端 bind 到不同的 IP 地址上),可以使用 systemd 的模板单元格式手动编写 rsync@.service 文件。

同时 rsyncd.conf 格式支持导入其他的配置文件,因此不同服务的共通部分可以提取出来。其中 &include 用于导入模块的定义,&merge 用于导入配置,类似如下:

# common.inc 包含配置
&merge /etc/rsyncd/common.inc
# common.conf 包含模块定义
&include /etc/rsyncd/common.conf

使用 systemd 安全加固 rsync 服务

Systemd 服务的安全加固参数可以帮助避免未知的安全问题影响 rsync 服务,特别是在 rsync 于 2025 年 1 月暴露了多个 CVE 的情况下(其中两个是服务端的漏洞),这样的加固就显得更加重要。

一份参考 rsync@.service 如下,其中包含安全加固以及降低 IO 与 CPU 优先级的设置(这同样也是科大镜像站目前使用的配置):

[Unit]
Description=fast remote file copy program daemon
ConditionPathExists=/etc/rsyncd/rsyncd-%i.conf
After=network.target network-online.target

[Service]
Type=exec
PIDFile=rsyncd-%i.pid
ExecStart=/usr/bin/rsync --daemon --no-detach --config=/etc/rsyncd/rsyncd-%i.conf

Nice=19
IOSchedulingClass=best-effort
IOSchedulingPriority=7
IOAccounting=true

ProtectSystem=strict
ProtectHome=true
ProtectProc=invisible
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=true
PrivateTmp=true
PrivateDevices=true
NoNewPrivileges=true
MemoryDenyWriteExecute=true

ReadWritePaths=/var/log/rsyncd

[Install]
WantedBy=multi-user.target
Alias=rsyncd@.service

也可以考虑将 uidgid 修改为在 systemd 服务中配置(而不是让 daemon 自己降权),并且提供 chroot 等必要的 capability。

Rsync 反向代理

ustclug/rsync-proxy 项目支持基于模块名的 rsync 反向代理,可以将不同的模块放在不同的服务器上,由 rsync-proxy 代理到不同的后端服务器上。

Rsync 客户端参数

一份参考的完整参数如下(修改自 ustcmirror-images 的 rsync 同步脚本):

rsync -pPrltvH --partial-dir=.rsync-partial --timeout 14400 --safe-links --delete-excluded --delete-delay --delay-updates --sparse --max-delete 4000 rsync://server/repo1/ /path/to/destination

其中部分参数含义如下:

参数 说明
-H 保留硬链接
--partial-dir 保证部分传输的文件均在该目录中
--timeout 超时时间
--safe-links 忽略指向对应仓库外部的符号链接,避免有问题的符号链接导致其他文件非预期暴露
--delete-excluded 删除被排除(exclude)的文件
--delete-delay 在同步完成后再删除文件,避免在同步过程中删除文件导致仓库不可用
--delay-updates 在同步完成后再更新文件,避免在同步过程中更新文件导致仓库不可用
--sparse 保留稀疏文件的稀疏性
--max-delete 限制删除文件的数量,避免误操作导致大量文件被删除

包含与排除

Rsync 支持 --include--exclude 参数。简单来讲,rsync 的处理规则如下:

  1. 每个文件/文件夹会按照用户在命令行中提供的顺序匹配,如果命中,则会被处理,后续的规则对该文件不再生效。具体匹配的规则可参考 rsync.1 手册的 "PATTERN MATCHING RULES" 部分。
  2. 如果没有命中任何规则,那么会被包含。
  3. 命中 --exclude 代表其子文件和文件夹也会被排除,因此如果要包含 a/b/c/d,那么 a/, a/b/, a/b/c/ 都需要被包含。

匹配练习

请尝试分别写出符合以下条件的 rsync 参数,假设需要从 src 同步到 dst:

  1. 排除 src 下的 manjaro 目录(不要排除其他名称包含 manjaro 的文件或目录)。
  2. 排除所有名称为 s390x 的目录。
  3. 排除 src 下的 cloud 目录,但是需要保留 cloud 目录下的所有 repodata.json 文件。

SSH 限制 rsync 访问

rsync 支持利用 rrsync 脚本限制使用 SSH 连接的用户只能够使用 rsync 读写指定的目录,确保用户不会通过 SSH 执行其他命令,或者访问到指定目录以外的文件。

详情可以参考本教程关于 SSH 的章节

文件备份

Rsync 本身不是完整的备份工具,其没有版本管理功能,因此如果某个文件被误删除/修改,那么 rsync 会将这个变化同步到备份中。不过基于 rsync 高效复制文件的能力,有工具实现了基于 rsync 和文件系统硬链接功能的备份,例如 Linux Mint 的 Timeshift 项目就通过硬链接实现不同时间点备份的去重操作,而 rsync 负责文件的复制。