OpenSSH/食谱/自动备份
使用带有密钥的 OpenSSH 可以方便地进行安全的自动备份。 rsync(1)[1]、 tar(1) 和 dump(8) 是大多数备份方法的基础。远程 root 访问必须允许的说法是错误的。如果需要 root 访问权限, sudo(8) 可以正常工作,或者在 zfs(8) 的情况下,使用 OpenZFS 委托系统。请记住,在备份数据经过测试并证明可以可靠地恢复之前,它不算是备份副本。
rsync(1) 通常用于本地和远程备份。它速度快、灵活,并且以增量方式复制,因此只传输更改,从而避免浪费时间重新复制已存在于目标位置的内容。它通过使用其著名的算法来做到这一点。在远程工作时,它需要 SSH 的一点帮助,通常的做法是通过 SSH 建立隧道。
rsync(1) 实用程序现在默认使用 SSH,并且从 2004 年开始使用[2]。因此,以下命令通过 SSH 连接,无需添加任何额外内容
$ rsync -a [email protected]:./archive/ \
/home/fred/archive/.
但是,如果必须将其他选项传递给 SSH 客户端,则仍然可以显式指定 SSH 的使用。
$ rsync -a -e 'ssh -v' \
[email protected]:./archive/ \
/home/fred/archive/.
对于某些类型的数据,如果两端的主机 CPU 能够处理额外的工作,则可以使用 rsync(1) 压缩(-z)来极大地加快传输速度。但是,它也可能降低速度。因此,压缩是需要在实际环境中进行测试才能确定它是否能提高或降低速度。
由于 rsync(1) 默认使用 SSH,因此它甚至可以使用 SSH 密钥进行身份验证,方法是使用 -e 选项指定其他选项。这样,就可以指定一个特定的 SSH 密钥文件,供 SSH 客户端在建立连接时使用。
$ rsync --exclude '*~' -avv \
-e 'ssh -i ~/.ssh/key_bkup_rsa' \
[email protected]:./archive/ \
/home/fred/archive/.
如果需要,也可以以相同的方式将其他配置选项发送到 SSH 客户端,或者通过 SSH 客户端的配置文件发送。此外,如果先将密钥添加到代理,则只需要输入一次密钥的密码短语。在现代桌面环境中,这很容易在交互式会话中完成。在自动脚本中,需要显式地设置代理,并将显式套接字名称传递给脚本,并通过 SSH_AUTH_SOCK 变量访问。
有时,备份过程需要访问除可以登录的帐户以外的其他帐户。该其他帐户通常是 root,出于最小特权原则,它通常被拒绝通过 SSH 直接访问。如果需要,rsync(1) 可以远程调用 sudo(8)。
假设您正在从服务器备份到客户端。客户端上的 rsync(1) 使用 ssh(1) 建立到服务器上 rsync(1) 的连接。客户端上使用 -v 选项调用 rsync(1),以便查看传递给服务器的具体参数。这些细节将需要用于将其整合到服务器上 sudo(8) 的配置中。这里,SSH 客户端以单级更高的详细程度运行,以便显示必须使用的选项。
$ rsync \
-e 'ssh -v \
-i ~/.ssh/key_bkup_rsa \
-t \
-l bkupacct' \
--rsync-path='sudo rsync' \
--delete \
--archive \
--compress \
--verbose \
bkupacct@server:/var/www/ \
/media/backups/server/backup/
参数 --rsync-path 告诉服务器使用什么来代替 rsync(1)。在这种情况下,它运行 sudo rsync
。参数 -e 用于指定要使用的远程 shell 工具。在本例中,它指的是 ssh(1)。对于由 rsync(1) 客户端调用的 SSH 客户端,-i 用于明确指定要使用的密钥。这与是否使用身份验证代理无关。使用多个密钥是可能的,因为可以为不同的任务使用不同的密钥。
您可以通过在客户端上以详细模式(-v)运行 SSH 来查找在 /etc/sudoers 中使用的确切设置。在处理模式时要小心,不要匹配超出安全范围的内容。
调整这些设置很可能是一个迭代过程。在观察详细输出时,继续对服务器上的 /etc/sudoers 进行更改,直到它按预期工作为止。最终,/etc/sudoers 将最终包含一行,允许 rsync(1) 以最少的选项运行。
以下示例基于从远程系统获取数据。也就是说,数据从远程系统上的 /source/directory/ 复制到本地上的 /destination/directory/。但是,反方向的步骤也是一样的,但是一些选项将被放置在不同的位置,并且将省略 --sender。无论哪种情况,以下示例中的复制粘贴都无法正常工作。
准备:创建一个专门用于备份的帐户,创建一个仅用于该帐户的密钥对,然后确保可以使用 ssh(1) 使用和不使用这些密钥登录到该帐户。
$ ssh -i ~/.ssh/key_bkup_ed25519 [email protected]
服务器上的帐户名为“bkupacct”,私钥 Ed25519 为客户端上的 ~/.ssh/key_bkup_ed25519。在服务器上,帐户“bkupacct”是“backups”组的成员。如有必要,请参阅 公钥身份验证 部分。
公钥,~/.ssh/key_bkup_ed25519.pub,必须复制到远程系统上的帐户“bkupacct”中,并放置在~/.ssh/authorized_keys中的正确位置。然后,需要确保服务器上的以下目录由 root 拥有,属于“backups”组,并且可读,但不可写,并且绝对不可被世界读取:~ 和 ~/.ssh/。该文件~/.ssh/authorized_keys 也一样(这还假设您没有使用 ACL)。但是,这只是在远程系统上设置权限的众多方法之一。
$ sudo chown root:bkupacct ~
$ sudo chown root:bkupacct ~/.ssh/
$ sudo chown root:bkupacct ~/.ssh/authorized_keys
$ sudo chmod u=rwx,g=rx,o= ~
$ sudo chmod u=rwx,g=rx,o= ~/.ssh/
$ sudo chmod u=rwx,g=r,o= ~/.ssh/authorized_keys
现在可以开始配置了。
步骤 1:配置 sudoers(5),以便 rsync(1) 可以与远程主机上的 sudo(8) 一起工作。在这种情况下,数据将保留在远程机器上。为了找到并设置稍后锁定这些数据的特定选项,'backups' 组将临时需要完全访问权限。
%backups ALL=(root:root) NOPASSWD: /usr/bin/rsync
这是一个过渡步骤,重要的是,这行不应长时间保持原样。
但是,当它就位时,请确保 rsync(1) 可以与 sudo(8) 一起工作,方法是使用--rsync-path 选项进行测试。
$ rsync --rsync-path='sudo rsync' \
-aHv [email protected]:/source/directotry/ /destination/directory/
传输应该在没有错误、警告或额外密码输入的情况下运行。
步骤 2:接下来,再次进行相同的传输,但使用密钥进行身份验证,以确保两者可以一起使用。
$ rsync -e 'ssh -i ~/.ssh/key_bkup_ed25519' --rsync-path='sudo rsync' \
-aHv [email protected]:/source/directory/ /destination/directory/
同样,如有必要,请参阅有关 公钥身份验证 的部分。
步骤 3:现在收集连接详细信息。它们是调整 sudoers(5) 的必要条件。
$ rsync -e 'ssh -E ssh.log -v -i ~/.ssh/key_bkup_ed25519' \
--rsync-path='sudo rsync' \
-aHv [email protected]:/source/directory/ /destination/directory/
$ grep -i 'sending command' ssh.log
第二个命令,即带有 grep(1) 的命令,应该生成类似以下内容的结果
debug1: Sending command: rsync --server --sender -vlHogDtpre.iLsfxCIvu . /source/directory/
请注意字母的长字符串和目录,因为它们将用于稍微调整 sudoers(5)。请记住,在这些示例中,数据将从远程机器上的/source/directory/ 复制到本地的/destination/directory/。
以下是与上述公式匹配的设置,假设帐户在 backups 组中
%backups ALL=(root:root) NOPASSWD: /usr/bin/rsync --server --sender -vlHogDtpre.iLsfxCIvu . /source/directory/
该行调整了 sudoers(5),以便备份帐户拥有足够的权限以 root 身份运行 rsync(1),但仅限于它应该运行的目录中,并且不能在系统上随意使用。
以后可能会进行更多改进,但这些是锁定 sudoers(5) 的基础。此时您几乎完成了,尽管该过程可以进一步自动化。请确保备份数据在本地存储后无法被其他人访问。
步骤 4:使用 sudo(8) 通过 ssh(1) 测试 rsync(1),以验证在 sudoers(5) 中进行的设置是否正确。
$ rsync -e 'ssh -i ~/.ssh/key_bkup_ed25519' --rsync-path='sudo rsync' \
-aHv [email protected]:/source/directory/ /destination/directory/
备份应该在此阶段正常运行。
步骤 5:最后,可以通过在authorized_keys 文件中使用command="..." 选项,在限制前面添加限制,从而将该密钥锁定到一项任务中。对此的解释可以在 sshd(8) 中找到。
command="/usr/bin/rsync --server --sender -vlHogDtpre.iLsfxCIvu . ./source/directory" ssh-ed25519 AAAAC3Nz...aWi
此后,该密钥仅用于备份。它是在 sudoers(5) 文件中已进行的设置的基础上增加的一层。
因此,您可以使用 rsync(1) 进行自动化远程备份,并具有 root 级别访问权限,同时避免远程 root 登录。尽管如此,请密切注意私钥,因为它仍然可以用来获取远程备份,而远程备份可能包含敏感信息。
从头到尾,该过程需要高度重视细节,但如果一步一步地进行,则很容易完成。设置从本地到远程方向的备份非常相似。当从本地到远程传输时,---sender 选项将被省略,并且目录将不同。
Rsync 协议的其他实现
[edit | edit source]openrsync(1) 是 samba.org 实现的 rsync(1) 所支持的 Rsync 协议版本 27 的纯净室重新实现[3]。它已包含在 OpenBSD 版本 6.5 之后的 OpenBSD 基本系统中。它使用不同的名称调用,因此,如果它在远程系统上,而 samba.org 的 rsync(1) 在本地系统上,则--rsync-path 选项必须按名称指向它。
$ rsync -a -v -e 'ssh -i key_rsa' \
--rsync-path=/usr/bin/openrsync \
[email protected]:/var/www/ \
/home/fred/www/
反方向操作,从 openrsync(1) 开始连接到远程系统上的 rsync(1),不需要进行任何调整。
使用 tar(1) 进行备份
[edit | edit source]创建存档的常见选择是 tar(1)。但由于它复制整个文件和目录,因此 rsync(1) 通常对于更新或增量备份来说效率更高。
以下将创建/var/www/ 目录的 tar 包,并通过本地机器上的 stdout 发送到远程机器上的sdtin(通过管道传输到 ssh(1)),然后将其定向到名为backup.tar 的文件。在这里,tar(1) 在本地机器上运行,并将 tar 包存储在远程位置。
$ tar cf - /var/www/ | ssh -l fred server.example.org 'cat > backup.tar'
该配方几乎有无限的变体。
$ tar zcf - /var/www/ /home/*/www/ \
| ssh -l fred server.example.org 'cat > $(date +"%Y-%m-%d").tar.gz'
该示例执行相同操作,但也获取用户 WWW 目录,使用 gzip(1) 压缩 tar 包,并根据当前日期为结果文件标记标签。也可以使用密钥执行此操作。
$ tar zcf - /var/www/ /home/*/www/ \
| ssh -i key_rsa -l fred server.example.org 'cat > $(date +"%Y-%m-%d").tgz'
对于 tar(1) 来说,从另一个方向获取远程机器上的内容并存储在本地也很容易。
$ ssh [email protected] 'tar zcf - /var/www/' > backup.tgz
或者,以下是一个在远程机器上运行 tar(1) 但将 tar 包存储在本地的更复杂的示例。
$ ssh -i key_rsa -l fred server.example.org 'tar jcf - /var/www/ /home/*/www/' \
> $(date +"%Y-%m-%d").tar.bz2
总之,使用 tar(1) 进行备份的秘诀是使用stdout 和stdin 通过管道和重定向进行传输。
使用 tar(1) 备份文件,但不创建 tar 包
[edit | edit source]有时需要直接传输文件和目录,而不在目标位置创建 tar 包。除了写入源机器上的stdin 之外,tar(1) 还可以从目标机器上的stdin 读取,以一次传输整个目录层次结构。
$ tar zcf - /var/www/ | ssh -l fred server.example.org "cd /some/path/; tar zxf -"
或者反方向操作,将是以下情况。
$ ssh 'tar zcf - /var/www/' | (cd /some/path/; tar zxf - )
但是,这些操作在每次运行时仍然会复制所有内容。因此,上述部分中描述的 rsync(1) 在许多情况下可能是更好的选择,因为它在后续运行时仅复制更改的内容。此外,根据数据类型、网络条件和可用的 CPU,压缩可能是使用 tar(1) 或 ssh(1) 本身进行压缩的好方法。
使用 dump 进行备份
[edit | edit source]使用 dump(8) 远程操作类似于使用 tar(1)。可以从远程服务器复制到本地服务器。
$ ssh -t source.example.org 'sudo dump -0an -f - /var/www/ | gzip -c9' > backup.dump.gz
请注意,sudo(8) 的密码提示可能不可见,并且必须盲目输入。
或者可以反方向操作,从本地服务器复制到远程服务器
$ sudo dump -0an -f - /var/www/ | gzip -c9 | ssh target.example.org 'cat > backup.dump.gz'
再次注意,密码提示可能隐藏在 dump(8) 的初始输出中。但是,即使不可见,它仍然存在。
使用 zfs(8) 快照进行备份
[edit | edit source]OpenZFS 可以轻松地创建完整或增量快照,这是写时复制的便利副产品。这些快照可以通过 SSH 发送到另一个系统或从另一个系统发送。这种方法对于备份或恢复数据同样有效。但是,带宽是一个需要考虑的因素,快照的大小必须适合所讨论的实际网络连接。OpenZFS 支持压缩复制,以便磁盘上已压缩的块在传输过程中保持压缩状态,从而减少了使用其他进程重新压缩的需要。传输可以是到常规文件或另一个 OpenZFS 文件系统,也可以是从这些文件系统。这应该是显而易见的,但重要的是要记住,较小的快照使用较少的带宽,因此比较大的快照传输速度更快。
首先需要完整快照,因为增量快照只包含部分数据,并且需要它们形成的基础存在。以下使用 zfs(8) 为名为 web 的池中名为 site01 的数据集创建名为 20210326 的快照。
$ zfs snapshot -r web/site01@20210326
程序本身很可能位于 /sbin/ 目录中,要么 PATH 环境变量需要包含它,要么应该使用绝对路径。随后可以使用 -i 选项在初始完整快照的基础上构建增量快照。但是,OpenZFS 管理的来龙去脉超出了本书的范围。这里将只检查两种系统间传输方法。一种方法是使用中间文件,另一种方法是使用管道更直接。两者都使用 zfs send 和 zfs receive,并且所涉及的帐户必须在 OpenZFS 委托系统中具有正确的权限。对于发送,它将是相关 OpenZFS 池的 send 和 snapshot。对于接收,它将是相关池的 create、mount 和 receive。
可以将快照通过 SSH 传输到本地或远程系统上的文件。这种方法不需要在任一系统上拥有特权访问权限,但运行 zfs 的帐户必须具有由 zfs allow 授予的正确的内部 OpenZFS 权限。这里从远程系统下载一个非常小的快照到本地文件
$ ssh [email protected] '/sbin/zfs send -v web/site01@20210326' > site01.openzfs
full send of web/site01@20210326 estimated size is 1.72M
total estimated size is 1.72M
如果复制增量快照,则需要复制它们所基于的完整快照。因此,应注意确保这是一个完整快照,而不仅仅是增量快照。
稍后,恢复快照是进行相反方向的问题。在这种情况下,数据将从文件检索并通过 SSH 发送到 zfs(8)。
$ cat site01.openzfs | ssh [email protected] '/sbin/zfs receive -v -F web/site01@20210326'
receiving full stream of web/site01@20210326 into web/site01@20210326
received 1.85M stream in 6 seconds (316K/sec)
这是可能的,因为在运行时直接调用程序时,通道在没有 PTY 的情况下启动时是 8 位干净的。请注意,目标 OpenZFS 数据集必须首先使用 zfs(8) 卸载。然后在传输后必须重新挂载。
从本地系统传输到远程系统是更改组件顺序的问题。
$ /sbin/zfs send -v web/site01@20210326 | ssh [email protected] 'cat > site01.openzfs'
full send of web/site01@20210326 estimated size is 1.72M
total estimated size is 1.72M
然后需要类似的更改才能从远程系统恢复到本地系统。
$ ssh [email protected] 'cat site01.openzfs' | /sbin/zfs receive -v -F web/site01@20210326'
receiving full stream of web/site01@20210326 into web/site01@20210326
received 1.85M stream in 6 seconds (316K/sec)
像往常一样,为了避免在这些活动中使用 root 帐户,运行 zfs(8) 的帐户必须在 OpenZFS 委托系统中具有正确的访问级别。
或者,可以将该快照通过 SSH 传输到远程计算机上的文件系统。这种方法需要特权访问权限,并将不可撤销地替换自快照以来在远程系统上进行的任何更改。
$ zfs send -v pool/www@20210322 | ssh [email protected] 'zfs receive -F pool/www@20210322'
因此,如果在远程系统上使用可移动硬盘驱动器,则这可以更新它们。
$ ssh [email protected] 'zfs send -v pool/www@20210322' | zfs receive -F pool/www@20210322
同样,远程帐户必须已经获得必要的内部 ZFS 权限。
同样,要从远程系统到本地系统进行另一个方向,需要更改组件的顺序。
$ ssh [email protected] 'zfs send -v pool/www@20210322' | zfs receive -F pool/www@20210322
并且,
$ zfs send -v pool/www@20210322 | ssh [email protected] 'zfs receive -F pool/www@20210322'
同样,使用 OpenZFS 委托系统可以避免在传输的任一端都需要 root 访问权限。
有时 CPU 和网络会在文件传输期间交替成为瓶颈。mbuffer(1) 实用程序可以允许稳定的数据流 [4],即使 CPU 超过网络。关键是要留出足够大的缓冲区,以便即使 CPU 赶上来,也总有一些数据在网络上传输。
$ cat site01.zfs | mbuffer -s 128k -m 1G \
| ssh [email protected] 'mbuffer -s 128k -m 1G | /sbin/zfs receive -v -F web/site01'
summary: 1896 kiByte in 0.2sec - average of 7959 kiB/s
receiving full stream of web/site01@20210326 into web/site01@20210326
in @ 2556 kiB/s, out @ 1460 kiB/s, 1024 kiB total, buffer 0% full
summary: 1896 kiByte in 0.8sec - average of 2514 kiB/s
received 1.85M stream in 2 seconds (948K/sec)
有关使用 OpenZFS 和管理其快照的更多详细信息超出了本书的范围。实际上,有关于 OpenZFS 的完整指南、教程,甚至书籍。
- ↑ "Rsync 工作原理". Samba.
- ↑ "Rsync 2.6.0 的新闻 (2004 年 1 月 1 日)". Samba. 2004-01-01. 检索于 2020-05-02.
- ↑ "openrsync 导入到树中". Undeadly. 2019-02-11. 检索于 2020-05-10.
- ↑ Dan Langille (2014-05-03). "在 FreeBSD 上通过 SSH 使用 mbuffer 发送 zfs". 检索于 2020-05-22.