1.  概述

amazon web services 中的 Amazon Simple Storage Service (Amazon S3) 是一个很好的在线文件存储集群服务,安全可靠、接口丰富,并且收费价钱也比较合理(1G 数据存储一个月只要 10 美分左右)。这比为了实现服务器文件备份专门准备一台服务器划算多了,而且 S3 的存储服务器可以选择在美国或者欧洲(最好还是选日本区,传输速度比较快),于是和被备份的服务器自然形成了跨地域的分布式存储,数据抵抗自然灾害的能力由此提高了很多。本文简要介绍将 Linux 服务器上的文件增量备份到 S3 服务上的配置实现方法。

2.  Duplicity 简介

官方网站位于 http://duplicity.us 。这是一个使用 Python 语言实现的文件增量备份工具,其利用 rsync 底层类库,实现仅把文件中变化了的数据存入增量备份包的工作方式(类似给文件生成 Patch 补丁的方式),调用 pgp 对数据包进行签名和加密,而且支持本地文件系统、远端 ftp、ssh/scp、rsync、WebDAV、WebDAVs、HSi 以及 Amazon S3 等非常丰富的备份存储介质。

2.1  特别注意事项!

duplicity 的文件如果损坏一点点,就会导致恢复失败。特别是在增量备份的情况下,如果有中间版本损坏,会导致从此之后的数据都完全恢复不出来(就只能试着手工抢救文件了,困难并且有相当可能仍然恢复不回来)。

所以对重要数据,应经常做全量备份(用 --full-if-older-than 参数)。未来不再更新的最终版本数据,更是应该在 duplicity 之外保留一个全量备份,来避免介质损坏。

此外,可以启用 par2 机制为数据包增加冗余存储修复功能。方法是在声明备份存储介质时声明 par2+ 前缀。

3.  Duplicity 安装方法

3.1  通过 snap 安装

只要 Linux 发行版自带的 Duplicity 版本不够新,都倾向于使用 snap 环境来进行安装。这样版本够新,可以很好地适应 S3 等外围接口的 api 变化。而且这种方式也可以避免干扰系统类库的版本。

安装最新稳定版:snap install duplicity --classic

3.2  手工安装

虽然 Debian 发行版已经提供了 Duplicity 的软件包,但 Debian Stable 带的 Duplicity 经常过于古老,以至于缺少很多关键功能。所以我们经常得另想办法搞定高版本的。(但其实很不推荐手工安装,容易污染系统类库的版本。非要这么干的话,可以考虑安装到 virtualenv 下。)

  1. 预先准备 Python3 环境。
  2. 安装源代码安装所必须的头文件:apt-get install python3-dev librsync-dev
  3. 准备 Duplicity 源代码安装所需要的编译环境:apt-get install build-essential duplicity
  4. 安装 Duplicity 依赖的命令行工具:apt-get install par2
  5. http://duplicity.us/index.html 的 Download 章节给的网址下载源代码包,解压。
  6. 处理依赖关系 pip3 install -r requirements.txt
    • 其中有些软件包的安装过程极其消耗内存和 CPU,可能会卡住。出现这种情况的话,可以强停,改为安装 Debian 发行版提供的对应软件包。
  7. 安装 duplicity: python3 setup.py install
  8. 应该已经顺利安装成功了,“duplicity --version”应该显示是 0.8.23 以上的版本。

3.3  从 deb 包进行安装

手工安装会使得 Debian 的 dpkg 管理工具搞不清 python-boto 和 duplicity 包的当前状况,因此如果发行版自带软件包的版本提供的功能够用了的话,也可以使用 Debian 的官方包。

3.4  在 Mac 下安装

Darwin Ports 有提供这个包,虽然不算很新。装好 Darwin Ports 之后,“sudo port install duplicity”即可,详细可以参照 http://duplicity.darwinports.com/

4.  用 Duplicity 来备份数据

4.1  简单试用

先不管 S3,我们先玩一下 duplicity 增加一点感性认识:

 duplicity /etc file:///tmp/dup_test
注意这里“file:”后面是三个斜杠。运行之后程序会问“GnuPG passphrase:”,并且让再确认输入一次,这大致可以理解成是一个标记此次备份的签名,如果 duplicity 在以前进行过的备份中找不到这个签名,则会进行完整备份,否则进行增量备份。这个签名不可以为空,此次测试,随便输入两次 test 就可以了。运行完成之后在 /tmp/dup_test 文件夹就可以看到生成的备份包了。

如果我们不希望 duplicity 运行时候问“GnuPG passphrase:”怎么办呢,可以把这个参数设置成 PASSPHRASE 环境变量。比如我们在刚才运行 duplicity 之前,可以先执行“export PASSPHRASE=test”就可以把手动输入签名的步骤给省了。

