SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

VPS快照、备份和克隆有什么区别?

VPS快照只能用于快速回滚,通常不能脱离服务商账户恢复。本文比较快照、备份和克隆的恢复范围,并说明克隆后需修复主机名、SSH密钥及机器身份。

快照、备份和克隆的实际含义

VPS 快照是服务器的磁盘映像,由服务提供商保存在其基础设施上,并存放在您的账户中。备份是数据的独立副本,即使不依赖保存原始数据的服务提供商,您也可以在其他位置恢复它。克隆是根据快照部署的新实例,因此从创建时起就是原服务器的精确副本,包括身份信息。

它们解决的问题不同。快照可以在几分钟内回滚失败的升级,但账户被关闭后,快照也无法使用。备份可以在服务提供商停止运营后继续使用,但恢复时间更长,因为您需要先重新构建服务器。克隆可以通过一个步骤获得第二台正在运行的服务器,但也会产生两台认为自己是同一台机器的服务器。

为什么 VPS 快照不是备份

问题在于故障域,而不是镜像质量。快照位于服务商的存储平台上,通常与源服务器处于同一区域,并且始终属于同一账户。一次事件就可能同时影响服务器和快照。

  • 账户被暂停、付款失败,或有人窃取了登录凭据。
  • 具有 API 访问权限的人员或脚本删除了实例。在许多服务商中,删除实例也会同时删除其快照。除非确认服务商文档另有说明,否则不要自行假设。
  • 该区域发生故障,其中的所有资源同时无法访问。
  • 服务器上以 root 身份运行的程序找到您留在 /root 中的服务商 API token,并在操作磁盘前删除快照。

备份是能够在这四种情况下都保留下来的副本。判断标准只有一个问题:如果您的服务商账户今天下午停止存在,您还能恢复什么,又会将其恢复到哪里?无法通过这个问题验证的内容,只能算回滚工具。继续创建快照,因为没有什么恢复速度更快。然后在服务商无法控制的存储上保留第二个副本。

旧规则仍然适用:保留 3 份数据副本,使用 2 种存储介质,其中 1 份位于平台之外。服务商快照加上位于独立基础设施上的 restic 备份仓库,即可通过两个组成部分满足这一规则。

运行中数据库的快照为何可能无法正常恢复

云服务商的快照会复制某个瞬间的块设备状态。它不会先要求应用停止,也无法读取仍停留在页缓存中的数据。因此,镜像最多只能达到崩溃一致性。其状态与直接拔掉电源后磁盘上的状态完全相同。

大多数组件都能处理这种情况。ext4 和 XFS 会在挂载时重放日志,因此文件系统可以正常启动。PostgreSQL 会在启动时重放预写式日志,日志中会记录相关信息:

LOG:  database system was not properly shut down; automatic recovery in progress

InnoDB 也会执行相同操作,并在启动期间输出自己的崩溃恢复日志。这种恢复是数据库的正常设计行为,因此,对处于空闲状态的 PostgreSQL 或 MySQL 创建单卷快照,通常可以正常恢复。

但在某些情况下,崩溃一致性确实不够,而这些情况往往会造成严重问题。如果数据分布在两个卷上,根磁盘和独立数据磁盘会在不同时间点创建快照,因此数据文件和日志目录可能不一致,恢复时也没有可正确重放的内容。应用写入但未调用 fsync 的任何文件,例如尚未接收完成的上传文件或队列文件,都可能以截断状态恢复。应用保存在内存中、按定时器刷新到磁盘的内容,则根本不会包含在镜像中。

因此,在创建快照前,先将转储写入磁盘。这样,无论实时数据文件处于什么状态,镜像中都会包含一个可以确认内部一致的文件。

sudo -u postgres pg_dumpall --clean --file=/var/backups/pg-$(date +%F).sql
sudo mysqldump --single-transaction --routines --all-databases > /var/backups/mysql-$(date +%F).sql

--single-transaction 可以在不阻塞写入的情况下,为 InnoDB 表创建一致的转储,因为转储会在一个可重复读事务中执行。它不涵盖 MyISAM 表;MyISAM 表需要加锁或停止服务器。确认转储不为空且未被截断后,再依赖它进行恢复:完整的 mysqldump 会以 tail -n 1 /var/backups/mysql-$(date +%F).sql 开头的 Dump completed 注释结尾。

如果使用独立数据卷,可以在快照所需的几秒钟内冻结该卷:

sudo fsfreeze -f /srv
# take the snapshot from your provider's panel or API
sudo fsfreeze -u /srv

只能冻结数据卷。绝不要冻结 /。冻结根文件系统会阻塞服务器上的所有写入,包括用于输入解冻命令的 shell,因此您会把自己锁在系统外,只能等待硬重置。

异地副本:restic 或 Borg

快照负责快速恢复。异地副本负责在云服务商发生故障时保留数据。restic 是不错的默认选择,因为它支持去重、客户端加密,并可写入兼容 S3 的对象存储、SFTP 或普通目录。将 存储 VPS 作为异地目标 很合适,因为备份仓库需要的是容量,而不是 IOPS。

