主题
03 ISO 无人值守安装 12 节点
本章目标:用一份可复用的
config.yaml+ 一个生成脚本,把 12 台机器装成 3 management + 9 worker 的高可用集群,装完即可登录 UI,并完成基础加固。
3.1 config.yaml 是怎么被安装器读到的
Harvester 安装器(基于 Elemental)在启动时按下面的优先级获取配置:
text
① 内核参数里显式给的单项配置(harvester.install.xxx=yyy)
↑ 优先级最高,会覆盖 config 文件里的同名项
② 内核参数 harvester.install.config_url=http://.../config.yaml
↑ 本书主用方式:远程 config,一份文件管一台机器
③ 安装器 TUI 里人工填写的值
↑ 官方明确:TUI 里人工填的值 **优先于** 远程 config 文件
④ 内置默认值⚠️ 第 ③ 条是官方文档里写明的行为,也是很多人「明明给了 config 却不生效」的原因:只要你在 TUI 界面里点过、填过某个字段,那个字段就以你填的为准。 真正的无人值守要靠
install.automatic: true跳过所有交互步骤,让安装器完全按 config 走。
三种送达方式:
| 方式 | 怎么做 | 适用 |
|---|---|---|
| 远程 URL(本书主方案) | ISO 启动 → 在安装器菜单选 "Harvester Installer" 并按 e(或直接在引导项)追加内核参数 harvester.install.automatic=true harvester.install.config_url=http://10.181.0.5:8080/configs/config-node01.yaml | 有 HTTP 服务,12 台机器可插介质/带外挂载 |
| PXE / iPXE 全自动 | DHCP + HTTP 托管 vmlinuz/initrd/rootfs.squashfs + iPXE 脚本按 MAC 分发不同 config_url | 大批量、机房有 PXE |
| U 盘内置 config | 把 ISO 写入 U 盘,再在 U 盘的 COS_OEM 分区里放 config.yaml | 完全离线、无 HTTP |
3.2 config.yaml 字段全景(v1.8 已核对)
下面是本书会用到的全部字段,按层级列出。加粗的是本环境必须写对的。
| 字段 | 类型/示例 | 说明与坑 |
|---|---|---|
scheme_version | 1 | 配置格式版本,v1.8 用 1 |
server_url | create 留空 "";join 填 https://10.181.0.100:443 | create 模式下写了非空值会直接安装失败 |
token | 32 位随机串 | create 与 join 必须一致;join 时它就是 node token |
sans | - harvester.example.local | 顶层字段(不在 install 下),加进 API server 证书的 SAN |
os.hostname | harvester-01 | 不填会生成随机名 |
os.password | "xxx" | 默认用户 rancher;可明文或 /etc/shadow 加密串;含 - 必须加引号 |
os.ssh_authorized_keys | - "ssh-rsa AAAA..." | 注意是下划线;也支持 github:username |
os.modules | [kvm, nvme] | 开机加载的内核模块 |
os.dns_nameservers | [10.181.0.2] | 宿主机 DNS |
os.ntp_servers | [ntp.aliyun.com, ...] | 强烈建议配,etcd 怕时间漂移 |
os.sysctls | map | 例:kernel.printk: "4 4 1 7" |
os.labels | map | 变成 K8s node label,可用于 VlanConfig 的 nodeSelector |
os.write_files | list | 往宿主机写文件(path/content/owner/permissions/encoding) |
os.after_install_chroot_commands | list | 装完在 chroot 里跑命令;注意此阶段没有 DNS |
os.persistent_state_paths | list | 让这些路径的修改跨重启保留(宿主机是只读系统) |
os.environment | map | 例:http_proxy / https_proxy |
os.externalStorageConfig | obj | SAN/multipath 场景才用 |
os.additionalKernelArguments | str | 例:"multipath=on" |
os.sshd.disable_password_auth | true | 关闭 SSH 密码登录;⚠️ 若同时没配 ssh_authorized_keys,将彻底失去 SSH 访问,只能走控制台 |
os.sshd.sftp | true | 开启 SFTP 子系统 |
install.mode | create / join | 第一台 create,其余 join |
install.role | default / management / worker / witness | 显式指定角色,避免"谁先 join 谁变 management"的不确定性 |
install.automatic | true | 跳过所有交互步骤,真正无人值守 |
install.silent | — | 文档标注 Reserved |
install.skipchecks | true / false | false 时硬件不达标会中止安装;警告写入 /var/log/console.log |
install.device | /dev/sda | 系统盘,会被分区格式化 |
install.data_disk | /dev/sdb | Longhorn 数据盘;PXE 场景建议用 /dev/disk/by-id/... |
install.persistent_partition_size | 150Gi | COS_PERSISTENT 分区大小,最小 150Gi(存镜像与包) |
install.no_format | true | 不分区不格式化(特殊场景) |
install.force_efi | true | 强制 EFI 安装(即使没检测到 EFI) |
install.poweroff | true | 装完关机而不是重启(批量装机时便于统一上电) |
install.iso_url | http://10.181.0.5:8080/iso/harvester-v1.8.2-amd64.iso | 从内核/vmlinuz 启动(PXE)时必用;ISO 引导时可不写 |
install.tty | ttyS0,115200n8 | 串口控制台,带外/远程装机必备 |
install.vip | 10.181.0.100 | UI/API 入口 |
install.vip_mode | static(或 dhcp + vip_hw_addr) | 本环境用 static |
install.vip_hw_addr | MAC | 仅 vip_mode: dhcp 时需要 |
install.management_interface | 见 3.3 | interfaces[].name/hwAddr、method、ip、subnet_mask、gateway、default_route、bond_options{mode,miimon,mtu}、mtu、vlan_id |
install.cluster_pod_cidr | 默认 10.52.0.0/16 | 本环境保持默认 |
install.cluster_service_cidr | 默认 10.53.0.0/16 | 本环境保持默认 |
install.cluster_dns | 默认 10.53.0.10 | 必须落在 service CIDR 内 |
install.addons | rancher_monitoring.enabled: true 等 | 可用键:harvester_vm_import_controller、harvester_pcidevices_controller、rancher_monitoring、rancher_logging、harvester_seeder;默认全部关闭 |
install.harvester.storage_class.replica_count | 3 | 默认 StorageClass harvester-longhorn 副本数,1–3 |
install.harvester.longhorn.default_settings.guaranteedInstanceManagerCPU | 12 | 每个 Longhorn IM 预留的 CPU 百分比(0–12,超范围按 12) |
install.harvester.longhorn.default_settings.storageReservedPercentageForDefaultDisk | 0 | 默认盘预留百分比(0–30) |
install.webhooks | list | 装机 STARTED/SUCCEEDED/FAILED 回调,可与 Cobbler/自动化系统联动 |
install.wipe_all_disks / install.wipe_disks_list | bool / list | 清除带 COS_OEM 标签的分区(重装老机器时有用) |
install.with-net-images | bool | true = 不预载 ISO 内镜像,装完联网拉(内网别开) |
system_settings | map | 只在 create 模式生效,join 忽略;值必须是字符串(JSON 也要写成字符串) |
⚠️ 字段名大小写不统一是 Harvester config 的历史包袱:
os.ssh_authorized_keys用下划线,os.externalStorageConfig用驼峰,install.force_efi用下划线,install.vip_hw_addr用下划线。照抄本文/附录的写法,不要凭直觉改。
3.3 第一台(create)完整配置:config-node01.yaml
这份文件是全书的「母版」,appendix/configs/config-node01.yaml 就是它。
yaml
# appendix/configs/config-node01.yaml
# ============================================================
# Harvester v1.8.2 - node01 (management, create)
# 主机名 harvester-01 管理IP 10.181.0.11/24 VIP 10.181.0.100
# ============================================================
scheme_version: 1
server_url: "" # create 模式必须为空或整行删除
# ---- 集群密钥:11 台 join 必须与此完全一致 ----
token: xxx
# ---- 证书 SAN:把 VIP / 域名 / 前几台节点 IP 写进去,避免浏览器与 Rancher 报证书错 ----
sans:
- "10.181.0.100"
- "harvester.example.local"
- "10.181.0.11"
- "10.181.0.12"
- "10.181.0.13"
os:
hostname: harvester-01
password: "xxx" # 默认用户 rancher;含特殊字符务必加引号
ssh_authorized_keys: # 注意:下划线,不是 os.ssh.authorized_keys
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...(你的公钥,整行粘贴)"
sshd:
sftp: true
disable_password_auth: false # 先留 false,验证公钥能登录后再改 true(见 3.10)
modules:
- kvm
- vhost_net
- nvme
dns_nameservers:
- 10.181.0.2
ntp_servers:
- ntp.aliyun.com
- 0.suse.pool.ntp.org
- 1.suse.pool.ntp.org
labels: # 变成 K8s node label,后面 VlanConfig 可用它选节点
topology.kubernetes.io/zone: zone-a
harvester-role: management
install:
mode: create
role: management # 显式声明,避免依赖 join 顺序
automatic: true # 跳过 TUI 交互,真正无人值守
skipchecks: false # 生产环境保持 false;硬件不达标就让它报错
force_efi: true
tty: ttyS0,115200n8 # 带外串口装机必备
poweroff: false # 装完自动重启进入系统
device: /dev/sda # 系统盘(会被格式化)
data_disk: /dev/sdb # Longhorn 数据盘
persistent_partition_size: 200Gi # 镜像/包分区,最小 150Gi
vip: 10.181.0.100
vip_mode: static
management_interface:
interfaces:
- name: eno1
hwAddr: "AA:BB:CC:DD:EE:01" # 强烈建议写 MAC:不同批次网口名可能不同
- name: eno2
hwAddr: "AA:BB:CC:DD:EE:02"
method: static
ip: 10.181.0.11
subnet_mask: 255.255.255.0
gateway: 10.181.0.1
default_route: true
mtu: 1500
vlan_id: 0 # 0 = untagged;若管理网带 VLAN 则填对应 VID
bond_options:
mode: active-backup # 装机阶段最稳,不依赖交换机 LACP
miimon: 100
mtu: 1500
# ---- 保持 Kubernetes 默认网段(本环境要求)----
cluster_pod_cidr: 10.52.0.0/16
cluster_service_cidr: 10.53.0.0/16
cluster_dns: 10.53.0.10
# ---- 存储:3 副本,跨 12 节点 ----
harvester:
storage_class:
replica_count: 3
longhorn:
default_settings:
guaranteedInstanceManagerCPU: 12
storageReservedPercentageForDefaultDisk: 0
# ---- 装完就开监控(日志按需)----
addons:
rancher_monitoring:
enabled: true
rancher_logging:
enabled: false
harvester_vm_import_controller:
enabled: false
harvester_pcidevices_controller:
enabled: false
# ---- 系统设置:只有 create 节点生效,join 会忽略 ----
system_settings:
auto-disk-provision-paths: ""
# 内网有私有镜像仓库时打开(值必须是字符串形式的 JSON)
# containerd-registry: '{"Mirrors":{"docker.io":{"Endpoints":["https://registry.example.local:5000"]}}}'
# ui-source: bundled最容易踩的 6 个坑
server_url必须为空:create 模式写任何非空值都会安装失败。install.automatic: true+ 不要在 TUI 里点任何字段:一旦人工填写,TUI 值覆盖 config。device与data_disk必须是不同物理盘:写反或写同一个盘会导致 Longhorn 无盘可用或系统被覆盖。skipchecks: false时硬件不达标会中止安装:内存不足、磁盘 IOPS 不够、CPU 核数不达标都会拦下来。测试环境可临时设true,但要清楚自己在做什么。sans要在第一台就写全:证书是 create 时签发的,后续加域名要走证书轮换流程,麻烦得多。system_settings只在 create 生效:想改集群级设置,要么写在 node01 的 config 里,要么装完用 UI/kubectl 改。
3.4 join 节点配置:只有几处不同
以 harvester-04(worker)为例,完整文件见 appendix/configs/config-node04.yaml。
| 字段 | node01(create) | node02/03(mgmt join) | node04–12(worker join) |
|---|---|---|---|
server_url | "" | https://10.181.0.100:443 | 同左 |
install.mode | create | join | join |
install.role | management | management | worker |
os.hostname | harvester-01 | harvester-02/03 | harvester-04…12 |
management_interface.ip | 10.181.0.11 | .12 / .13 | .14 … .22 |
management_interface.interfaces[].hwAddr | node01 的 MAC | 各节点自己的 MAC | 各节点自己的 MAC |
install.vip / vip_mode | 10.181.0.100 / static | 同样要写 | 同样要写 |
sans | 生效 | 被忽略(证书由集群签发) | 被忽略 |
system_settings | 生效 | 被忽略 | 被忽略 |
token | 定义者 | 必须完全一致 | 必须完全一致 |
yaml
# appendix/configs/config-node04.yaml
scheme_version: 1
server_url: https://10.181.0.100:443 # ← 关键:指向 VIP,不是 node01 的 IP
token: xxx # ← 必须与 node01 完全一致
os:
hostname: harvester-04
password: "xxx"
ssh_authorized_keys:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...(同一把公钥)"
sshd:
sftp: true
disable_password_auth: false
modules: [kvm, vhost_net, nvme]
dns_nameservers: [10.181.0.2]
ntp_servers:
- ntp.aliyun.com
- 0.suse.pool.ntp.org
- 1.suse.pool.ntp.org
labels:
topology.kubernetes.io/zone: zone-b
harvester-role: worker
install:
mode: join
role: worker
automatic: true
skipchecks: false
force_efi: true
tty: ttyS0,115200n8
device: /dev/sda
data_disk: /dev/sdb
persistent_partition_size: 200Gi
vip: 10.181.0.100
vip_mode: static
management_interface:
interfaces:
- name: eno1
hwAddr: "AA:BB:CC:DD:EE:07" # ← node04 自己的 MAC
- name: eno2
hwAddr: "AA:BB:CC:DD:EE:08" # ← node04 自己的 MAC
method: static
ip: 10.181.0.14 # ← node04 自己的 IP
subnet_mask: 255.255.255.0
gateway: 10.181.0.1
default_route: true
mtu: 1500
vlan_id: 0
bond_options:
mode: active-backup
miimon: 100
mtu: 1500
cluster_pod_cidr: 10.52.0.0/16
cluster_service_cidr: 10.53.0.0/16
cluster_dns: 10.53.0.10
harvester:
storage_class:
replica_count: 3
addons:
rancher_monitoring:
enabled: true⚠️ join 最常犯的三个错:
- 复制 node01 的 config 后忘改
hwAddr/ip/hostname→ 安装器找不到网卡,或集群里出现同名节点;server_url写成 node01 的 IP 而不是 VIP → node01 维护/重启时后续节点会失联;token有多余空格或换行 → join 认证失败,控制台报 401/unauthorized。
⚠️
install.role与自动提升:不写 role 时规则是「create 的节点是 management,集群满 3 台后随后加入的前两台自动提升为 management」。并发 join 时顺序不确定,因此本书显式写 role。若你已经用旧方式装完(02/03 被自动提升),后续可用kubectl查看node-role.kubernetes.io/*标签确认实际角色。
3.5 一键生成 12 份 config:gen-configs.sh
脚本见 appendix/configs/gen-configs.sh。思路:节点清单(CSV)+ 参数区 → 12 份可直接下载的 config,并自带一致性自检(hostname/IP/MAC 唯一、token 一致、create 只有 1 台)。
bash
#!/usr/bin/env bash
# appendix/configs/gen-configs.sh
# 生成 12 台 Harvester 节点的无人值守安装配置
set -euo pipefail
# ========== 全局参数 ==========
VIP="10.181.0.100"
TOKEN="xxx"
PASSWORD='xxx'
GW="10.181.0.1"; NETMASK="255.255.255.0"; DNS="10.181.0.2"
NTP1="ntp.aliyun.com"; NTP2="0.suse.pool.ntp.org"; NTP3="1.suse.pool.ntp.org"
ISO_URL="http://10.181.0.5:8080/iso/harvester-v1.8.2-amd64.iso"
SYS_DISK="/dev/sda"; DATA_DISK="/dev/sdb"; PERSIST="200Gi"
SSH_KEY="ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...(替换成你的公钥整行)"
SAN_DOMAIN="harvester.example.local"
BOND_MODE="active-backup" # 交换机 LACP 就绪后可改 802.3ad
OUT="./out"
# ========== 节点清单:序号,主机名,IP,角色,zone,MAC1,MAC2 ==========
NODES=$(cat <<'CSV'
01,harvester-01,10.181.0.11,management,zone-a,AA:BB:CC:DD:EE:01,AA:BB:CC:DD:EE:02
02,harvester-02,10.181.0.12,management,zone-b,AA:BB:CC:DD:EE:03,AA:BB:CC:DD:EE:04
03,harvester-03,10.181.0.13,management,zone-c,AA:BB:CC:DD:EE:05,AA:BB:CC:DD:EE:06
04,harvester-04,10.181.0.14,worker,zone-a,AA:BB:CC:DD:EE:07,AA:BB:CC:DD:EE:08
05,harvester-05,10.181.0.15,worker,zone-b,AA:BB:CC:DD:EE:09,AA:BB:CC:DD:EE:0A
06,harvester-06,10.181.0.16,worker,zone-c,AA:BB:CC:DD:EE:0B,AA:BB:CC:DD:EE:0C
07,harvester-07,10.181.0.17,worker,zone-a,AA:BB:CC:DD:EE:0D,AA:BB:CC:DD:EE:0E
08,harvester-08,10.181.0.18,worker,zone-b,AA:BB:CC:DD:EE:0F,AA:BB:CC:DD:EE:10
09,harvester-09,10.181.0.19,worker,zone-c,AA:BB:CC:DD:EE:11,AA:BB:CC:DD:EE:12
10,harvester-10,10.181.0.20,worker,zone-a,AA:BB:CC:DD:EE:13,AA:BB:CC:DD:EE:14
11,harvester-11,10.181.0.21,worker,zone-b,AA:BB:CC:DD:EE:15,AA:BB:CC:DD:EE:16
12,harvester-12,10.181.0.22,worker,zone-c,AA:BB:CC:DD:EE:17,AA:BB:CC:DD:EE:18
CSV
)
mkdir -p "$OUT"; rm -f "$OUT"/config-node*.yaml
SANS=" - \"${VIP}\"\n - \"${SAN_DOMAIN}\"\n - \"10.181.0.11\"\n - \"10.181.0.12\"\n - \"10.181.0.13\""
echo "$NODES" | while IFS=, read -r idx host ip role zone mac1 mac2; do
[ -z "${idx:-}" ] && continue
f="$OUT/config-node${idx}.yaml"
if [ "$idx" = "01" ]; then mode="create"; server_url='""';
else mode="join"; server_url="https://${VIP}:443"; fi
{
echo "# 自动生成:Harvester v1.8.2 ${host} (${role}, ${mode})"
echo "scheme_version: 1"
echo "server_url: ${server_url}"
echo "token: ${TOKEN}"
[ "$mode" = "create" ] && echo -e "sans:\n${SANS}"
cat <<EOF
os:
hostname: ${host}
password: "xxx"
ssh_authorized_keys:
- "${SSH_KEY}"
sshd:
sftp: true
disable_password_auth: false
modules: [kvm, vhost_net, nvme]
dns_nameservers: [${DNS}]
ntp_servers: [${NTP1}, ${NTP2}, ${NTP3}]
labels:
topology.kubernetes.io/zone: ${zone}
harvester-role: ${role}
install:
mode: ${mode}
role: ${role}
automatic: true
skipchecks: false
force_efi: true
tty: ttyS0,115200n8
poweroff: false
iso_url: ${ISO_URL}
device: ${SYS_DISK}
data_disk: ${DATA_DISK}
persistent_partition_size: ${PERSIST}
vip: ${VIP}
vip_mode: static
management_interface:
interfaces:
- name: eno1
hwAddr: "${mac1}"
- name: eno2
hwAddr: "${mac2}"
method: static
ip: ${ip}
subnet_mask: ${NETMASK}
gateway: ${GW}
default_route: true
mtu: 1500
vlan_id: 0
bond_options:
mode: ${BOND_MODE}
miimon: 100
mtu: 1500
cluster_pod_cidr: 10.52.0.0/16
cluster_service_cidr: 10.53.0.0/16
cluster_dns: 10.53.0.10
harvester:
storage_class:
replica_count: 3
longhorn:
default_settings:
guaranteedInstanceManagerCPU: 12
storageReservedPercentageForDefaultDisk: 0
addons:
rancher_monitoring:
enabled: true
rancher_logging:
enabled: false
harvester_vm_import_controller:
enabled: false
harvester_pcidevices_controller:
enabled: false
EOF
if [ "$mode" = "create" ]; then
echo 'system_settings:'; echo ' auto-disk-provision-paths: ""'
fi
} > "$f"
echo "生成 $f"
done
# ---- 自检 ----
echo "== 自检 =="
echo "文件数: $(ls "$OUT"/config-node*.yaml | wc -l) (期望 12)"
echo "hostname 唯一: $(grep -h '^ hostname:' "$OUT"/*.yaml | sort -u | wc -l) (期望 12)"
echo "ip 唯一: $(grep -h '^ ip: ' "$OUT"/*.yaml | sort -u | wc -l) (期望 12)"
echo "MAC 唯一: $(grep -h 'hwAddr:' "$OUT"/*.yaml | sort -u | wc -l) (期望 24)"
echo "token 一致: $(grep -h '^token:' "$OUT"/*.yaml | sort -u | wc -l) (期望 1)"
echo "create 数: $(grep -l 'mode: create' "$OUT"/*.yaml | wc -l) (期望 1)"发布到 HTTP 并验证可下载:
bash
bash gen-configs.sh
cp out/config-node*.yaml /srv/harvester/configs/
chmod 600 /srv/harvester/configs/*.yaml # 内含 token 与密码
for i in $(seq -w 1 12); do
curl -sf "http://10.181.0.5:8080/configs/config-node${i}.yaml" >/dev/null \
&& echo "node${i} OK" || echo "node${i} FAIL"
done3.6 装机执行:从 ISO 启动到节点 Ready
3.6.1 单台流程
- 带外 KVM(iDRAC/iLO/IPMI)挂载
harvester-v1.8.2-amd64.iso,或插 U 盘,开机进引导菜单。 - 选中 Harvester Installer,按
e编辑引导项,在linux ...行末尾追加:
text
harvester.install.automatic=true harvester.install.config_url=http://10.181.0.5:8080/configs/config-node01.yamlCtrl-X启动。控制台依次输出:拉取 config → 硬件检查 → 分区格式化 → 写镜像 → 首启 RKE2 → 拉起 Harvester 组件。- 装完自动重启(
poweroff: true时改为关机)。首次启动到 UI 可用约 5–15 分钟。 - 日志位置:安装阶段看控制台与安装环境内
/var/log/console.log;装完看journalctl -u rancherd -f、journalctl -u kubelet -f。
3.6.2 12 台的执行节奏
text
Day1 上午 HTTP 就绪 + 12 份 config 自检通过 + preflight 全绿
Day1 下午 装 node01(create) → 验证 UI/VIP/单节点健康(3.6.3)
Day1 晚 装 node02、03(join, management) → 验证 etcd 3 副本
Day2 上午 装 node04–07(4 台并发)
Day2 下午 装 node08–12(5 台并发)→ 全集群验证 + 加固(3.9)并发 join 建议不超过 5 台:新节点加入会触发证书分发、镜像同步、Longhorn 初始化,并发过高会把 API server/etcd 压出超时。
3.6.3 装完第一台立刻做的 4 个检查
bash
ssh rancher@10.181.0.11 && sudo -i
kubectl get nodes -o wide # 1 个 Ready
kubectl get pods -A | grep -vE 'Running|Completed' # 期望为空
ip -br addr show mgmt-br # 应含 10.181.0.11 与 VIP 10.181.0.100
curl -sk https://10.181.0.100/ping # 期望 200 / pong这四项没过就别装后面 11 台,否则等于把一个错误复制 12 遍。
3.7 变体一:PXE / iPXE 全自动装机
同一份 config,改用 iPXE 脚本引导即可实现「上电即装」。要点:
- 从 ISO 中取出内核与 initrd(Full ISO 里含
rootfs.squashfs),与 ISO 一起放到 HTTP 服务器:
bash
mkdir -p /srv/harvester/pxe && cd /srv/harvester/pxe
mount -o loop /srv/harvester/iso/harvester-v1.8.2-amd64.iso /mnt
cp /mnt/boot/kernel/vmlinuz /mnt/boot/kernel/initrd* /mnt/isolinux/*.efi . 2>/dev/null || \
cp -r /mnt/boot . ; ls -l # 不同版本目录略有差异,按实际路径拷贝
umount /mnt- iPXE 脚本按 MAC 分发不同 config(
boot.ipxe):
ipxe
#!ipxe
set base http://10.181.0.5:8080
set cfg ${base}/configs
kernel ${base}/pxe/vmlinuz initrd=initrd \
root=live:${base}/iso/harvester-v1.8.2-amd64.iso \
harvester.install.automatic=true \
harvester.install.config_url=${cfg}/config-${netX/mac:hexhyp}.yaml \
harvester.install.device=/dev/sda harvester.install.data_disk=/dev/sdb \
console=ttyS0,115200n8
initrd ${base}/pxe/initrd
boot- DHCP 指向 iPXE(option 66/67 或 chainload),并确保装机 DHCP 与 VLAN 91 的 VM DHCP 完全隔离。
- 内网环境记得
install.iso_url指向本地 HTTP(config 里已写),否则装机会尝试联网。
⚠️ PXE 场景强烈建议在
install.device/install.data_disk用/dev/disk/by-id/...或/dev/disk/by-path/...,避免不同机器盘符漂移导致装错盘(这是 PXE 批量装机最危险的一类事故)。
3.8 变体二:U 盘离线装机(无 HTTP 服务)
- 把 ISO 写入 U 盘:
dd if=harvester-v1.8.2-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync。 - U 盘上会有
COS_OEM分区,把config.yaml放进去:
bash
mkdir -p /mnt/oem && mount /dev/sdX3 /mnt/oem # 分区号以 lsblk 实际输出为准
cp config-node01.yaml /mnt/oem/config.yaml
sync && umount /mnt/oem- 从 U 盘启动,安装器会自动读取
COS_OEM分区里的config.yaml(同样建议在内核参数加harvester.install.automatic=true)。 - 12 台机器就要 12 个 U 盘(或每装完一台改一次文件)——所以只在完全隔离网时用这个方式。
3.9 12 节点装完后的全集群验证
在任一 management 节点上执行(ssh rancher@10.181.0.100 走 VIP,或登录任意节点 sudo -i):
bash
# 1) 12 个节点全部 Ready,角色正确
kubectl get nodes -o wide
kubectl get nodes -L node-role.kubernetes.io/management,node-role.kubernetes.io/worker,\
topology.kubernetes.io/zone,harvesterhci.io/vlanconfig
# 2) 所有系统 Pod 正常(输出应为空)
kubectl get pods -A | grep -vE 'Running|Completed'
# 3) etcd 三副本健康
kubectl -n kube-system get pods -l component=etcd -o wide
ETCDCTL_API=3 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
# 4) VIP 由哪个节点持有,UI 是否可达
ip -br addr | grep 10.181.0.100
curl -sk https://10.181.0.100/ping
kubectl -n harvester-system get pods -l component=kube-vip -o wide 2>/dev/null || \
kubectl get pods -A | grep -i vip
# 5) Longhorn 12 个节点全部就绪、默认盘已创建
kubectl -n longhorn-system get pods -o wide | grep -c Running
kubectl get nodes.longhorn.io -o custom-columns=\
'NODE:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,DISK:.status.diskStatus'
# 6) 默认 StorageClass 副本数为 3
kubectl get sc harvester-longhorn -o jsonpath='{.parameters.numberOfReplicas}{"\n"}'
# 7) 集群网段确实是默认值
kubectl -n kube-system get pod -l component=kube-apiserver -o yaml | grep -o 'service-cluster-ip-range=[^"]*'
kubectl get clusternetwork -o wide # 应只有 mgmt
# 8) 时间同步(逐台)
for ip in $(seq 11 22); do
echo -n "10.181.0.$ip "
ssh -o StrictHostKeyChecking=no rancher@10.181.0.$ip 'timedatectl | grep -i synchronized' 2>/dev/null || echo unreachable
doneUI 侧验证(https://10.181.0.100):
- [ ] 首次登录设置 admin 密码(若装机时未通过 config 设置),登录后到 Settings 检查
server-version=v1.8.2。 - [ ] Hosts 页 12 台全部
Active,角色显示 3 management + 9 worker。 - [ ] Networks → Cluster Networks 只有
mgmt,且mgmt状态正常。 - [ ] Settings 里
auto-disk-provision-paths、backup-target、storage-network、overcommit-config等值符合预期。 - [ ] Addons 页
rancher-monitoring为 Enabled(装机时 config 里开的)。
3.10 装机后加固(建议当天完成)
3.10.1 关闭 SSH 密码登录(官方推荐做法)
Harvester 宿主机默认开启 SSH 密码登录,便于装机排障;装完应关闭。官方方式是通过 CloudInit CRD 下发(不是手改 /etc/ssh/sshd_config,因为宿主机 OS 是不可变的,手改重启即失效):
bash
cat <<EOF | kubectl apply -f -
apiVersion: node.harvesterhci.io/v1beta1
kind: CloudInit
metadata:
name: ssh-config
spec:
matchSelector:
harvesterhci.io/managed: "true" # 作用于所有 Harvester 节点
filename: 99-ssh-config
contents: |
stages:
network:
- name: "disable password login"
commands:
- sed -i -E 's/^#?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
- sed -i -E 's/^#?ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
- sed -i -E 's/^#?UsePAM .*/UsePAM no/' /etc/ssh/sshd_config
- systemctl restart sshd
paused: false
EOF⚠️ 两个前提:① 每台节点都已通过
os.ssh_authorized_keys注入了你的公钥;② 所有受影响节点需要重启,CloudInit 配置才生效。请逐台重启(配合 7.4 的维护流程),不要 12 台一起重启。
验证:
bash
ssh -o PreferredAuthentications=password rancher@10.181.0.11
# 期望:Permission denied (publickey,keyboard-interactive).
ssh rancher@10.181.0.11 'echo key login OK' # 期望成功3.10.2 其余加固动作清单
| 动作 | 怎么做 | 章节 |
|---|---|---|
| 设置备份目标 | UI → Settings → backup-target(S3/NFS),并验证 backup-target 状态健康 | 7.5 |
| 开启日志 | Addons → rancher-logging(需要时) | 7.7 |
| 配置 VM 超卖上限 | Settings → overcommit-config | 1.6 |
| 开启自动均衡 | 安装并启用 virtual-machine-auto-balance addon | 7.6 |
| 配置告警 | Monitoring 的 AlertManagerConfig / 对接外部告警 | 7.7 |
| 收紧 UI 访问 | 通过 Rancher 集成或反向代理限制来源 IP;轮换 admin 密码 | 7.9 |
| 关闭不需要的 addon | harvester-seeder、pcidevices-controller 等按需 | 7.9 |
| 留存基线 | appendix/ops/cluster-snapshot.sh 跑一次并归档 | 7.8.2 |
| 建立 VM 网络 | 下一章的主角:ClusterNetwork cn-vm + VlanConfig + net-91 | 04 |
3.11 常见装机失败与处理
| 现象 | 根因 | 处理 |
|---|---|---|
| 安装器停在 "Fetching config" 或用了默认配置 | config_url 不可达 / 拼错 / HTTP 403 | 在安装环境里 curl -v <config_url>;检查防火墙与路径 |
| 装完 hostname 是随机串 | TUI 里填过 hostname(覆盖 config),或 os.hostname 拼错 | 重装或 hostnamectl + 检查 config 优先级 |
| 提示硬件不满足生产要求后中止 | skipchecks: false 且内存/CPU/磁盘不达标 | 补硬件,或临时 skipchecks: true(明确知道风险) |
| 找不到网卡 / 拿不到 IP | interfaces[].name 在目标机不存在 | 用 hwAddr 匹配;或先在 LiveCD 里 ip -br link 核对名字 |
| VIP 无法访问,UI 打不开 | VIP 冲突 / 交换机不允许 gratuitous ARP / bond 未 up | arping -D 10.181.0.100 查冲突;看 ip -br addr show mgmt-br |
| join 一直转圈最后失败 | token 不一致 / server_url 写成节点 IP / 时间不同步 | 核对 token;server_url 必须 https://VIP:443;配 NTP |
| 第 4 台起 join 失败,etcd 报错 | 3 台 management 已满,仍指定 role: management | worker 节点写 role: worker(或不写 role 走默认) |
| Longhorn 节点没有默认盘 | data_disk 写错/与 device 相同/盘上有旧分区 | 核对盘符;必要时 install.wipe_disks_list 清理后重装 |
| 装完 Pod 一直 ImagePullBackOff | 用了 Net install ISO 但内网无法联网 | 换 Full ISO;或配 system_settings.containerd-registry 私有仓库 |
| 串口看不到输出 | 未设 install.tty 或 BIOS 串口重定向未开 | config 加 tty: ttyS0,115200n8,BIOS 开 console redirection |
通用原则:装机是破坏性且幂等的操作。排障超过 20 分钟,直接重装比重装更快——尤其是 12 台批量场景,重装还能顺便验证你的 config 是否真的可复现。
3.12 本章验证清单
- [ ] 12 份 config 由脚本生成并通过自检(hostname/IP/MAC 唯一、token 一致、create 仅 1 台)。
- [ ] node01 装完即通过 3.6.3 的 4 项检查,再装其余节点。
- [ ] 12 节点全部 Ready,角色为 3 management + 9 worker。
- [ ] etcd 3 副本健康;VIP
10.181.0.100可访问 UI;server-version= v1.8.2。 - [ ] Longhorn 12 节点就绪,默认 StorageClass
harvester-longhorn副本数 3。 - [ ] Pod/Service CIDR 仍为
10.52.0.0/16/10.53.0.0/16(未被误改)。 - [ ] 公钥登录可用后,已下发
CloudInit/ssh-config并逐台重启验证密码登录被拒。 - [ ] 备份目标已配置且状态健康。
- [ ] 基线快照(nodes / network / settings)已归档。
下一章:在 eno3/eno4 上建 ClusterNetwork cn-vm,把 10.181.91.0/24(VLAN 91)变成 VM 可选的网络。