在 Linux 上进行自动备份

级别: 初级

Carlos Justiniano ([email protected]), 软件设计师, Ecuity Inc.

2004 年 8 月 08 日

重要数据的丢失可能意味着致命的破坏。尽管如此,还是有无数专业人员忽视了对他们的数据的备份。虽然原因可能各不相同,但是,最常见的一个解释是,执行例行备份确实烦琐。由于机器擅长于完成普通而重复的任务,因此,自动化备份的过程是降低工作内在的枯燥性和人们与生俱来的拖延倾向的关键所在。
如果您使用 Linux,那么您就已经可以使用那些创建定制备份解决方案的极其强大的工具。本文中的方案,可以让您使用几乎每个 Linux 发行版本都附带的开放源代码工具来执行从简单的到更高级而且安全的网络备份。

简单备份

本文按照一步一步的方法来进行讲述,只要您遵循那些基本的步骤,此方法会是非常直观的。

在研究更高级的分布式备份解决方案之前,让我们首先来看一个简单而强大的存档机制。让我们来分析一个名为 arc 的方便的脚本,它可以让我们在 Linux shell 提示符中创建备份快照。

清单 1. arc shell 脚本

#!/bin/sh
tar czvf $1.$(date +%Y%m%d%-H%M%S).tgz $1
exit $?

arc 脚本接收一个单独的文件或目录名作为参数,创建一个压缩的存档文件,同时将当前日期嵌入到生成的存档文件的名字之中。例如,如果您有一个名为 beoserver 的目录,您可以调用 arc 脚本,将 beoserver 目录名传递给它以创建一个压缩的存档文件,如: beoserver.20040321-014844.tgz

使用 date 命令是为了嵌入一个日期和时间戳以帮助您组织存档文件。日期的格式是年、月、日、小时、分、秒 —— 虽然秒域的使用有一些多余。查看 data 命令的手册( man date )来了解其他选项。另外,在清单 1 中,我们向 tar 传递了 -v (verbose)选项。这就使得 tar 显示出它正在存档的文件。如果您喜欢静默地进行备份,那么删除这个 -v 选项。

清单 2. 存档 beoserver 目录
$ ls
arc beoserver
$ ./arc beoserver
beoserver/
beoserver/bookl.dat
beoserver/beoserver_ab_off
beoserver/beoserver_ab_on
$ ls
arc beoserver beoserver.20040321-014844.tgz

高级备份

这个简单备份是实用的;不过,它仍然包含一个人工备份的过程。行业最佳经验通常建议将数据备份到多个媒体上,并备份到分开的不同地理位置。中心思想是避免依赖于任何一个单独的存储媒体或单独的位置。

在下一个例子中我们将应对这一挑战,我们将分析一个如图 1 所示的假想的分布式网络,它展示了对两台远程服务器和一台离线存储服务器的系统管理。

图 1. 分布式网络

服务器 #1 和 #2 上的备份文件将安全地传输到离线存储服务器上,而且整个分布式备份过程将在没有人工干涉的情况下定期进行。我们将使用一组标准的工具(开放安全 shell 工具套件(OpenSSH)的一部分),以及磁带存档器(tar)和 cron 任务调度服务。我们的全部计划是,使用 cron 进行调度,使用 shell 程序和 tar 应用程序完成备份过程,使用 OpenSSH 安全 shell(ssh)加密进行远程访问、认证、安全 shell 拷贝(scp)以自动完成文件传输。要获得另外的资料请务必查看每个工具的手册。

使用公钥/私钥进行安全的远程访问

在数字安全的上下文中,密钥(key)指的是用来加密或解密其他数据片断的一个数据片断。公钥私钥模式的有趣之处在于,使用公钥加密的数据,只有用相应的私钥才可以解密。您可以自由地发布一个公钥,这样别人就可以对发送给您的消息进行加密。公钥/私钥模式彻底改变了数字安全的原因之一是,发送者和接收者不必再共享一个通用的密码。除了其他方面的贡献,公钥/私钥加密使用电子商务和其他安全传输成为可能。在本文中,我们将创建并使用公钥和私钥来创建一个非常安全的分布式备份解决方案。

要进行备份过程的每台机器都必须运行 OpenSSH 安全 shell 服务(sshd),同时让 22 端口可以通过任何内部防火墙被访问。如果您访问远程的服务器,那么很有可能您正在使用安全 shell。

