当了解了etckeeper之后,读者可能会如我一样的提问到:“有没有像etckeeper一样的工具,但是能备份任意的文件和目录呢?”
我在Google上搜索类似的工具无果,终于决定动手开发一个,因为无论是我还是我的客户,都需要一个更好用的备份工具。这就是Gistore。
Gistore = Git + Store
2010年1月,我在公司的博客上发表了Gistore 0.1版本的消息,参见:http://blog.ossxp.com/2010/01/406/。并将Gistore的源代码托管在了Github上,参见:http://github.com/ossxp-com/gistore。
Gistore出现受到了etckeeper的启发,通过Gistore用户可以对全盘任何目录的数据纳入到备份中,定制非常简单和方便。特点有:
说明:Gistore只能运行在Linux/Unix上,而且最好以root用户身份运行,以避免因为授权问题导致有的文件不能备份。
从源代码安装Gistore,可以确保安装的是最新的版本。
先用git从Github上克隆代码库。
$ git clone git://github.com/ossxp-com/gistore.git
执行setup.py脚本完成安装
$ cd gistore
$ sudo python setup.py install
$ which gistore
/usr/local/bin/gistore
Gistore是用Python语言开发,已经在PYPI上注册:http://pypi.python.org/pypi/gistore。就像其他Python软件包一样,可以使用easy_install进行安装。
确保机器上已经安装了setuptools。
Setuptools的官方网站在http://peak.telecommunity.com/DevCenter/setuptools。几乎每个Linux发行版都有setuptools的软件包,因此可以直接用包管理器进行安装。
在Debian/Ubuntu上可以使用下面的命令安装setuptools:
$ sudo aptitude install python-setuptools
$ which easy_install
/usr/bin/easy_install
使用easy_install命令安装Gistore
$ sudo easy_install -U gistore
先熟悉一下Gistore的术语:
备份库:通过gistore init命令创建的,用于数据备份的数据仓库。备份库包含的数据有:
备份项:可以为一个备份库指定任意多的备份项目。
例如备份/etc目录,/var/log目录,/boot/grub/menulist文件等。
备份项在备份库的.gistore/config文件中指定,如上述备份项在配置文件中写法为:
[store /etc]
[store /var/log]
[store /boot/grub/menu.lst]
备份任务:在执行gistore命令时,可以指定一个任务或者多个任务。
任务别名。
在使用Gistore开始备份之前,必须先初始化一个备份库。命令行格式如下:
gistore init [备份任务]
初始化备份库的示例如下:
将当前目录作为备份库进行初始化:
$ mkdir backup
$ cd backup
$ gistore init
将指定的目录作为备份库进行初始化:
$ sudo gistore init /backup/database
当一个备份库初始化完毕后,包含下列文件和目录:
在每一个备份库的.gistore目录下的config文件是该备份库的配置文件,用于记录Gistore的备份项内容以及备份回滚设置等。
例如下面的配置内容:
1 # Global config for all sections
2 [main]
3 backend = git
4 backup_history = 200
5 backup_copies = 5
6 root_only = no
7 version = 2
8
9 [default]
10 keep_empty_dir = no
11 keep_perm = no
12
13 # Define your backup list below. Section name begin with 'store ' will be backup.
14 # eg: [store /etc]
15 [store /opt/mailman/archives]
16 [store /opt/mailman/conf]
17 [store /opt/mailman/lists]
18 [store /opt/moin/conf]
19 [store /opt/moin/sites]
如何理解这个配置文件呢?
第2行到第7行的[main]小节用于Gistore的全局设置。
第3行设置了Gistore使用的SCM后端为Git,这是目前唯一可用的设置。
第4行设置了Gistore的每一个历史分支保存的最多的提交数目,缺省200个提交。当超过这个提交数目,进行备份回滚。
第5行设置了Gistore保存的历史分支数量,缺省5个历史分支。每当备份回滚时,会将备份主线保存到名为gistore/1的历史分支。
第6行设置非root_only模式。如果开启root_only模式,则只有root用户能够执行此备份库的备份。
第7行设置了Gistore备份库的版本格式。
第9行开始的[default]小节设置后面的备份项小节的缺省设置。在后面的[store ...]小节可以覆盖此缺省设置。
第10行设置是否保留空目录。暂未实现。
第11行设置是否保持文件属主和权限。暂未实现。
第15行到第19行是备份项小节,小节名称以store开始,后面的部分即为备份项的路径。
如[store /etc]的含义是:要对/etc目录进行备份。
当然可以直接编辑.gistore/config文件,通过添加或者删除[store...]小节的方式管理备份项。Gistore还提供了两个命令进行备份项的管理。
添加备份项
进入备份库目录,执行下面的命令,添加备份项/some/dir。注意备份项要使用全路径,即要以“/”开始。
$ gistore add /some/dir
删除备份项
进入备份库目录,执行下面的命令,策删除备份项/some/dir。
$ gistore rm /some/dir
查看备份项
进入备份库目录,执行gistore status命令,显示备份库的设置以及备份项列表。
$ gistore status
Task name : system
Directory : /data/backup/gistore/system
Backend : git
Backup capability : 200 commits * 5 copies
Backup list :
/backup/databases (--)
/backup/ldap (--)
/data/backup/gistore/system/.gistore (--)
/etc (AD)
/opt/cosign/conf (--)
/opt/cosign/factor (--)
/opt/cosign/lib (--)
/opt/gosa/conf (--)
/opt/ossxp/conf (--)
/opt/ossxp/ssl (--)
从备份库的状态输出,可以看到:
备份库的路径是/data/backup/gistore/system。
备份库有一个任务别名为system。
备份的容量是200*5,如果按每天一次备份计算的话,总共保存1000天,差不多3年的数据备份。
在备份项列表,可以看到多达10项备份列表。
每个备份项后面的括号代表其备份选项,其中/etc的备份选项为AD。A代表记录并保持授权,D的含义是保持空目录。
执行备份任务非常简单:
进入到备份库根目录下,执行:
$ sudo gistore commit
或者在命令行上指定备份库的路径。
$ sudo gistore ci /backup/database
说明:ci为commit命令的简称。
备份库中的repo.git就是备份数据所在的Git库,这个Git库是一个不带工作区的裸库。可以对其执行git log命令来查看备份日志。
因为并非采用通常.git作为版本库名称,而且不带工作区,需要通过--git-dir参数制定版本库位置,如下:
$ git --git-dir=repo.git log
当然,也可以进入到repo.git目录,执行git log命令。
下面是我公司内的服务器每日备份的日志片断:
commit 9d16b5668c1a09f6fa0b0142c6d34f3cbb33072f
Author: Jiang Xin <jiangxin@ossxp.com>
Date: Thu Aug 5 04:00:23 2010 +0800
Changes summary: total= 423, A: 407, D: 1, M: 15
------------------------------------------------
A => etc/gistore/tasks/Makefile, opt/cosign/lib/share/locale/cosign.pot, opt/cosign/lib/templates-local.old/expired_error.html, opt/cosign/lib/templates-local.old3/error.html, opt/cosign/lib/templates/inc/en/0020_scm.html, ...402 more...
D => etc/gistore/tasks/default
M => .gistore/config, etc/gistore/tasks/gosa, etc/gistore/tasks/testlink, etc/group, etc/gshadow-, ...10 more...
commit 01b6bce2e4ee2f8cda57ceb3c4db0db9eb90bbed
Author: Jiang Xin <jiangxin@ossxp.com>
Date: Wed Aug 4 04:01:09 2010 +0800
Changes summary: total= 8, A: 7, M: 1
-------------------------------------
A => backup/databases/blog_bj/blog_bj.sql, backup/databases/ossxp/mysql.sql, backup/databases/redmine/redmine.sql, backup/databases/testlink/testlink-1.8.sql, backup/databases/testlink/testlink.sql, ...2 more...
M => .gistore/config
commit 15ef2e88f33dfa7dfb04ecbcdb9e6b2a7c4e6b00
Author: Jiang Xin <jiangxin@ossxp.com>
Date: Tue Aug 3 16:59:12 2010 +0800
Changes summary: total= 2665, A: 2665
-------------------------------------
A => .gistore/config, etc/apache2/sites-available/gems, etc/group-, etc/pam.d/dovecot, etc/ssl/certs/0481cb65.0, ...2660 more...
commit 6883d5c2ca77caab9f9b2cfd68dcbc27526731c8
Author: Jiang Xin <jiangxin@ossxp.com>
Date: Tue Aug 3 16:55:49 2010 +0800
gistore root commit initialized.
从上面的日志可以看出:
如果想查看详细的文件变更列表?
使用下面的命令:
$ git --git-dir=repo.git show --stat 9d16b56
commit 9d16b5668c1a09f6fa0b0142c6d34f3cbb33072f
Author: Jiang Xin <jiangxin@ossxp.com>
Date: Thu Aug 5 04:00:23 2010 +0800
Changes summary: total= 423, A: 407, D: 1, M: 15
------------------------------------------------
A => etc/gistore/tasks/Makefile, opt/cosign/lib/share/locale/cosign.pot, opt/cosign/lib/templates-local.old/expired_error.html, opt/cosign/lib/templ
D => etc/gistore/tasks/default
M => .gistore/config, etc/gistore/tasks/gosa, etc/gistore/tasks/testlink, etc/group, etc/gshadow-, ...10 more...
.gistore/config | 4 +
backup/databases/redmine/redmine.sql | 44 +-
etc/apache2/include/redmine/redmine.conf | 40 +-
etc/gistore/tasks/Makefile | 1 +
etc/gistore/tasks/default | 1 -
etc/gistore/tasks/gosa | 2 +-
...
opt/gosa/conf/sieve-spam.txt | 6 +
opt/gosa/conf/sieve-vacation.txt | 4 +
opt/ossxp/conf/cron.d/ossxp-backup | 8 +-
423 files changed, 30045 insertions(+), 51 deletions(-)
在备份库的logs目录下,还有一个备份过程的日志文件logs/gitstore.log。记录了每次备份的诊断信息,主要用于调试Gistore。
所有的备份数据,实际上都在repo.git目录指向的Git库中维护。如何获取呢?
克隆方式检出
执行下面的命令,克隆裸版本库repo.git:
$ git clone repo.git data
进入data目录,就可以以Git的方式查看历史数据,以及恢复历史数据。当然恢复的历史数据还要拷贝到原始位置才能实现数据的恢复。
分离的版本库和工作区方式检出
还有一个稍微复杂的方法,就是既然版本库已经在repo.git了,可以直接利用它,避免克隆导致空间上的浪费,尤其是当备份库异常庞大的情况。
创建一个工作目录,如export。
$ mkdir export
设置环境变量,制定版本库和工作区的位置。注意使用绝对路径。
下面的命令中,用pwd命令获得当前工作路径,借以得到绝对路径。
$ export GIT_DIR=`pwd:file:`/repo.git
$ export GIT_WORK_TREE=`pwd:file:`/export
然后就可以进入:file:` export`目录,执行Git操作了。
$ git status
$ git checkout .
为什么没有历史备份?
当针对repo.git执行git log的时候,满心期望能够看到备份的历史,但是看到的却只有孤零零的几个备份记录。不要着急,可能是备份回滚了。
参见下节的备份回滚,会找到如何获取更多历史备份的方法。
我在开发Gistore时,最麻烦的就是备份历史的管理。如果不对备份历史进行回滚,必然会导致提交越来越多,备份空间占用越来越大,直至磁盘空间占慢。
最早的想法是使用git rebase。即将准备丢弃的早期备份历史合并成为一个提交,后面的提交变基到合并提交之上,这样就实现了对历史提交的丢弃。但是这样的操作即费时,又比较复杂。忽然又一天灵机一动,为什么不用分支来实现对回滚数据的保留?至于备份主线(master分支)从一个新提交开始重建。
回滚后master分支如何从一个新提交开始呢?较早的实现是直接重置到一个空提交(gistore/0)上,但是这样会导致接下来的备份非常耗时。一个更好的办法是使用git commit-tree命令,直接从回滚前的master分支创建新提交。在读者看到这本书的时候,我应该已经才用了新的实现。
具体的实现过程是:
如何找回历史备份?
通过上面介绍的Gistore回滚的实现方法,会知道当回滚发生后,主线master只包含两个提交。一个是上一次备份的数据,另外一个是最新的数据备份。似乎大部分备份历史被完全丢弃了。其实,可以从分支gistore/1中看到最近备份的历史,还可以从其他分支(如果有的话)会看到更老的历史。
查看回滚分支的提交历史:
$ git --git-dir=repo.git log gistore/1
通过日志找出要恢复的时间点和提交号,使用git checkout即可检出历史版本。
因为Gistore可以在任何目录下创建备份任务,管理员很难定位当前到底存在多少个备份库,因此需要提供一个机制,让管理员能够看到系统中有哪些备份库。还有,就是在使用Gistore时若使用长长的备份库路径作为参数会显得非常笨拙。任务别名就是用来解决这些问题的。
任务别名实际上就是在备份库在目录/etc/gistore/tasks下创建的符号连接。
为备份任务创建任务别名非常简单,只需要在/etc/gistore/tasks目录中创建的备份库的符号链接,该符号链接的名称,作为这些备份库的任务别名。
$ sudo ln -s /home/jiangxin/Desktop/mybackup /etc/gistore/tasks/jx
$ sudo ln -s /backup/database /etc/gistore/tasks/db
于是,就创建了两个任务别名,在以后执行备份时,可以简化备份命令:
$ sudo gistore commit jx
$ sudo gistore commit db
查看一份完整备份列表也非常简单,执行gistore list命令即可。
$ gistore list
db : /backup/database
jx : /home/jiangxin/Desktop/mybackup
当gistore list命令后面指定某个任务列表时,相当于执行gistore status命令,查看备份状态信息:
$ gistore list db
可以用一条命令对所有的任务别名执行备份:
$ gistore commit-all
在/etc/cron.d/目录下创建一个文件,如/etc/cron.d/gistore,包含如下内容:
## gistore backup
0 4 * * * root /usr/bin/gistore commit-all
这样每天凌晨4点,就会以root用户身份执行gistore commit-all命令。
为了执行相应的备份计划,需要将备份库在/etc/gistore/tasks目录下创建符号链接。
Gistore备份库的主体就是repo.git,即一个Git库。可以通过架设一个Git服务器,远程主机通过克隆该备份库实现双机备份甚至是异地备份。而且最酷的是,整个数据同步的过程是可视的、快速的和无痛的,感谢伟大而又神奇的Git。
最好使用公钥认证的基于SSH的Git服务器架设,因为一是可以实现无口令的数据同步,二是增加安全性,因为备份数据中可能包含敏感数据。
还有可以直接利用现成的/etc/gistore/tasks目录作为版本库的根。当然还需要在架设的Git服务器上,使用一个地址变换的小巧门。Gitosis服务器软件的地址变换魔法正好可以帮助实现。参见第31章第31.5节“轻量级管理的Git服务”。