主题
04 · Harvester ISO 集群运维手册(巡检 / 重置 / 网络打通)
1. 一条命令巡检
bash
cd /mnt/xfs/harvester && bash scripts/70-verify.sh输出 9 个检查段(详见 01 §10.1)。判健康的硬指标:
| 指标 | 期望值 |
|---|---|
virsh list --all | 3 个域 running |
Autostart | 3 个域均 enable |
kubectl get nodes | 3 行,STATUS 为 Ready 或 Ready,SchedulingDisabled |
ROLES | 3 个均 control-plane,etcd |
| 异常 Pod(非 Running/Completed/Succeeded) | 空 |
etcd-harvester-0N | 3 个 Running(quorum = 2/3) |
kube-vip | 3 个(每节点 1 个) |
Longhorn nodes.longhorn.io | 3 个节点,default-disk schedulable=true |
| StorageClass | 含 harvester-longhorn(默认) |
VIP /ping | 200 + 响应体 pong |
VIP /dashboard/ | 200 且能取到 <title> |
| 三节点 SSH | 均 OK <hostname> <kernel> |
实测健康基线(2026-09-06 09:19):以上全部满足,异常 Pod 数 0, 内核 6.12.0-160000.36-default,Kubernetes v1.35.7+rke2r1。
2. 常用运维命令
2.1 宿主机侧(libvirt)
bash
virsh list --all # 三台 VM 状态
virsh dominfo harvester-01 # 含 Autostart 是否 enable
virsh start|shutdown|destroy harvester-01
virsh vncdisplay harvester-01 # VNC 兜底人工介入
virsh dumpxml harvester-01 | grep -c '<kernel>' # 0=硬盘启动, 1=仍在 netboot 装机
virsh domblklist harvester-01 # 确认 vda/vdb 指向正确 qcow2
bash scripts/30-console-capture.sh 01 start # 开始/恢复录制串口
bash scripts/30-console-capture.sh 01 status
tail -f logs/harvester-01-console.log # 实时串口
tail -f logs/progress.log # 30s 粒度汇总(VM 状态/磁盘写入/控制台末行)
cat logs/deploy.status # 当前部署阶段
tail -50 logs/deploy-all.log # 编排全过程2.2 节点侧(Harvester OS)
bash
# ★ kubectl 必须用绝对路径(02 案例 11)
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'
$H "$K get nodes -o wide"
$H "$K get pods -A -o wide | grep -vE 'Running|Completed'" # 异常 Pod
$H "$K -n kube-system get pods | grep -E '^etcd-|kube-vip'" # HA 组件
$H "$K -n longhorn-system get nodes.longhorn.io" # 存储节点
$H "$K get sc" # 存储类
$H "$K -n harvester-system get vlanconfigs; $K get networks.harvesterhci.io -A"
# ★ 镜像仓库(私仓 / mirror)——只看"设置"和被 RKE2 渲染的实物,别手写
$H "$K get settings.harvesterhci.io containerd-registry -o jsonpath='{.value}'" # 声明源
$H "sudo cat /etc/rancher/rke2/registries.yaml" # 控制器产物(会被回收)
$H "sudo ls /var/lib/rancher/rke2/agent/etc/containerd/certs.d/" # RKE2 渲染的 mirror
$H "sudo cat '/var/lib/rancher/rke2/agent/etc/containerd/certs.d/docker.io/hosts.toml'"
# ★ crictl / ctr 必须显式指定 RKE2 的 socket(本集群没有 /etc/crictl.yaml)
$H "sudo /var/lib/rancher/rke2/bin/crictl -r unix:///run/k3s/containerd/containerd.sock images | head"
$H "sudo /var/lib/rancher/rke2/bin/ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls -q | head"
# 漏掉 --address 时 ctr 会静默返回空 → 极易误判"镜像不存在"
# 安装期排障(仅 live 安装环境)
$H "sudo journalctl -b --no-pager | grep -iE 'level=(error|fatal)' | tail -30"
$H "ls -l /var/log/console.log /rke2.log 2>/dev/null"装完的系统里安装期日志在
/var/log/console.log;/rke2.log只在 chroot 安装环境内存在。⚠️ 改
containerd-registry设置会让harvester-node-manager逐节点重写registries.yaml并滚动重启rke2-server(containerd 是它的子进程,无独立 unit)——运行中的 Pod 不受影响, 但 CDIimporter-*之类会被重建。重负载导入/迁移期间不要改集群级设置。 完整实操与验证链见02案例 30。
3. 节点角色与 HA 机制
3.1 自动提升(不要手工干预)
node01 (install.mode=create) → control-plane,etcd (首节点,建集群)
node02 (install.mode=join) → <none> → 短暂 Ready,SchedulingDisabled → control-plane,etcd
node03 (install.mode=join) → 同上提升由 harvester-promote-node-controller 完成,是设计行为(02 案例 20)。
| 纪律 | 原因 |
|---|---|
装机配置 install.role 留空 | 3 节点场景自动提升即可拿到正确角色 |
不要在提升期手工 uncordon | 会干扰控制器流程 |
就绪判定用 ^Ready(,|$) | Ready,SchedulingDisabled 是合法就绪态(02 案例 19) |
3.2 quorum 与容错
| 存活节点 | etcd quorum(3 成员需 ≥2) | 集群可用性 |
|---|---|---|
| 3 | ✅ | 正常 |
| 2 | ✅ | 正常(可读写,但不能再容忍第二台故障) |
| 1 | ❌ | API 不可用;已运行的 VM 通常仍在跑,但无法做任何变更 |
VIP 由 kube-vip 承载(vip_mode: static,三节点各一个 Pod), 任一节点宕机 VIP 会漂移,Dashboard 地址不变。
VIP 漂移演练(非日常操作):
bash
virsh shutdown harvester-01 # 或 destroy 模拟硬故障
sleep 60; curl -sk https://192.168.150.200/ping # 仍应返回 pong
virsh start harvester-014. 重置与重建
4.1 彻底重置(★ 会删除全部数据)
bash
cd /mnt/xfs/harvester
for n in 01 02 03; do
virsh destroy harvester-$n 2>/dev/null
virsh undefine harvester-$n --keep-nvram 2>/dev/null # ★ UEFI 域必须 --keep-nvram
done
rm -f disks/*.qcow2 nvram/*_VARS.fd
rm -f logs/rescue-*.done logs/retry-*.count logs/retry-*.stamp
rm -rf logs/rescue-*.lock
bash scripts/10-prepare.sh # 重建磁盘/NVRAM/DHCP 保留清理自愈标记很重要:残留的
rescue-NN.done会让下一轮跳过救援, 残留的retry-NN.count会让下一轮没有重试额度。 (60-deploy.sh: install_node开头也会清一遍,手工重置时别漏。)
4.2 单节点重装
bash
bash scripts/60-deploy.sh node02 # 重新走 netboot 装机 → 切盘 → 等 Ready重装后节点以 join 身份重新入集群,再被自动提升。 注意:Longhorn 三副本会因盘被清空而重建,期间存储降级,务必逐台操作。
4.3 宿主机重启后的恢复
三台域都已 virsh autostart,宿主机重启后自动拉起,无需人工干预。恢复后建议:
bash
virsh list --all # 确认 3 台 running
bash scripts/20-http-server.sh # HTTP 引导服务非 systemd 单元,需重起(仅装机期需要)
bash scripts/90-progress.sh & # 如需继续记录进度
bash scripts/70-verify.sh # 巡检
sudo bash scripts/99-rancher-internal-net.sh check # 若做过跨网段放行,复核规则是否自动重插4.4 单节点 guest 硬死锁(NotReady 且 SSH/console 全失联)的安全恢复 ★
适用症状:kubectl get nodes 某节点 NotReady,但该节点 SSH 超时、virsh console 无回显、 virsh reboot(ACPI)也不理;宿主机侧 virsh domblkerror 显示 No errors found。 实例见 02 案例 29(harvester-03,2026-09-07 05:02 soft lockup → 挂 22 小时)。
第 1 步 · 判定"guest 硬死锁"而非宿主机故障(两个判据缺一不可):
bash
virsh domstate harvester-03 # running(不是 shut off)
virsh domblkerror harvester-03 # No errors found → 宿主块层干净
virsh domblkstat harvester-03 vda; sleep 10; virsh domblkstat harvester-03 vda
# ★ 两次采样 I/O 计数不增长 → guest 在空转
virsh cpu-stats harvester-03 # CPU 仍在烧(空转)而非空闲
sudo dmesg | grep -iE "i/o error|nvme|ata[0-9]|oom" | tail # 宿主内核干净第 2 步 · 硬重启前的三项安全判定(决定"能不能立刻拔电"):
bash
# ① etcd quorum 是否还健康(3 节点容忍挂 1 台;若已有另一台不健康 → 先别动这台)
# 在健康节点(node01/02)上执行:
sudo /var/lib/rancher/rke2/bin/etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/var/lib/rancher/rke2/server/tls/etcd/server-ca.crt \
--cert=/var/lib/rancher/rke2/server/tls/etcd/client.crt \
--key=/var/lib/rancher/rke2/server/tls/etcd/client.key endpoint health
# ② 该节点上有没有正在跑的虚机(有 → 硬重启等于强杀这些 VM,需先评估/迁移)
kubectl get vmi -A -o wide | grep harvester-03
# ③ 有没有 Longhorn 卷"只靠"这台(活跃附着 / 唯一副本在这台)
kubectl -n longhorn-system get volumes.longhorn.io -o wide
kubectl -n longhorn-system get replicas.longhorn.io \
-o custom-columns=V:.spec.volumeName,N:.spec.nodeID,S:.status.currentState | sort说明:若唯一副本所在的卷已经
faulted(副本本就不可用),硬重启不会让数据情况更坏; 但要意识到这类卷在恢复前对该 workload 是不可用的。
第 3 步 · 硬重启并留证:
bash
# 留证:先抓现场(重启后 guest 侧证据只能靠 journalctl -b -1 找回)
mkdir -p /mnt/xfs/harvester/logs/postmortem-nodeNN
virsh dominfo harvester-03 > …/host-side.txt 2>&1
virsh domblkerror harvester-03 >> …/host-side.txt 2>&1
kubectl get nodes -o wide > …/nodes.txt
kubectl get pods -A -o wide > …/pods-all.txt
kubectl describe node harvester-03 > …/describe-node03.txt
# 硬重启(ACPI 无效时的唯一手段)
virsh destroy harvester-03 && sleep 5 && virsh start harvester-03
virsh console harvester-03 # 观察引导,Ctrl+] 退出第 4 步 · 恢复后取证与验证:
bash
# guest 起来后立刻取上一轮启动的内核日志(根因就在这里)
ssh rancher@192.168.150.13 'sudo journalctl -b -1 --no-pager | tail -200' > …/prev-boot-journal-tail.txt
ssh rancher@192.168.150.13 'sudo journalctl -b -1 --no-pager | \
grep -iE "soft lockup|rcu: INFO|EXT4-fs error|beyond end of device|limit=0|Out of memory"' >> …/prev-boot-journal-tail.txt
# 验证:节点回归 + 管理面 + 存储 + 入口
kubectl get nodes -o wide # 3×Ready,污点自动清除(无需 uncordon)
bash scripts/70-verify.sh # 全量巡检
kubectl -n longhorn-system get volumes.longhorn.io # faulted/degraded 应逐步转 healthy
kubectl get pods -A -o wide | grep -vE "Running|Completed" # 残留异常 Pod第 5 步 · 内核自愈加固(⏳ 待执行:等 CDI 导入跑完再开,理由见 02 案例 29"复发信号")
现状(三节点实测一致):kernel.softlockup_panic=0、hung_task_panic=0、hung_task_timeout_secs=0、 panic_on_warn=0,而 kernel.panic=10 → panic 后 10s 自动重启这条自愈路径走不到,节点可无限期挂着。
bash
# 查(注意 PATH / D-Bus 两个坑)
ssh rancher@192.168.150.11 'sudo /usr/sbin/sysctl kernel.softlockup_panic kernel.panic \
kernel.hung_task_panic kernel.hung_task_timeout_secs'⚠️ 坑 1:裸
sysctl在非登录 SSH 里 PATH 不含/usr/sbin→ 输出全空(不是"没有这个参数"), 与案例 11 的kubectl陷阱同源。一律sudo /usr/sbin/sysctl,或直接读/proc/sys/kernel/*。⚠️ 坑 2:裸
systemctl报Failed to query system state: No such device or address(SSH 会话拿不到 D-Bus)→ 一律sudo systemctl(实测sudo systemctl is-system-running→running)。journalctl同样要sudo。
关键前提:/etc 里的改动不持久! Harvester OS 是 immutable:/ 为 ext2 ro, /etc 是 overlay 且 upperdir=/run/overlay/etc.overlay/upper(tmpfs) → 重启即丢。 systemd-sysctl.service 是 static(开机由 sysinit.target 拉起、会读 /etc/sysctl.d/*.conf), 所以持久化的正确姿势是让每次开机把文件重新写出来 —— 这正是 Harvester 自己的做法: /oem/90_custom.yaml(COS_OEM 分区,持久)里的 elemental stages.initramfs[].files[] 每次启动重写 /etc/multipath/conf.d/99-longhorn.conf、/etc/rancher/rancherd/config.yaml 等。
bash
# ① 立即生效(runtime;重启后失效)——三节点都要
for ip in 11 12 13; do
ssh rancher@192.168.150.$ip 'sudo /usr/sbin/sysctl -w kernel.softlockup_panic=1'
done
# ② 持久化:先备份,再往 /oem/90_custom.yaml 的 initramfs stage 增加一个 files 条目
ssh rancher@192.168.150.11 'sudo cp /oem/90_custom.yaml /oem/90_custom.yaml.bak.$(date +%F)'
printf 'kernel.softlockup_panic = 1\n' | base64 -w0 # → 贴进 files[].content(encoding: base64)
# files:
# - path: /etc/sysctl.d/99-harvester-selfheal.conf
# permissions: 420 # 0644
# owner: 0
# group: 0
# content: <上面的 base64>
# encoding: base64
# 可选(重载期易误报,建议先不开):kernel.hung_task_timeout_secs = 120 / kernel.hung_task_panic = 1⚠️
/oem/90_custom.yaml由安装器按harvester.os.*生成,升级或改配置时可能被重写 → 改完必须重启一台节点验证:开机后/etc/sysctl.d/99-harvester-selfheal.conf存在, 且sudo /usr/sbin/sysctl -n kernel.softlockup_panic= 1、kernel.panic仍为 10。 运行时kernel.panic=10的来源未定位(cmdline 里是panic=0,/usr/lib/sysctl.d、/etc/sysctl.d、/oem、/etc/rancher全 grep 不到panic)→ 加固后务必复验它没被覆盖成 0,否则 panic 后不会自动重启。 更彻底是把softlockup_panic=1加进内核 cmdline(/oem/grubcustom),但改引导项风险更高,非必要不动。
代价:开了之后"短暂卡顿也会重启",I/O 重载期(如并发 CDI 导入)可能误杀并丢导入进度 → 所以先让导入跑完再开。回滚:sysctl -w kernel.softlockup_panic=0 + 删掉 ② 写的文件。
预警信号(出现即应立即排查,别等它挂死):
bash
# ① guest 内核侧(案例 29 的签名)
ssh rancher@192.168.150.1X 'sudo journalctl -b 0 --no-pager | \
grep -iE "soft lockup|rcu: INFO|hung_task|EXT4-fs error|beyond end of device|limit=0" | tail'
# ② Longhorn 侧(**更早的前兆**:引擎 8s R/W 超时把副本打成 ERR,之后才会拆设备)
kubectl -n longhorn-system logs <该节点的 longhorn-manager pod> --since=10m | \
grep -iE "R/W Timeout|Setting replica .* to ERR|Failed to sync Longhorn engine"
# ③ 负载侧(8 vCPU 节点:load 持续 >25 或 %wa >40 就该降并发/串行化导入)
ssh rancher@192.168.150.13 'cat /proc/loadavg; top -bn1 | sed -n 3p'5. 跨网段访问 Rancher(99-rancher-internal-net.sh)
让 Harvester 网段(virbr10 / 192.168.150.0/24)能用内网域名访问 Rancher (192.168.122.20/21/22,RKE2 ingress-nginx:443)。
5.1 背景(三个实测发现,缺一不可)
- libvirt 的两个 NAT 网络各自插入
FORWARD … REJECT规则,导致harvester-01(192.168.150.11) → 192.168.122.20:443/22双向全不通, 而→ 192.168.150.1(宿主机)正常。 → 只加 DNS 记录解决不了"转发被拒"的问题,这是最容易走错的一步。 harvester网络的 dnsmasqaddnhosts为空,节点会把ai-ear.cn解析成 公网8.153.84.140;而公网 443 是文档站,Rancher 只在公网30443(frp 隧道)。- ★ CoreDNS 随机上游(最隐蔽):节点
resolv.conf并列192.168.150.1与223.5.5.5时,Harvester 的 CoreDNS 是forward . /etc/resolv.conf且默认policy=random, 直查 20 次有 10 次返回公网 IP →cattle-cluster-agent拿到 VitePress 首页 HTML, 报websocket: bad handshake,集群反复抖动(02案例 24)。
5.2 脚本做的六件事(A/B/C 在宿主机,D/E 在 3 个节点内,F 走 Harvester API)
| 步骤 | 位置 | 动作 | 关键点 |
|---|---|---|---|
| A | 宿主 | iptables -I FORWARD 1 放行 virbr10 ↔ virbr0 双向 | 必须插到链首,先于 libvirt 的 REJECT;用 iptables -C 先探测避免重复插入 |
| B | 宿主 | 写 /etc/libvirt/hooks/network,在网络 started/plugged 时自动重插 | 追加而非覆盖(已有 hook 内容保持不动);带 MARKER 幂等;改前备份 .bak.<时间戳>;chmod 755 + bash -n 语法自检 |
| C | 宿主 | virsh net-update harvester add dns-host … --live --config 下发内网静态解析 | --live 立即生效,--config 持久化到网络 XML |
| D | 节点 ×3 | nmcli con mod bridge-mgmt ipv4.dns 192.168.150.1 + nmcli device reapply mgmt-br | ★ 用 device reapply 不断链;仅在真改动时才 rollout restart CoreDNS |
| E | 节点 ×3 | 写 certs.d/<私仓>/hosts.toml | containerd config_path 已启用 → 动态生效免重启 |
| F | Harvester API | 设置 containerd-registry | ★ 官方持久机制:控制器据此在各节点渲染 /etc/rancher/rke2/registries.yaml,rke2 再重写 certs.d。手写该文件会被回收(02 案例 26) |
内外域名分离:VM 走内网直连 ingress;宿主机/公网仍走 frp:30443,互不影响。
⚠️
rollback只回滚 A/C/D:删掉 E/F 的私仓配置后cattle-cluster-agent将再也拉不到镜像。装机配置也已同步收敛——
50-gen-configs.sh与config-node0*.yaml的dns_nameservers只保留192.168.150.1(原文件备份为*.bak-dns),避免重装后又引入随机上游。 完整的导入流程、验证命令与代理路径见05-接入Rancher与内外网分离解析.md。
5.3 用法
bash
sudo bash scripts/99-rancher-internal-net.sh check # 只检查不改动
sudo bash scripts/99-rancher-internal-net.sh apply # 应用(默认)
sudo bash scripts/99-rancher-internal-net.sh rollback # 回滚 A/C,并按 MARKER 移除放行块需要 root(要改 iptables 与 /etc/libvirt/hooks),非 root 会直接报错并提示 sudo。 check 会从 harvester-01 实测 getent hosts <域名> 与 curl -sk https://<域名>/ping, 是最直接的验收方式;apply 结束时会自动再跑一次 check。
6. 故障速查表(症状 → 第一步看哪 → 对应案例)
| 症状 | 第一步看什么 | 对应 |
|---|---|---|
| VM 装机 20 分钟还没 poweroff | tail -f logs/harvester-NN-console.log | 01 §8 判卡阈值 |
串口刷 ctr: failed to list images … connection refused | 进 live 环境看 containerd/rke2 是否还在 | 02 案例 7 |
串口出现 Can't apply networks: exit status 1 后不再动 | 确认 97 号注入是否成功;否则等 96 自动重试 | 02 案例 13/14 |
Install failed: exit status 1 紧跟在 Stop RKE2... 之后 | pkill rke2 + set -e 陷阱 | 02 案例 8 |
节点加入后一直 NotReady | kubectl describe node + 该节点 journalctl | 02 案例 19/20 |
节点显示 Ready,SchedulingDisabled | 正常,等自动提升完成,别 uncordon | 02 案例 20 |
Dashboard 登录 401 / API must authenticate | 跑 75-set-admin-password.sh | 03 全文 |
浏览器登录 401,但 curl 用同一密码能拿到 201 | 客户端问题:全角 @、自动填充旧密码、Cookie 残留 → 先用无痕窗口重试 | 02 案例 28 |
sudo kubectl 报 command not found | 改用绝对路径 | 02 案例 11 |
| 编排脚本静默消失、后续节点没开始 | 查是否 || exit 1;改为 FAILED + continue | 02 案例 16 |
| 串口日志被清空、无法复盘 | 找 .prev 轮转文件 | 02 案例 17 |
| 脚本文件"莫名变回旧版" | ls -l --time-style=full-iso scripts/ 核对 mtime | 02 案例 18 |
建测试 VM 报 doc is missing path: …/cpu/maxSockets | VM spec 缺 domain.cpu 段 | 02 案例 23(未解决) |
| Harvester 网段访问不到 Rancher 网段 | sudo bash scripts/99-rancher-internal-net.sh check | 本文 §5 |
cattle-cluster-agent 报 websocket: bad handshake、时好时坏 | for i in $(seq 1 20); do dig +short <域名> @10.53.0.10; done | sort | uniq -c 看是否分裂 | 02 案例 24 |
crictl pull 报 http: server gave HTTP response to HTTPS client | 私仓是 HTTP,需声明 insecure endpoint | 02 案例 25 |
手写的 registries.yaml 过一会儿变回 0 字节 | 被控制器按 containerd-registry 设置回收 | 02 案例 26 |
| 改了节点 DNS 但解析仍走公网 | CoreDNS forward 只在启动时读 resolv.conf → 需 rollout restart | 02 案例 27 |
sudo crictl 报 command not found | 绝对路径 + --runtime-endpoint | 02 案例 27 |
| 宿主机重启后 VM 没起来 | virsh dominfo harvester-NN | grep Autostart | 本文 §4.3 |
节点 NotReady 且 SSH/console 全失联、virsh reboot 无效 | virsh domblkerror + domblkstat 两次采样判定 guest 硬死锁 → 三项安全判定后 destroy+start | 本文 §4.4、02 案例 29 |
guest 日志出现 soft lockup / rcu: INFO / EXT4-fs error / limit=0 | 立即排查(这是挂死前兆,节点不会自愈:softlockup_panic=0) | 本文 §4.4 第 5 步、02 案例 29 |
Pod ErrImagePull,但镜像本地已有 / 私仓也有 | 看镜像引用是不是 docker.io 短名(节点无外网 + DNS 污染);对照能跑的同类 Pod 用的引用 → 修法是给 containerd-registry 设置加 docker.io mirror(本文 §2.2) | 02 案例 30 ✅ |
crictl 报 validate CRI v1 image API … no such file or directory;ctr 输出空白 | 本集群无 /etc/crictl.yaml:crictl -r unix:///run/k3s/containerd/containerd.sock、ctr --address 同一 socket(漏了就静默返空) | 02 案例 27/30 |
| 想看实际生效的镜像 mirror 配置 | sudo cat /var/lib/rancher/rke2/agent/etc/containerd/certs.d/docker.io/hosts.toml(/etc/rancher/rke2/certs.d 不存在) | 本文 §2.2、02 案例 30 |
CDI importer-prime-* 长时间 ContainerCreating、导入目标 PVC 一直 Pending | 先看其卷是否 faulted/degraded(尤其单副本 harvester-longhorn-r1) | 02 案例 29 |