Skip to content

04 · Harvester ISO 集群运维手册(巡检 / 重置 / 网络打通)

面向装完之后的日常运维。部署过程见 01, 故障根因见 02,认证见 03


1. 一条命令巡检

bash
cd /mnt/xfs/harvester && bash scripts/70-verify.sh

输出 9 个检查段(详见 01 §10.1)。判健康的硬指标

指标期望值
virsh list --all3 个域 running
Autostart3 个域均 enable
kubectl get nodes3 行,STATUSReadyReady,SchedulingDisabled
ROLES3 个均 control-plane,etcd
异常 Pod(非 Running/Completed/Succeeded)
etcd-harvester-0N3 个 Running(quorum = 2/3)
kube-vip3 个(每节点 1 个)
Longhorn nodes.longhorn.io3 个节点,default-disk schedulable=true
StorageClassharvester-longhorn(默认)
VIP /ping200 + 响应体 pong
VIP /dashboard/200 且能取到 <title>
三节点 SSHOK <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 不受影响, 但 CDI importer-* 之类会被重建。重负载导入/迁移期间不要改集群级设置。 完整实操与验证链见 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正常(可读写,但不能再容忍第二台故障)
1API 不可用;已运行的 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-01

4. 重置与重建

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=0hung_task_panic=0hung_task_timeout_secs=0panic_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:裸 systemctlFailed to query system state: No such device or address (SSH 会话拿不到 D-Bus)→ 一律 sudo systemctl(实测 sudo systemctl is-system-runningrunning)。 journalctl 同样要 sudo

关键前提:/etc 里的改动不持久! Harvester OS 是 immutable:/ext2 ro/etc 是 overlay 且 upperdir=/run/overlay/etc.overlay/upper(tmpfs)重启即丢systemd-sysctl.servicestatic(开机由 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 背景(三个实测发现,缺一不可)

  1. libvirt 的两个 NAT 网络各自插入 FORWARD … REJECT 规则,导致 harvester-01(192.168.150.11) → 192.168.122.20:443/22 双向全不通, 而 → 192.168.150.1(宿主机)正常。 → 只加 DNS 记录解决不了"转发被拒"的问题,这是最容易走错的一步。
  2. harvester 网络的 dnsmasq addnhosts 为空,节点会把 ai-ear.cn 解析成 公网 8.153.84.140;而公网 443 是文档站,Rancher 只在公网 30443(frp 隧道)。
  3. CoreDNS 随机上游(最隐蔽):节点 resolv.conf 并列 192.168.150.1223.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节点 ×3nmcli con mod bridge-mgmt ipv4.dns 192.168.150.1 + nmcli device reapply mgmt-br★ 用 device reapply 不断链仅在真改动时rollout restart CoreDNS
E节点 ×3certs.d/<私仓>/hosts.tomlcontainerd config_path 已启用 → 动态生效免重启
FHarvester 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.shconfig-node0*.yamldns_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 会直接报错并提示 sudocheck 会从 harvester-01 实测 getent hosts <域名>curl -sk https://<域名>/ping, 是最直接的验收方式;apply 结束时会自动再跑一次 check


6. 故障速查表(症状 → 第一步看哪 → 对应案例)

症状第一步看什么对应
VM 装机 20 分钟还没 powerofftail -f logs/harvester-NN-console.log01 §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
节点加入后一直 NotReadykubectl describe node + 该节点 journalctl02 案例 19/20
节点显示 Ready,SchedulingDisabled正常,等自动提升完成,别 uncordon02 案例 20
Dashboard 登录 401 / API must authenticate75-set-admin-password.sh03 全文
浏览器登录 401,但 curl 用同一密码能拿到 201客户端问题:全角 、自动填充旧密码、Cookie 残留 → 先用无痕窗口重试02 案例 28
sudo kubectl 报 command not found改用绝对路径02 案例 11
编排脚本静默消失、后续节点没开始查是否 || exit 1;改为 FAILED + continue02 案例 16
串口日志被清空、无法复盘.prev 轮转文件02 案例 17
脚本文件"莫名变回旧版"ls -l --time-style=full-iso scripts/ 核对 mtime02 案例 18
建测试 VM 报 doc is missing path: …/cpu/maxSocketsVM spec 缺 domain.cpu02 案例 23(未解决
Harvester 网段访问不到 Rancher 网段sudo bash scripts/99-rancher-internal-net.sh check本文 §5
cattle-cluster-agentwebsocket: bad handshake、时好时坏for i in $(seq 1 20); do dig +short <域名> @10.53.0.10; done | sort | uniq -c 看是否分裂02 案例 24
crictl pullhttp: server gave HTTP response to HTTPS client私仓是 HTTP,需声明 insecure endpoint02 案例 25
手写的 registries.yaml 过一会儿变回 0 字节被控制器按 containerd-registry 设置回收02 案例 26
改了节点 DNS 但解析仍走公网CoreDNS forward 只在启动时读 resolv.conf → 需 rollout restart02 案例 27
sudo crictl 报 command not found绝对路径 + --runtime-endpoint02 案例 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 ✅
crictlvalidate CRI v1 image API … no such file or directoryctr 输出空白本集群无 /etc/crictl.yamlcrictl -r unix:///run/k3s/containerd/containerd.sockctr --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-r102 案例 29