我们的目标将是,不需要人工提供密码就可以安全地访问机器。一些人认为最简单的办法是设置无密码的访问:不要这样做。这样做不安全。不用那样,本文中我们将使用的方法可能会占用您大约一个小时的时间,建立起一个与使用“无密码”帐号同样方便的系统 —— 不过它是公认非常安全的。

让我们首先确保 OpenSSH 已经安装,接下来查看它的版本号。完成本文时,最新的发行的 OpenSSH 是 2004 年 2 月 24 日发布的版本 3.8。您应该考虑使用一个较新的而且稳定的发布版本,至少所用的版本应该要比版本 2.x 新。访问 OpenSSH Security 网页以获得关于特定旧版本的缺陷的细节(请参阅本文后面的 参考资料 中的链接)。到目前为止,OpenSSH 是非常稳定的,而且已经证明不存在其他 SSH 工具所报告的很多缺陷。

在 shell 提示符中,输入 ssh 并给出重要的 V 选项来检查版本号:

$ ssh -V
OpenSSH_3.5p1, SSH protocols 1.5/2.0, OpenSSL 0x0090701f

如果 ssh 返回的版本号大于 2.x,则机器处于相对良好的状态。无论如何,建议您所有的软件都使用最新的稳定版本,这对于安全相关的软件来说尤其重要。

我们的第一个步骤是,使用将会有特权访问服务器 1 和 2 的帐号登录到离线存储服务器机器(见图 1)。

$ ssh [email protected]

登录到离线存储服务器以后,使用 ssh-keygen 程序并给出 -t dsa 选项来创建一个公钥/密钥对。 -t 选项是必须的,用来指定我们要生成的密钥类型。我们将使用数字签名算法(Digital Signature Algorithm,DSA),它让我们可以使用更新的 SSH2 协议。参阅 ssh-keygen 手册以获得更多细节。

在 ssh-keygen 执行的过程中,在询问您口令(passphrase)之前,将提示您输入 ssh 密钥存储的位置。当询问在何处存储密钥时只需要按下回车键,然后 ssh-keygen 程序将创建一个名为 .ssh 的隐藏目录(如果原来不存在),以及两个文件,一个公钥文件和一个私钥文件。

ssh-keygen 的一个有趣特性是,当提示输入一个口令时,它让您可以只是简单地按下回车键。如果您没有给出口令,那么 ssh-keygen 将生成没有加密的密钥!如您所想,这不是个好主意。当要求输入口令时,确保输入一个足够长的字符消息,最好包含混合字符而不仅仅是一个简单的密码字符串。

清单 3. 永远选择好的口令
[offsite]:$ ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/home/accountname/.ssh/id_dsa):
Enter passphrase (empty for no passphrase): (enter passphrase)
Enter same passphrase again: (enter passphrase)
Your identification has been saved in /home/accountname/.ssh/id_dsa.
Your public key has been saved in /home/accountname/.ssh/id_dsa.pub.
The key fingerprint is:
7e:5e:b2:f2:d4:54:58:6a:fa:6b:52:9c:da:a8:53:1b accountname@offsite

由于 ssh-keygen 生成的 .ssh 目录是一个隐藏的“dot”目录,所以要给 ls 命令传入一个 -a 选项来查看新创建的目录:

[offsite]$ ls -a
. .. .bash_logout .bash_profile .bashrc .emacs .gtkrc .ssh

进入隐藏的 .ssh 目录并列出其内容:

[offsite]$ cd .ssh
[offsite]$ ls -lrt
id_dsa id_dsa.pub

现在,在隐藏的 .ssh 目录中,我们已经拥有了一个私钥(id_dsa)和一个公钥(id_dsa.pub)。您可以使用 vi 或 emacs 等文本编辑工具或者简单地使用 less 或 cat 命令来分析每个密钥文件的内容。您将看到由混合字符构成的内容已经经过了 base64 编码。

然后,我们需要将公钥拷贝并安装到服务器 1 和 2 上。不要使用 ftp。更合理的是,使用安全拷贝程序来将公钥传送到每一台远程机器上。