4.2  备份到 S3

下面我们来考虑和 S3 之间的联动,这需要 boto3 这个包。

boto3 需要进行一些设置才能正常工作。首先要在 http://aws.amazon.com 注册 Amazon Simple Storage Service (Amazon S3) 服务的帐号,并且确定付款方式(比如通过Visa信用卡支付美元)。然后查找自己访问 S3 服务的 ID 和 Key(在AWS注册并登陆后,页面右上角附近有一个写着“Your Web Services Account”的黄色下拉菜单,鼠标在这个菜单上停留,选择“AWS Access Identifiers”即可以查到自己的 Access Key ID 以及 Secret Access Key,这两个相当于访问 S3 用的用户名和密码)。这两个参数通过环境变量传递给duplicity(对于0.5.00版本来说),也就是需要:

export AWS_ACCESS_KEY_ID=<your access key>
export AWS_SECRET_ACCESS_KEY=<your access key>
这样以后 duplicity 以及 boto3 就会使用这组 ID 和 Key 来访问 S3 存储空间。

此时如果我们把前面测试用的指令改成

 duplicity /etc s3:///BucketName
这里 BucketName 是在 Amazon 上的一个 Bucket 名称,只有在 Bucket 下面,才能存储文件。这个 Bucket 需要提前创建好,最新的 duplicity 已经不会再自动创建对应 Bucket 了。

如果要取回备份的文件,可以类似这样:

 duplicity s3:///BucketName /home/tmp
这里注意取回时的 PASSPHRASE 要和备份时的一致,否则无法成功取回文件。

duplicity 的详细使用说明参见“man duplicity”或者 http://duplicity.us/vers8/duplicity.1.html

5.  安全性探讨

我的理解是 duplicity 在生成备份文件包的时候用 pgp 加密过了,所以理论上备份文件包被别人弄走了也不怕,不过我没有仔细确认过这一点。

另外,S3 有自身的权限控制机制,参见S3 ACLs。我的 S3 空间默认就是屏蔽公众访问的,所以权限控制这一块其实我也没有更仔细地去确认过。。

6.  问题与技巧

6.1  关于 S3 访问工具

如果想要登陆 S3 操作已经存储的文件备份,那么最好还是有一个对应的客户端才方便。Mac 下可以使用 Transmit。考虑所有平台的话,有开源的 Java 客户端实现 JetS3t Cockpit。另外听说收钱的 Jungle Disk 也是相当好用的。

绝大多数 Linux 发行版带有一个叫 s3cmd 的包,是一个基于 Python 完成的命令行工具,功能非常全。

6.2  关于403 Forbidden错误

有时访问 S3 时,会得到一个 S3ResponseError 错误,并且说 403 Forbidden。那是因为 duplicity 所在主机的时间与 S3 服务器差别太大(超过了 10 分钟)造成的。仔细看错误提示,里面有给出服务器当前时间和客户端当前时间。可以用 ntpdate 等互联网对时服务校正时间,或者干脆用“date --set="2008-01-23 13:47:43"”这样的指令手工进行修正。

6.3  关于Invalid SSH password错误

有时访问 ssh 或者 scp 时,会得到 Invalid SSH password 错误提示,虽然已经用类似 ssh://user:pass@host 这样的格式设置了正确的登陆密码。此时应当放弃在 ssh:// 中写入密码,并且给 duplicity 增加 --ssh-askpass 运行参数,即可以正确完成向 ssh 或者 scp 进行的数据备份工作。

6.4  一次备份多个目录的方法

当需要一次备份多个目录时,可以这么写:

 duplicity --include /etc --include /var/www --exclude '**' / file:///usr/local/backup

6.5  备份任务的自动执行

这里要用到 Cron 了。我在 root 用户下用 crontab -e 设定每天凌晨 3 点执行 /root/script/backup.sh 脚本:

 0 3 */1 * * /root/script/backup.sh
backup.sh 脚本中的内容则是:
export PASSPHRASE=backup
export AWS_ACCESS_KEY_ID=<your access key>
export AWS_SECRET_ACCESS_KEY=<your access key>
duplicity --include /etc --include /var/www --exclude '**' / par2+s3:///elias.cn-backup
注意这里要把 elias.cn-backup 改成你自己的 bucket 名字。

注意如果 S3 存储桶不在美国大区,那么还得为 duplicity 设定 --s3-region-name 参数来指明所在分区。

给 backup.sh 用“chmod +x”添加可执行权限。

6.6  备份MySQL

