封面图:Bing AI 生成

博客东西一稍微有点多,就开始担心数据安全了。这个时候就要有忧患意识,知道怎么自动对博客站点进行备份。

为什么不用宝塔备份

宝塔可以备份网站文件夹,对于新手而言还是比较简便的。但是备份数据库就不能用宝塔自带的了。请看下图:

左图为宝塔备份的数据库文件大小,右图为实际查看的总大小

实际上,宝塔只备份了一个表。如果傻傻地没发现,以后若是真的需要还原那就要骂。

然后捣鼓出来能备份数据库的脚本之后,干脆连网站文件夹也一起备份了。

先决条件

  • 有一定的 Linux 命令行基础。能大致读懂本文中的命令,并基于自己的实际情况修改。
  • (可选)一台远程数据库服务器,用于数据库异地备份与作为后备数据库。
  • (可选)一台远程服务器,用于异地备份。

备份架构图

目前博客有两个需要备份的东西:网站文件夹和 sql 数据库。

网站文件夹和 sql 数据库将会使用 rsync 与备份镜像同步(相较于就地打包完再复制过去,使用 rsync 可以减少同步消耗的时间和流量)。备份镜像放在“wordpress_blog备份单元”的 main 文件夹下。 main 文件夹将会定期打包(使用 crondtab),打包后的归档文件将会移入“每日备份文件夹”;同时如果是每月 1 日的备份,会复制一份移入“每月备份”,每年 1 月 1 日就会再复制一份移入“每年备份文件夹”。每日备份和每月备份设置了数量限制,将删除较老的备份文件。

除了本地备份,还可选在一台远程服务器上设立异地备份,流程和本地备份一样。不过,rsync 镜像同步通过 Tailscale 组建的 VNet 传输数据,这样比直接通过公网传输更加安全。

备份库文件夹内含多个备份单元,使得其可以执行多个备份任务,甚至是来自其他服务器的远程备份。

安装 rsync

参考:rsync 用法教程 - 阮一峰的网络日志

sudo apt-get install rsync

本地备份

备份脚本:https://github.com/Eterance/Wordpress_backup_script

准备工作

下载备份脚本

sudo -i
mkdir -p /usr/local/sbin/backup_script
cd /usr/local/sbin/backup_script
wget https://raw.githubusercontent.com/Eterance/Wordpress_backup_script/main/archive_core.sh
wget https://raw.githubusercontent.com/Eterance/Wordpress_backup_script/main/backup.sh
chmod 700 backup.sh
chmod 700 archive_core.sh
exit

创建存放备份文件的目录

# backup_vault 用来存放多种备份的库
sudo mkdir -p /backup/backup_vault
# 创建日志文件
sudo touch /backup/backup_vault/log.txt

修改自动备份脚本

打开自动备份脚本

sudo vim /usr/local/sbin/backup_script/backup.sh

内容如下:

#!/bin/bash
# 自动备份脚本

vault_dir="/backup/backup_vault"
# 设置日志文件路径
log_file="/backup/backup_vault/log.txt"
# 重定向所有输出到控制台和日志文件
exec > >(tee -a "$log_file") 2>&1

# 备份命令
unit_name="wordpress_blog"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
sudo rsync -az --delete "/www/wwwroot/wordpress" "$vault_dir/$unit_name/main"
sudo mysqldump -uroot -proot_password --opt --databases db_name > "$vault_dir/$unit_name/main/db.sql"
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "12" -b "7"

echo "########## [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 本次备份结束"
echo ""

要修改的地方(用尖括号标明)如下:

# 自己起一个名字
unit_name="<单元名>"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
# 使用 rsync 在网站目录和备份目录里双向同步
sudo rsync -az --delete "<整个网站目录>" "$vault_dir/$unit_name/main"
# 导出数据库到备份目录中。
# 要使用 root 或者有管理员权限的mysql帐户
# 数据库名和root的密码可以在宝塔中复制
sudo mysqldump -uroot -p<root密码> --opt --databases <数据库名> > "$vault_dir/$unit_name/main/db.sql"
# 月度和每日备份保留的份数如果指定为0就是不限制;否则会在超出限制后删除最老的
# 年度备份不作数量限制
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "<月度备份保留的份数>" -b "<每日备份保留的份数>"

