每一份备份都能正常工作——直到真正需要它的那一天。快照还留在那个刚刚宕机的平台上。夜间打包的 tarball,自从三月某块磁盘写满之后,就一直在悄无声息地失败。数据库转储其实是写入过程中截下的一次文件复制,恢复出来什么也没有。备份是服务器上唯一一个无法靠看一眼判断好坏的部分——只能靠真正恢复一次来验证。下面讲的是如何搭建一套能扛住硬件故障、你自己手滑,以及别人拿到你凭证这三种情况的备份,同时不会把这台机器的可读副本交到存放它的那一方手里。
毁掉一台服务器的四种情况,每一种需要的应对方式都不一样
备份方案应该围绕故障场景来设计,而不是围绕工具来设计。现实中几乎所有的数据损失,都可以归到四种场景里,而每一种场景,对你保留的那份副本都有不同的要求。只应付了其中一种场景的方案,会显得完全够用——直到它遇上另一种场景。
硬件或主机故障
硬盘坏了,节点挂了,或者整个数据中心出了问题。只要在别处还留着一份副本,就能把你救回来。这也是同平台快照唯一能可靠应对的故障——正因如此,才有那么多人以为快照就够了。
你自己手滑
一个打错的参数,一个不小心指向生产环境的迁移脚本,一次清理删掉了比预期更多的东西。这种破坏会瞬间同步到所有镜像它的地方,所以真正能救你的是历史版本——一份出错之前的版本,而不是出错之后的最新副本。
入侵与勒索软件
一个拿到 root 权限的攻击者,能触及的范围和你的备份任务完全一样:同一套凭证、同一个目标地址、同一个执行计划。如果服务器有能力删除自己的备份,它就会被删掉。能不能扛过去,取决于那份副本是不是只能追加写入,或者干脆完全够不着。
丢掉的是账号,不是服务器
账单逾期、账号被封,或者在一个不该踩的司法管辖区里被下架。硬件本身毫发无损,你只是根本连不上它。这种情况下,只有放在另一家主机商、走另一条付款路径的副本才帮得上忙。
在选择任何工具之前,先写清楚你实际要防的是这四种情况里的哪一种。把数据每晚复制到同一块硬盘上的另一个目录,恰好只能应付其中一种——而且是最不可能发生的那一种——但感觉上却完全像是一份备份。生产环境里大多数失效的备份方案,失效的原因,就是从来没有人把场景说清楚过。
快照不是备份
这两个词经常被当作同义词混用,但它们描述的是两种不同的对象,有着不同的故障范围。搞清楚自己手上到底是哪一种,决定了你到底有没有备份可言。
把快照用在它真正擅长的地方:在你动内核、引导程序或数据库表结构之前,给自己留一个五秒钟就能撤销的机会。打一个快照,做那件有风险的事,成功之后再删掉它。但绝不能让“反正还有快照”变成不做备份的理由——快照和它所在的平台共享同一个命运,账号一旦被封,两者会同时被一起带走。
3-2-1 原则,以及人人都会漏掉的那两个数字
这条老规矩之所以还站得住脚,是因为它描述的是独立性,而不是一套归档方式:三份数据副本,存放在两种不同的介质上,其中一份放在别的地方。另外还有两点补充,用来应对这条规矩刚提出时还很罕见、如今却已是家常便饭的那些故障。
- 三份副本。生产数据加两份备份。只有两份副本,意味着只要有一次恢复失败,你就会一无所有。
- 两种介质,或者两家主机商。在租来的基础设施上,“介质”实际指的是管理层面的独立性——用同一个账号、在同一个平台上、从同一笔余额里付费得到的第二份副本,不过是多绕了几步的同一份副本而已。
- 一份放在异地。不同的建筑,不同的网络,如果对你重要的话,还可以是不同的司法管辖区。/locations 就是这条原则的实际落地:挑一个和你要保护的那个地区互不牵连、能独立出故障的区域。
- 一份不可篡改或离线保存。一份服务器自己删不掉的副本,因为谁掌握了服务器,谁就掌握了它的全部凭证。
- 零次未经验证的恢复。一份从没恢复过的备份,只是一个假设。真正算数的,是你完成过多少次恢复,而不是有多少个任务报告了成功。
备份数据,其余的重建就好
本能的做法是把所有东西都做成镜像。这样做既昂贵,恢复起来又慢,而且会原原本本地保留下那个已经被攻陷的二进制文件、那个损坏的软件包状态,以及你早就不记得是怎么形成的配置漂移。服务器上的内容其实分三类,而真正该放进备份里的,只有其中一类。
- 应该纳入备份:/etc、/home、/root、/srv、你的网站根目录、应用的状态目录、有名字的容器卷,以及一个存放最新数据库转储文件的目录。
- 把部署方案本身也纳入进来——compose 文件、Ansible 或 shell 脚本、你凌晨两点写下的那些笔记。这些内容也应该纳入版本控制,但同时在备份里也留一份,这样恢复时就不必依赖另一个服务是否还连得上。
- 应该排除在外:/proc、/sys、/dev、/run 以及各种临时目录。它们是内核接口和临时空间,复制它们只会浪费时间,甚至直接卡死整个备份任务。
- 排除软件包缓存、构建目录、依赖树和虚拟环境——任何构建步骤能重新生成出来的东西。在一台典型的应用服务器上,这些内容往往占了磁盘的大半空间。
- 如果你已经在正确地转储数据库,就把数据库的原始文件排除在外。两者都备份的结果,往往是凌晨三点有人手忙脚乱地恢复了那份更大、却没用的副本。
- 不要排除隐藏文件。一台 Linux 机器上,一半重要的东西,文件名都是以点开头的。
数据库经不起一次简单的文件复制
这是恢复失败最常见的一个原因。运行中的数据库,状态部分保存在内存里,写入顺序也并不是按序进行的;在它运行的时候直接复制文件,拿到的会是一种撕裂、写了一半的状态——恢复的时候,可能能恢复,可能悄悄恢复出一份损坏的数据,也可能干脆恢复不了。正确的做法是先做一次规范的转储,再去备份这份转储文件。
- PostgreSQL:对单个数据库用 pg_dump,要连角色一起备份整个集群就用 pg_dumpall。如果丢失一天的数据是不能接受的,再加上 WAL 归档,这样就能恢复到某个精确的时间点,而不只是恢复到昨晚。
- MySQL 或 MariaDB:mysqldump 搭配 --single-transaction,在 InnoDB 上能得到一份一致的快照,还不会阻塞写入。放到 MyISAM 上就不行了——这也是不该再用 MyISAM 的又一个理由。对于大型数据集,mariabackup 这类物理备份工具的恢复速度,比重放一份转储文件快得多。
- SQLite:永远不要直接复制文件。使用 .backup 命令或者 VACUUM INTO,它们能正确处理锁。复制一个正在写预写日志(WAL)的数据库,就是一场你迟早会输掉的赌博。
- Redis:触发一次后台保存,备份生成的快照文件;或者开启 append-only 模式,备份那份日志。在快照还在写入时就去复制它,得到的会是一个被截断的文件,加载出来就是一个空数据集。
- 容器:卷就是数据本身。要么在复制所需的那几秒钟里停掉整套服务,要么在容器内部直接运行转储工具——但不要直接把一个正在运行的数据库的卷打个 tar 包,就当成是备份了。
- 其他任何带有常驻进程的东西——搜索索引、消息队列、账本类守护进程——都有各自的一致性导出命令。现在就把它找出来,别等到出故障的时候才去找。
文件系统快照是从另一个方向解决这个问题的:LVM、ZFS 和 btrfs 能在几毫秒内冻结出卷的一个一致视图,你备份的是这个冻结下来的视图,数据库本身则继续对外提供服务。对于转储要花上好几个小时的数据集来说,这才是正确答案。但对一个只有 200 MB 的数据库来说,这并不能成为跳过转储的理由——转储更简单,能跨版本移植,出问题的时候人还能直接读懂里面的内容。
选择工具
四款工具几乎能覆盖所有场景。其中最关键的一点,是加密发生在哪个环节。如果数据只是在传输过程中被加密,落地之后再由存储服务商负责静态加密,那么存储服务商就能读到这些数据——而备份也就悄悄变成了整套系统里最薄弱的那一环,尽管你在其他地方都做了充分的加固。
不管选哪一款,密码短语或密钥都必须存放在被备份的那台服务器之外的某个地方。这是一个连细心的人都会掉进去的陷阱:仓库密钥存放在 /root 里,然后又老老实实地被备份进了它自己所解锁的那个仓库。把它打印出来,或者存进一个能在另一台还活着的机器上打开的密码管理器里。一个解不开的仓库,和压根没有备份,没什么两样。
第二份副本应该放在哪里
目标存放地既是一块磁盘空间,同时也是一个司法管辖区和一段账单关系。下面是四种选择,大致按它们与你要保护的那台服务器之间的独立程度排序。
另一个地区的第二台服务器
最直接的答案:在另一个国家找一台便宜的实例,能通过 SSH 连接,除了 sshd 和一个仓库之外什么都不跑。一个 1 GB 的 /vps 套餐,就能装下一小批服务器的备份,而且这个账号还可以被锁定为只能执行一条强制命令,这样即使密钥被盗,也打不开任何 shell。
存储级硬盘
一旦数据集达到几百 GB 的规模,不限流量上行链路上的 HDD 容量,每 TB 的价格会比 NVMe 便宜得多,而备份写入本来也用不上 NVMe 那种延迟。/storage 就是为此而生:TB 级、有 RAID 保护的硬盘,完整 root 权限,不做内容审查。
兼容 S3 的对象存储
方便,按 GB 计价,而且通常支持对象锁定,能提供真正意义上的不可篡改性。依赖它之前先看清楚出站流量的计费方式——真到了紧急情况要把 1 TB 数据拉回来的那一刻,才发现取回数据要花多少钱,可不是什么好时机。
你自己名下的硬件
一块外置硬盘,或者家里的一台机器,靠拉取而不是推送来同步。速度慢,还得手动操作,但这也是这份清单里唯一一份,不管是主机商、法院命令,还是账单纠纷,都碰不到的副本。对于那些你真的没法重新生成的数据,哪怕落后一周,也值得保留这样一份。
备份的隐私属性,应该和服务器本身保持一致。用 LUKS 加密了磁盘,却每晚把明文备份传到一个用你的银行卡注册的存储桶里,等于把之前做的一切都推翻了:数据现在是可读的,而且还归档在你的名字底下。如果这台服务器值得你匿名付费,那这份副本同样值得——在客户端完成加密,用买下源服务器时同样的方式,去买下这个目标存放地。
一步一步搭建起来
- 1
先定下两个数字
一是你能承受丢失多少数据——也就是两次备份之间的时间差;二是你能承受多长时间的宕机。其余一切都是从这两个答案推导出来的。给一个静态网站做每小时备份纯属做戏;而给一个订单数据库做每晚备份,应该是你主动做出的决定,而不是照抄某篇教程得来的习惯。
- 2
创建目标存放地
在另一个地区开一台第二实例,配上专属的 SSH 密钥,以及一个专门的、没有特权的用户,它的家目录就是仓库所在的位置。这台机器上不跑任何其他东西。把密钥限制成只能执行一条强制命令,这样即使凭证被盗,对方也只能追加备份,打不开任何 shell。
- 3
生成密钥,并存放到别处
一串又长又随机的密码短语,记录在一个哪怕服务器丢了也不会跟着丢的地方。初始化仓库,然后只凭你记下来的东西,在第三台机器上验证一下自己能不能列出仓库的内容。如果不能,就先把这个问题解决掉,再写入第一份备份。
- 4
先转储数据库
写一个简短的脚本,把一致性转储写进一个暂存目录,只要有一个失败就以非零状态退出——这一步要在文件备份之前运行,而不是和它同时进行。一个转储失败了还继续往下走的任务,正是人们最终攒下三十天全是零字节文件的原因。
- 5
备份真正重要的路径
把工具指向该纳入的路径清单,应用排除规则,并对第一次运行的结果做个合理性检查。如果一台 40 GB 服务器的全新仓库最后只有 300 MB,说明有什么东西被悄悄跳过了;如果结果是 38 GB,说明你的排除规则根本没起作用。
- 6
定好计划任务,让失败变得响亮
用一个带随机延迟的 systemd 定时器,或者如果你习惯用 cron 也行。然后让这个任务学会汇报:成功时向监控系统发一次心跳,心跳一旦停止到达就发出告警。悄无声息地失败,是备份最常见的失效方式,因为它们停止工作的时候,表面上什么都不会坏——直到有一天,一切都坏了。
- 7
设定保留策略,并真正执行清理
每小时一份保留一天,每天一份保留两周,每周一份保留几个月,每月一份保留一年。然后真正运行一次清理,确认仓库不再继续膨胀。一套只配置却从没执行过的保留策略,会把目标空间填满,最后连备份本身也一起拖垮。
- 8
今天就恢复点什么
不是跑一个测试任务——而是找一个真实的文件,恢复到一个临时目录里,打开检查一遍。然后在日历上定一个日期,安排一次整机恢复。第一次完整恢复,总会暴露出点什么:一个缺失的路径、一个权限问题、一张证书,或者一个只存在于旧机器上的数据库用户。
从服务器往外推送备份,就等于默认了任何拿到 root 权限的人,都能摧毁所有副本。反过来做——由备份主机主动连进来,拉取数据,再断开连接——一台被攻陷的服务器就完全够不着仓库,因为它压根没有相应的凭证。拉取模式配置起来更麻烦,但对大多数方案来说,这是能带来的最大一项改进。
保留策略,以及为什么更长的保留期比听起来便宜
保留策略通常是按磁盘能装多少来随手定的,定完就再也没人管过。它其实值得你认真想一想,因为它决定了哪些失误还有得救。一个七天的窗口,能追回周二删掉的一个文件。但它追不回六周前就已经开始、直到某份报表跑出错误结果才浮出水面的数据损坏,也追不回一个在机器里安安静静潜伏了一个月才动手的入侵者。
有了去重,实际成本会比上表看起来的低得多:针对一台内容大多没变的服务器,第二次运行只会存下发生变化的部分,所以在一台 40 GB 的机器上,一年的月度还原点通常只需要几个 GB,而不是半个 TB。先根据你需要从什么样的失误中恢复来定策略,再去看账单。你通常会发现,自己完全负担得起更宽松的那个版本。
不可篡改性:能挡住勒索软件的那一环
前面这些内容,默认对手只是熵——也就是自然而然的损耗。如果对手是一个拿到 root 权限的人,一份普通的备份任务就等于是一份操作说明书:凭证就在机器上,目标地址写在配置文件里,仓库会在加密勒索开始之前就先被清空。有四种机制能打破这条链条,只要用上其中任何一种,结果就会不一样。
- 仅追加仓库。Borg 的服务端仅追加模式,或者 restic 的 REST 服务端开启仅追加模式,都只接受新数据写入,拒绝删除操作。服务器能写入;只有你,从别的地方,才能执行清理。
- 基于拉取的备份。由备份主机发起连接,唯一的凭证也握在它手里。生产服务器没有密钥,没有目标地址,压根没有任何通往仓库的路径。
- 对象锁定。兼容 S3 的存储在存储桶层面强制设定一个保留期限,删除操作会被存储层本身拒绝,不管凭证原本允许做什么。
- 每台主机使用各自独立的凭证。一台机器被攻陷,不应该连带暴露出其他每一台机器的历史记录。各自独立的密钥、路径和限制规则。
- 一份真正离线的副本。一块拔了电源的硬盘,对迄今为止写出来的任何远程攻击都免疫。不时髦,但从没输过。
恢复演练
恢复是一套流程,而一套从没人执行过的流程,就只是虚构出来的东西。现在就完整跑一遍,以后每六个月再跑一遍,把学到的东西记下来——到最后,这些笔记会和数据本身一样有价值。
- 1
部署一台全新实例
操作系统版本相同,不装任何其他东西。一台用完即弃的 VPS 就够了,整场演练的花费,还不到一顿饭钱。
- 2
只凭你记下来的东西去恢复
仓库地址、密码短语、要用到的命令。如果你发现自己需要一样只存在于那台假装已经死掉的服务器上的东西,你就刚好找到了方案里的一个漏洞——而且是在一个发现它完全不用付出代价的日子里找到的。
- 3
先恢复数据,再启动应用
加载数据库转储文件,把各个文件放回该在的位置,再修正属主和权限模式。属主是最常见的意外来源:旧机器上的数字用户 ID,很少能和新机器上的对得上号。
- 4
启动服务,并真正用一用
不是跑一条状态查询命令——而是要登录进去,加载一个页面,跑一条查询,发一条消息。一个能启动的服务,和一个真正能用的服务,不是一回事。
- 5
给整个过程计时,并把数字记下来
整个过程一共花了多长时间?这才是你真实的恢复用时,而且几乎总是比预估的要长上好几倍。把这份记录和密码短语放在一起保管,每次技术栈有变动,两边都要一起更新。
老实说说要花多少钱
在基础设施里,备份是最便宜的一份保险,却常常因为价格被跳过。下面是一台小型服务器的一些具体数字,假设数据的压缩和去重效果和普通数据差不多。
把这些数字,和它们所能避免的那场事故的代价比一比。把数字写出来,本身就是重点所在——一旦真把钱算清楚了,反对做备份的理由,就从来都不真的是钱的问题。
主机商在其中扮演的角色
一套备份方案,和它所保护的那台服务器,依赖的是同样的两样东西:一个足够独立、能存放副本的地方,以及一种不会留下新的身份记录的付款方式。这两点在备份这一端,比在源服务器那一端更加重要,因为备份是源服务器所保护的一切内容的完整副本。
这里的每一个 /vps 和 /storage 套餐,都是从一笔免 KYC 的预付加密货币余额中开通的,覆盖 /locations 上列出的十五个地区——这样第二份副本就可以放在和第一份不同的司法管辖区,而整条链路上不会多出第二次身份核验。每个实例都自带即时快照,方便你随时五秒撤销;不限流量的带宽,意味着不管是第一次上传,还是紧急情况下的恢复,都不会按流量计费;/storage 还提供起价 $8.99/mo、TB 级、有 RAID 保护的硬盘,不做内容审查。/pay-with 列出了我们接受的币种,/offshore-hosting 讲清楚了司法管辖区到底能改变什么,/guides 里还有配套的文章,讲磁盘加密,以及主机商到底能看到、看不到一台机器的哪些信息。
检查清单
- 在选择工具之前,先说清楚你要防的是哪种故障。
- 把快照当成一个撤销按钮,而不是备份本身。
- 备份数据和配置;操作系统靠脚本重建。
- 用每个数据库各自的一致性导出命令去转储,再备份这份转储文件。
- 在任何数据离开机器之前,先在客户端完成加密。
- 把仓库的密码短语存放在一个哪怕服务器丢了也不会跟着丢的地方。
- 至少让一份副本放在不同的主机商、不同的地区、走不同的付款路径。
- 让至少一份副本做到仅追加、基于拉取,或者完全离线,这样即使 root 被攻陷也删不掉它。
- 一旦没有收到成功运行的信号就发出告警——失败是无声的,而无声本身就是症状。
- 今天就恢复一个文件,每年再完整恢复两次整台机器。计时,并把结果记下来。
只靠主机商的快照够不够?
不够,而且这是那些原本已经很谨慎的方案里,最常见的一个漏洞。快照和它所复制的那个卷,存活在同一个平台上,所以它扛不住平台级故障、账号被封,或者账单逾期——恰恰是你最需要它的那三种场景。而且快照通常只保留很短的历史,所以它救不回上个月删掉的文件,也救不回六周前就已经开始的数据损坏。快照非常适合用作有风险的改动之前那五秒钟的撤销手段:留着它们,每天都用,但另外在别处保留一份真正的备份。
restic 还是 Borg——该用哪一个?
如果目标端是对象存储,或者只是一个普通的 SSH 账号,选 restic,因为它对端什么都不用装,而且原生支持 S3。如果目标端是一台你自己掌控的 Linux 机器,而且你想要它的仅追加服务端模式和稍微更好一点的压缩效果,选 Borg。两者都支持去重,都在客户端完成加密和认证,都足够成熟、部署也很广泛,选哪一个都站得住脚。真正错误的做法,是花上一个月去比较它们,而服务器在这段时间里完全没有任何备份——今天下午就先选一个用起来,以后真有需要再改主意也不迟。
VPS 应该多久备份一次?
备份间隔,说到底就是你愿意重做的最大工作量。对一个静态网站来说,每周一次已经算诚实。对任何有用户在写入的东西来说,每晚一次是底线,而一旦去重开始起作用,每小时一次的成本也很低——一天里的第二次运行,通常只会多存下几个 MB。把一天数据的价值,和存放二十四个还原点而不是一个还原点的成本放在一起权衡,答案通常一目了然。
应该备份整块磁盘,还是只备份自己的数据?
几乎在任何情况下,都应该是数据和配置。一份完整的磁盘镜像,会把机器完全还原成原来的样子,包括你正想从中恢复过来的那次入侵,以及你自己都已经搞不清楚的软件包状态,而且创建和恢复都更慢。对 /etc、应用数据和数据库转储文件做文件级备份,再配上一个能重建操作系统的脚本,恢复起来既更快,结果也更干净。只有在你需要逐位保留的取证级留档,或者这台机器是别人搭建、你自己也说不清里面是什么的黑箱时,才有必要对整块磁盘做镜像。
主机商说硬盘是加密的——那我的备份是加密的吗?
在任何对你有实际意义的层面上,都不是。主机商那一端的加密,防的是硬盘被人从机架上拆走带走这种情况;密钥握在主机商手里,所以数据对主机商本身,以及任何能强制要求主机商配合的人来说,依然是可读的。客户端加密——不管是 restic、Borg、rclone 的 crypt 层,还是一个加密归档文件——意味着到达目标端的内容,如果没有那个从未离开过你机器的密码短语,就毫无意义。对一套隐私攸关的技术栈来说,这个区别就是全部重点,因为备份是这台服务器所保护的一切内容的完整副本。
怎么防止勒索软件把我的备份也一起加密掉?
假设服务器能触及的任何东西,一个拿到 root 权限的攻击者也都能摧毁——凭证、目标地址、执行计划,全都放在这台机器上。用下面三种方式之一切断这种触及:一个只接受写入、拒绝删除的仅追加仓库;一种由备份主机主动向内连接、服务器本身完全不持有任何凭证的拉取模型;或者一个由存储层自己强制执行锁定的对象存储。然后再把保留期拉长,这样一次缓慢而悄无声息的入侵,就不会在没人察觉之前先一步滑出保留窗口,同时留一份离线的副本,放在任何远程攻击都够不着的地方。


