背景
一套已经配置好的树莓派系统被完整复制到另一张存储卡上,随后分别在两台树莓派设备上启动。这样做可以节省大量重复配置时间,尤其适合系统中已经预先配置好远程访问、网络脚本、代理服务、Web 服务和若干常驻服务的情况。
不过,完整克隆系统也会带来一个明显问题:两台设备虽然硬件不同,但系统内部身份可能完全相同。
如果不处理,后续可能出现:
主机名重复
machine-id 重复
SSH Host Key 重复
远程访问入口混乱
内网穿透配置冲突
代理节点身份混淆
日志与系统服务识别异常
局域网设备识别混乱
因此,在两台设备正式长期运行前,需要先把系统身份拆分开。
克隆系统后需要处理哪些身份信息
克隆系统后,至少需要检查和处理以下内容:
hostname
/etc/hostname
/etc/hosts
cloud-init 主机名配置
/etc/machine-id
/var/lib/dbus/machine-id
SSH Host Key
远程访问服务配置
内网穿透服务配置
代理服务节点配置
其中,最基础的三项是:
hostname
machine-id
SSH Host Key
这三项分别对应:
设备名称
系统身份
SSH 服务器身份
只改主机名是不够的。
如果 machine-id 和 SSH Host Key 仍然相同,两台设备在系统层面和安全身份层面仍然带有克隆痕迹。
第一次只改主机名为什么失败
一开始使用了常规方式修改主机名:
hostnamectl set-hostname 新主机名
并同步修改:
/etc/hostname
/etc/hosts
当时看起来已经成功,但重启后主机名又恢复成旧值。
这说明问题并不在 hostnamectl 本身,而是系统启动过程中还有其他组件在覆盖主机名。
进一步检查发现,这套系统启用了 cloud-init。
在该类系统中,主机名可能不是只由 /etc/hostname 决定,而是还会受到启动分区中的 cloud-init 配置影响。
关键位置包括:
/boot/firmware/user-data
/etc/cloud/cloud.cfg
如果启动分区的 user-data 中仍然写着旧主机名,同时 cloud-init 没有设置为保留当前主机名,那么重启后系统可能再次把主机名改回旧值。
因此,正确做法不是只改 /etc/hostname,而是要同时处理 cloud-init 的源头配置。
正确修改第一台设备主机名
假设第一台设备的新主机名为:
设备A
在 root 用户下,可以一次性执行:
cp -a /boot/firmware/user-data /boot/firmware/user-data.bak.$(date +%F-%H%M%S)
cp -a /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.bak.$(date +%F-%H%M%S)
cp -a /etc/hostname /etc/hostname.bak.$(date +%F-%H%M%S)
cp -a /etc/hosts /etc/hosts.bak.$(date +%F-%H%M%S)
sed -i -E 's/^hostname:[[:space:]]*.*/hostname: 设备A/' /boot/firmware/user-data
sed -i -E 's/^preserve_hostname:[[:space:]]*.*/preserve_hostname: true/' /etc/cloud/cloud.cfg
echo 设备A > /etc/hostname
hostnamectl set-hostname 设备A
if grep -qE '^127\.0\.1\.1[[:space:]]+' /etc/hosts; then
sed -i -E 's/^127\.0\.1\.1[[:space:]].*/127.0.1.1 设备A/' /etc/hosts
else
echo '127.0.1.1 设备A' >> /etc/hosts
fi
hostname
cat /etc/hostname
hostnamectl
grep -nE 'preserve_hostname|hostname:' /etc/cloud/cloud.cfg /boot/firmware/user-data
grep -nE '127\.0\.1\.1' /etc/hosts
reboot
重启后确认:
hostname
cat /etc/hostname
hostnamectl
grep -nE 'preserve_hostname|hostname:' /etc/cloud/cloud.cfg /boot/firmware/user-data
目标状态是:
hostname 显示为设备A
/etc/hostname 显示为设备A
hostnamectl 中 Static hostname 为设备A
/boot/firmware/user-data 中 hostname 为设备A
/etc/cloud/cloud.cfg 中 preserve_hostname 为 true
这样,主机名才算真正持久修改成功。
重新生成 machine-id
主机名处理完成后,需要重新生成 machine-id。
machine-id 是 systemd 体系中的系统身份标识。
如果两台克隆设备保留相同 machine-id,可能导致 systemd、D-Bus、journald、DHCP 相关身份、日志识别等出现混乱。
在 root 用户下执行:
cp -a /etc/machine-id /root/machine-id.bak.$(date +%F-%H%M%S) 2>/dev/null || true
rm -f /etc/machine-id /var/lib/dbus/machine-id
systemd-machine-id-setup
ln -sf /etc/machine-id /var/lib/dbus/machine-id
cat /etc/machine-id
reboot
重启后确认:
cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
hostnamectl
目标状态是:
/etc/machine-id 已生成新的值
/var/lib/dbus/machine-id 指向 /etc/machine-id
hostnamectl 中显示新的 Machine ID
注意,两台克隆设备的 machine-id 必须不同。
重新生成 SSH Host Key
SSH Host Key 是 SSH 服务器自己的身份密钥。
它不是用户登录用的公钥。
SSH Host Key 通常位于:
/etc/ssh/ssh_host_*
用户登录用的授权公钥通常位于:
/root/.ssh/authorized_keys
或者普通用户的:
~/.ssh/authorized_keys
因此,重新生成 SSH Host Key 不会删除已配置好的登录公钥,也不会破坏公钥登录。
它只会让客户端在下次连接时发现“服务器身份变了”。
在 root 用户下执行:
backup="/root/ssh-hostkey-backup-$(date +%F-%H%M%S)"
mkdir -p "$backup"
cp -a /etc/ssh/ssh_host_* "$backup"/ 2>/dev/null || true
rm -f /etc/ssh/ssh_host_*
ssh-keygen -t rsa -b 8192 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
chmod 600 /etc/ssh/ssh_host_*_key
chmod 644 /etc/ssh/ssh_host_*_key.pub
systemctl restart ssh
ls -l /etc/ssh/ssh_host_*
systemctl status ssh --no-pager
如果客户端下次连接时提示服务器身份变化,这是正常现象。
在客户端删除旧的 known_hosts 记录后重新连接即可。
示例:
ssh-keygen -R "[远程入口域名]:远程端口"
这里的域名和端口应替换为实际连接入口。公开文章中不应写出真实值。
第二台设备的处理
第二台设备也是从同一份系统复制出来的,所以处理逻辑完全相同。
假设第二台设备的新主机名为:
设备B
root 用户下可以一次性执行:
cp -a /boot/firmware/user-data /boot/firmware/user-data.bak.$(date +%F-%H%M%S)
cp -a /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.bak.$(date +%F-%H%M%S)
cp -a /etc/hostname /etc/hostname.bak.$(date +%F-%H%M%S)
cp -a /etc/hosts /etc/hosts.bak.$(date +%F-%H%M%S)
cp -a /etc/machine-id /root/machine-id.bak.$(date +%F-%H%M%S) 2>/dev/null || true
sed -i -E 's/^hostname:[[:space:]]*.*/hostname: 设备B/' /boot/firmware/user-data
sed -i -E 's/^preserve_hostname:[[:space:]]*.*/preserve_hostname: true/' /etc/cloud/cloud.cfg
echo 设备B > /etc/hostname
hostnamectl set-hostname 设备B
if grep -qE '^127\.0\.1\.1[[:space:]]+' /etc/hosts; then
sed -i -E 's/^127\.0\.1\.1[[:space:]].*/127.0.1.1 设备B/' /etc/hosts
else
echo '127.0.1.1 设备B' >> /etc/hosts
fi
rm -f /etc/machine-id /var/lib/dbus/machine-id
systemd-machine-id-setup
ln -sf /etc/machine-id /var/lib/dbus/machine-id
backup="/root/ssh-hostkey-backup-$(date +%F-%H%M%S)"
mkdir -p "$backup"
cp -a /etc/ssh/ssh_host_* "$backup"/ 2>/dev/null || true
rm -f /etc/ssh/ssh_host_*
ssh-keygen -t rsa -b 8192 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ''
chmod 600 /etc/ssh/ssh_host_*_key
chmod 644 /etc/ssh/ssh_host_*_key.pub
hostname
cat /etc/hostname
hostnamectl
cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
ls -l /etc/ssh/ssh_host_*
grep -nE 'preserve_hostname|hostname:' /etc/cloud/cloud.cfg /boot/firmware/user-data
grep -nE '127\.0\.1\.1' /etc/hosts
reboot
重启后确认:
hostname
cat /etc/hostname
hostnamectl
cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
ls -l /etc/ssh/ssh_host_*
grep -nE 'preserve_hostname|hostname:' /etc/cloud/cloud.cfg /boot/firmware/user-data
两台设备最终应满足:
主机名不同
machine-id 不同
SSH Host Key 不同
远程访问入口不同
代理节点身份不同
网络与服务入口的注意事项
两台设备本机监听相同端口并不一定冲突。
因为它们位于不同设备上,各自拥有自己的网络接口和本机端口空间。
真正容易冲突的是远程入口层面的配置,例如:
内网穿透代理名
远程端口
远程域名
子域名
代理节点 ID
客户端节点名称
如果两台设备都连接到同一个远程中转服务,就必须避免使用相同的代理名、远程端口或域名入口。
公开记录中不应写出真实服务名、真实端口、真实域名或真实节点名。
可以统一写成:
远程访问入口A
远程访问入口B
代理节点A
代理节点B
内网穿透规则A
内网穿透规则B
Wi-Fi 与 DHCP 的整理
在其中一台设备上,Wi-Fi 是通过自定义脚本手动连接的。
该脚本大致会完成:
拉起无线网卡
扫描 Wi-Fi
选择 SSID
输入密码
生成临时无线认证配置
启动无线连接进程
启动 DHCP 客户端
调整默认路由优先级
曾经观察到无线网卡上同时存在两个 IPv4 地址。
这通常意味着两个 DHCP 客户端同时管理了同一个接口。
如果后续重新连接后只剩一个 IPv4 地址,并且默认路由正常,则可以暂时不处理。
如果未来要整理,应让同一个网络接口只由一个 DHCP 客户端管理。
操作习惯
本次整理还形成了几个实践习惯:
root 用户下不写 sudo
需要手动编辑时使用 Vim
不要把当前生效和重启后持久生效混为一谈
克隆系统后不要只检查服务是否能跑,还要检查系统身份是否已经拆分
常用确认命令:
hostname
cat /etc/hostname
hostnamectl
cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
ls -l /etc/ssh/ssh_host_*
grep -nE 'preserve_hostname|hostname:' /etc/cloud/cloud.cfg /boot/firmware/user-data
总结
克隆树莓派系统后,不能只改主机名。
更完整的处理流程应该是:
修改 cloud-init 主机名源头
禁止 cloud-init 后续覆盖主机名
修改 /etc/hostname
修改 /etc/hosts
重新生成 machine-id
重新生成 SSH Host Key
区分远程访问入口
区分内网穿透配置
区分代理节点配置
其中最容易忽略的是 cloud-init。
在 Ubuntu Raspberry Pi 系统上,如果启动分区的 user-data 里仍然写着旧主机名,并且 cloud-init 没有设置为保留当前主机名,那么即使 hostnamectl 修改成功,重启后仍可能恢复旧主机名。
因此,克隆系统正式上线前,应先完成系统身份拆分,再处理远程访问和代理服务入口。这样才能避免两台设备在网络、安全身份、日志和远程连接层面互相混淆。