配置定时运行

输入以下命令进入root的定时任务

sudo -i
crontab -e

第一次运行会要求选编辑器,不知道选什么就选第二个 vim.basic。以后想切换输入 select-editor 即可重新选择。

在末尾追加

0 4 * * * /usr/local/sbin/backup_script/backup.sh

备注

含义:

  • 第一个字段(0)表示分钟,取值范围为0到59。
  • 第二个字段(4)表示小时,取值范围为0到23。
  • 第三个字段(*)表示一个月中的哪一天,取值范围为1到31。
  • 第四个字段(*)表示月份,取值范围为1到12。
  • 第五个字段(*)表示星期几,取值范围为0(星期日)到6(星期六)。
  • 最后一个字段(/usr/local/sbin/backup_script/backup.sh)是要执行的命令或脚本的路径。

保存并退出,然后启动一次cron服务(以防有的服务器没启动)并退出root

service cron start
exit

查看 log.txt

tail -fn 50 /backup/backup_vault/log.txt

ctrl+c 即可退出查看。

添加更多备份任务

假设我需要备份另一个用 hugo 做的静态博客(没有数据库)。修改后的 /usr/local/sbin/backup_script/backup.sh 如下:

#!/bin/bash
# 自动备份脚本

vault_dir="/backup/backup_vault"
# 设置日志文件路径
log_file="/backup/backup_vault/log.txt"
# 重定向所有输出到控制台和日志文件
exec > >(tee -a "$log_file") 2>&1

# 备份命令
unit_name="wordpress_blog"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
sudo rsync -az --delete "/www/wwwroot/wordpress" "$vault_dir/$unit_name/main"
sudo mysqldump -uroot -proot_password --opt --databases db_name > "$vault_dir/$unit_name/main/db.sql"
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "12" -b "7"

unit_name="hugo_blog"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
sudo rsync -az --delete "/www/wwwroot/hugo_website" "$vault_dir/$unit_name/main"
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "12" -b "35"

echo "########## [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 本次备份结束"
echo " "

推送备份到远程数据库(可选)

定时备份时,将导出来的数据库推送到远程数据库里。

重要

这样做可以加多一份数据库备份,而且如果本地数据库挂了,只需要进宝塔设置一下(见后文)就可以快速切换到远程数据库,然后自己再慢慢排障或恢复。不过使用远程数据库可能会大幅增加网站访问延迟(具体对比见:WordPress 加速系列(5): 不要让数据库查询拖慢你的网站 )。

至于为什么不做主从同步,是因为操作过于复杂,对于一个小型个人博客站点而言完全是杀鸡用牛刀。

新建 Azure MySQL 服务器请参见: https://blog.baldcoder.top/articles/azure-guide-3-azure-database-for-mysql-flexible-servers/

首先在远程服务器上新建数据库:

# <远程数据库名> 建议与本地数据库同名方便记忆
mysql -h "<远程数据库地址或IP>" -u "<远程数据库管理员用户名>" -p"<远程数据库管理员密码>" -e "CREATE DATABASE <远程数据库名>"

然后修改 /usr/local/sbin/backup_script/backup.sh 如下:

#!/bin/bash
# 自动备份脚本

vault_dir="/backup/backup_vault"
# 设置日志文件路径
log_file="/backup/backup_vault/log.txt"
# 重定向所有输出到控制台和日志文件
exec > >(tee -a "$log_file") 2>&1

