主题
Harvester v1.8.2 三节点 ISO 无人值守部署 · 一键部署包
本目录是可直接落地的部署资料包:一键入口 + 19 个已实测脚本 + 真实装机配置 + libvirt 域/网络定义。 原理与逐字段说明见
../01-实施手册-ISO无人值守装机.md, 踩坑与根因见../02-故障排查与修复记录.md, 认证专题见../03-凭据与首登认证.md。实测结果:3 节点全部
Ready control-plane,etcd,etcd 成员 3/3,异常 Pod 0, Dashboardadmin可登录;总编排耗时 42 分钟。
1. 资料包内容
deploy/
├── README.md # ← 本文:一键部署文档
├── run-all.sh # ★ 一键入口(前置检查 + 按序调用下列脚本)
├── scripts/ # 19 个已实测脚本(可直接运行)
│ ├── 00-download.sh # 下载 vmlinuz/initrd/rootfs/ISO(9.5GB,断点续传)
│ ├── 10-prepare.sh # 磁盘/NVRAM/HTTP 符号链接/DHCP 静态保留
│ ├── 20-http-server.sh # 启动 HTTP 引导服务(幂等)
│ ├── 30-console-capture.sh # 串口录制(pty + script,日志轮转 .prev)
│ ├── 40-define-vm.sh # 生成三态域定义 probe/install/disk
│ ├── 50-gen-configs.sh # 生成 config-nodeNN.yaml + token + credentials
│ ├── 60-deploy.sh # 单节点编排(装机→切盘→autostart→等 Ready)
│ ├── 61-autostart-after-iso.sh # 等 ISO 下完自动接力 node01
│ ├── 65-deploy-all.sh # ★ 总编排(node02/03 + 改密 + 验证 + 自愈)
│ ├── 70-verify.sh # 集群验证(9 个检查段)
│ ├── 75-set-admin-password.sh # ★ Dashboard 首登改密(幂等)
│ ├── 80-smoke-vm.sh # 端到端测试 VM(当前受已知问题阻塞,见 §9)
│ ├── 90-progress.sh # 30s 粒度进度记录
│ ├── 95-rescue-image-preload.sh # 自愈 L2:镜像预加载卡死救援(宿主侧)
│ ├── 95-rescue-image-preload-guest.sh # 自愈 L2:救援体(guest 内执行)
│ ├── 96-install-retry.sh # 自愈 L3:安装器卡死自动重启重试
│ ├── 97-installer-netfix.sh # 自愈 L1:抢注入 nm-online 包装
│ ├── 98-nm-online-wrapper.sh # 自愈 L1:被注入的包装体
│ └── 99-rancher-internal-net.sh # 可选:接入内网 Rancher(A~F 六步,见 ../05)
└── configs/ # 真实配置样本(可直接比对/复用)
├── config-node01.yaml # create 模式(含 vip / vip_mode;DNS 已收敛为单一宿主 dnsmasq)
├── config-node02.yaml # join 模式(含 server_url,无 vip)
├── config-node03.yaml # join 模式
├── harvester-net.xml # libvirt 网络定义(virbr10 / NAT / DHCP 池)
└── vm/
├── harvester-01-install.xml # netboot 装机态域定义(含完整 cmdline)
└── harvester-01-disk.xml # 硬盘启动态域定义2. ★ 一键部署(三条命令)
bash
# ① 落地资料包到项目目录(若从零开始)
mkdir -p /mnt/xfs/harvester/scripts
cp -a deploy/scripts/*.sh deploy/run-all.sh /mnt/xfs/harvester/scripts/
# ② 前置检查(不改动任何状态,强烈建议先跑)
bash /mnt/xfs/harvester/scripts/run-all.sh --preflight
# ③ 一键部署:下载介质 → 建盘 → HTTP → 配置 → node01 → node02/03 → 改密 → 验证
setsid bash /mnt/xfs/harvester/scripts/run-all.sh </dev/null > /tmp/run-all.log 2>&1 &
tail -f /tmp/run-all.log介质已下载过时用 --skip-download;中断后续跑用 --from <阶段>。
run-all.sh不含任何新的部署逻辑:它只做前置检查,然后按序调用scripts/里已实测的分步脚本,行为与手工逐条执行完全一致。 因此排障时随时可以退回手工分步(见 §8)。
2.1 run-all.sh 参数
| 参数 | 作用 |
|---|---|
--preflight | 只做前置检查后退出(不改状态) |
--skip-download | 跳过 9.5GB 介质下载 |
--from <阶段> | 从指定阶段续跑 |
--help | 打印用法 |
阶段顺序:preflight → download → prepare → http → configs → node01 → all → verify
| 环境变量 | 默认 | 说明 |
|---|---|---|
BASE | /mnt/xfs/harvester | 项目根目录 |
VIP | 192.168.150.200 | 管理 VIP |
HTTP_IP / HTTP_PORT | 192.168.150.1 / 8080 | 引导服务监听 |
OS_DISK_DIR | $HOME/harvester-disks | ★ os 盘实体目录,必须在 NVMe |
退出码:0 成功 | 1 前置检查/参数错误 | 2 某阶段失败。
2.2 前置检查都查什么(8 项,均来自实测踩坑)
| # | 检查项 | 不通过的后果 | 对应案例 |
|---|---|---|---|
| 1 | BASE 与 9 个必需脚本存在 | 流程中断 | — |
| 2 | 依赖命令 virsh/qemu-img/python3/curl/jq/setsid/script/awk(sshpass 仅告警) | 改密脚本要 jq;救援脚本要 sshpass | 03 §5.2 |
| 3 | /dev/kvm 存在且可读写;CPU 有 vmx/svm | guest 内 KubeVirt 跑不了虚机 | 案例 6 |
| 4 | ★ OS_DISK_DIR 是否机械盘(读 /sys/block/<disk>/queue/rotational) | 预加载 >30min → 永久卡死 | 案例 7 |
| 5 | BASE 可用空间 ≥40G | 介质 + 磁盘增长放不下 | — |
| 6 | HTTP 端口状态(已监听则复用) | — | — |
| 7 | VIP 归属:/ping → pong 判定为"已有集群",否则有应答即告警 | VIP 冲突会导致 kube-vip 抢地址 | — |
| 8 | ★ 是否已有编排器在跑(pgrep) | 重复实例互相 destroy/define/start | 01 §7 阶段 5 |
实测输出(本环境,集群已就绪):
=== 阶段 preflight:前置检查 ===
OK os 盘实体目录 /home/<user>/harvester-disks(/dev/nvme0n1p2 → nvme0n1,rotational=0 非机械盘)
OK 192.168.150.1:8080 已在监听(20-http-server.sh 会直接复用)
OK VIP 192.168.150.200 上已有 Harvester 集群在跑(/ping → pong):本次为**重跑/续跑**
preflight 通过第 4 项已做双向验证:把 OS_DISK_DIR=/mnt/xfs(真实机械盘)传入时正确告警 rotational=1,说明判定是实读 sysfs 而非硬编码。
3. 宿主机要求与容量规划
| 项 | 最低 | 本环境实测 | 说明 |
|---|---|---|---|
| CPU | 支持 VT-x/AMD-V,≥24 核可用 | 8 vCPU × 3 = 24 vCPU | guest 内要跑 KubeVirt,需嵌套虚拟化 |
| 内存 | ≥96 GiB | 32 GiB × 3 = 96 GiB | Harvester 单节点门槛较高 |
| NVMe/SSD | ≥250 GB 可用 | /home/<user>/harvester-disks(283GB 可用) | ★ os 盘实体必须放这里,否则踩案例 7 |
| 大容量盘 | ≥1.2 TB | /mnt/xfs(data 盘 3×400GiB qcow2) | Longhorn 数据盘;qcow2 精简置备,实际占用随写入增长 |
| 系统 | Linux + libvirt ≥8.0 + qemu-kvm | libvirt 8.0 | 注意 8.0 不支持 discard='unpin'(案例 2) |
| 依赖包 | virsh qemu-img python3 curl jq sshpass util-linux(script) procps | 齐备 | jq 供改密脚本,sshpass 供装机期救援 |
| 出网 | 能访问 releases.rancher.com | ✅ | 下载 9.5GB 介质;生产建议先内网镜像化 |
磁盘布局(★ 这是本次最重要的一条经验):
NVMe(宿主根文件系统) → 3 × os 盘实体 250GiB qcow2 → guest /dev/vda
/mnt/xfs(机械盘) → 3 × data 盘实体 400GiB qcow2 → guest /dev/vdb(Longhorn)
/mnt/xfs/harvester/disks/harvester-NN-os.qcow2 是**符号链接**指向 NVMe 实体os 盘放机械盘会让装机期"8GB ISO 下载 + 200 个镜像导入"读写同轴争抢, 实测预加载 ~34 分钟 > rke2 的 30 分钟 static-pods 死线 → 永久卡死(案例 7)。
4. 部署过程会发生什么(实测时间线)
| 时刻 | 阶段 | 你会看到 | 耗时 |
|---|---|---|---|
| T+0 | download | 4 个介质文件下载进度 | ~44 分钟(9.5GB) |
| T+44m | prepare | 建 6 个 qcow2、3 个 NVRAM、3 条 DHCP 保留 | 秒级 |
| T+44m | http | 192.168.150.1:8080 开始监听 | 秒级 |
| T+44m | configs | 生成 3 份 config.yaml + token.txt + credentials.txt | 秒级 |
| T+45m | node01 装机 | 串口日志滚动:dracut → live 环境 → harv-install → elemental → 预加载镜像 | 15~20 分钟 |
| — | node01 切盘启动 | powered off after install → 切 disk 定义 → autostart → 开机 | ~1 分钟 |
| — | node01 就绪 | ssh up (~50s) → VIP responding → Ready | ~2 分钟 |
| — | node02 装机+join | 同上,随后被自动提升为 control-plane,etcd | ~19 分钟(实测装机 1006s) |
| — | node03 装机+join | 同上 | ~22 分钟 |
| — | 改密 | === set dashboard admin password =xxx → mustChangePassword: xxx | 秒级 |
| — | 验证 | 70-verify.sh 9 个检查段 → === ALL DONE === | ~1 分钟 |
| T+87m | 完成 | 三节点 HA 集群可用 | 总编排 42 分钟(不含下载) |
4.1 观察点(按重要性排序)
bash
tail -f /mnt/xfs/harvester/logs/deploy-all.log # ★ 总编排:阶段推进与自愈动作
tail -f /mnt/xfs/harvester/logs/deploy.log # 单节点编排细节
cat /mnt/xfs/harvester/logs/deploy.status # 当前阶段(一行)
tail -f /mnt/xfs/harvester/logs/harvester-02-console.log # ★ 装机串口实况
tail -f /mnt/xfs/harvester/logs/progress.log # 30s 汇总(VM 状态/磁盘写入量)4.2 判卡阈值(超过就该介入)
| 现象 | 阈值 | 动作 |
|---|---|---|
单节点装机未 powered off | >25 分钟 | 看串口日志定位阶段 |
| 同上 | >30 分钟 | 基本可断定踩中预加载死线 → 案例 7 |
串口刷 connection refused | 持续 >2 分钟 | L2 救援应已触发;未触发则手工 95 … run |
| 串口停在错误界面且静默 | >60s | L3 应自动 virsh reboot;查 logs/retry-NN.count |
节点 NotReady | >10 分钟 | kubectl describe node + 节点 journalctl |
5. 验收清单
5.1 集群层(bash scripts/70-verify.sh 可自动覆盖)
- [ ]
virsh list --all:3 个域running - [ ]
virsh dominfo harvester-0N | grep Autostart:3 个均enable - [ ]
kubectl get nodes -o wide:3 行,STATUS为Ready(或提升期Ready,SchedulingDisabled) - [ ]
ROLES:3 个均control-plane,etcd(自动提升完成) - [ ]
kube-system下etcd-harvester-01/02/03:3 个 Running(quorum 成立) - [ ]
kube-system下kube-vip:3 个(每节点 1 个) - [ ] 异常 Pod(非
Running/Completed/Succeeded):0 - [ ]
longhorn-system的nodes.longhorn.io:3 节点,default-diskschedulable=true - [ ]
kubectl get sc:含默认harvester-longhorn - [ ] 三节点 SSH 免密可登录,
uname -r一致(6.12.0-160000.36-default)
5.2 接入层
- [ ]
curl -sk https://<VIP>/ping→200且响应体pong - [ ]
curl -skL https://<VIP>/dashboard/→200且能取到<title> - [ ]
bash scripts/75-set-admin-password.sh→rc=0(幂等短路即代表凭据可用) - [ ] 浏览器打开
https://<VIP>/,用 \1xxx\2 登录成功,不弹强制改密页
5.3 交付物
- [ ]
configs/credentials.txt存在且权限600,三类凭据标签正确(见../03§6) - [ ]
configs/token.txt存在(三节点一致的 RKE2 join token) - [ ]
logs/下保留完整串口日志(含.prev轮转),可供复盘 - [ ]
logs/deploy-all.log末尾出现=== ALL DONE ===
6. 失败处置
6.1 先别急着重跑:三层自愈会自己处理
| 层 | 触发 | 你会在日志里看到 |
|---|---|---|
| L1 网络竞态 | 开机 +47~55s 抢注入 | 97-installer-netfix.sh 的输出 |
| L2 预加载卡死 | 每 ~30s 探测 | harvester-0N: preload rescue triggered |
| L3 安装器卡死 | 每 ~30s 探测 | harvester-0N: installer restarted (自动重试) |
L3 限流:每节点 ≤3 次、间隔 ≥120s,重启后等待预算顺延(不会因为重试而更容易超时)。
6.2 需要人工介入的情形
| 情形 | 处置 |
|---|---|
| 某节点三次重试后仍失败 | 看 logs/harvester-NN-console.log(及 .prev)定位;修好后 bash scripts/60-deploy.sh nodeNN 单独重装 |
总编排报 部署结束, 但以下节点失败: NN | 逐台修,然后重跑 bash scripts/65-deploy-all.sh(幂等,已就绪节点会被快速跳过) |
改密失败(WARN: 设定 admin 密码失败xxxbash scripts/75-set-admin-password.sh | |
| 编排器被误杀 | 先 pgrep -af '60-deplo[y]|65-deplo[y]' 确认没有残留实例,再重启(重复实例会互相打断) |
| 域卡在 netboot 装机态 | virsh dumpxml harvester-NN | grep -c '<kernel>' 为 1 即仍在装机;ensure_booted_from_disk 会自动切盘 |
| 需要彻底重来 | 见 ../04-运维手册-巡检重置与网络打通.md §4.1(会删数据) |
6.3 重跑安全性(幂等性一览)
| 脚本 | 重复执行 | 说明 |
|---|---|---|
20-http-server.sh | ✅ 安全 | 已监听则直接退出 |
50-gen-configs.sh | ✅ 安全 | token.txt 已存在则复用(三节点必须一致) |
65-deploy-all.sh | ✅ 安全 | 已就绪节点走 running from disk 快速通道;改密短路 |
70-verify.sh | ✅ 安全 | 纯只读 |
75-set-admin-password.sh | ✅ 安全 | 先用目标密码试登录,成功即短路 |
10-prepare.sh | ⚠️ 谨慎 | 会创建磁盘;已有 qcow2 时注意别覆盖数据 |
60-deploy.sh nodeNN | ⚠️ 会重装 | 重新走 netboot 装机 = 抹掉该节点,务必确认 |
00-download.sh | ✅ 安全 | 断点续传 |
7. 参数定制指南(换环境要改哪些地方)
7.1 支持环境变量覆盖的(优先用这些)
| 变量 | 生效脚本 | 默认值 |
|---|---|---|
BASE | run-all.sh | /mnt/xfs/harvester |
VIP | run-all.sh、75-set-admin-password.sh | 192.168.150.200 |
HTTP_IP / HTTP_PORT | run-all.sh | 192.168.150.1 / 8080 |
OS_DISK_DIR | run-all.sh(仅用于前置检查判定) | $HOME/harvester-disks |
NVME_DISK_DIR | 10-prepare.sh(★ 真正决定 os 盘实体位置) | /home/<user>/harvester-disks |
BOOT_PW | 75-set-admin-password.sh | admin |
IMG_URL | 80-smoke-vm.sh | Ubuntu noble cloud image |
7.2 需要改脚本内变量的(硬编码点,逐项列出)
| 要改什么 | 文件 : 行/变量 |
|---|---|
| Harvester 版本 | 00-download.sh:6、10-prepare.sh:6、40-define-vm.sh:6、50-gen-configs.sh:6 的 VER=v1.8.2;另 61-autostart-after-iso.sh 的 EXPECT=(ISO 字节数) |
| 项目根目录 | 所有脚本首部的 BASE=/mnt/xfs/harvester(run-all.sh 除外,它支持环境变量) |
| 磁盘大小 | 10-prepare.sh:7 OS_DISK_SIZE=250G、:8 DATA_DISK_SIZE=400G |
| vCPU / 内存 | 40-define-vm.sh:8 RAM_KIB=33554432、:9 VCPUS=8 |
| 节点 IP | 10-prepare.sh:20 IP[]、60-deploy.sh:16 node_ip()、65-deploy-all.sh:15 node_ip()、70-verify.sh 的 IP 列表、80-smoke-vm.sh:6 NODE、95/96/97 各自的 node_ip |
| MAC | 10-prepare.sh:19 MAC[]、40-define-vm.sh:12 mac="52:54:00:15:01:${n}"、50-gen-configs.sh 的 hwAddr 生成 |
| VIP | 50-gen-configs.sh:9、60-deploy.sh:9、65-deploy-all.sh:13、70-verify.sh:5、75-set-admin-password.sh:25 |
| HTTP 引导服务 | 20-http-server.sh:5 BIND / :6 PORT、50-gen-configs.sh:7-8、40-define-vm.sh:7 HTTP |
| guest 网卡名 | 50-gen-configs.sh:10 IFNAME=enp1s0 ← ★ 换机型/PCI 布局必须重新探测引导(案例 1) |
| 节点 DNS | 50-gen-configs.sh 的 dns_nameservers ← ★ 只留宿主 dnsmasq,勿并列公网 DNS(CoreDNS policy=random 会间歇解析到公网,案例 24) |
| 密码 | xxx 的 PASSWORD(OS/SSH = xxx)与 DASHBOARD_PW(dashboard = xxx);75-set-admin-password.sh 的 NEW_PW 默认取后者。★ 两者故意不同值,避免把 SSH 密码当 dashboard 密码(xxx 案例 28) |
| SSH 公钥 | 50-gen-configs.sh:12 SSHKEY="$(cat $HOME/.ssh/id_rsa.pub)" |
| RKE2 join token | configs/token.txt(首次由 openssl rand -hex 16 生成;重装时必须保持一致,否则 join 失败) |
7.3 批量换网段/根目录(一键 sed)
bash
cd /mnt/xfs/harvester
# 换项目根目录 + 换整个 /24 网段(所有 IP 共用前缀,故可整体替换)
find scripts -name '*.sh' -exec sed -i \
-e 's#/mnt/xfs/harvester#/data/harvester#g' \
-e 's/192\.168\.150\./10.0.99./g' {} +
grep -rn '192\.168\.150\|/mnt/xfs/harvester' scripts/ | head # 验收:应为空
for f in scripts/*.sh; do bash -n "$f" || echo "语法错误: $f"; done # ★ 改完必须语法检查⚠️
VIP也要跟着换:10.0.99.200必须落在新网段的 DHCP 池之外 (池是.100-.199),否则会与动态分配地址冲突。
8. 手工分步部署(等价命令,排障时用)
bash
cd /mnt/xfs/harvester
# 1) 介质(9.5GB,断点续传)
bash scripts/00-download.sh
# 2) 磁盘 / NVRAM / HTTP 符号链接 / DHCP 静态保留
NVME_DISK_DIR=/home/$USER/harvester-disks bash scripts/10-prepare.sh
# 3) HTTP 引导服务
bash scripts/20-http-server.sh
# 4) 装机配置(create/join)+ token + credentials
bash scripts/50-gen-configs.sh
# 5) (建议)探测引导确认设备名
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
virsh destroy harvester-01
# 6) 部署(★ 用 setsid 脱离会话;两个编排器各只能起一个实例)
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
# 7) 验证与收尾
bash scripts/70-verify.sh
bash scripts/75-set-admin-password.sh # 65 号已自动调用,这里用于复核(幂等)
bash scripts/80-smoke-vm.sh create # 端到端 VM(见 §9 已知问题)
# 8) 可选:接入内网 Rancher(A~F 六步:跨网桥放行/hook 持久化/内网 DNS/
# 节点 DNS 收敛/containerd 私仓/containerd-registry 设置)
sudo bash scripts/99-rancher-internal-net.sh check
sudo bash scripts/99-rancher-internal-net.sh apply
# 随后按 ../05-接入Rancher与内外网分离解析.md §5 设置 cluster-registration-url 完成导入9. 已知问题(部署前请知悉)
| 问题 | 影响 | 状态 | 参考 |
|---|---|---|---|
80-smoke-vm.sh 创建 VM 被 KubeVirt 准入拒绝:doc is missing path: /spec/template/spec/domain/cpu/maxSockets | 端到端 VM 验证未完成;集群本身健康不受影响 | ❌ 未解决(已定位到 VM spec 缺 domain.cpu 段,候选修法见文档) | ../02 案例 23 |
残留 PVC test-vm-disk0(default,20Gi,Bound,空卷) | 占一份 Longhorn 卷,无功能影响 | ⚠️ 待清理(删除命令见 ../02 案例 23) | 同上 |
logs/deploy-all.log 末尾是旧版凭据块 | 看日志尾部会拿到过期凭据说明 | ⚠️ 下次部署自动刷新;以 configs/credentials.txt 为准 | ../03 §6.2 |
| smoke VM 镜像走公网 URL | 生产不可依赖 | ⚠️ 建议改为内网镜像源 | ../02 案例 23 |
10. 快速参考卡
bash
# ── 部署 ────────────────────────────────────────────────
bash scripts/run-all.sh --preflight # 前置检查(安全,不改状态)
setsid bash scripts/run-all.sh </dev/null >/tmp/run-all.log 2>&1 & # 一键部署
bash scripts/run-all.sh --from configs # 断点续跑
# ── 观察 ────────────────────────────────────────────────
tail -f logs/deploy-all.log # 总编排
tail -f logs/harvester-02-console.log # 装机串口
cat logs/deploy.status # 当前阶段
# ── 验收 ────────────────────────────────────────────────
bash scripts/70-verify.sh # 集群 9 项体检
bash scripts/75-set-admin-password.sh; echo rc=$? # rc=0 → dashboard 凭据可用
# ── 访问 ────────────────────────────────────────────────
Dashboard : https://192.168.150.200/ user: admin password: xxx
SSH : rancher@192.168.150.11 / .12 / .13 (密钥免密;密码: xxx ≠ dashboard 密码)
kubectl : sudo /var/lib/rancher/rke2/bin/kubectl --kubeconfig /etc/rancher/rke2/rke2.yaml
# ── 自愈(自动,无需干预;手工触发时用这些)────────────────
bash scripts/95-rescue-image-preload.sh 01 run # L2 预加载卡死救援
bash scripts/96-install-retry.sh 01 # L3 安装器卡死重试
bash scripts/97-installer-netfix.sh 01 # L1 nm-online 注入