Linode 全盘备份与恢复:在 Rescue Mode 通过 SSH 复制磁盘
全盘镜像会逐字节复制 Linode 磁盘,适合保留可启动系统或迁移旧环境。它也会复制空闲空间,速度和文件大小取决于整块磁盘容量,而不是已用数据量。
dd不理解文件内容,写错目标设备会立即覆盖数据。本文默认从已停机的源盘下载镜像,并恢复到新实例的空磁盘;在业务验证完成前保留源实例和原磁盘。
先判断是否真的需要全盘镜像
| 目标 | 更合适的方法 |
|---|---|
| 定期保护业务数据 | 应用原生备份、文件级备份或异地对象存储 |
| 快速恢复 Linode | 已启用时使用官方 Backup Service,并恢复到测试实例验证 |
| 迁移少量文件 | SFTP、rsync 或应用导出 |
| 保留完整磁盘布局与可启动系统 | Rescue Mode 下复制块设备 |
一次 .img 文件不是持续备份策略。镜像创建后仍要加密、限制访问、保留多份,并定期实际恢复验证。
变更前记录源盘和接收端
先进入Linode Rescue Mode,在 Rescue 表单中记录系统盘被映射到哪个设备。本文用 /dev/sda 举例,不能根据旧截图或实例名称猜测。
在 Lish 的 Finnix 环境中核对:
SOURCE_DEVICE=/dev/sda
lsblk -f
if findmnt --source "$SOURCE_DEVICE" >/dev/null; then
echo '源设备仍在挂载,停止创建镜像' >&2
exit 1
fi
SOURCE_BYTES=$(blockdev --getsize64 "$SOURCE_DEVICE") || exit 1
printf '%s\n' "$SOURCE_BYTES"
只有 Rescue 表单、磁盘容量、文件系统和 lsblk -f 都指向同一块盘,才能设置 SOURCE_DEVICE。源设备必须未挂载;记录最后输出的源盘字节数。
临时 SSH 尚未开放时,先在接收机器检查保存位置和可用容量。提示出现后,粘贴刚才从源端 Lish 记录的纯数字:
DEST_IMAGE=/home/archive/linode.img.partial
DEST_DIR=$(dirname "$DEST_IMAGE")
test -d "$DEST_DIR" || { echo '接收路径不存在' >&2; exit 1; }
test ! -e "${DEST_IMAGE%.partial}" || { echo '正式镜像已存在,改用新名称' >&2; exit 1; }
read -r -p '源盘字节数(来自源端 Lish): ' SOURCE_BYTES
case "$SOURCE_BYTES" in
''|*[!0-9]*) echo '源盘字节数无效' >&2; exit 1 ;;
esac
DEST_FREE=$(df --output=avail -B1 "$DEST_DIR" | awk 'END {print $1}') || exit 1
case "$DEST_FREE" in
''|*[!0-9]*) echo '无法读取接收路径的可用字节数' >&2; exit 1 ;;
esac
test "$DEST_FREE" -ge "$SOURCE_BYTES" || { echo '接收路径空间不足' >&2; exit 1; }
接收机器必须位于实例外。镜像可能包含数据库、密码哈希、API Token 和私钥;保存位置应只允许授权管理员访问,并纳入静态加密、保留期限和删除流程。
仅为本次传输启动 Rescue SSH
Finnix 默认不接受 SSH。通过 Lish 设置本次 Rescue 环境的临时 root 密码并启动服务:
passwd
service ssh start
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
这个密码不会修改源盘里的系统 root 密码。Cloud Firewall 只允许接收机器的公网地址访问 SSH;不要把 Rescue root 登录开放给所有 IPv4 或 IPv6 来源。
在接收机器第一次连接时,确认 IP 确实是当前 Rescue 实例,并将客户端提示的 Ed25519 指纹与 Lish 输出逐字比较。若连接失败或指纹不一致,先在源端 Lish 执行 service ssh stop 并删除临时规则,再停止排查。
下载磁盘到 .partial 镜像
在接收机器设置本次实际值。SOURCE_DEVICE 必须与 Lish 中确认的一致:
RESCUE_IP=192.0.2.9
SOURCE_DEVICE=/dev/sda
DEST_IMAGE=/home/archive/linode.img.partial
启用 pipefail,并再次读取远端设备大小。只有它仍与源端 Lish 记录一致、SSH 读取和本地 dd 写入都成功,临时文件才会改名为正式镜像:
set -o pipefail
TRANSFER_FAILED=1
REMOTE_SOURCE_BYTES=$(ssh root@"$RESCUE_IP" "blockdev --getsize64 '$SOURCE_DEVICE'") \
|| REMOTE_SOURCE_BYTES=
if test "$REMOTE_SOURCE_BYTES" != "$SOURCE_BYTES"; then
echo '远端源盘大小读取失败或与 Lish 记录不一致' >&2
elif ssh root@"$RESCUE_IP" "dd if='$SOURCE_DEVICE' bs=16M status=progress" \
| dd of="$DEST_IMAGE" bs=16M status=progress conv=fsync; then
if mv -- "$DEST_IMAGE" "${DEST_IMAGE%.partial}"; then
TRANSFER_FAILED=0
else
echo '传输完成但正式镜像改名失败,不能进入校验' >&2
fi
else
echo '传输失败:保留 .partial 仅供诊断,不能用于恢复' >&2
fi
网络中断后,.partial 只是未完成的文件,无法用于恢复或自动续传。重新确认源设备与空间后,从第 0 字节传输;成功生成完整文件后再校验。
无论成功还是传输失败,都先在源端 Lish 停止临时 SSH:
service ssh stop
随后从源端 Cloud Firewall 删除本次临时规则。完成这两项清理后回到接收机器;失败分支此时退出,成功分支才继续校验:
test "$TRANSFER_FAILED" -eq 0 || exit 1
比较源盘与镜像 SHA-256
源盘必须保持未挂载且不再变化。在 Lish 对整个源设备计算摘要:
sha256sum "$SOURCE_DEVICE"
在接收机器对正式镜像计算摘要:
FINAL_IMAGE=/home/archive/linode.img
sha256sum "$FINAL_IMAGE"
读取整块磁盘会花费额外时间,但两个 SHA-256 必须完全一致。任何读取错误、大小差异或摘要不一致都表示镜像不能用于恢复。
再以只读方式查看镜像内容:
mkdir -p /mnt/linode-image
mount -o loop,ro "$FINAL_IMAGE" /mnt/linode-image
findmnt /mnt/linode-image
ls -la /mnt/linode-image
umount /mnt/linode-image
列出文件后,还要检查数据库导出、配置、文件时间和权限,并记录镜像摘要与创建日期。完成这些检查,才能判断应用是否可以恢复。
恢复到新实例的空磁盘
优先创建新 Linode 和空磁盘,避免覆盖唯一源盘。目标磁盘容量必须不小于镜像大小,并在目标实例的 Rescue 表单中记录设备映射。
通过目标实例的 Lish 核对:
TARGET_DEVICE=/dev/sda
lsblk -f
if findmnt --source "$TARGET_DEVICE" >/dev/null; then
echo '目标设备仍在挂载,停止恢复' >&2
exit 1
fi
TARGET_BYTES=$(blockdev --getsize64 "$TARGET_DEVICE") || exit 1
printf '%s\n' "$TARGET_BYTES"
目标设备必须未挂载。记录最后输出的目标磁盘字节数,并确认它是准备覆盖的空磁盘且没有其他配置引用。
临时 SSH 尚未开放时,先在保存镜像的机器检查正式镜像和容量。提示出现后,粘贴刚才从目标 Lish 记录的纯数字:
TARGET_IP=192.0.2.10
TARGET_DEVICE=/dev/sda
FINAL_IMAGE=/home/archive/linode.img
test -f "$FINAL_IMAGE" || { echo '找不到正式镜像' >&2; exit 1; }
IMAGE_BYTES=$(stat -c %s "$FINAL_IMAGE") || exit 1
read -r -p '目标磁盘字节数(来自目标 Lish): ' TARGET_BYTES
case "$TARGET_BYTES" in
''|*[!0-9]*) echo '目标磁盘字节数无效' >&2; exit 1 ;;
esac
test "$TARGET_BYTES" -ge "$IMAGE_BYTES" || { echo '目标磁盘小于镜像' >&2; exit 1; }
上述检查全部通过后,才在目标 Lish 为目标 Rescue 环境设置临时密码、启动 SSH,并记录本次临时主机指纹:
passwd
service ssh start
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
目标 Rescue 的 Ed25519 指纹必须与保存镜像机器首次连接时的提示一致。目标 Cloud Firewall 只临时允许保存镜像机器的公网地址访问 SSH,不能开放给所有来源。若连接失败或指纹不一致,先在目标 Lish 执行 service ssh stop 并删除临时规则,再停止排查。
再次从 Cloud Manager 与 lsblk -f 确认目标后,执行覆盖写入:
set -o pipefail
RESTORE_FAILED=1
REMOTE_TARGET_BYTES=$(ssh root@"$TARGET_IP" "blockdev --getsize64 '$TARGET_DEVICE'") \
|| REMOTE_TARGET_BYTES=
if test "$REMOTE_TARGET_BYTES" = "$TARGET_BYTES" \
&& test "$REMOTE_TARGET_BYTES" -ge "$IMAGE_BYTES"; then
if dd if="$FINAL_IMAGE" bs=16M status=progress \
| ssh root@"$TARGET_IP" "dd of='$TARGET_DEVICE' bs=16M status=progress conv=fsync"; then
RESTORE_FAILED=0
echo '写入完成,进入摘要验证'
else
echo '写入失败:目标盘内容不完整,不能启动' >&2
fi
else
echo '远端目标盘大小读取失败、与 Lish 记录不一致或小于镜像' >&2
fi
这条命令会覆盖目标设备。不要把 TARGET_DEVICE 指向 Finnix 设备、源实例磁盘、已挂载磁盘或包含其他数据的盘。
若写入失败,先在目标 Lish 执行 service ssh stop,再从目标 Cloud Firewall 删除本次临时规则。完成清理后回到保存镜像的机器退出,不能继续摘要或启动步骤:
test "$RESTORE_FAILED" -eq 0 || exit 1
验证写回的有效字节
目标磁盘可能大于镜像,因此只比较前 IMAGE_BYTES 字节:
sha256sum "$FINAL_IMAGE"
ssh root@"$TARGET_IP" "head -c '$IMAGE_BYTES' '$TARGET_DEVICE' | sha256sum"
两个摘要一致,才证明镜像范围已完整写入。摘要验证前不要创建 Configuration Profile 或尝试启动目标系统;不一致时把目标盘标记为不完整,不能启动。
无论写回摘要是否一致,验证命令结束后都要在目标 Lish 停止临时 SSH,并从目标 Cloud Firewall 删除本次临时放行规则:
service ssh stop
如果目标盘更大,只有在确认文件系统类型且目标盘未挂载后,才使用该文件系统的官方检查和扩容工具。e2fsck 与 resize2fs 只适用于 ext 文件系统,不能用于 XFS 或 Btrfs。
创建启动配置并保留回滚
在目标 Linode 新建 Configuration Profile,将恢复后的系统盘映射到镜像原来使用的设备位置,并按实际需要配置 swap、内核与 Network Helper。
先通过 Lish 观察启动,再验证 lsblk、findmnt、网络地址、SSH、失败服务、数据库与真实业务请求。镜像中的旧网络配置、密钥和软件不一定适合新实例。
在上述验证和业务切换完成前,保留源实例、原实例磁盘与正式镜像。若目标无法启动,关机后修正 Configuration Profile 或重新创建空目标盘,不要回头覆盖唯一源盘。
iVPSer 使用边界
iVPSer 控制台可用于查看当前可选方案;Rescue、SSH 临时访问、磁盘镜像、恢复写入和应用验证由使用者负责。实例容量、库存、系统和网络能力以实时控制台及实际实例为准。