sudo apt update && sudo apt install -y restic
sudo sh -c 'umask 077; head -c 24 /dev/urandom | base64 > /root/.restic-pass'
sudo chmod 600 /root/.restic-pass
sudo cat /root/.restic-pass

现在将该密码短语复制到密码管理器中,并确保密码管理器位于另一台设备上,而不是这台服务器。没有密码短语就无法打开 restic 仓库,也不存在恢复途径。如果密码的唯一副本保存在刚刚丢失的服务器上,备份就只是一堆无法解密的数据。

export RESTIC_REPOSITORY="s3:https://s3.example.com/vps-backups"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
sudo -E restic init
sudo -E restic backup /etc /srv /var/backups
sudo -E restic snapshots

sudo -E 会保留这些变量,因为不使用它时,root 会获得干净的环境,restic 也会报告未指定仓库位置。restic snapshots 应列出刚刚执行的备份,并显示其主机和路径。应按计划验证仓库本身,并实际读回部分数据,而不只是检查仓库结构:

sudo -E restic check --read-data-subset=5%
sudo -E restic restore latest --target /tmp/restore-check
sudo -E restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

未经测试的备份只是猜测。至少在另一台 VPS 上恢复一次,记录所需时间,因为这个时间才是实际的恢复目标。Borg 是另一个可靠的选择,它通过 SSH 存储仓库,而不是使用对象存储;相关权衡见restic 与 BorgBackup 对比

克隆 VPS 上线生产前需要修复的问题

克隆是原始系统的精确副本。这既是它的优势,也是问题所在。原始系统中用于区分自身的一切内容都会被复制,重复内容会发生冲突。

重新生成 SSH 主机密钥。 克隆会携带原始系统的 /etc/ssh/ssh_host_* 文件,因此两台服务器会使用相同的主机身份。控制其中一台服务器的人员可以向所有已接受该密钥的客户端冒充另一台服务器。SSH 不会发出警告,因为客户端收到的正是它预期的密钥。

sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -A
sudo systemctl restart ssh
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

ssh-keygen -A 会为守护进程所需的每种类型写入新密钥。上一条命令显示的指纹必须与原始服务器上的指纹不同。重启 sshd 不会关闭已建立的连接,因此当前会话不会中断。应在任何人连接克隆服务器之前完成此操作。如果延后处理,所有已经信任继承密钥的客户端都会收到 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!,并且必须先运行 ssh-keygen -R <host>

重置 machine ID。 /etc/machine-id 是 systemd 在首次启动时生成的一次性唯一标识符,克隆会继承该标识符。

sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
sudo reboot

空的 /etc/machine-id 会让 systemd 在下一次启动时生成新值,因此应截断文件,而不是删除文件。该标识符重复时会导致两类问题。对于通过 DHCP 获取地址的镜像,systemd-networkd 默认根据 machine ID 生成 DHCP 客户端标识符。因此,两个克隆会以同一个客户端身份请求租约,DHCP 服务器会向它们分配同一个地址。journald 还会在每条日志中写入 machine ID,因此集中式日志收集器会把两台服务器记录为同一台主机。重启后运行 cat /etc/machine-id,确认该值已经改变。

更改主机名。

sudo hostnamectl set-hostname web-02
grep 127.0.1.1 /etc/hosts

hostnamectl 会写入 /etc/hostname,并立即应用该名称。它不会修改 /etc/hosts,因此还要编辑其中的 127.0.1.1 行,使其保持一致。跳过此步骤后,新名称无法解析,所有 sudo 调用都会等待解析失败,并输出 sudo: unable to resolve host web-02: Name or service not known

轮换镜像中预置的所有凭据。 克隆包含原始系统的机密信息,因此现在有两台机器可以冒充原始系统。检查 SSH authorized_keys 文件、云服务商和 DNS API 令牌、应用程序 .env 文件、数据库密码、TLS 私钥、监控注册令牌以及 restic 仓库密码。以下命令可以找到其中大部分内容:

sudo grep -rIlE 'PASSWORD|SECRET|TOKEN|API_KEY' /etc /srv /opt /home 2>/dev/null
sudo find / -name '.env' -not -path '/proc/*' -not -path '/sys/*' 2>/dev/null

如果克隆只是永远不会承载流量的测试副本,应撤销凭据,而不是轮换凭据。持有生产环境有效 API 令牌的预发布服务器,本质上就是一台补丁管理更差的生产服务器。

关闭现在会重复运行的任务。 两台服务器使用相同的 crontab,会在同一分钟访问相同的外部系统。

systemctl list-timers --all
sudo crontab -l
sudo ls -l /etc/cron.d /etc/cron.daily