# 备份命令
unit_name="wordpress_blog"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
sudo rsync -az --delete "/www/wwwroot/wordpress" "$vault_dir/$unit_name/main"
sudo mysqldump -uroot -proot_password --opt --databases db_name > "$vault_dir/$unit_name/main/db.sql"
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "12" -b "7"
# 加到这里
mysql -h "<远程数据库地址或IP>" -u "<远程数据库管理员用户名>" -p"<远程数据库管理员密码>" "<远程数据库名>" < "$vault_dir/$unit_name/main/db.sql"

echo "########## [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 本次备份结束"
echo " "

设置远程数据库作为 WordPress 后备数据库(可选)

需要完成 推送备份到远程数据库(可选) 章节。

这里不使用宝塔管理远程数据库服务器,因为傻逼宝塔不允许数据库同名(即使不同服务器的也不行)。

如果想要将远程数据库作为 WP 的后备数据库,需要下面的设置:

按照链接设置phpmyadmin远程访问服务器。

然后使用远程数据库服务器的管理员帐户登录phpmyadmin,并打开数据库的权限页面,点击新建用户:

填入用户名和密码(建议和原本地数据库的用户名密码相同,当然如果你记得住不同也行),勾上权限,然后滚到最底下“执行”。

回到宝塔面板,把网站根目录下的 wp-config.php 文件名后面加上个 localdb

然后打开你的网站首页,重新走一遍数据库设置流程,凭据填远程数据库的。

之后提交,然后回到宝塔把新出现的 wp-config.php 后面加上 remotedb ,老的文件名恢复,就能重新访问本地数据库。

以后只需要恢复 wp-config.phpremotedb 的命名,就能随时切换到后备远程数据库。

重要

一些插件会在 wp-config.php 内写入自己的设置,如下图示例。新创建的远程数据库的 wp-config.php 不会写入这些设置,需要自己手动从老的 wp-config.php 里复制过去。

异地备份-主动拉取(可选)

虽然对于大部分情况而言,本地备份已经足够,这是因为一般云服务商存储数据就已经有本地三副本设计,数据中心的可靠性与可用性也极高,因此数据丢失的概率非常低(远低于你自己的个人电脑丢数据)。但是只要出现以下极端情况中的一种,你的本地备份就会和网站一同陪葬:

  • 数据中心所在地发生火灾、洪水、地震、战争等不可抗力灾害;或者发生人为意外事故;
  • 你的服务器乃至云服务账号遭到封禁,数据被锁定无法取出;
  • 服务器受到勒索病毒加密或者黑客攻击。
经济下行公司裁员的大环境下,此类人为事故只会越来越多。不要把数据安全完全寄托于他人身上

将数据备份多一份到相隔数百上千公里乃至半个地球之外的另一台服务器(注意数据出入境需要符合当地法律法规),并且经过合理配置,你的数据就能(理论上)抵御以上情况。

同步架构的选择

现在有服务器 A 和备份服务器 B。如果 rsync 语句放在服务器 A 上,就是 A 主动推送备份数据;如果 rsync 语句放在服务器 B 上,就是 B 自己拉取数据。

我这里选择 B 自己拉取数据,是因为:

  • 如果 A 主动推送备份数据,那么 rsync ssh 传输时就是 A 的公钥加到 B 上,极端情况下 A 被攻破,A 的私钥泄露,攻击者就能顺藤摸瓜登录 B。
  • 反之, B 自己拉取数据就是 B 的公钥加到 A 上,攻击者能登录 A 但是不能登录 B。

以上情况建立在服务器 B 相对比 A 更加安全的情况下,比如我的 A 服务器跑博客,有可能成为某些脚本小子的目标;而我的 B 服务器并不对外提供服务(虽然也暴露在公网),可以推测几乎不会有人知道 B 的存在。

不过,服务器设置好防火墙,只允许公钥登录,不安乱七八糟的软件,再套上 CDN,网站名气别那么大,就已经非常安全了。

前置工作

备份服务器 B 设置

首先按照 本地备份->准备工作 的内容在 B 服务器上下载备份脚本。