清单 4. 将公钥安装到远程服务器上
[offsite]$ scp .ssh/id_dsa.pub [email protected]:offsite.pub
[email protected]’s password: (enter password, not new
passphrase!)
id_dsa.pub 100% |*| 614 00:00
[offsite]$ scp .ssh/id_dsa.pub [email protected]:offsite.pub
[email protected]’s password: (enter password, not new
passphrase!)
id_dsa.pub 100% |*| 614 00:00

在安装完新的公钥后,我们就可以使用创建私钥和公钥时指定的口令来登录到每一台机器。现在,登录到每台机器,并将 offsite.pub 文件的内容附加到一个名为 authorized_keys 的文件中,这个文件存储在每台远程机器的 .ssh 目录下。我们可以使用一个文本编辑器或者简单地使用 cat 命令来将 offsite.pub 文件的内容附加到 authorized_keys 文件:

清单 5. 将 offsite.pub 添加到已授权密钥列表
[offsite]$ ssh [email protected]
[email protected]’s password: (enter password, not new
passphrase!)
[server1]$ cat offsite.pub >> ./ssh/authorized_keys

接下来的步骤是考虑一些额外的安全性。首先,我们修改 .ssh 的访问权限,以使得只有所有者有读、写和执行的权限。然后,我们确保 authorized_keys 文件只能由所有者来访问。最后,将先前上传的 offsite.pub 密钥文件删除,因为再也不需要它了。确保设置适当的访问权限很重要,因为 OpenSSH 服务器可能会拒绝使用具有不安全访问权限的密钥。

清单 6. 使用 chmod 修改权限
[server1]$ chmod 700 .ssh
[server1]$ chmod 600 ./ssh/authorized_keys
[server1]$ rm offsite.pub
[server1]$ exit

在服务器 2 上完成同样的步骤后,我们就可以返回到离线存储机器上来测试新的口令类型的访问。在离线服务器上您可以输入下面的内容:

[offsite]$ ssh -v [email protected]

在检验您的帐号现在可以使用新的口令而不是原来的密码来访问远程的服务器时,使用 -v 或 verbose 标记选项来显示调试信息。调试输出除了能让您在一个高的层面上观察到认证过程是如何工作的之外,还可以显示出您可以以其他方式无法得到的重要信息。在以后的连接中您可能并不需要指定 -v 标记;但是在测试连接时它相当有用。

使用 ssh-agent 自动化机器访问

ssh-agent 程序如同一个看门人,它根据需要安全地提供对安全密钥的访问。ssh-agent 启动后,它就会在后台运行,并且可以由 ssh 和 scp 程序等其他 OpenSSH 应用程序所使用。这就使得 ssh 程序可以请求一个已经解密了的密钥,而不是在每次需要时向您询问私钥的安全口令。

让我们来仔细研究一下 ssh -agent。ssh-agent 运行时它会输出 shell 命令:

清单 7. ssh-agent 应用
[offsite]$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-XX1O24LS/agent.14179; export SSH_AUTH_SOCK;
SSH_AGENT_PID=14180; export SSH_AGENT_PID;
echo Agent pid 14180;

我们可以使用 shell 的 eval 命令来让 shell 执行 ssh-agent 显示的输出命令:

[offsite]$ eval ssh-agent
Agent pid 14198

