克隆VPS后machine-id重复怎么修复?
克隆VPS若共享同一个/etc/machine-id,DHCP会生成相同DUID并争用租约。本文用4条命令安全重建ID,说明D-Bus副本陷阱,并教您在制作模板快照前清空该文件。
/etc/machine-id 的含义,以及重复值为何会造成问题
克隆出的 VPS 会使用与源服务器相同的 /etc/machine-id,但该值应仅属于一个安装实例。修复只需执行 4 个命令:清空文件;如果 D-Bus 副本是普通文件,则将其删除;重新生成;重启。重启是人们常常跳过的步骤,也是让更改生效的步骤。
/etc/machine-id 保存一个以换行符结尾的 32 字符小写十六进制字符串。解码后,它是一个 16 字节(128 位)的值。machine-id(5) 手册页称其为机密信息,并说明不得通过网络公开,因为任何读取它的对象都可以在以后再次识别你的机器。系统安装时会写入该值,之后不会再更改。
这里有 3 个容易混淆的标识符,因此有必要分别说明。主机名是你选择的标签,可以随时更改。/sys/class/dmi/id/product_uuid 中的 DMI(桌面管理接口)产品 UUID 由 hypervisor 提供,只有 root 可以读取。machine ID 是第三个标识符:它由操作系统生成,机器上的每个用户都可以读取。
实际读取 machine ID 的组件
DHCP 客户端标识符。 这是影响最直接的部分。systemd.network(5) 在 [DHCPv4] 部分记录了 ClientIdentifier= 的默认值为 duid。该设置会发送由 IAID 和 DUID(DHCP 唯一标识符)构成的 RFC 4361 客户端 ID。networkd.conf(5) 记录了默认 DUID 类型为 vendor。在这种类型下,DUID 值使用 43793 作为供应商标识符(systemd),并根据 machine ID 的哈希内容生成。DHCPv6 使用相同的 DUID。两个具有相同 machine ID 的克隆系统会生成相同的 DUID;如果它们还保留了相同的接口名称,发送的客户端标识符就会逐字节相同。DHCP 服务器随后会将两个系统识别为一个客户端,并向两台服务器提供同一个租约。表现通常是 IP 地址在两台服务器之间来回移动,或者其中一台服务器在另一台续租时丢失自己的地址。
journald。 Journal 文件位于 /var/log/journal/<machine-id>/。该目录的名称就是这个 ID。将两个克隆系统的 journal 发送到同一个收集器后,日志会写入同一个目录,并被识别为同一台主机。
D-Bus。 /var/lib/dbus/machine-id 是这种文件格式的起点。在 Debian 和 Ubuntu 上,它是指向 /etc/machine-id 的符号链接。在某些系统上,它是一个独立的实际文件,保存着自己的副本;该副本正是下面操作中的陷阱。
每台主机上的代理。 监控代理、许可证检查工具、资产清单工具和备份客户端通常将 machine ID 用作默认主机标识符,因为它稳定且无需配置。两台服务器上报同一个标识时,指标会合并为一条时间序列,或者一个许可证席位会覆盖两台机器。请检查代理如何生成主机 ID,不要假定它使用 hostname。
如何判断是否存在重复标识
在两台服务器上运行此命令,然后比较输出。
cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
sudo cat /sys/class/dmi/id/product_uuid两台正在运行的服务器具有相同的 machine ID,说明其中一台是从另一台克隆而来的。如果您希望使用一条命令,hostnamectl 会在其 Machine ID: 行输出相同的值。
ls -l 的结果决定下一步操作。符号链接如下所示:
lrwxrwxrwx 1 root root 15 Aug 21 09:12 /var/lib/dbus/machine-id -> /etc/machine-id以 -rw-r--r-- 开头的行表示这是一个存储旧 ID 副本的实际文件。您必须删除该文件,因为 systemd-machine-id-setup 会先读取它,然后才执行其他操作。
产品 UUID 同样重要。systemd-machine-id-setup(1) 会优先使用 KVM UUID,无法使用时才回退到随机生成。因此,如果您的服务提供商为两个克隆实例分配了相同的 SMBIOS(系统管理 BIOS)UUID,重新生成后仍会得到相同的 machine ID。两台服务器的产品 UUID 不同,则无需担心这一问题。
在克隆的 VPS 上重新生成 machine ID
执行顺序很重要。systemd-machine-id-setup(1) 规定,如果系统已经配置了有效的 D-Bus machine ID,系统会复制该 D-Bus machine ID,并用它初始化 /etc/machine-id。请保留真实的 /var/lib/dbus/machine-id,否则重新生成的仍是您想要删除的那个值。
sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id # only if ls -l showed a real file
sudo systemd-machine-id-setup
sudo ln -sf /etc/machine-id /var/lib/dbus/machine-id
cat /etc/machine-id必须先截断文件,因为该工具仅在文件不存在或为空时执行;如果文件已包含有效 ID,则不会执行任何操作。systemd-machine-id-setup 会将执行结果输出到标准错误。在 KVM VPS 上,通常会看到:
Initializing machine ID from KVM UUID.Initializing machine ID from random generator. 表示没有可用的 hypervisor UUID。只要 cat /etc/machine-id 现在输出的内容与另一台服务器不同,哪种结果都可以。
该符号链接可确保 D-Bus 和 systemd 使用同一个值。如果您希望使用独立的真实文件,请改为运行 sudo dbus-uuidgen --ensure:文件不存在时,该命令会使用新的 UUID 创建文件。如果未安装 dbus,则根本不存在 /var/lib/dbus 目录,ln 会因 No such file or directory 失败,此时可以跳过这两行。
然后重新启动。
sudo reboot为何重启不是可选步骤
所有已经读取旧值的进程仍在使用该值。sd_id128_get_machine() 会将 ID 缓存在调用进程中,因此运行中的 daemon 不会发现文件已更改。journald 已经打开 /var/log/journal/<old-id>/system.journal,并会继续向其中追加内容。systemd-networkd 在启动时确定了 DUID,并在每次续租时继续发送旧的客户端标识符,而这通常正是你要解决的问题。D-Bus 也会在启动时读取 ID。你可以逐个重启服务,但总会漏掉某个服务;PID 1 也在持有旧值。
重启后,检查以下两部分:
cat /etc/machine-id
ls /var/log/journal//var/log/journal/ 现在包含一个以新 ID 命名的第二个目录,新条目会写入该目录。普通的 journalctl 只读取当前计算机的目录,因此克隆前的历史记录不会显示在默认视图中。日志仍保存在磁盘上:journalctl --merge 会读取所有 journal 目录,包括旧目录。确认不再需要旧日志后,再删除旧目录。
这也是不能在容器中演练此过程的原因。容器共享主机内核,并且不会启动自己的 PID 1,而重启正是整个过程的关键。应按生产环境中的实际方式测试:克隆一个 VM,运行这些命令,重启,然后将该 ID 与源计算机进行比较。
在创建快照前清空,不要在克隆后清空
逐个修复克隆服务器可以解决问题。修复镜像更好,因为从错误快照恢复的每台服务器都会继承相同的值。将此作为关闭模板前执行的最后一步。
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 shutdown -h now清空该文件,不要删除它。machine-id(5) 建议为用于多台机器的镜像使用空文件,因为保留空文件后,使用只读镜像时,可以将临时文件绑定挂载到该文件上。在只读的 /etc 上,启动时生成的 ID 会写入该临时文件;文件系统变为可写后,systemd-machine-id-setup --commit 会将该 ID 写入文件。
需要提前规划一个副作用:未填充的 machine ID 会使下一次启动被视为首次启动。因此,包含 ConditionFirstBoot=yes 的单元会在这次启动时运行,之后的每次启动都会跳过。构建模板前,使用 grep -rl ConditionFirstBoot /usr/lib/systemd/system/ 查看镜像将在首次启动时运行哪些内容。
模板和快照是不同的对象。这一区别决定是否会复制身份信息。模板是有意准备的构建产物,而 快照是某台运行中服务器在特定时间点的副本,会将该服务器的身份信息及其数据一并保留。
云镜像为什么能正确处理,而您的快照不能
发行版云镜像的构建目标就是用于克隆,因此其中的 machine ID 默认未填充,首次启动时会生成该值。cloud-init 针对这一流程提供了明确的步骤。在使用 systemd 的系统上,cloud-init clean --machine-id 会将 /etc/machine-id 设置为字面字符串 uninitialized。cloud-init CLI 参考文档也将此列为克隆 golden image 时的最佳实践,因此该镜像下次启动时会生成唯一的 machine ID。
您自行创建的快照则不同。单击创建快照时,该文件已经包含 machine ID,因此从快照恢复的每台服务器都会携带同一个值,而恢复流程不会清除它。这与 将运行中的服务器迁移到新的 VPS 属于同一类问题:副本会完整保留原服务器内容,而您不希望复制的正是其身份信息。
克隆还会复制哪些内容
- SSH 主机密钥。
/etc/ssh/ssh_host_*也会被复制,因此两台服务器会向客户端提供相同的指纹。删除这些文件,然后运行sudo ssh-keygen -A;在 Debian 和 Ubuntu 上运行sudo dpkg-reconfigure openssh-server。之后,客户端会警告主机密钥已更改,这是正确的行为。 - 主机名。 使用
sudo hostnamectl set-hostname app02设置主机名,然后确认/etc/hosts仍能解析到新名称。 - 静态网络配置。 如果克隆的是使用静态地址的主机,克隆机启动后会立即与原主机发生地址冲突。克隆机接入网络前,先查看
/etc/netplan/。 - 系统时钟。 恢复的快照会继续使用创建快照时的时间。恢复的 VPS 发生大幅时间跳变 会导致 TLS 证书验证失败,并在时间同步完成前打乱日志顺序。
还应在克隆机上执行新 VPS 的前 10 分钟检查清单。克隆主机会继承源主机的用户帐户、SSH 密钥、防火墙规则和计划任务,但这些内容都没有针对克隆机即将执行的任务进行审核。
FAQ
更改 /etc/machine-id 后必须重启吗?
是。进程只读取一次 machine ID,并将其缓存,因此新值不会传递给任何已经运行的进程。journald 仍会继续写入以旧 ID 命名的 journal 目录,DHCP 客户端也会继续发送根据旧值生成的客户端标识符,而这通常正是您更改该值的原因。单独重启服务可以修复其中一部分问题,但 PID 1 也保存着旧值。请重启系统,然后使用 cat /etc/machine-id 确认,并与另一台服务器进行比较。
/etc/machine-id 与硬件 UUID 相同吗?
不相同。/sys/class/dmi/id/product_uuid 中的 DMI 产品 UUID 来自 hypervisor,只有 root 可以读取。machine ID 由操作系统生成,存储在任何用户都可以读取的普通文件中。两者只存在单向关联:对于 KVM guest,当没有可复制的 D-Bus ID 时,systemd-machine-id-setup 会使用 hypervisor UUID 生成新的 machine ID。如果两个克隆实例共享同一个产品 UUID,它们会重新生成相同的 machine ID。因此,在确认结果之前,也要比较该文件。
应删除 /etc/machine-id,还是将其留空?
准备镜像时,请将其留空。machine-id(5) 更偏好空文件,因为当镜像以只读 /etc 运行时,systemd 可以将临时文件 bind mount 到该文件上。在可写系统中,删除文件也可行,某些克隆脚本会采用这种方式,但空文件是更安全的默认选择。cloud-init 出于同样目的,会将单词 uninitialized 写入该文件。
为什么我克隆的两台服务器获得了相同的 DHCP 地址?
因为两台服务器发送了相同的客户端标识符。对于 DHCPv4,systemd-networkd 默认使用 ClientIdentifier=duid;默认 DUID 根据 /etc/machine-id 的哈希值生成。因此,如果克隆实例的 machine ID 和接口名称也相同,它们会生成相同的标识符。DHCP 服务器根据该标识符进行匹配,将两个请求视为同一个客户端,并分配同一个租约。请为每台服务器设置独立的 machine ID,然后重启两台服务器。如果服务器仍提供旧地址,请直接在 DHCP 服务器上清除过期租约。