主题
01 · Harvester v1.8.2 ISO 无人值守装机实施手册
适用范围:在 KVM/libvirt 宿主机上用 netboot 方式自动化安装 3 节点 Harvester OS 一体机集群。 本文所有命令、字段、耗时均在真实环境实测通过;失败案例与根因见
02-故障排查与修复记录.md,凭据专题见03-凭据与首登认证.md,可直接运行的资料在deploy/。
1. 方案与架构
1.1 为什么用 netboot 而不是挂载 ISO
| 方式 | 交互性 | 可否全自动 | 说明 |
|---|---|---|---|
| 挂载 ISO 引导 | 需要人工走安装向导 | ❌ | 除非配 harvester.install.automatic 且仍要处理引导菜单 |
| libvirt 直接内核引导(本方案) | 无 | ✅ | <kernel> + <initrd> + <cmdline> 直接注入,等效 iPXE 的 kernel/initrd 加载 |
关键收益:引导参数与装机配置全部由宿主机脚本控制,虚拟机开机即进入自动安装, 装完 poweroff,宿主机再把域定义切成硬盘启动并 autostart——全程零人工。
1.2 引导链路(实测)
宿主机 /mnt/xfs/harvester/
boot/ vmlinuz + initrd + rootfs.squashfs + ISO ← 00-download.sh 从 releases.rancher.com 拉取
│ (符号链接)
http/ python3 -m http.server 绑定 192.168.150.1:8080 ← 20-http-server.sh
│ ├── harvester-v1.8.2-vmlinuz-amd64
│ ├── harvester-v1.8.2-initrd-amd64
│ ├── harvester-v1.8.2-rootfs-amd64.squashfs (root=live:http://...)
│ ├── harvester-v1.8.2-amd64.iso (install.iso_url,装机时再取)
│ └── configs/config-nodeNN.yaml (install.config_url)
▼
libvirt 域 harvester-NN(UEFI/OVMF)
│ ① 直接内核引导 → dracut 取 rootfs → 进入 live 安装环境
│ ② harvester-installer 读 config_url → 应用网络/分区/装机
│ ③ elemental install → 格式化 data 盘 → 取 ISO → 预加载镜像 → 写 GRUB
▼ poweroff(install.poweroff=true)
宿主机 60-deploy.sh:切 disk 模式重定义 + virsh autostart + 开机
▼
Harvester OS 启动 → RKE2 起 → node01 create 出集群;node02/03 用 server_url+token join
▼
harvester-promote-node-controller 自动把 join 节点提升为 control-plane,etcd
▼
kube-vip 承载 VIP 192.168.150.200 → Dashboard 可用1.3 介质清单(实测字节数,可用于完整性核对)
| 文件 | 字节数 | 用途 |
|---|---|---|
harvester-v1.8.2-vmlinuz-amd64 | 15,227,376 | 引导内核 |
harvester-v1.8.2-initrd-amd64 | 108,951,900 | initramfs(dracut) |
harvester-v1.8.2-rootfs-amd64.squashfs | 1,176,158,208 | live 根文件系统(root=live:) |
harvester-v1.8.2-amd64.iso | 8,232,370,176 | 装机镜像源(install.iso_url) |
下载基址:https://releases.rancher.com/harvester/v1.8.2,合计约 9.5 GB。
ISO 完整性核对要点(02 案例 5):文件大小是 2048 的整数倍;PVD 魔数 CD001、卷标 COS_LIVE。 HEAD 请求返回的 x-goog-stored-content-length 不是真实对象大小,不能作为校验依据。
2. 目录结构
/mnt/xfs/harvester/
├── boot/ # 安装介质实体:vmlinuz / initrd / rootfs squashfs / ISO
├── disks/ # data 盘实体文件(400GiB qcow2,机械盘)
│ # + os 盘符号链接 → /home/<user>/harvester-disks/(NVMe,见 02 案例 7)
├── nvram/ # 每节点 UEFI 变量存储(*_VARS.fd + .bak),保留安装后生成的 EFI 启动项
├── http/ # HTTP 服务根目录:介质符号链接 + configs/ + images/
│ └── configs/ # config-node01.yaml(create) / config-node02,03.yaml(join)
├── configs/ # 虚拟机 XML(probe/install/disk 三态)、token.txt、credentials.txt(600)
├── logs/ # deploy.log / deploy-all.log / deploy.status / progress.log
│ # / harvester-NN-console.log(+.prev) / download/
├── import/ # 独立的镜像导入工作流(RHEL 9.6 审计/预处理,未纳入部署链路)
└── scripts/ # 19 个自动化脚本(见 §6)
http/下的介质都是符号链接指向boot/,所以00-download.sh重下介质不需要重建 HTTP 目录。
3. 网络引导原理(cmdline 逐项)
libvirt 域 XML 的 <os> 段直接指定内核引导(实测原文,单行):
xml
<kernel>/mnt/xfs/harvester/boot/harvester-v1.8.2-vmlinuz-amd64</kernel>
<initrd>/mnt/xfs/harvester/boot/harvester-v1.8.2-initrd-amd64</initrd>
<cmdline>ip=dhcp rd.neednet=1 net.ifnames=1 rd.cos.disable rd.noverifyssl
console=ttyS0,115200n8
root=live:http://192.168.150.1:8080/harvester-v1.8.2-rootfs-amd64.squashfs
harvester.install.automatic=true
harvester.install.config_url=http://192.168.150.1:8080/configs/config-node01.yaml
harvester.install.skipchecks=true
harvester.install.tty=ttyS0,115200n8</cmdline>
<boot dev='hd'/>| 参数 | 作用 | 不写会怎样 |
|---|---|---|
ip=dhcp | dracut 早期网络用 DHCP 取地址 | initramfs 无网络,拉不到 rootfs |
rd.neednet=1 | 强制 initramfs 阶段建网 | 同上(root=live:http:// 依赖它) |
net.ifnames=1 | 启用可预测网卡名 | 引导期叫 eth0、安装后叫 enp1s0,配置里的接口名对不上 |
rd.cos.disable | 官方 PXE 安装要求的 dracut 参数 | COS 的 dracut 模块接管,引导流程与官方不一致 |
rd.noverifyssl | 不校验 SSL | HTTPS 源会失败(本例是 HTTP,属照抄官方要求) |
console=ttyS0,115200n8 | 内核与 systemd 输出到串口 | 无法录制安装全过程,失败不可复盘 |
root=live:http://…squashfs | live 根文件系统来自 HTTP | 无根文件系统 |
harvester.install.automatic=true | 进入自动安装 | 停在交互式安装向导 |
harvester.install.config_url=… | 指定装机配置 | 自动安装无参数可用 |
harvester.install.skipchecks=true | 跳过硬件最小要求检查 | 虚拟机(8C/32G/250G)不满足生产级门槛,安装被拒 |
harvester.install.tty=ttyS0,115200n8 | 安装器自身 TTY | 安装器输出跑到 VNC,串口日志缺失 |
install.poweroff: true写在config.yaml里而非 cmdline:安装完成后关机而非重启, 避免再次进入网络安装循环;随后由60-deploy.sh用disk模式重定义域并设autostart。
4. 虚拟机定义要点
完整域定义见 deploy/configs/vm/harvester-01-install.xml (netboot 装机态)与 harvester-01-disk.xml(硬盘启动态)。 两者只差 <os> 段:disk 态去掉 kernel/initrd/cmdline。
| 配置项 | 取值 | 为什么必须这样 |
|---|---|---|
<memory> | 33554432 KiB(32 GiB) | Harvester 生产门槛;配合 skipchecks 才能过 |
<vcpu> / topology | 8,sockets=1 dies=1 cores=8 threads=1 | 固定拓扑,避免 guest 内 CPU 计数漂移 |
<cpu mode> | host-passthrough + check='none' + migratable='off' | ★ migratable='on' 会过滤掉 vmx,guest 内 KubeVirt 就无法跑虚机(02 案例 6) |
<machine> | pc-q35-6.2 | 与 libvirt 8.0 兼容的固定机型 |
<loader> + <nvram> | OVMF_CODE_4M.fd(readonly/pflash)+ 每节点独立 nvram/harvester-NN_VARS.fd | v1.8 强制 UEFI;NVRAM 必须独立,否则三节点共享 EFI 变量会互相覆盖启动项 |
<on_poweroff> | destroy | 装机 poweroff 后域进入 shut off,编排器据此判断安装结束 |
<on_reboot> | restart | ★ 96-install-retry.sh 靠 virsh reboot 重跑装机;netboot 参数在域定义里不变,重启即重新自动安装 |
<disk> cache | writeback | 装机期大量写入(18GB 镜像预加载),默认 cache 太慢 |
| PCI 地址固定 | 网卡 bus=0x01、os 盘 bus=0x02、data 盘 bus=0x03 | ★ 保证 guest 内命名确定为 enp1s0/vda/vdb,装机配置才能写死设备名 |
<serial> / <console> | type='pty' | type='file' 会被 libvirt 置为 root:root 600,普通用户读不到日志(02 案例 3) |
<rng model='virtio'> | backend /dev/urandom | 装机期大量加密运算(拉取/校验/证书),缺熵会显著拖慢 |
<graphics type='vnc'> | port='-1' autoport='yes' listen='0.0.0.0' | 兜底人工介入通道:virsh vncdisplay harvester-NN |
4.1 三态域定义(40-define-vm.sh <NN> <mode>)
| 模式 | 用途 | 特征 |
|---|---|---|
probe | 探测引导:确认 guest 内真实网卡名/盘名 | 加 rd.debug、不带 root=(不落盘、不装机) |
install | netboot 自动装机 | 注入 kernel/initrd/cmdline |
disk | 装完后从硬盘正常启动 | 移除引导注入,只留 <boot dev='hd'/> |
网卡名不要猜:先用
bash scripts/40-define-vm.sh 01 probe跑一次探测引导, 从串口日志实测得到enp1s0再写进装机配置(02案例 1)。
4.2 libvirt 兼容性注意(本机 libvirt 8.0)
- 不支持
driver discard='unpin'→ 已从 XML 移除。 - UEFI 域
virsh undefine必须加--keep-nvram,否则报cannot undefine domain with nvram。 - 未先
undefine就重复virsh define会报domain 'harvester-01' already exists with uuid ...。
5. 装机配置(config-nodeNN.yaml)逐字段说明
由 50-gen-configs.sh 生成,样本见 deploy/configs/config-node01.yaml(create)与 config-node02.yaml(join)。
yaml
scheme_version: 1
server_url: "https://192.168.150.200:443" # 仅 join;★ 顶层键,不是 install.server_url
token: <三节点必须一致,存于 configs/token.txt> # ★ RKE2 集群加入令牌,不是 Dashboard API token
os:
hostname: harvester-NN
password: "xxx" # ★ OS/SSH 密码,不是 dashboard 密码
# dashboard 密码是**另一个值**(50 号脚本的
# DASHBOARD_PW),由 75 号脚本设定,见 03
ssh_authorized_keys: ["<宿主机公钥>"] # 注入后可免密 SSH(rancher 用户)
modules: [kvm, nvme] # kvm: 嵌套虚拟化必需
dns_nameservers: [192.168.150.1] # ★ 只写宿主 dnsmasq,勿并列公网 DNS(02 案例 24)
ntp_servers: [ntp.aliyun.com, 0.suse.pool.ntp.org, 1.suse.pool.ntp.org]
install:
mode: create | join
automatic: true
skipchecks: true # 虚拟机不满足生产硬件门槛
silent: true
force_efi: true # v1.8 强制 UEFI
device: /dev/vda # 系统盘:ESP/COS_STATE/COS_RECOVERY/COS_PERSISTENT
data_disk: /dev/vdb # Longhorn 数据盘
iso_url: http://192.168.150.1:8080/harvester-v1.8.2-amd64.iso
tty: ttyS0,115200n8
poweroff: true # 装完关机,避免再次进入网络安装循环
management_interface:
interfaces:
- name: enp1s0
hwAddr: "52:54:00:15:01:NN" # ★ 必须与域 XML 的 MAC 完全一致
method: dhcp
default_route: true # 位于 management_interface 下
bond_options: {mode: active-backup, miimon: 100, mtu: 1500}
vip: 192.168.150.200 # ★ 仅 create 节点
vip_mode: static # ★ 仅 create 节点
system_settings:
auto-disk-provision-paths: "" # 关闭自动纳管,避免误把 /dev/vda 交给 Longhorn5.1 create 与 join 的差异(只有 3 处)
| 字段 | node01(create) | node02/03(join) |
|---|---|---|
install.mode | create | join |
server_url | 无(自己就是 server) | https://192.168.150.200:443(指向 VIP) |
install.vip / vip_mode | 192.168.150.200 / static | 不写 |
os.hostname / hwAddr | 各自的值 | 各自的值 |
token | 三节点完全一致 | 三节点完全一致 |
5.2 字段位置的三个易错点(对照官方 v1.8《Harvester Configuration》《PXE Boot Installation》核对)
server_url是顶层键,写成install.server_url会被静默忽略 → join 节点找不到集群。default_route与bond_options位于install.management_interface下,不是install直下。hwAddr大小写与冒号格式必须与 MAC 一致,Harvester 靠它挑管理网卡; 写错会导致 bondmgmt-bo/ bridgemgmt-br建在错误接口上。
5.3 ★ 装机配置不能设定 Dashboard 密码(实测证明)
对 rootfs.squashfs 内的 /usr/bin/harvester-installer 做字符串统计:
| 字符串 | 出现次数 | 结论 |
|---|---|---|
scheme_version | 1 | 该二进制确实解析装机配置(对照组,证明方法有效) |
admin_password | 0 | 无此配置能力 |
admin_token | 0 | 无此配置能力 |
server_url | 0 | 由其他组件处理,不在 installer 二进制内 |
所以全新集群的 Dashboard 初始状态只能是引导密码 xxx + mustChangePassword=xxx 必须由脚本在装完后补一次首登改密。完整根因链、验证数据与修复脚本见 [03-凭据与首登认证.md`](03-凭据与首登认证.md)。
6. 脚本清单与编排状态机
6.1 19 个脚本(deploy/scripts/ 内含全部可直接运行副本)
| 脚本 | 作用 |
|---|---|
00-download.sh | 从 releases.rancher.com 下载 vmlinuz/initrd/rootfs/ISO(断点续传、并行) |
10-prepare.sh | 创建 qcow2 磁盘、NVRAM、HTTP 目录符号链接、DHCP 静态保留(MAC→IP→主机名) |
20-http-server.sh | 在 192.168.150.1:8080 启动 HTTP 引导服务并自检(已监听则直接退出) |
30-console-capture.sh <NN> start|stop|status | virsh console + pty 后台录制串口;FIFO 支持向控制台注入按键 |
40-define-vm.sh <NN> <probe|install|disk> | 生成并 virsh define 三态域定义 |
50-gen-configs.sh | 生成 3 份 config.yaml、token.txt、credentials.txt |
60-deploy.sh <node01|node02|node03|verify> | 单节点编排:网络安装 → 等 poweroff(含卡死自愈探测)→ 切硬盘启动 → autostart → 等 SSH/API/Ready |
61-autostart-after-iso.sh | 等 ISO 下载完成后自动 exec 60-deploy.sh node01(首次部署接力用) |
65-deploy-all.sh | 总编排:等 node01 退出 → 确保切硬盘启动并开机 → 等 Ready → node02/node03 → 改密 → 整体验证 |
70-verify.sh | 集群验证(节点/Pod/Longhorn/网络/VIP/Dashboard/SSH) |
75-set-admin-password.sh | ★ 自动完成 Dashboard 首登改密(幂等,见 03) |
80-smoke-vm.sh <create|status|delete> | 端到端验证:在 Harvester 上建测试 VM(KubeVirt + Longhorn + 嵌套虚拟化) |
90-progress.sh | 后台每 30s 记录各节点 VM 状态/磁盘写入量/控制台末行到 logs/progress.log |
95-rescue-image-preload.sh <NN> detect|run|log | 检测/修复安装期"镜像预加载卡死"(02 案例 7/8/15) |
95-rescue-image-preload-guest.sh | 上一脚本推进 guest 内执行的救援体(不由宿主机直接调用) |
96-install-retry.sh | 检测"安装器已报错卡死"并 virsh reboot 自动重试(每节点≤3 次,间隔≥120s) |
97-installer-netfix.sh | SSH 一可用就把 nm-online 兜底包装注入 live 环境(窗口仅数秒) |
98-nm-online-wrapper.sh | 被注入的包装体:短超时反复重试真 nm-online(02 案例 13) |
99-rancher-internal-net.sh check|apply|rollback | 接入内网 Rancher:A~F 六步(跨网桥放行 + libvirt hook 持久化 + 内网 DNS + 节点 DNS 收敛 + containerd 私仓 certs.d + containerd-registry 设置),详见 05 |
6.2 编排状态机
单节点编排 60-deploy.sh <nodeNN>(install_node()):
清理上轮自愈标记(rescue-NN.done / retry-NN.count / .stamp / .lock)
→ 40-define-vm.sh NN install # 定义 netboot 域
→ 30-console-capture.sh NN start # 开始录串口
→ virsh start
→ setsid 97-installer-netfix.sh NN & # ★ 后台抢注入 nm-online 包装(窗口仅数秒)
→ wait_poweroff NN 540 # 预算 540×10s = 90 分钟,基于"截止时间"而非固定轮数
│ 每 3 轮(≈30s)做两类自愈探测:
│ a) 95-rescue-image-preload.sh detect → run (镜像预加载卡死)
│ b) 96-install-retry.sh (安装器报错卡死 → virsh reboot)
│ 命中重试则**等待预算顺延** 90 分钟,避免重试反而更容易超时
→ 30-console-capture.sh NN stop
→ 40-define-vm.sh NN disk # 切硬盘启动定义
→ virsh autostart NN # 宿主机重启后自动拉起
→ 30-console-capture.sh NN start
→ virsh start
→ wait_ssh NN # 30 分钟预算
→ (node01) wait_api:VIP /ping 返回 200|401|404
→ (node02/03) wait Ready:awk '$1==name && $2 ~ /^Ready(,|$)/'总编排 65-deploy-all.sh:
等 `60-deploy.sh node01` 进程退出(pgrep 轮询)
→ ensure_booted_from_disk 01 # 50 分钟预算,见下方四态处理
→ wait_ssh 01
→ 等 VIP /ping 响应(200|401|403|404)
→ wait_node_ready 01
→ for n in 02 03:
60-deploy.sh nodeNN # 非 0 退出**不中断**,交给恢复逻辑
ensure_booted_from_disk NN
wait_ssh NN
wait_node_ready NN
任一步失败 → 记 ERROR + 计入 FAILED + continue 下一个节点
→ 若 FAILED 非空:跳过整体验证,exit 1
→ 75-set-admin-password.sh # ★ 首登改密(失败只 WARN,不中断)
→ 70-verify.sh
→ ALL DONEensure_booted_from_disk() 的四态处理(这是自愈的核心,旧版有两个致命缺陷见 02 案例 9):
| 域状态 | 域定义 | 动作 |
|---|---|---|
shut off | 含 <kernel>(netboot) | 安装已完成但没切盘 → 停录 → 40-define-vm disk → autostart → 开录 → virsh start |
shut off | 不含 <kernel>(disk) | 已是硬盘定义但没开机 → virsh start(旧版此处直接 return 0,节点永不开机) |
running | 不含 <kernel> | 已从硬盘运行 → 返回成功 |
running | 含 <kernel> | 仍在安装 → 每 3 轮做 95/96 两类自愈探测 |
| XML 读不到(空) | — | 先判空再继续等(旧版 ! grep -q '<kernel>' 在 virsh 偶发返回空时误判成"已从硬盘运行") |
7. 完整实施流程(含每阶段验收点)
阶段 0 · 宿主机前置条件
bash
# 需要:KVM 可用、libvirt、qemu-img、python3、sshpass、curl、jq(75 号脚本用)
ls -l /dev/kvm # 必须存在
sudo virsh net-list --all # libvirt 正常
grep -c vmx /proc/cpuinfo # >0,嵌套虚拟化前提
df -h /mnt/xfs /home # os 盘实体放 NVMe,data 盘放 /mnt/xfs| 验收点 | 判据 |
|---|---|
| KVM 可用 | /dev/kvm 存在,当前用户在 kvm 组 |
| 磁盘布局正确 | os 盘实体在 NVMe(否则触发 02 案例 7 的预加载卡死);data 盘在大容量机械盘 |
| VIP 未被占用 | arping -I virbr10 192.168.150.200 无应答 |
| 端口未被占用 | ss -lnt | grep 192.168.150.1:8080 为空 |
阶段 1 · 下载介质(约 9.5 GB)
bash
bash scripts/00-download.sh # 断点续传 + 并行;日志在 logs/download/
ls -l boot/ # 核对 4 个文件字节数(见 §1.3)若想在下载完成前就排好后续动作,可用
61-autostart-after-iso.sh:它轮询 ISO 到位后 自动exec 60-deploy.sh node01。
阶段 2 · 准备磁盘 / NVRAM / DHCP 保留
bash
bash scripts/10-prepare.sh
sudo virsh net-dumpxml harvester | grep -A3 '<host mac' # 验收:3 条 MAC→IP→hostname 保留
ls -l disks/ nvram/ # 验收:3×os(符号链接) 3×data 3×VARS.fdDHCP 静态保留是关键:MAC 决定 IP,IP 决定主机名,这样装机配置里的 hwAddr、 server_url、脚本里的 node_ip() 才能全部写死。
阶段 3 · 起 HTTP 引导服务 + 生成装机配置
bash
bash scripts/20-http-server.sh
curl -sI http://192.168.150.1:8080/harvester-v1.8.2-rootfs-amd64.squashfs | head -1 # 200
bash scripts/50-gen-configs.sh
cat configs/token.txt; ls -l configs/credentials.txt # 验收:token 一致、凭据文件 600阶段 4 · (可选但强烈建议)探测引导确认设备名
bash
bash scripts/40-define-vm.sh 01 probe
bash scripts/30-console-capture.sh 01 start && virsh start harvester-01
grep -oE 'enp[0-9a-z]+|vd[ab]' logs/harvester-01-console.log | sort -u # 验收:enp1s0 / vda / vdb
virsh destroy harvester-01阶段 5 · 一键无人值守部署
bash
setsid bash scripts/60-deploy.sh node01 </dev/null >/dev/null 2>&1 &
setsid bash scripts/65-deploy-all.sh </dev/null >/dev/null 2>&1 &
pgrep -af '60-deplo[y]|65-deplo[y]' # 验收:各自**只有一个**实例
tail -f logs/deploy-all.log用 setsid 完全脱离当前会话:终端关闭或父进程被信号波及,都不会连带杀掉编排器。
| 验收点(按日志顺序) | 期望 |
|---|---|
harvester-01: running from disk | node01 已从硬盘运行 |
VIP https://192.168.150.200 responding | kube-vip 起来了 |
harvester-01: Ready | 首节点入集群 |
harvester-02: powered off after install (~1006s) | 装机完成关机(≈17 分钟) |
harvester-02 is Ready / harvester-03 is Ready | join 成功 |
admin 密码已是目标值xxx密码修改成功 | 首登改密完成 |
=== ALL DONE === | 全流程结束 |
阶段 6 · 验证与收尾
bash
bash scripts/70-verify.sh # 集群体检
bash scripts/80-smoke-vm.sh create # 端到端 VM(当前受 02 案例 23 阻塞)
sudo bash scripts/99-rancher-internal-net.sh apply # 可选:打通到 Rancher 网段8. 耗时基线(实测,可用于判断"是否卡住")
健康一轮(2026-09-06 08:03:50 → 08:45:08):
| 阶段 | 实测耗时 | 日志依据 |
|---|---|---|
| node01(已装完)从硬盘运行 → ssh up | ~10s | harvester-01: ssh up (~10s) |
| VIP 响应 | ~10s | VIP https://192.168.150.200 responding (~10s) |
| node01 Ready | ~10s | harvester-01: Ready (~10s) |
| node02 netboot 装机 → poweroff | ~1006s(16.8 分钟) | powered off after install (~1006s) |
| node02 硬盘启动 → ssh up | ~50s | harvester-02 (…12): ssh is up (~50s) |
| node02 → Ready | ~50s | harvester-02 is Ready (~50s) |
| node03 装机 → Ready 全程 | ~22 分钟 | 08:22:33 → 08:44:57 |
| 三节点总编排(node01 已就绪起算) | 42 分钟 | === ALL DONE === 08:45:08 |
| 介质下载 9.5 GB | ~44 分钟 | 20:03 → 20:47 |
判卡阈值:单节点装机 >25 分钟仍未 powered off 就该介入看串口日志; >30 分钟基本可断定踩中镜像预加载死线(02 案例 7),因为 chroot 内引导用 rke2 会在 启动约 30 分钟后自杀。
失败样本(供对照,都是修复前的真实记录):
| 时间 | 事件 | 耗时 | 根因 |
|---|---|---|---|
| 09-05 20:49 → 21:29 | node01 装机 TIMEOUT | 40 分钟 | OS 盘在机械盘 → 预加载 ~34 分钟越线(案例 7) |
| 09-05 23:00 → 09-06 00:31 | node02 装机 TIMEOUT | 90 分钟 | nm-online 亚秒竞态致安装器报错卡死(案例 13);期间还被误触发一次救援(案例 15) |
| 09-06 08:03 → 08:45 | 三节点全部成功 | 42 分钟 | OS 盘迁 NVMe + 97/98 注入 + 96 自动重试后 |
9. 三层自愈机制(无人值守的关键)
| 层 | 脚本 | 触发判据(全部满足) | 动作 | 防误伤设计 |
|---|---|---|---|---|
| L1 网络竞态 | 97-installer-netfix.sh + 98-nm-online-wrapper.sh | SSH 一可用就开始(开机 +47~55s),从 virsh start 起每 0.5s 轮询 | 把兜底 nm-online 包装注入 live 环境的 /usr/local/bin/ 与 /usr/sbin/ | 三次 SSH 往返压成一次(实测 1.4s 完成注入);没赶上由 L3 兜底 |
| L2 预加载卡死 | 95-rescue-image-preload.sh + -guest.sh | /run/cos/target 已挂载 + images-lists 存在 + ctr-check-images.sh 已跑 >300s + containerd 不在 + 真正的引导用 rke2 不在 | ①用与 harv-install 完全相同的参数重启 containerd ②逐列表比对,缺哪个就 zstd -d + ctr images import --no-unpack ③起名为 rke2 的诱饵进程让收尾的 pkill rke2 返回 0 ④核对结束就停 containerd | 模式串一律用 [x] 括号写法避免 pgrep -f 自匹配;mkdir 原子锁防两个看门狗同秒重复救援;guest 侧前置校验不满足就什么都不做直接退出 |
| L3 安装器卡死 | 96-install-retry.sh | ①level=error 出现在最后 5 行非空行内 ②harv-install/elemental/ctr-check-images/rsync/mkfs/qemu-img/parted 等干活进程全都不在 ③日志静默 ≥60s | virsh reboot(域定义 <on_reboot>restart</on_reboot> + netboot 参数不变 → 自动安装重跑) | 每节点**≤3 次**、两次间隔**≥120s**;重启后重新调 L1 注入(live 环境是内存 overlay,重启即失效) |
L1 为什么必须抢时间(实测时间线,来自 guest 内 /var/log/console.log):
15:02:04.938 manager: disable requested -> NetworkManager state is now ASLEEP
15:02:05 level=error msg="exit status 1\rConnecting............... 30s [offline]\n"
15:02:05.31 dhcp4 (mgmt-br): activation: beginning transaction
15:02:05.44 device (mgmt-br): Activation: successful, device activated
15:02:06.90 manager: NetworkManager state is now CONNECTED_GLOBAL网络其实 0.5 秒后就完全就绪,安装器却已报错并永久停在错误界面 (harvester-installer / start-installer.sh 两个进程会一直挂着,磁盘一个字节都没写)。
⚠️ L3 的判据绝不能用"日志里存在
level=error":健康安装也有无害错误, 实测 node02 就有unable to determine NIC speed from /sys/class/net/enp1s0/speed (got -1)。 该判据已双向验证:对健康安装中的节点返回 1(不触发);对真失败的归档日志能命中。
10. 验证方法
10.1 70-verify.sh 的 9 个检查段
| # | 检查段 | 判据 |
|---|---|---|
| 1 | 虚拟机状态(libvirt) | 3 个域 running,且 Autostart: enable |
| 2 | VIP 连通性 / Dashboard | arp 有条目;/ping → 200;/dashboard/ → 200;能取到 <title> |
| 3 | 节点 | kubectl get nodes -o wide 三行且 STATUS 为 Ready* |
| 4 | Harvester 版本 / 节点角色 | harvesterhci.io/node-role 标签、osImage、kubeletVersion |
| 5 | 系统 Pod 异常统计 | 非 Running/Completed/Succeeded 的行应为空 |
| 6 | Longhorn 存储 | nodes.longhorn.io 的 Ready 与 disks;get sc 含默认 harvester-longhorn |
| 7 | Harvester 管理网络 | vlanconfigs、networks.harvesterhci.io |
| 8 | 各节点登录验证 | 三个 IP 均 OK <hostname> <kernel>(密钥认证) |
| 9 | 凭据 | 打印 configs/credentials.txt |
10.2 手工深度核对(本次实测通过的命令)
bash
K='sudo /var/lib/rancher/rke2/bin/kubectl --kubeconfig /etc/rancher/rke2/rke2.yaml'
H='ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null rancher@192.168.150.11'
# ① 三节点角色与 etcd quorum(HA 的硬指标)
$H "$K get nodes -o wide"
$H "$K -n kube-system get pods | grep '^etcd-'" # 期望 3 个 etcd-harvester-0N Running
# ② kube-vip 是否三节点都有
$H "$K -n kube-system get pods -o wide | grep kube-vip"
# ③ Longhorn 默认盘是否可调度(虚机存储的地基)
$H "$K -n longhorn-system get nodes.longhorn.io -o json | \
jq -r '.items[] | .metadata.name as \$n | .spec.disks | to_entries[] |
\"\(\$n) \(.key) schedulable=\(.value.schedulable)\"'"
# ④ Dashboard 凭据是否真的可用(不能只看 HTTP 200,见 03 号文档)
curl -sk -o /dev/null -w '%{http_code}\n' https://192.168.150.200/ping # 200 + pong
bash scripts/75-set-admin-password.sh # 幂等,短路即 OK实测健康基线(2026-09-06 09:19):
harvester-01 Ready control-plane,etcd
harvester-02 Ready control-plane,etcd
harvester-03 Ready control-plane,etcd
etcd 成员: 3 异常 Pod: 0
test-vm-disk0 Bound 20Gi RWO harvester-longhorn ← smoke test 残留(02 案例 23)