eval 命令告诉 shell 去评价(执行)ssh-agent 程序生成的命令。确保您指定的是反引号()而不是单引号!执行后, evalssh-agent` 语句将返回代理的进程标识符。在幕后, SSH_AUTH_SOCK 和 SSH_AGENT_PID shell 变量已经被导出而可以使用。您可以将它们显示在 shell 控制台中来查看它们的值:

[offsite]$ echo $SSH_AUTH_SOCK
/tmp/ssh-XX7bhIwq/agent.14197

$SSH_AUTH_SOCK (SSH Authentication Socket 的缩写)是一个本地套接字的位置,应用程序可以通过它来与 ssh-agent 通信。将 eval ssh-agent 语句加入到您的 ~/.bash_profile 文件以确保 SSH_AUTH_SOCK 和 SSH_AGENT_PID 始终被注册。

ssh-agent 现在就已经成为一个后台进程,可以用 top 和 ps 命令查看得到。

现在我们就已经可以使用 ssh-agent 共享我们的口令。为此,我们必须使用一个名为 ssh-add 的程序,这个程序将我们的口令添加(发送)到运行着的 ssh-agent 程序。

清单 8. 用于免口令登录的 ssh-add
[offsite]$ ssh-add
Enter passphrase for /home/accountname/.ssh/id_dsa: (enter passphrase)
Identity added: /home/accountname/.ssh/id_dsa
(/home/accountname/.ssh/id_dsa)

现在,当我们访问 server1 时,不会再被提示输入口令:

[offsite]$ ssh [email protected]
[server1]$ exit

如果您还不相信,那么尝试去掉( kill -9 )ssh-agent 进程,然后重新连接 server1。这一次,您将注意到,server1 将会询问存储在 .ssh 目录下 id_dsa 中的私钥的口令:

[offsite]$ kill -9 $SSH_AGENT_PID
[offsite]$ ssh [email protected]
Enter passphrase for key ‘/home/accountname/.ssh/id_dsa’:

使用 keychain 简化密钥访问

到现在为止,我们已经了解了几个 OpenSSH 程序(ssh、scp、ssh-agent 和 ssh-add),而且我们已经创建并安装了私钥和公钥来启用一个安全而且自动的登录过程。您可能已经意识到,大部分设置工作只需要进行一次。例如,创建密钥、安装密钥、通过 .bash_profile 执行 ssh-agent 的过程在每台机器只需要进行一次。那真是好消息。

不太理想的消息是,我们每次登录到离线的机器上时,都必须调用 ssh-add,而且,ssh-agent 与我们将要用来自动化备份工作的 cron 调度进程并不直接兼容。cron 进程不能与 ssh-agent 通信的原因是,cron 作业是作为 cron 的子进程来执行,这样它们就不会继承 $SSH_AUTH_SOCK shell 变量。

幸运的是,有一个解决方案不但可以消除 ssh-agent 和 ssh-add 的局限,而且可以让我们使用 cron 来自动进行各种需要对其他机器进行安全地无密码访问的过程。在他 2001 年发表的三篇 developerWorks 系列文章中,即 OpenSSH key management(参阅 参考资料 以获得链接),Daniel Robbins 介绍了一个名为 keychain 的 shell 脚本,它是 ssh-add 和 ssh-agent 的一个前端,简化了整个无密码的过程。随着时间的过去,keychain 脚本已经经历了很多改进,现在由 Aron Griffis 维护,其最新的 2.3.2-1 发布版本公布于 2004 年 6 月 17 日。

keychain shell 脚本太长以致于无法在本文中列出,因为精心编写的脚本中包括了很多错误检测、丰富的文档以及非常多的跨平台代码。不过,keychain 可以自项目的 Web 站点上方便地下载得到(参阅 参考资料以获得链接)。

下载并安装了 keychain 后,使用它就很简单了。只需要登录到每台机器并将下面两行添加到每个 .bash_profile 文件:

keychain id_dsa
. ~/.keychain/$HOSTNAME-sh

在您第一次重新登录到每台机器时,keychain 将向您询问口令。不过,除非机器被重新启动,否则,以后再登录时,keychain 将不会再要求您重新输入口令。最好的是,cron 任务现在可以使用 OpenSSH 命令来安全地访问远程的机器,而不需要交互地使用口令。更好的安全和更容易的使用,现在我们已经兼得。

清单 9. 在每台机器上初始化
KeyChain 2.3.2; http://www.gentoo.org/projects/keychain
Copyright 2002-2004 Gentoo Technologies, Inc.; Distributed under the
GPL

  • Initializing /home/accountname/.keychain/localhost.localdomain-sh
    file…
  • Initializing /home/accountname/.keychain/localhost.localdomain-csh
    file…
  • Starting ssh-agent
  • Adding 1 key(s)…
    Enter passphrase for /home/accountname/.ssh/id_dsa: (enter passphrase)

脚本化备份过程

我们的下一个任务是创建执行必要的备份过程的 shell 脚本。目标是执行服务器 1 和 2 的完全数据库备份。在我们的例子中,每个服务器都运行着 MySQL 数据库服务器,我们使用 mysqldump 命令行工具来将一些数据库表导出到一个 SQL 输入文件中。

清单 10. 服务器 1 的 dbbackup.sh shell 脚本

#!/bin/sh

change into the backup_agent directory where data files are stored.

cd /home/backup_agent

use mysqldump utility to export the sites database tables

mysqldump -u sitedb -pG0oDP@sswrd –add-drop-table sitedb –tables
tbl_ccode tbl_machine tbl_session tbl_stats > userdb.sql

compress and archive

tar czf userdb.tgz userdb.sql

在服务器 2 上,我们将设置一个类似的脚本来备份站点数据库中给出的独有表单。每个脚本都通过下面的步骤标记为可执行的:

[server1]:$ chmod +x dbbackup.sh

在服务器 1 和 2 上设置了 dbbackup.sh 后,我们返回到离线的数据服务器,在那里我们将创建一个 shell 脚本来调用各个远程 dbbackup.sh 脚本并随后传输压缩的(.tgz)数据文件。

清单 11. 用在离线的数据服务器上的 backup_remote_servers.sh shell 脚本

#!/bin/sh

use ssh to remotely execute the dbbackup.sh script on server 1

/usr/bin/ssh [email protected] “/home/backup_agent/dbbackup.sh”

use scp to securely copy the newly archived userdb.tgz file

from server 1. Note the use of the date command to timestamp

the file on the offsite data server.

/usr/bin/scp [email protected]:/home/backup_agent/userdb.tgz
/home/backups/userdb-$(date +%Y%m%d-%H%M%S).tgz

execute dbbackup.sh on server 2

/usr/bin/ssh [email protected] “/home/backup_agent/dbbackup.sh”

use scp to transfer transdb.tgz to offsite server.

/usr/bin/scp [email protected]:/home/backup_agent/transdb.tgz
/home/backups/transdb-$(date +%Y%m%d-%H%M%S).tgz

backup_remote_servers.sh shell 脚本使用 ssh 命令来执行远程服务器上的脚本。由于我们已经设置的无密码的访问,ssh 命令可以通过离线的服务器在服务器 1 和 2 上远程地执行命令。感谢 keychain,整个认证过程现在可以自动完成。

调度

我们的下一个步骤,也是最后一个步骤,是调度 backup_remote_servers.sh shell 脚本在离线的数据存储服务器上的执行。我们将向 cron 调度服务器中添加两个条目,以要求每天执行备份脚本两次,3:34 执行一次,8:34 再执行一次。在离线的服务器上使用 edit( -e )选项调用 crontab 程序。

[offsite]:$ crontab -e

crontab 调用 VISUAL 或 EDITOR shell 环境变量所指定的默认的编辑器。然后,输入两个条目并保存和关闭文件。

清单 12. 离线的服务器上的 Crontab 条目
34 3 /home/backups/remote_db_backup.sh
34 20
/home/backups/remote_db_backup.sh

一个 crontab 行包括两个主要部分,时间表部分和后面的命令部分。时间表分为多个域,用来指定一个命令应该何时执行:

清单 13. Crontab 格式
+—- minute
| +—– hour
| | +—— day of the month
| | | +—— month
| | | | +—- day of the week
| | | | | +– command to execute
| | | | | |
34 3 * /home/backups/remote_db_backup.sh

检验您的备份

您应该对备份进行例行地检查,以确保程序正确进行。自动程序可以使烦琐的工作得到避免,但是永远不能因此而懒惰。如果您的数据值得备份,那么它也值得您时常进行抽样检查。

考虑添加一个 cron 作业来提醒您自己至少每个月对备份进行一次检查。另外,经常修改安全密钥也是一个好主意,同样您也可以调度一个 cron 作业来提醒您做这件事。

另外的安全防范

要获得更高的安全性,可以考虑在每台机器上安装并配置一个入侵检测系统(ntrusion Detection System,IDS),比如 Snort。可以预见,当入侵正在发生或者近期发生过时,IDS 将会通知您。IDS 到位后,您将可以添加其他层次上的安全,比如对您的备份进行数字签名和加密。

GNU Privacy Guard(GnuPG)、OpenSSL 和 ncrypt 等流行的开放源代码工具可以支持通过 shell 脚本对存档文件进行加密,不过不建议在没有 IDS 提供的额外层次保护的情况下这样做(参阅 参考资料 以获得关于 Snort 的更多信息)。

结束语

本文向您展示了如何让您的脚本在远程服务器执行以及如何执行安全自动的文件传输。我希望您能由此得到灵感而开始考虑保护您自己的重要数据,并使用 OpenSSH 和 Snort 等开放源代码工具来构建新的解决方案。