然后打开自动备份脚本

sudo vim /usr/local/sbin/backup_script/backup.sh

内容修改为如下

#!/bin/bash
# 自动备份脚本

vault_dir="/backup/backup_vault"
# 设置日志文件路径
log_file="/backup/backup_vault/log.txt"
# 重定向所有输出到控制台和日志文件
exec > >(tee -a "$log_file") 2>&1

# 备份命令
unit_name="wordpress_blog"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
# 在两机的备份文件夹之间建立镜像同步
sudo rsync -az --delete "root@<A服务器的tailscale ip地址>:$vault_dir/$unit_name/main/" "$vault_dir/$unit_name/main"
# 删除了数据库导出语句
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "12" -b "7"

echo "########## [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 本次备份结束"
echo ""

备注

严格来说,B 服务器的rsync 语句应该写成从 A 服务器的网站数据同步更加合适,而不是访问 A 服务器的本地备份。这里是为了省事才这么写的。

此外,远程源目录中的 $vault_dir/$unit_name/main/ 最后的 / 不可省略,详见rsync 用法教程 - 阮一峰的网络日志

然后先运行一次查看是否成功:

sudo /usr/local/sbin/backup_script/backup.sh

第一次可能会出现提示 Are you sure you want to continue connecting (yes/no/[fingerprint])? ,输入 yes 接受指纹。受限于服务器之间带宽的限制,如果网站数据较多,第一次同步可能会花费非常久的时间。

接下来按照 本地备份->配置定时运行 在远程服务器上配置定时运行。

多个异地备份

如果使用多个异地服务器进行备份,每个远程服务器按照上面的设置就好。

异地备份-被动推送(可选)

本步骤讲解了怎么把 rsync 语句放在在 A 服务器上(A 推送到备份服务器 B)。

前置工作

备份服务器 B 设置

首先按照 本地备份->准备工作 的内容在 B 服务器上下载备份脚本。

然后打开自动备份脚本

sudo vim /usr/local/sbin/backup_script/backup.sh

内容修改为如下

#!/bin/bash
# 自动备份脚本

vault_dir="/backup/backup_vault"
# 设置日志文件路径
log_file="/backup/backup_vault/log.txt"
# 重定向所有输出到控制台和日志文件
exec > >(tee -a "$log_file") 2>&1

# 备份命令
unit_name="wordpress_blog"
echo "###### [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 开始备份 $unit_name"
sudo mkdir -p "$vault_dir/$unit_name/main"
# 删除了 rsync 语句
# 删除了数据库导出语句
sudo /usr/local/sbin/backup_script/archive_core.sh -d "$vault_dir/$unit_name" -m "12" -b "7"

echo "########## [$(date +'%Y-%m-%d %H:%M:%S UTC%:::z')] 本次备份结束"
echo ""

接下来按照 本地备份->配置定时运行 在远程服务器上配置定时运行。

服务器 A 设置

首先按照 本地备份 的步骤在服务器 A 上配置了本地备份与自动运行。

然后打开自动备份脚本

sudo vim /usr/local/sbin/backup_script/backup.sh

在备份单元的备份语句中添加一句(建议添加在本地 rsync 后面):

sudo rsync -az --delete "$vault_dir/$unit_name/main/" "root@<B服务器的tailscale ip地址>:$vault_dir/$unit_name/main" 

手动试运行

先在 A 服务器上运行一次查看是否成功传输:

sudo /usr/local/sbin/backup_script/backup.sh

第一次可能会出现提示 Are you sure you want to continue connecting (yes/no/[fingerprint])? ,输入 yes 接受指纹。受限于服务器之间带宽的限制,如果网站数据较多,第一次同步可能会花费非常久的时间。

然后再在 B 服务器上运行一次。

恢复演练

请务必实行定期备份之后,隔一段时间尝试(在测试服务器)上演练恢复,以防备份恢复不了的蛋疼事情发生。