duplicity 自己只能备份文件系统上的东西,我们可以通过与其他脚本的配合来解决 MySQL、SVN 等服务的备份问题。比如我们把 backup.sh 改成这样:

 
export PASSPHRASE=backup
export AWS_ACCESS_KEY_ID=<your access key>
export AWS_SECRET_ACCESS_KEY=<your access key>
mysqldump --password=YourSqlPass --opt --skip-extended-insert database > /root/backup/backup-file.sql
duplicity --include /etc --include /var/www --include /root/backup --exclude '**' / s3:///elias.cn-backup

6.7  “GnuPG exited non-zero”错误

如果 Duplicity 使用过程中遇到如下错误并造成程序运行中止:

  File "/var/lib/python-support/python2.4/GnuPGInterface.py", line 639, in wait
    raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8)
IOError: GnuPG exited non-zero, with code 131072

简单来讲,这是因为 Duplicity 运行过程中调用 GnuPG 进行处理时,GnuPG 出错了。这时应该查询系统的错误信息日志(比如dmesg)看看到底 GnuPG 是为什么出错。我当时遇到这个问题时,是 GnuPG 把系统内存用光了导致出错。我备份后删除了历史备份存档,让 Duplicity 需要检查的历史备份深度减少,就可以大致改善这个内存占用问题。(比如,让 Duplicity 删除15天以前的备份存档。)

6.8  “Invalid data - SHA1 hash mismatch for file”时如何手工挽救数据

参考 Restoring by Hand 一文。

主要原理是先用 gpg 解密 duplicity 的备份包,然后用 tar 解包就是了。类似这样:

gpg --multifile --decrypt duplicity-full.20110127T131352Z.*.difftar.gpg
和这样:
 for t in duplicity-full.20110127T131352Z.*.difftar; do tar xf $t; done

6.9  让 Duplicity 更健壮可靠

有时(比如由于网络原因,或者多个 duplicity 指令同时在运行并且在备份同一份文件),duplicity 的备份过程会没有完全完成,在 S3 等存储上留下不完整的包,这有可能影响后续备份的正确进行。所以通常我会在执行 duplicity 的备份指令之前,先执行一下

 duplicity cleanup --force s3:///elias.cn-backup
指令来清除不完整的备份包。

一定要注意避免对 cleanup 指令使用 --extra-clean 参数,这个参数会在遇到一个损坏包时,删除整个备份链条上的增量备份包的索引,导致全部备份无法用于 restore 。

如果确实不放心,可以考虑利用 s3cmd 工具的 sync 指令,给 duplicity 的 S3 存储空间做一个镜像,以便误删除数据时可以找回。用 Amazon 官方的 CLI 工具应该也能做到同样效果。

 aws s3 sync s3://bucket1/folder1 s3://bucket2/folder2
再或者,也许开启 S3 空间的数据版本记录,也有助于这种找回。

6.10  让 Duplicity 的文件存储更健壮可靠

由于存储介质本身的可靠性问题,在某些小概率情况下,Duplicity 生成的具体备份数据存储文件可能会出现极其微小的损坏,于是导致在恢复数据时 gpg 解密失败(gpg 特别严格,几乎无法接受哪怕是一个字节的错误),可以让 Duplicity 在生成备份数据文件的时候,用 par2 做一个冗余恢复记录(默认会多占用 10% 左右的存储空间)。

Duplicity 大致从 0.7 版本开始能比较靠谱地支持这个特性,使用方法是在目标存储介质的 url 前加 “par2+”这样的前缀,例如“s3:///bucket2/folder2”就改成写“par2+s3:///bucket2/folder2”。

7.  其他类似解决方案

其实 Perl 实现的 backup-manager 也是能够备份到 S3 上的,etch 自带的版本就可以。但是试着向 S3 进行备份,会报错。查看 /var/log/user.log,能看到详细的错误提升,原因是缺少了 Net::Amazon::S3 这个库。可以用“cpan -i Net::Amazon::S3”来安装,也可以直接用 Debian 里面的 libnet-amazon-s3-perl 这个包(这个包 etch 里面没有……得自己想办法盗用高版本的)。此外还得安装 libxml2-dev 这个包。如果仍然不能正常工作的话,就看 user.log 的错误提示吧。

但是 backup-manager 工作在 S3 上有一个重大问题:当上传 3M 的文件时,backup-manaer 会占用系统 26M 左右的内存;上传单个文件 10M 大小的文件时,大约占用 64M 左右的内存。这怕是有点儿恐怖。因此虽然 backup-manager 别的功能都感觉相当不错,仍然是放弃了它,改用 duplicity。而 duplicity 无论怎样使用,占用的内存都保持在 10M 左右。

GlossyBlue theme adapted by David Gilbert
Powered by PmWiki