发布于2019/05/31更新于2024/7/5
起先给这篇日志起的名是《WordPress站点和数据库的自动、异地备份:……》,虽然前后也修改了几次,但还是读起来不够理想。再想了一下,实际上这篇日志内容不止适用于WordPress站点,甚至不止适用于Web站点,也可以当做其他重要资源的备份参考。还有呢,这篇日志是在《网络服务配置笔记:……》的配置基础上完成的,很多备份方式的理念和脚本也是根据那篇日志里所述内容来决定的。哦,对了,文末也曾说大概应该会写的内容还包括“SSL证书定期自动更新”,后来查到如果Certbot是采用官方的软件包系统安装的话,在一些系统上会默认设置定期自动更新SSL证书的计划任务,当然这也都是可以自己添加cron job来实现的。
注意:这篇日志为我记录与获取方便而发布;内容中有很多处未涉及严格的考究和对选择的充分讨论,代码部分亦有存在纰漏的可能;请酌情参考,小心使用!
那么,结合在《网络服务配置笔记》中已经完成的工作,先讲讲这篇日志里所记录的备份工作的背景:
- Web服务和MySQL服务、数据库存储都在同一台VPS上
- 以TLD站点
*.site.com为例,所属用户组grpSiteCom的用户负责该TLD站点所有子站点文件的维护,该站点文件存储在/var/www/site_com/下:- Web站点文件位于:
/var/www/site_com/htdocs/ - 各类log文件位于:
/var/www/site_com/logs/
- Web站点文件位于:
- 由
site.com及其子站点所使用MySQL数据库的用户的权限一般是根据数据库使用情况来确定的,可能不适合用作数据库备份
其次,需要讲明的是配置备份工作的目标:
- 备份该TLD对应的Web站点文件,其他log等日志文件不备份
- 备份该TLD所使用的MySQL数据库
- 自动备份,定期备份,如按天、按周、按月等备份
- 维持最近X天、周、月的备份,以免占用过多存储空间
- 远程备份
- 以尽可能安全的方式完成
无论是备份Web站点文件还是MySQL数据库,都可以从(至少)两个层面来完成:一是在操作系统层面完成备份工作,二是以专用的Web应用程序来完成。对于第二种方法来说,不仅需要部署额外的Web站点为这些Web应用程序提供访问界面,还需要Web服务器拥有所需要备份文件的读权限。前者会引入更多的不安全因素,如在phpMyAdmin的简易安装中还做了额外的加固,以WordPress站点为例,若采用备份类插件也会有潜在的漏洞[如],此外,使用Web应用还不方便实现自动备份;而对后者来说,可能存在无法满足的情形。
选择在操作系统层面完成这里所想要的备份是最方便的了。那么,在Ubuntu系统上,考虑以这几点原则来部署备份:
- 使用
tar对Web站点文件进行存档,再加以压缩,如gzip、xz - 使用MySQL Server自带的
mysqldump对所需备份的MySQL数据库导出SQL文件,必要时对其进行压缩 - 建立相应的
cron条目,实现自动备份和定期备份的功能 - 利用
bash脚本实现对过期备份文件的清理功能 - 用
rsync从这台Web服务器与其他机器实现远程备份 - 建立专用的用户,作为备份文件的所有者和用于
rsync的SSH连接之用
结合上面讲明的备份工作背景、配置目标以及原则,下面记录配置备份的过程。
设置备份专用用户及备份文件夹
如果只做本地备份,那么用root账户完成文件打包、数据库导出等工作是完全可行和安全的。但考虑到远程备份,备份数据、甚至服务器内数据的安全性,就从这单一台Web服务器的安全性来决定,扩大到由Web服务器和远程备份服务器的安全性来决定了。而远程备份涉及到的两台服务器之间的通信,需要由账户验证来完成传输通道的建立。这里的“账户”并非必需是操作系统中的账户,如可以通过专用的FTP或FTPS服务器来实现远程备份,但如使用SFTP完成远程文件传输,则需要使用系统用户账户进行验证。因此,应当选择一个不具有超级权限的用户,甚至是可以满足工作要求的最低权限用户。
今次决定用rsync实现远程备份,无论在哪一端pull(拉取)或者push(推送)备份文件,都需要另一端的用户具有shell权限运行相应的rsync命令。此外,无论哪一端,也需要有用户的主目录(home directory),用于存储该用户的SSH密钥对(key pairs)和/或已验证主机的公钥(public key)。为什么不用密码登录呢?原因有两个:不安全,无法自动化。因此,在Ubuntu中正常建立一个用户就可以了:
# 建立专用于备份的用户 # 用户主目录/home/webbackup,默认bash shell sudo adduser webbackup # 锁定其密码登录(无法在控制台使用密码登录),不影响使用SSH密钥对验证登录 sudo passwd -l webbackup
其实Ubuntu系统中内置了一个名为“backup”的用户,是上源系统Debian中存在很久了的默认配置,给予的解释是用来做本地备份/恢复的。我们可以直接用这个已有的用户,但需要为其配置一个可用的shell。不过,这些内置用户有可能会在以后的发行版中有所变动(可是看起来又不怎么会变了),就有可能影响下面的备份工作流程,因此还是新建一个用户来的舒服。
接着建立一个专用来存放备份文件的目录。跟着上一篇日志,Web站点的根目录是/var/www/,既然是Web站点的备份,也就一齐放在这个目录下吧:
# 为TLD站点site.com建立备份文件夹 sudo mkdir -p /var/www/.webbackup/site_com # 设置站点site.com备份文件夹的所有权、文件夹权限及setgid权限 sudo chown -R webbackup:grpSiteCom /var/www/.webbackup/site_com sudo chmod 2750 /var/www/.webbackup/site_com
对Web站点文件存档并压缩
做这一点很简单,另外再加用打包文件时的日期作为站点备份文件名的一部分,并用xz进行压缩:
# 参数开关“-C {PATH}”指在路径“{PATH}”下执行,
# 这样得到的打包不会将从根“/”开始的“/var/www/site_com”包含在内
tar --xz -cf /var/www/.webbackup/site_com/site_com-$(date +%Y%m%d).tar.xz -C /var/www/site_com ./htdocs
需要注意的是,一般以root权限执行上面的命令,之后再更改所生成文件的所有者和读写权限。
对MySQL数据库导出SQL文件
对MySQL数据库的备份方法有很多种。使用Web应用自带的数据库备份工具,如果有提供的话,大概应该是最人性化、与Web应用兼容性最好的方法了。但可能不是很适合配置自动备份。MySQL官方提出了几种备份方法:
- 使用MySQL企业备份工具实现热备份:一听就不是免费的
- 用MySQL Server所带的
mysqldump备份:命令行程序,提供多个参数、选项控制备份行为 - 直接备份数据库所在文件夹:适合采用
MyISAM引擎的数据表,采用InnoDB引擎的数据表可能不合适 - ……
mysqldump是备份MySQL数据库的常用的工具,而且方便控制,容易集成到脚本中,也提供了参数选项来读入MySQL配置文件加载/覆写命令行参数。很多VPS提供商的教程也提供了如何用mysqldump备份数据库,比如可以参考Linode这篇。而mysqldump的解释、用法、局限性,应当咨询MySQL官方的mysqldump说明文档及其用于备份的参考指南。至于这篇日志里,使用的命令和Linode教程中的基本相同:
# {MYSQL_DATABASE_NAME} 是数据库名
mysqldump --defaults-extra-file=/var/www/.webbackup/.script/my.cnf --single-transaction {MYSQL_DATABASE_NAME} > /var/www/.webbackup/site_com/{MYSQL_DATABASE_NAME}-$(date +%Y%m%d).sql其中/var/www/.webbackup/.script/my.cnf文件中写入了具有所需权限的MySQL用户及其密码(如果密码中含有#符号,需要将整个密码用英文半角双引号"引用起来):
[client] user = resu password = ~drowssap~ # 如果密码有“#”字符 #password = "~dr#owssap~"
这里所说的权限,是指mysqldump所使用的MySQL用户需要具有对数据库“{MYSQL_DATABASE_NAME}”中数据表进行相应备份操作的权限,应当根据数据库中数据表的引擎和备份策略来确定。另外,如果需要额外的数据库导出行为,也需要用户具有导出行为所需要的SQL执行权限。最后,设置该MySQL配置文件的所有者和权限,以保障安全:
sudo chown root:root /var/www/.webbackup/.script/my.cnf sudo chmod 600 /var/www/.webbackup/.script/my.cnf
设立cron条目以实现自动按期备份
在/etc/cron.d/目录下建立一个用于备份工作的cron job文件:
sudo vim /etc/cron.d/backup
并编辑其内容:
# 在每天凌晨4点备份MySQL数据库“TEST” 0 4 * * * root mysqldump --defaults-extra-file=/var/www/.webbackup/.script/my.cnf --single-transaction TEST > /var/www/.webbackup/site_com/TEST-$(date +%Y%m%d).sql # 在每周第3天凌晨3点备份Web站点文件 0 3 * * 3 root tar --xz -cf /var/www/.webbackup/site_com/site_com-$(date +%Y%m%d).tar.xz -C /var/www/site_com ./htdocs
之后刷新或重启cron服务:
sudo service cron reload # 或 sudo service cron restart
上面的定期自动任务是由root用户执行的,生成的备份文件也所属root用户,有必要更改备份文件的所有者和权限。以MySQL数据库备份为例,将备份、压缩与权限设置写在同一个bash脚本文件中/var/www/.webbackup/.script/mysql_backup_TEST.sh:
#!/bin/bash
MYSQL_DBNAME="TEST"
MYSQL_CNF_FILE="/var/www/.webbackup/.script/my.cnf"
BAK_DOMAIN="site_com"
BAK_USER="webbackup"
BAK_GROUP="grpSiteCom"
BAK_PERM=660
BAK_DIR_BASE="/var/www/.webbackup/site_com"
MYSQLDUMP_EXEC="$(which mysqldump)"
BAK_FN_PART="${MYSQL_DBNAME}-$(date +%Y%m%d)"
BAK_PATH_SQL="${BAK_DIR_BASE}/${BAK_FN_PART}.sql"
BAK_PATH_TAR="${BAK_DIR_BASE}/${BAK_FN_PART}.tar.xz"
# mysqldump导出数据库为SQL文件
${MYSQLDUMP_EXEC} --defaults-extra-file=${MYSQL_CNF_FILE} --single-transaction ${MYSQL_DBNAME} > ${BAK_PATH_SQL}
# 以xz压缩SQL文件
tar --xz -cf ${BAK_PATH_TAR} -C ${BAK_DIR_BASE} ${BAK_PATH_SQL}
# 删除SQL文件
rm ${BAK_PATH_SQL}
# 设置所有者和权限
chown ${BAK_USER}:${BAK_GROUP} ${BAK_PATH_TAR}
chmod ${BAK_PERM} ${BAK_PATH_TAR}编辑好之后,为脚本文件增加执行权限,也应当建议设置该脚本文件的所有者为root,权限不高于740。最后,更改之前设置的cron job条目为:
# 在每天凌晨4点备份MySQL数据库“TEST”、压缩并处理文件权限 0 4 * * * root /var/www/.webbackup/.script/mysql_backup_TEST.sh
利用脚本实现过期备份文件清理
由于备份文件名已经包含了生成文件的日期部分,那来筛选过期文件就变得很简单,直接对文件名排序就可以了。我不知道有什么好的小工具可以来用,就自己写了一个直接、简单且“丑陋”的脚本。直接加在上一节的shell文件后面就可以:
# 如果单另一个脚本,应顶头写“#!/bin/bash”,并复制所需变量在文件开头
# 只保留最新的3个备份文件
BAK_FILE_RECENT=3
BAK_FILE_LIST=$(ls "${BAK_DIR_BASE}/${MYSQL_DBNAME}-*.tar.xz | sort -V -r)
BAK_FILE_NUM=0
for BAK_FILE_PER in ${BAK_FILE_LIST}
do
BAK_FILE_NUM=$(($BAK_FILE_NUM+1))
if [ $BAK_FILE_NUM -le $BAK_FILE_RECENT ]; then
continue
fi
rm ${BAK_FILE_PER}
done配置远程备份服务器以实现远程备份
远程备份,和异地备份应该是不同的概念。不过,是不是远程备份的服务器和源服务器不在同一地,就可以称作是异地备份了?(划掉上一句,有空的时候查一查学习一下。)
上面的几部分中,在Web服务器上已经做好了本地备份的配置,也设置了专门用于备份的用户。这一小节来配置远程备份。简单的远程备份,(我想)也就是将备份文件由Web服务器传输到另一台远程服务器上(吧),一时间想得到的方法有:
- FTP over TLS:或者称FTPS,是增加了TLS验证的FTP,和SFTP是不同的。
- SCP:Secure copy protocol,利用SSH协议加密。
- SFTP:SSH File Transfer Protocol,虽然也以FTP结尾,但与FTP/FTPS是不同的协议。
因为我也就用过这几个,还有很多其他的文件传输方法,可以咨询Wikipedia上关于多种文件传输方法的比较。上面这3种也都是安全的传输方法,但缺少一些类似“同步”的直接使用方式。虽然可以通过在Web服务器和远程服务器两端加以脚本实现“同步”也好、“增量备份”也好,但增加了很多的配置压力。因此,不得不提的是rsync恰恰可以满足我的需要:将Web服务器上的备份文件同步到远程服务器上,也就是说,保持两端的备份文件是一致的(,当然,也可以不同步文件的删除)。
为实现Web服务器和远程备份服务器两端的备份文件同步,也就是说,需要自动、定期的执行rsync任务,这可以通过设置cron任务来实现。此外,在同步这一过程中,很重要的一点是两端服务器的连接。在第一节中,已经在Web服务器上建立了备份专用的用户。考虑到密码登录因其不安全、无法自动验证登录,接着先配置由远程备份服务器到Web服务器的SSH密钥对验证。
还有,应当提前说的一点是,在远程备份服务器上,除了设置了日常管理的管理员用户之外,并无其他用户,服务器也无其他用途。因此,在这台服务器上以root用户完成备份文件同步,而同步过来的文件也属root用户所有,权限也暂时无需更改了。
差点有一点忘记讲,就是pull还是push的选择。对于从Web服务器发起文件传输到远程备份服务器的方式,也就是push(推送)的方式。而从远程备份服务器提起由Web服务器传输文件的方式,也就是pull(拉取)的方式。这两种方式都是可行的,对rsync来说本质上可能也没什么太大差异。不过,结合SSH的密钥验证过程,考虑下面的内容:
- 如果采用push的方式,则需要由Web服务器发起SSH连接到远程备份服务器,需要在远程备份服务器上存储Web服务器中某一用于备份同步的用户的SSH公钥。也就是说,Web服务器将具有远程备份服务器部分资源的访问权限。
- 如果采用pull的方式,则发起SSH连接的方向相反,由远程备份服务器发起SSH连接到Web服务器,因此需要在Web服务器上存储远程备份服务器中用于备份同步的用户的SSH公钥。也就是说,远程服务器将具有Web服务器部分资源的访问权限。
这样说起来可能还不是很清楚,不过看过这篇文章(竟然是……算了)之后,应该就可以给这两台服务器贴个标签了:相比我这个远程备份服务器,Web服务器是高危的。高危的意思也就是说,因为运行着不少服务器端软件、各种Web应用脚本,Web服务器更倾向于存在漏洞、容易被打开后门,而远程备份服务器没有其他的用途,相比之下安全的多。因此,在这个时候,对于最不安全的服务器,应给与其最少的可访问的机密资源。用最简单的白话说,就是如果rsync采用push方式实现备份同步,Web服务器则可以访问到远程备份服务器的部分资源,若是Web服务器被侵入,则远程备份服务器的那部分资源也一并暴露在外了。
通过这一段分析,应选择以pull方式用rsync完成备份文件的同步。即使Web服务器被攻陷,存储之上的远程备份服务器的用于备份同步的用户的SSH公钥,也并没有什么直接的使用价值。在之前已在Web服务器上建立了备份专用的用户webbackup的基础上,rsync远程备份同步的过程也即以下几步:
- 由远程备份服务器以
root身份打开SSH验证连接到Web服务器,登录用户为webbackup - 在Web服务器上
webbackup用户的已认证密钥中,通过其中的远程服务器的root用户的公钥,与远程备份服务器(拥有该公钥对应的私钥)完成验证,为webbackup用户生成bash shell - 从该shell发送rsync命令和参数,在Web服务器端运行对应rsync完成同步
服务器域名及IP配置(前提)
与上一篇的域名解析配置原则保持一致,为两台服务器分配不作为Web服务使用的域名,但没必要再加CNAME解析了:
- A记录:
web1.servers.com=>a.b.c.d - AAAA记录:
web1.servers.com=>[a:b:c:d::1] - A记录:
bak1.servers.com=>a.b.c.e - AAAA记录:
bak1.servers.com=>[a:b:c:e::1]
以上,Web服务器的域名为web1.servers.com,远程备份服务器的域名为bak1.servers.com。
SSH密钥对验证配置
与密码方式登录相比,SSH密钥对是相对安全很多的方式,是很适合在Internet环境中使用的验证方式。同时,采用SSH密钥对验证登录,可以实现自动化的登录,也就可以实现从一台服务器到另一台服务器的自动任务了。文章[1,2]对SSH及其密钥对登录方式讲的很白话,实际上也是这一小节配置的实现基础。还有,文章[3]基本也就是本节内容了,除了有那么一点点不同。
远程备份服务器生成SSH密钥对
前面已经提到在远程备份服务器上除了sudoer之外没有其他用户,而且备份同步都由root完成(,但用一个专用的、低权限的用户似乎更好一些),因此,之后在远程备份服务器上的操作都以root运行。首先在远程备份服务器上为root生成SSH密钥对,如果在安装服务器时没有生成:
# 运行下面的命令,根据提示,要求输入passphrase时按Enter跳过, # 即不使用passphrase对私钥(private key,或identification)加密 # 生成密钥对的路径,按照默认设置即可 ssh-keygen
完成之后,将会在root的主目录(一般是/root)下生成.ssh目录,存储SSH密钥对:
/root/.ssh/
|- id_rsa # SSH私钥(private key)或身份(identification)
|- id_rsa.pub # SSH公钥(public key),可根据情况自由分发检查这个文件夹和两个文件的权限,可以看到已经是设置好的够安全的状态:
drwx------ root root .ssh/ -rw------- root root id_rsa -rw-r--r-- root root id_rsa.pub
接下来就是将SSH公钥(文件id_rsa.pub中的内容)放置到Web服务器中webbackup用户的已验证密钥列表中了。
在Web服务器上添加SSH公钥
要在Web服务器上添加远程备份服务器root用户的SSH公钥有几种方法。除了在远程备份服务器上运行ssh-copy-id工具之外,其他方法一般都需要直接或间接编辑Web服务器上所需连接用户的已验证密钥列表,可以参考Digital Ocean上的这个教程。
作为一个懒人,我还是用避免编辑文件的方法,也就是用ssh-copy-id工具来完成。这里有点不同的是,之前在Web服务器上建立webbackup用户之后,就直接禁止了其密码登录。因此,需要在使用ssh-copy-id工具之前打开该用户的密码登录:
# 在Web服务器端,解除密码登录限制 sudo passwd -u webbackup # 为其设置密码,如果有必要的话 sudo passwd webbackup
之后就可以在远程服务器上操作,分发root用户的SSH公钥:
# 在远程备份服务器端,以root身份运行下面的命令, # 根据提示接受到Web服务器(web1.servers.com)的连接, # 再根据提示输入webbackup用户的密码,则完成了SSH公钥分发 ssh-copy-id [email protected]
完成之后,从远程备份服务器以root身份,测试到Web服务器的SSH连接,预期是不需要输入密码即可建立SSH连接:
ssh [email protected]
查看webbackup用户主目录的.ssh目录之下的authorized_keys文件,则应当看到其中某行是远程备份服务器的公钥(可能应以root@bak1结尾)。确定成功之后,在Web服务器上禁止用户webbackup的密码登录:
sudo passwd -l webbackup
至此,基础工作都做完了。
测试rsync运行效果并添加cron任务
远程备份服务器到Web服务器的SSH连接的自动验证做好之后,只需要在远程备份服务器上添加rsync的cron任务项,就可以实现备份文件的同步了。rsync的官方手册、官方FAQ、Ubuntu发行版(16.04 LTS)手册、Ubuntu帮助Wiki,这些都是很好的帮助材料。
一条简单易行的同步命令,可以写作:
# 远程备份服务器上备份文件的存放目录:/var/webbackup # -a, --archive 存档模式,相当于 -rlptgoD # -r, --recursive 递归子目录 # -l, --links 复制符号链接 # -p, --perms 保持权限 # -t, --times 保持修改时间 # -g, --group 保持用户组所有者 # -o, --owner 保持用户所有者 # -D 相当于 --devices --specials # --devices 保持设备文件 # --specials 保持特殊文件 # --delete 清除目标目录中,不存在于源中的文件/目录 # --exclude=PATT 同步时,从源中排除符合PATT规则的项目 rsync -a --delete --exclude=/.scripts/ [email protected]:/var/www/.webbackup/ /var/webbackup
在写入cron job之前,先做好测试:
# 添加 --dry-run 参数,测试运行,不执行实际的同步 # 添加 -vv 参数,增加详细输出 rsync --dry-run -vv -a --delete --exclude=/.scripts/ [email protected]:/var/www/.webbackup/ /var/webbackup # 添加 --progress 参数,实际执行同步时,显示进度 # 添加 --stats 参数,提供文件传输统计 rsync --progress --stats -vv -a --delete --exclude=/.scripts/ [email protected]:/var/www/.webbackup/ /var/webbackup
测试好之后,添加cron job:
vim /etc/cron.d/rsync_webbackup
编辑这个文件:
# 在每3天的凌晨3点30分和13点30分,以root身份运行rsync远程pull同步 30 3,13 */3 * * root rsync -a --delete --exclude=/.scripts/ [email protected]:/var/www/.webbackup/ /var/webbackup
完成编辑之后,刷新或重启cron服务。至此,远程备份就配置完成了,自动、定期、(应该是比较)安全,这些要求都满足了。
喔,终于把这一篇日志写完了。前两天写一会儿,读一会儿,就发现自己写下的文字真是……真是……真是前言不搭后语。今天的时间又不早了,睡一觉起来之后通读一遍,没有什么大问题后再发表。
看完了,基本上、应该是、可能是没什么问题了。如果有问题的话即使没有问题的话,也真是贻笑大方了。之后可能还需要记录就不是很成系统的内容了。但还想做或者了解的工作呢,还有SSH安全加固、操作系统的无人值守更新、备份文件同步到Dropbox或Google Drive等在线存储、增加Syncthing同步等。有空再玩吧。
最近这段时间,脑子实在不怎么工作,就连组织语言,比如“说话”,对我来说也成了一个很大的挑战。写日志也是想到很多,但化成文字却是很困难。还是随随便便耍嘴皮子比较容易,或者不说话。