restic 的情况值得单独说明,因为它会破坏保留策略,而不只是明确报错。restic 会使用主机名标记每个快照,restic forget --keep-daily 7 会按主机分别应用策略。两台服务器报告相同的主机名时,会被视为同一台主机。因此,7 个“每日”快照可能全部来自克隆,而原始服务器的快照会被清理。应在第一次备份运行前修复主机名,或在克隆上停止该计时器。certbot 的情况更简单:两台服务器为相同域名续期时,会触发证书颁发机构的重复证书速率限制,失败的任务会报告该域名集合已颁发过过多证书。克隆的域名仍指向原始服务器时,也无法通过 HTTP challenge,因此应在克隆上禁用续期。

处理监控代理。 大多数代理根据主机名或安装时写入的 ID 文件识别主机。因此,两个代理以同一主机身份上报时,会把指标交错写入同一条时间序列。CPU 图表显示的值可能不是任何一台机器实际产生的,告警也会反复触发。停止并删除克隆上的代理,或按照供应商记录的流程,使用新主机名重新注册代理。

检查网络配置中是否仍使用原始服务器的地址。 如果镜像中的 netplan 配置了静态地址,克隆会占用属于另一台机器的 IP。

ip -br addr
sudo grep -r addresses /etc/netplan/

如果要将该克隆用作模板,请清除 cloud-init 状态。

sudo cloud-init clean --logs

这会删除 /var/lib/cloud 下的 cloud-init 状态,使下一次启动重新运行首次启动模块,其中包括在不存在 SSH 主机密钥时生成密钥。某些版本还提供用于重置 machine ID 的标志。请在自己的镜像上运行 cloud-init clean --help,确认当前版本支持哪些选项,不要直接依赖其他来源的选项列表。

何时使用哪种方式

回滚有风险的升级:创建快照。 在变更前几分钟创建快照,然后执行升级;如果出现问题,再恢复该镜像。恢复操作会丢弃快照之后的所有写入,因此对于正在接收实时流量的服务器,应先导出数据库,并明确了解可能丢失哪个时间窗口的数据。对于可以下线十分钟的 do-release-upgrade 所在服务器,快照就是完整的回滚方案。

迁移到更大的方案:部署克隆。 从快照创建克隆,并将其部署到更大的方案上。按照上面的身份清单逐项处理,然后在流量切换前使用独立 IP 测试克隆。提前一天降低 DNS TTL,以便快速完成切换;在新服务器承载真实流量并稳定运行前,保持原服务器继续运行。首先使用在两台服务器上采用相同的基准测试方法确认更大的方案确实更适合您的工作负载,因为在更繁忙的硬件上增加 vCPU 数量并不总是升级。

构建模板:为已清理的机器创建快照。 安装并加固一台服务器,然后在创建镜像前删除所有唯一信息。不要保留主机密钥,清空 machine ID,删除个人 authorized_keys 和凭据,并清理 cloud-init。然后创建快照。基于该快照部署的每个实例都会在首次启动时生成自己的身份,因此上面的清单将不再只是清单。将其与新 VPS 上的标准前十分钟操作结合使用,这样模板就会预先包含原本需要重复执行的工作。

FAQ

VPS 快照是备份吗?

不是,因为它与源服务器共享故障域。快照存储在服务提供商的存储中,属于您的账户,通常位于同一区域。账户被暂停、API key 被盗,或意外删除实例,都可能通过一次操作同时删除服务器及其快照;而且许多服务提供商默认会在删除实例时一并删除快照。快照是最快的回滚方式,因此应持续创建快照,并在服务提供商无法控制的基础设施上保留第二份加密副本。

创建快照前是否需要停止数据库?

不一定,但您需要接受快照的实际状态。服务提供商创建的快照具有崩溃一致性,也就是说,镜像反映的是断电后磁盘可能呈现的状态。PostgreSQL 和 InnoDB 会在启动时从该状态恢复,PostgreSQL 在此过程中会记录 database system was not properly shut down; automatic recovery in progress。如果数据分布在两个不同时间创建快照的卷上,或者应用未使用 fsync 写入数据,则无法保证恢复成功。先将 pg_dumpallmysqldump --single-transaction 写入磁盘,使镜像包含一个您确认一致的文件。

为什么两个克隆服务器会争用同一个 IP 地址?

因为它们共享 /etc/machine-id。在使用 DHCP 的镜像中,systemd-networkd 默认根据 machine ID 生成 DHCP 客户端标识符,因此两个克隆会以同一个客户端身份请求租约,DHCP 服务器会向两者提供同一个地址。将 /etc/machine-id 截断为 0 字节,删除 /var/lib/dbus/machine-id,再将其符号链接回 /etc/machine-id,然后重启,使 systemd 生成新值。另一个常见原因是 /etc/netplan/ 中写入了静态地址,而克隆完整复制了该地址;使用 ip -br addr 检查。

检查克隆是否可以安全投入生产的最快方法是什么?

将以下 4 项与原服务器进行比较。在两台服务器上运行 ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub,确认指纹不同。在两台服务器上运行 cat /etc/machine-id,确认值不同。运行 hostnamectl status,确认名称是新的且可以解析,这样 sudo 就不会发出警告。然后运行 systemctl list-timers --all,停止所有与共享系统通信的 timer,例如备份、证书续期或监控 agent,直到确定由哪台机器负责该任务。