OpenShift 4.18 UPI on Proxmox — 单桥部署指南

在 Proxmox VE 上以 UPI + platform: none 安装 OpenShift 4.18:单网桥、单网段方案,所有 VM 仅连接 vmbr010.10.10.0/24),无需 NAT 或第二块网桥。

前置条件: PVE 与 Ceph 已按 上一篇 PVE 部署指南 完成。

本文采用单桥单网段方案;所有 VM 单网卡连接 vmbr0

1. 概述与规划

UPI + platform: none

项目
集群名 lab-ocp
baseDomain example.com(须与 DNS 规划一致)
API https://api.lab-ocp.example.com:6443
PVE root@10.10.10.41ssh -i deploy-key.pem root@10.10.10.41
Helper VM(§6 安装,§7 配置) 10.10.10.42(VMID 100);ssh -i deploy-key.pem root@10.10.10.42
OCP 节点(Bootstrap/Master/Worker) 10.10.10.43.51(单网卡 vmbr0);ssh -i deploy-key.pem core@10.10.10.$((VMID-67))
企业代理 http://10.10.10.254:8080(CONNECT 隧道,无需额外 CA)
NTP(chrony) 10.10.10.20110.10.10.20210.10.10.203(UDP/123,见 §2.5、§12.7;替换为内网 NTP)

节点引导方式:

角色 引导方式
Bootstrap(110) 全程 Live PXEbootstrap-complete 后删除;不需要 coreos-installer
Master / Worker(111–118) Live PXE 仅作安装介质coreos-installer 写入磁盘 → PVE 改 boot order 从磁盘启动(§10、§11)

Live 环境下存在 /run/ostree-live,MCO firstboot 无法完成 rpm-ostree 写盘,crio/kubelet 起不来。不要让 Master/Worker 长期停在 Live 上运行。

2. 阶段 0 — 前置检查

2.1 硬件与 PVE 状态

1
2
# 本地执行,使用 deploy-key.pem
ssh -i deploy-key.pem root@10.10.10.41 "pveversion; pvesm status; ceph -s"
  • PVE 版本为 9.2.4
  • vm-storage 可用容量 ≥ 1.3 TB
  • Ceph HEALTH_OKocp-pool 存在
  • 物理机内存 ≥ 256 GB(本方案合计约 240 GB VM 内存 + PVE 开销)

2.2 网络连通性

1
ssh -i deploy-key.pem root@10.10.10.41 "curl -x http://10.10.10.254:8080 -I https://access.redhat.com"
  • 企业代理可用
  • 可访问 Red Hat 官网

2.5 NTP 可达性

RHCOS 默认 chrony 使用公网 NTP pool,隔离/企业网络中通常不可达,会导致 chrony-wait.service 长期阻塞 kubelet 启动。安装前确认节点能访问内网 NTP:

服务器 地址(示例)
a) 10.10.10.201
b) 10.10.10.202
c) 10.10.10.203

替换为你环境中实际可达的内网 NTP 服务器地址(下文 §12.7 须同步修改)。

1
2
3
4
# Helper 上执行(UDP/123;需已安装 chrony)
for s in 10.10.10.201 10.10.10.202 10.10.10.203; do
chronyd -q "server ${s} iburst" && echo "${s}: sync OK" || echo "${s}: FAIL"
done
1
2
3
4
# 任意 OCP 节点上执行(Live 或已装盘的 Master/Worker 均可)
for s in 10.10.10.201 10.10.10.202 10.10.10.203; do
timeout 2 bash -c "echo >/dev/udp/${s}/123" 2>/dev/null && echo "${s}: UDP/123 OK" || echo "${s}: UDP/123 FAIL"
done
  • 至少一台 NTP 可从 10.10.10.0/24 网段访问(路由/防火墙放行 UDP 123)
  • 若全部 FAIL,先联系网络团队放通,不要长期依赖 systemctl disable chrony-wait 绕过

2.3 Red Hat 账号

1
2
3
4
5
6
7
# 工作站上执行 — 导出公钥(安装 Helper、填写 install-config 时使用)
ssh-keygen -y -f deploy-key.pem > deploy-key.pub
cat deploy-key.pub

# 验证密钥登录
ssh -i deploy-key.pem root@10.10.10.41 hostname # PVE
ssh -i deploy-key.pem root@10.10.10.42 hostname # Helper(§6 装完后)

2.4 验收

1
2
3
4
5
6
7
8
9
10
# 工作站上执行
ssh -i deploy-key.pem root@10.10.10.41 "
echo '--- PVE ---'
pveversion | head -1
pvesm status | grep vm-storage
ceph -s | grep HEALTH
curl -sS -o /dev/null -w 'corp-proxy: %{http_code}\n' -x http://10.10.10.254:8080 --connect-timeout 10 -I https://access.redhat.com
"
test -f deploy-key.pem && ssh-keygen -y -f deploy-key.pem >/dev/null && echo 'sshKey: OK'
test -f pull-secret.txt 2>/dev/null && echo 'pull-secret: OK' || echo 'pull-secret: 待下载'

预期输出:

1
2
3
4
5
6
pve-manager/9.2.4/...          # PVE 版本
vm-storage ... active # 存储池可用
HEALTH_OK # Ceph 健康
corp-proxy: 200 # 或 301/302,非 000/超时
sshKey: OK
pull-secret: OK # 或「待下载」
  • PVE / Ceph / 企业代理 / SSH 密钥 / Pull Secret 均就绪

3. 阶段 1 — 下载软件

可访问 Red Hat 官网的工作站上执行。所有文件版本须与 OCP 4.18.19 一致。

3.1 下载

文件 来源
openshift-install-linux-4.18.19.tar.gz Mirror 或 Portal OCP 4.18.19
openshift-client-linux-4.18.19.tar.gz Mirror
rhel-9.8-x86_64-boot.iso Portal RHEL 9.8 Boot ISO
RHCOS kernel/initrd/rootfs openshift-install coreos print-stream-json 输出 URL(须与 4.18.19 绑定)
Pull Secret console.redhat.com/openshift/install/pull-secret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 工作站上执行(Linux)
OCP_VER=4.18.19
BASE=https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/${OCP_VER}
mkdir -p ocp-artifacts && cd ocp-artifacts
curl -LO ${BASE}/openshift-install-linux-${OCP_VER}.tar.gz
curl -LO ${BASE}/openshift-client-linux-${OCP_VER}.tar.gz
tar xzf openshift-install-linux-${OCP_VER}.tar.gz openshift-install && chmod +x openshift-install

# 查询安装器绑定的 RHCOS PXE 三件套 URL(须同一次下载,版本一致)
./openshift-install coreos print-stream-json > rhcos-stream.json
KERNEL_URL=$(python3 -c 'import json; d=json.load(open("rhcos-stream.json")); p=d["architectures"]["x86_64"]["artifacts"]["metal"]["formats"]["pxe"]; print(p["kernel"]["location"])')
INITRD_URL=$(python3 -c 'import json; d=json.load(open("rhcos-stream.json")); p=d["architectures"]["x86_64"]["artifacts"]["metal"]["formats"]["pxe"]; print(p["initramfs"]["location"])')
ROOTFS_URL=$(python3 -c 'import json; d=json.load(open("rhcos-stream.json")); p=d["architectures"]["x86_64"]["artifacts"]["metal"]["formats"]["pxe"]; print(p["rootfs"]["location"])')
echo "KERNEL_URL=$KERNEL_URL"
echo "ROOTFS_URL=$ROOTFS_URL"

curl -L -o rhcos-live-kernel-x86_64 "$KERNEL_URL"
curl -L -o rhcos-live-initramfs.x86_64.img "$INITRD_URL"
curl -L -o rhcos-live-rootfs.x86_64.img "$ROOTFS_URL"

3.2 上传到 PVE

1
2
3
4
5
# 工作站上执行(替换为实际路径)
ISO_DIR="/var/lib/vz/template/iso"
scp -i deploy-key.pem rhel-9.8-x86_64-boot.iso \
rhcos-live-kernel-x86_64 rhcos-live-initramfs.x86_64.img rhcos-live-rootfs.x86_64.img \
root@10.10.10.41:${ISO_DIR}/
  • 验证: ssh -i deploy-key.pem root@10.10.10.41 "ls -lh ${ISO_DIR}/rhcos* ${ISO_DIR}/rhel*"

3.3 验收(进入阶段 2 前)

1
2
3
4
5
6
7
# 工作站上执行
ssh -i deploy-key.pem root@10.10.10.41 '
ISO=/var/lib/vz/template/iso
for f in rhcos-live-kernel-x86_64 rhcos-live-initramfs.x86_64.img rhcos-live-rootfs.x86_64.img rhel-9.8-x86_64-boot.iso; do
test -f $ISO/$f && echo "OK $f" || echo "MISSING $f"
done
'

预期输出:

1
2
3
4
OK rhcos-live-kernel-x86_64
OK rhcos-live-initramfs.x86_64.img
OK rhcos-live-rootfs.x86_64.img
OK rhel-9.8-x86_64-boot.iso
  • RHEL ISO、RHCOS kernel/initrd/rootfs 已上传到 PVE

4. 阶段 2 — PVE 准备

PVE 安装与 Ceph 存储A1_HP_DL380部署PVE.md(已完成可跳过)。本节仅确认 vmbr0 可用并 上传 ISO(本方案不需要 vmbr1 / NAT)。

4.1 网段规划(CIDR)

CIDR 类型 配置位置 用途
10.10.10.0/24 Machine Network install-config.yaml 所有 VM 单网卡 vmbr0;网关 10.10.10.1;HAProxy 后端
172.28.0.0/16 + hostPrefix: 23 Cluster Network install-config.yaml Pod Overlay(OVN 分配)
172.29.0.0/16 Service Network install-config.yaml Kubernetes Service ClusterIP

网卡分工(所有 VM 单网卡):

组件 接口 桥接 地址示例 网关 用途
PVE vmbr0 10.10.10.41/24 10.10.10.1 宿主机管理
Helper / OCP 节点 ens18 vmbr0 10.10.10.42/24(Helper) 10.10.10.1 DNS、HAProxy、API 后端、节点 node-ip;出站经企业代理

IP 分配(10.10.10.0/24):

末段 用途
.41 PVE
.42 Helper(VM 100)
.43 Bootstrap(VM 110,临时)
.44.46 Master(VM 111–113)
.47.51 Worker(VM 114–118)
.52.62 预留

IP 公式(OCP 节点): 10.10.10.$((VMID - 67))(110→.43,118→.51)。Helper 固定 .42

4.2 确认 vmbr0(无需 vmbr1)

本方案不创建 vmbr1不配置 NAT。所有 VM 仅接入 vmbr0,经网关 10.10.10.1 访问企业代理与外网。

1
2
3
# PVE 上执行
ip -br addr show vmbr0
ip route | grep default
  • vmbr010.10.10.41/24 且状态 UP;默认路由 via 10.10.10.1

4.3 PVE 防火墙放行(如已启用)

确认内网 VM 所需端口不被 PVE 防火墙阻断(按实际策略调整)。

4.4 验收(进入阶段 3 前)

1
2
3
# PVE 上执行
echo '--- vmbr0 ---' && ip -br addr show vmbr0
echo '--- ISO ---' && ls -lh /var/lib/vz/template/iso/rhcos-live-kernel-x86_64

预期输出:

1
2
3
4
5
--- vmbr0 ---
vmbr0 UP 10.10.10.41/24 ...

--- ISO ---
-rw-r--r-- 1 root root ... /var/lib/vz/template/iso/rhcos-live-kernel-x86_64
  • vmbr0 UP;ISO 文件在位

5. 阶段 3 — 创建 VM

PVE 规则: VMID 必须 ≥ 100。OCP 节点 IP 公式 10.10.10.$((VMID-67))(110→.43)。所有 VM 单网卡 net0vmbr0

5.1 IP 与 VMID 分配

VMID IP 主机名 角色
10.10.10.41 pve-host-1 PVE Host
100 10.10.10.42 ocp-helper-node Helper
110 10.10.10.43 ocp-bootstrap Bootstrap(临时)
111 10.10.10.44 master-111 Master
112 10.10.10.45 master-112 Master
113 10.10.10.46 master-113 Master
114 10.10.10.47 worker-114 Worker
115 10.10.10.48 worker-115 Worker
116 10.10.10.49 worker-116 Worker
117 10.10.10.50 worker-117 Worker
118 10.10.10.51 worker-118 Worker

5.2 仅重装 Helper(可选)

若 OCP 节点 VM 已存在、只需重建 VM 100,在 PVE 上执行:

1
2
3
4
5
6
# PVE 上执行
qm stop 100 2>/dev/null
qm destroy 100
# destroy 未必删除 thin 卷,确认后手动清理:
lvremove -f /dev/vm-storage/vm-100-disk-0 2>/dev/null || true
ls /dev/vm-storage/vm-100-disk-0 2>/dev/null && echo 'WARN: 旧盘仍在,请先 lvremove' || echo 'OK: 无残留卷'

5.3 创建步骤

VM 100(Helper,空壳): 单网卡 + RHEL ISO + boot order。

1
2
3
4
5
6
7
# Helper 100 — qm create → lvcreate → qm set
qm create 100 --name ocp-helper-node --memory 16384 --cores 4 --cpu host \
--scsihw virtio-scsi-pci --agent enabled=1 --ostype l26 \
--net0 virtio,bridge=vmbr0 \
--ide2 "local:iso/rhel-9.8-x86_64-boot.iso,media=cdrom" --boot 'order=ide2;scsi0'
lvcreate -V 120G -T vm-storage/data -n vm-100-disk-0
qm set 100 --scsi0 vm-storage:vm-100-disk-0,size=120G

OCP 节点(Bootstrap/Master/Worker): 每台 VM 三步创建,单网卡 vmbr0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 通用模式:qm create → lvcreate → qm set

# Bootstrap 110
qm create 110 --name ocp-bootstrap --memory 16384 --cores 8 --cpu host \
--scsihw virtio-scsi-pci --agent enabled=0 --ostype l26 \
--net0 virtio,bridge=vmbr0 --boot order=net0
lvcreate -V 100G -T vm-storage/data -n vm-110-disk-0
qm set 110 --scsi0 vm-storage:vm-110-disk-0,size=100G

# Master 111-113(80G 磁盘)
for i in 111 112 113; do
qm create $i --name master-$i --memory 16384 --cores 8 --cpu host \
--scsihw virtio-scsi-pci --agent enabled=0 --ostype l26 \
--net0 virtio,bridge=vmbr0 --boot order=net0
lvcreate -V 80G -T vm-storage/data -n vm-$i-disk-0
qm set $i --scsi0 vm-storage:vm-$i-disk-0,size=80G
done

# Worker 114-118(160G 磁盘)
for i in 114 115 116 117 118; do
qm create $i --name worker-$i --memory 32768 --cores 16 --cpu host \
--scsihw virtio-scsi-pci --agent enabled=0 --ostype l26 \
--net0 virtio,bridge=vmbr0 --boot order=net0
lvcreate -V 160G -T vm-storage/data -n vm-$i-disk-0
qm set $i --scsi0 vm-storage:vm-$i-disk-0,size=160G
done

5.4 验收(进入阶段 4 前)

1
2
3
4
5
6
# PVE 上执行
qm list
echo '--- VM 100 nets ---' && qm config 100 | grep ^net
echo '--- VM 111 nets ---' && qm config 111 | grep ^net
echo '--- VM 110 nets ---' && qm config 110 | grep ^net
echo '--- count ---' && qm list | grep -cE '100|110|111|112|113|114|115|116|117|118'

预期输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      VMID NAME                 STATUS     MEM(MB)    BOOTDISK(GB) PID
100 ocp-helper-node stopped 16384 120.00 0
110 ocp-bootstrap stopped 16384 100.00 0
111 master-111 stopped 16384 80.00 0
...(112–118 同理,均为 stopped)

--- VM 100 nets ---
net0: virtio=...,bridge=vmbr0

--- VM 111 nets ---
net0: virtio=...,bridge=vmbr0

--- VM 110 nets ---
net0: virtio=...,bridge=vmbr0

--- count ---
10
  • 10 台 VM 存在;均为单网卡 vmbr0;均为 stopped

6. 阶段 4 — 安装 Helper

6.1 启动并安装

1
2
qm start 100
# PVE Web UI → VM 100 → Console

6.2 安装参数

阶段 操作
Boot 菜单 直接选择安装,进入到图形化配置界面。
Network ens1810.10.10.42/24,网关 10.10.10.1,DNS 10.10.10.3
Hostname ocp-helper-node.lab-ocp.example.com(或短名 ocp-helper-node
Software 登录redhat账号的时候,需要配置代理,注册账号成功后,需要等很长一段时间,installation source的cdn才会变成可操作按钮,然后选择Minimal Install。
Root SSH key 栏粘贴 deploy-key.pub可不设置 root 密码

6.3 弹出安装 ISO

1
2
3
# PVE 上执行
qm set 100 --ide2 none
qm stop 100 && qm start 100 # Guest Agent 可能不可用,stop/start 更可靠
  • 验证: ssh -i deploy-key.pem root@10.10.10.42 "ip -4 addr show ens18"

6.4 SSH 公钥

本方案统一使用 deploy-key.pem / deploy-key.pubPVE、Helper、OCP 节点均通过公钥登录。工作站上:

1
2
3
ssh -i deploy-key.pem root@10.10.10.41   # PVE
ssh -i deploy-key.pem root@10.10.10.42 # Helper
ssh -i deploy-key.pem core@10.10.10.44 # OCP 节点示例(Master 111)

Helper 安装时: 在 RHEL 安装界面 Root Password 步骤的 SSH key 栏,粘贴 deploy-key.pub 全文即可。PVE 上的公钥应在部署前已配置完成。

  • 验证: 上述两条 ssh 均可无交互登录

6.5 注册 Red Hat(需代理)

1
2
3
4
# Helper 上执行
subscription-manager config --server.proxy_hostname=10.10.10.254 --server.proxy_port=8080
subscription-manager register --username=<REDHAT_USER> --password='<REDHAT_PASS>'
subscription-manager attach --auto
  • 验证: subscription-manager status → Overall Status: Current

6.6 验收

1
2
3
4
5
6
7
# 工作站上执行
ssh -i deploy-key.pem root@10.10.10.42 "
echo '--- IPs ---'
ip -4 addr show ens18 | grep inet
echo '--- RH sub ---'
subscription-manager status 2>/dev/null | grep -E 'Overall|Status'
"

预期输出:

1
2
3
4
5
--- IPs ---
inet 10.10.10.42/24 ...

--- RH sub ---
Overall Status: Current
  • ens18=.42/24;订阅有效;ssh -i deploy-key.pem root@10.10.10.42 可登录

7. 阶段 5 — 配置 Helper

7.0 配置上游 DNS(可选)

若组织内有集中 DNS,将 api.lab-ocp.example.comapi-int.lab-ocp.example.com*.apps.lab-ocp.example.com 解析到 Helper(10.10.10.42)即可。
实验环境可仅依赖 Helper 上的 BIND(§7.4),工作站将 DNS 指向 10.10.10.42 或配置 dig @10.10.10.42

7.1 安装软件包

1
ssh -i deploy-key.pem root@10.10.10.42
1
2
3
4
5
6
7
8
9
# Helper 上执行
export http_proxy=http://10.10.10.254:8080
export https_proxy=http://10.10.10.254:8080

dnf install -y bind bind-utils haproxy nginx firewalld jq policycoreutils-python-utils

unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY

mkdir -p /var/www/html/{rhcos,ignition}

7.2 上传 OCP 工具与 RHCOS 文件

1
2
3
4
5
6
# 工作站上执行
scp -i deploy-key.pem openshift-install-linux-4.18.19.tar.gz openshift-client-linux-4.18.19.tar.gz \
root@10.10.10.42:/root/

scp -i deploy-key.pem rhcos-live-kernel-x86_64 rhcos-live-initramfs.x86_64.img rhcos-live-rootfs.x86_64.img \
root@10.10.10.42:/var/www/html/rhcos/
1
2
3
4
# Helper 上执行 — scp 来的文件默认无 httpd 可读 SELinux 标签,须修复(否则 Nginx autoindex 为空、访问 403)
restorecon -Rv /var/www/html
chmod 755 /var/www/html /var/www/html/rhcos /var/www/html/ignition
ls -laZ /var/www/html/rhcos/ # 应见 httpd_sys_content_t
1
2
3
4
5
6
# Helper 上执行
cd /root
tar xzf openshift-install-linux-4.18.19.tar.gz -C /usr/local/bin/
tar xzf openshift-client-linux-4.18.19.tar.gz -C /usr/local/bin/
chmod +x /usr/local/bin/openshift-install /usr/local/bin/oc
oc version

7.3 防火墙

1
2
3
4
5
6
7
8
9
10
11
# Helper 上执行
systemctl enable --now firewalld

firewall-cmd --permanent --add-service=dns
firewall-cmd --permanent --add-port=6443/tcp
firewall-cmd --permanent --add-port=22623/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
firewall-cmd --list-all
  • 验证(仅防火墙): firewall-cmd --list-ports8080/tcp6443/tcp22623/tcpfirewall-cmd --list-servicesdns

7.4 配置 BIND

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# Helper 上执行

# 1. named.conf
cat > /etc/named.conf << 'EOF'
options {
listen-on port 53 { 127.0.0.1; 10.10.10.42; };
directory "/var/named";
allow-query { any; };
allow-query-cache { any; };
recursion yes;
forwarders { 10.10.10.3; };
forward first;
dnssec-enable no;
dnssec-validation no;
};

zone "lab-ocp.example.com" IN {
type master;
file "lab-ocp.example.com.zone";
};

zone "lab.local" IN {
type master;
file "lab.local.zone";
};

zone "10.10.10.in-addr.arpa" IN {
type master;
file "10.10.10.zone";
};
EOF

# 2. OCP 集群域名 zone
cat > /var/named/lab-ocp.example.com.zone << 'EOF'
$TTL 300
@ IN SOA ocp-helper-node. admin.lab-ocp.example.com. (
2026071801 ; serial
3600 ; refresh
600 ; retry
86400 ; expire
300 ) ; minimum

IN NS ocp-helper-node.

ocp-helper-node IN A 10.10.10.42

api IN A 10.10.10.42
api-int IN A 10.10.10.42
*.apps IN A 10.10.10.42
EOF

# 3. lab.local 正向 zone(内部主机名解析)
cat > /var/named/lab.local.zone << 'EOF'
$TTL 1h
@ IN SOA ocp-helper-node.lab.local. admin.lab.local. (
2026071802 ; serial
1h ; refresh
30m ; retry
7d ; expire
5m ; minimum
)

@ IN NS ocp-helper-node.lab.local.

ocp-helper-node IN A 10.10.10.42
helper-node IN CNAME ocp-helper-node

ocp-bootstrap IN A 10.10.10.43
bootstrap IN CNAME ocp-bootstrap

master-111 IN A 10.10.10.44
master-111 IN CNAME master-111
master-112 IN A 10.10.10.45
master-112 IN CNAME master-112
master-113 IN A 10.10.10.46
master-113 IN CNAME master-113

worker-114 IN A 10.10.10.47
worker-114 IN CNAME worker-114
worker-115 IN A 10.10.10.48
worker-115 IN CNAME worker-115
worker-116 IN A 10.10.10.49
worker-116 IN CNAME worker-116
worker-117 IN A 10.10.10.50
worker-117 IN CNAME worker-117
worker-118 IN A 10.10.10.51
worker-118 IN CNAME worker-118

api IN A 10.10.10.42
api-int IN A 10.10.10.42
*.apps IN A 10.10.10.42
EOF

# 4. 反向解析 zone(IP → 主机名)
# 注意:zone 名是 IP 倒序!10.10.10.0/24 → 10.10.10.in-addr.arpa
cat > /var/named/10.10.10.zone << 'EOF'
$TTL 1h
@ IN SOA ocp-helper-node.lab.local. admin.lab.local. (
2026071802 ; serial
1h ; refresh
30m ; retry
7d ; expire
5m ; minimum
)

@ IN NS ocp-helper-node.lab.local.

42 IN PTR ocp-helper-node.lab.local.
43 IN PTR ocp-bootstrap.lab.local.
44 IN PTR master-111.lab.local.
45 IN PTR master-112.lab.local.
46 IN PTR master-113.lab.local.
47 IN PTR worker-114.lab.local.
48 IN PTR worker-115.lab.local.
49 IN PTR worker-116.lab.local.
50 IN PTR worker-117.lab.local.
51 IN PTR worker-118.lab.local.
EOF

# 5. 设置权限并重载
chown named:named /var/named/lab-ocp.example.com.zone /var/named/lab.local.zone /var/named/10.10.10.zone
restorecon -v /var/named/*.zone 2>/dev/null || true
named-checkconf /etc/named.conf
named-checkzone lab-ocp.example.com /var/named/lab-ocp.example.com.zone
named-checkzone lab.local /var/named/lab.local.zone
named-checkzone 10.10.10.in-addr.arpa /var/named/10.10.10.zone
systemctl enable --now named
  • 验证正向: dig api.lab-ocp.example.com @10.10.10.42 +short10.10.10.42
  • 验证正向: dig master-111.lab.local @10.10.10.42 +short10.10.10.44
  • 验证反向: dig -x 10.10.10.44 @10.10.10.42 +shortmaster-111.lab.local.
  • 验证反向: dig -x 10.10.10.47 @10.10.10.42 +shortworker-114.lab.local.

7.5 配置 Nginx(Ignition + RHCOS HTTP 服务)

端口分工: HAProxy(§7.6)占用 80/443(Ingress);Nginx 仅监听 10.10.10.42:8080(RHCOS/ignition)。须先于 HAProxy 配置,释放默认 :80。RHEL 9 的默认 listen 80 写在 /etc/nginx/nginx.conf 主配置里(不是 conf.d/default.conf),须注释或删除该 server 块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Helper 上执行 — 禁用 nginx.conf 内默认 :80 server 块
cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

awk '
/^[[:space:]]*server[[:space:]]*\{/ { capture=1; buf=$0 ORS; depth=1; next }
capture {
buf=buf $0 ORS
depth+=gsub(/\{/,"&",$0)-gsub(/\}/,"&",$0)
if (depth<=0) {
if (buf ~ /listen[[:space:]]+80;/) {
print " # default :80 server removed (HAProxy uses 80/443)"
} else {
printf "%s", buf
}
capture=0; buf=""
}
next
}
{ print }
' /etc/nginx/nginx.conf.bak > /etc/nginx/nginx.conf

grep -q 'listen[[:space:]]*80;' /etc/nginx/nginx.conf && \
echo 'WARN: default :80 server block may still exist in nginx.conf'

# conf.d 里若有 default.conf 也一并禁用
mv -f /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak 2>/dev/null || true

nginx -t # 应 syntax is ok

# SELinux:Nginx 绑定 8080 须加入 http_port_t(若 start 报 Permission denied)
semanage port -a -t http_port_t -p tcp 8080 2>/dev/null || semanage port -m -t http_port_t -p tcp 8080

cat > /etc/nginx/conf.d/ocp-bootstrap.conf << 'EOF'
server {
listen 10.10.10.42:8080;
root /var/www/html;

location /rhcos/ {
autoindex on;
}
location /ignition/ {
autoindex on;
types { application/json ign; }
default_type application/json;
}
}
EOF

systemctl enable --now nginx

# RHCOS/ignition 文件若已上传,确保 SELinux 标签正确(见 §7.2)
restorecon -Rv /var/www/html
chmod 755 /var/www/html /var/www/html/rhcos /var/www/html/ignition
  • 验证: nginx -t 通过;ss -tlnp | grep nginx → 仅 10.10.10.42:8080不应0.0.0.0:80
  • 验证: curl -I http://10.10.10.42:8080/rhcos/rhcos-live-rootfs.x86_64.img → HTTP 200(需 §7.2 已上传 RHCOS 文件;若 403 且目录为空,执行 restorecon -Rv /var/www/html
  • 验证: 从 PVE:curl -s -o /dev/null -w '%{http_code}' http://10.10.10.42:8080/rhcos/200

7.6 配置 HAProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
stats socket /var/lib/haproxy/admin.sock mode 600 level admin
user haproxy
group haproxy
maxconn 50000

defaults
mode tcp
log global
option dontlognull
timeout connect 5s
timeout client 300s
timeout server 300s

frontend api_frontend
bind *:6443
default_backend api_backend

backend api_backend
balance roundrobin
server ocp-bootstrap 10.10.10.43:6443 check
server master111 10.10.10.44:6443 check
server master112 10.10.10.45:6443 check
server master113 10.10.10.46:6443 check

frontend mcs_frontend
bind *:22623
default_backend mcs_backend

backend mcs_backend
balance roundrobin
server ocp-bootstrap 10.10.10.43:22623 check
server master111 10.10.10.44:22623 check
server master112 10.10.10.45:22623 check
server master113 10.10.10.46:22623 check

frontend ingress_http
bind *:80
mode http
default_backend ingress_http_backend

frontend ingress_https
bind *:443
mode tcp
default_backend ingress_https_backend

backend ingress_http_backend
balance roundrobin
mode http
server worker114 10.10.10.47:80 check
server worker115 10.10.10.48:80 check
server worker116 10.10.10.49:80 check
server worker117 10.10.10.50:80 check
server worker118 10.10.10.51:80 check

backend ingress_https_backend
balance roundrobin
mode tcp
server worker114 10.10.10.47:443 check
server worker115 10.10.10.48:443 check
server worker116 10.10.10.49:443 check
server worker117 10.10.10.50:443 check
server worker118 10.10.10.51:443 check
EOF

# stats socket 是套接字文件路径,不能 mkdir 成目录;若曾误建须删掉
rm -rf /var/lib/haproxy/stats

# systemd 会同时加载 conf.d/,清空默认片段避免重复 bind
rm -f /etc/haproxy/conf.d/*.cfg 2>/dev/null || true

# 确认 80 未被 nginx 占用(见 §7.5)
ss -tlnp | grep ':80 ' || true

haproxy -c -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ # 与 systemd 校验方式一致

# SELinux:允许 haproxy 绑定 6443/22623 等非标准端口(否则报 Permission denied)
setsebool -P haproxy_connect_any 1

systemctl enable --now haproxy

验证:

1
2
systemctl status haproxy
ss -tlnp | grep -E '6443|22623|80|443'
  • HAProxy active,6443/22623 在监听

7.7 验收

说明: 此阶段 不检查 bootstrap.ign(ignition 在 §8 生成后才存在)。仅验证 Helper 基础服务与 RHCOS HTTP。不部署 registry-proxy。

1
2
3
4
5
6
7
# Helper 上一次性验证
echo "=== DNS ===" && dig api.lab-ocp.example.com @10.10.10.42 +short
echo "=== Nginx RHCOS ===" && curl -s -o /dev/null -w '%{http_code}\n' http://10.10.10.42:8080/rhcos/rhcos-live-rootfs.x86_64.img
echo "=== HAProxy ===" && ss -tlnp | grep -cE '6443|22623'
echo "=== corp proxy ===" && curl -x http://10.10.10.254:8080 -s -o /dev/null -w '%{http_code}\n' https://quay.io/v2/ --max-time 15
echo "=== CLI ===" && openshift-install version && oc version --client
echo "=== Firewall ===" && firewall-cmd --list-all | grep -E '8080|6443'

预期输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
=== DNS ===
10.10.10.42

=== Nginx RHCOS ===
200

=== HAProxy ===
2 # 6443 与 22623 均在监听

=== corp proxy ===
200 # 或 401

=== CLI ===
openshift-install 4.18.19
Client Version: 4.18.19

=== Firewall ===
8080/tcp 6443/tcp 22623/tcp ...
  • DNS → 10.10.10.42;RHCOS rootfs HTTP → 200;HAProxy 监听;企业代理可达 quay.io;openshift-install 版本 4.18.19

8. 阶段 6 — install-config

8.1 编写 install-config.yaml

1
2
# Helper 上执行
mkdir -p /root/ocp-install && cd /root/ocp-install

将 Pull Secret、SSH 公钥(ssh-keygen -y -f deploy-key.pem)填入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
baseDomain: example.com
metadata:
name: lab-ocp
platform:
none: {}
compute:
- name: worker
replicas: 5
controlPlane:
name: master
replicas: 3
networking:
clusterNetwork:
- cidr: 172.28.0.0/16
hostPrefix: 23
serviceNetwork:
- 172.29.0.0/16
machineNetwork:
- cidr: 10.10.10.0/24
networkType: OVNKubernetes
pullSecret: '<从 console.redhat.com 获取的 JSON,单行>'
sshKey: '<deploy-key.pub 内容>'
proxy:
httpProxy: http://10.10.10.254:8080
httpsProxy: http://10.10.10.254:8080
noProxy: .example.com,.lab-ocp.example.com,10.10.10.0/24,172.28.0.0/16,172.29.0.0/16,localhost,127.0.0.1,.svc,.cluster.local

禁止添加 publish: 字段none 平台不支持 Integrated/Internal)。
proxy 指向企业 HTTP 代理 10.10.10.254:8080

8.2 生成 Ignition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd /root/ocp-install
cp install-config.yaml install-config.yaml.bak

# 本步骤需访问 quay.io 拉 release 元数据:仅当前 shell 临时代理
export http_proxy=http://10.10.10.254:8080
export https_proxy=http://10.10.10.254:8080
openshift-install create ignition-configs --log-level=info
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY

# 同时生成管理员凭据(§12.2):
# auth/kubeconfig — oc / openshift-install 使用的集群管理员 kubeconfig
# auth/kubeadmin-password — kubeadmin 初始密码(单行随机字符串)
ls -l auth/kubeconfig auth/kubeadmin-password

cp -v *.ign /var/www/html/ignition/
chmod 644 /var/www/html/ignition/*.ign # 默认 600 时 nginx 用户不可读 → 403
restorecon -Rv /var/www/html/ignition # 须 httpd_sys_content_t,否则 Nginx 403

8.3 验收

1
2
3
4
5
# Helper 上执行
for f in bootstrap master worker; do
curl -s -o /dev/null -w "${f}.ign: %{http_code}\n" http://10.10.10.42:8080/ignition/${f}.ign
done
grep -E 'baseDomain|name:' install-config.yaml.bak

预期输出:

1
2
3
4
5
bootstrap.ign: 200
master.ign: 200
worker.ign: 200
name: lab-ocp
baseDomain: example.com
  • bootstrap.ign / master.ign / worker.ign HTTP 200;API FQDN 为 api.lab-ocp.example.com
  • auth/kubeconfigauth/kubeadmin-password 已生成(权限 640,仅 root 可读)

9. 阶段 7 — Bootstrap

Bootstrap 全程 Live PXEbootstrap-complete 后删除即可,不需要 coreos-installer 写盘。

9.0 启动 Bootstrap

1
2
3
4
5
6
7
8
9
10
11
12
# PVE 上 — 配置 kernel 引导并启动
qm config 110 >/dev/null 2>&1 || { echo 'ERROR: VM 110 不存在,请先完成 §5.3 创建 VM'; exit 1; }

KERNEL="/var/lib/vz/template/iso/rhcos-live-kernel-x86_64"
INITRD="/var/lib/vz/template/iso/rhcos-live-initramfs.x86_64.img"
VMID=110
NODE_IP="10.10.10.$((VMID-67))"
APPEND="coreos.live.rootfs_url=http://10.10.10.42:8080/rhcos/rhcos-live-rootfs.x86_64.img ignition.config.url=http://10.10.10.42:8080/ignition/bootstrap.ign ignition.firstboot ignition.platform.id=qemu rd.neednet=1 nameserver=10.10.10.42 nameserver=10.10.10.3 ip=${NODE_IP}::10.10.10.1:255.255.255.0::ens18:none console=tty0"

qm stop 110 2>/dev/null || true
qm set 110 --args "-kernel ${KERNEL} -initrd ${INITRD} -append \"${APPEND}\""
qm start 110
  • Console 显示 ens18: 10.10.10.43;rootfs 下载无 failed to mount sysroot
  • 启动后 ip route | grep defaultvia 10.10.10.1 dev ens18

9.1 验收

启动后约 5–15 分钟 再验收。通过即可并行启动 Master(§10.1);bootstrap-complete 在 §10.6 等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 工作站上执行
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i deploy-key.pem core@10.10.10.43 '
echo "=== network ==="
ip -4 -br addr show dev ens18
echo "=== default route ==="
ip route | grep default
echo "=== resolv ==="
cat /etc/resolv.conf
echo "=== services ==="
sudo systemctl is-active crio release-image bootkube
echo "=== pods ==="
sudo crictl pods 2>/dev/null | head -5
'

预期输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
=== network ===
ens18 10.10.10.43/24 ...

=== default route ===
default via 10.10.10.1 dev ens18 ...

=== resolv ===
nameserver 10.10.10.42
nameserver 10.10.10.3

=== services ===
active
active
active

=== pods ===
POD ID ... etcd-... # 或 kube-apiserver 等 static pod 开始出现
  • SSH 可达;IP 10.10.10.43;默认路由经 10.10.10.1
  • criorelease-imagebootkube 均为 active
  • crictl pods 已出现 static pod

10. 阶段 8 — Master

与 Bootstrap 的区别: Bootstrap(110)可全程 Live PXE。Master(111–113)不能长期停在 Live 环境——存在 /run/ostree-live 时 MCO firstboot 无法完成 rpm-ostree 写盘,systemctl start crio无输出一直卡住(在等 firstboot)。正确流程:

1
Live PXE 引导(安装介质)→ 验收网络 → coreos-installer 写盘 → PVE 改 boot order → 磁盘启动 → kubelet 加入集群

Master 可与 Bootstrap 并行;镜像从 quay.io 经企业代理拉取(install-config.yamlproxy → 节点 /etc/mco/proxy.env)。

10.1 Live PXE 引导(安装介质)

1
2
3
4
5
6
7
8
9
10
11
12
# PVE 上执行
KERNEL="/var/lib/vz/template/iso/rhcos-live-kernel-x86_64"
INITRD="/var/lib/vz/template/iso/rhcos-live-initramfs.x86_64.img"

for VMID in 111 112 113; do
qm stop "$VMID" 2>/dev/null || true
qm set "$VMID" --delete args 2>/dev/null || true
NODE_IP="10.10.10.$((VMID-67))"
APPEND="coreos.live.rootfs_url=http://10.10.10.42:8080/rhcos/rhcos-live-rootfs.x86_64.img ignition.config.url=http://10.10.10.42:8080/ignition/master.ign ignition.firstboot ignition.platform.id=qemu rd.neednet=1 nameserver=10.10.10.42 nameserver=10.10.10.3 ip=${NODE_IP}::10.10.10.1:255.255.255.0::ens18:none console=tty0"
qm set "$VMID" --args "-kernel ${KERNEL} -initrd ${INITRD} -append \"${APPEND}\""
qm start "$VMID"
done
  • 三台 Console 显示 10.10.10.44.113;rootfs 下载无 failed to mount sysroot

10.2 验收 Live 网络(尚未装盘)

Live 起来后约 5 分钟 再验收。此阶段不要求 kubelet/crioactive

1
2
3
4
5
6
7
8
9
10
11
# 工作站上执行(以 111 为例)
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i deploy-key.pem core@10.10.10.44 '
echo "=== network ==="
ip -4 -br addr show dev ens18
ip route | grep default
cat /etc/resolv.conf
echo "=== live check ==="
test -e /run/ostree-live && echo "Live 环境(预期,继续 §10.3 装盘)"
curl -sf -o /dev/null http://10.10.10.42:8080/ignition/master.ign && echo "master.ign OK"
lsblk /dev/sda
'

预期:

1
2
3
4
ens18  10.10.10.44/24 ...
default via 10.10.10.1 dev ens18 ...
Live 环境(预期,继续 §10.3 装盘)
master.ign OK
  • IP 10.10.10.44;默认路由 10.10.10.1/dev/sda 存在
  • 不要在 Live 上手动 systemctl start crio/kubelet(会卡住或失败)

排障: resolv.conf 缺失 → 检查 APPEND 是否含 nameserver=;Ignition 未拉 master.ign → 检查是否含 ignition.firstboot

10.3 coreos-installer 安装到磁盘

每台 Master 分别 SSH 执行(以 111 为例):

1
ssh -i deploy-key.pem core@10.10.10.44
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Master 111 上执行
VMID=111
NODE_IP="10.10.10.$((VMID-67))"

sudo mkdir -p /etc/NetworkManager/system-connections/

sudo tee /etc/NetworkManager/system-connections/ens18.nmconnection << EOF
[connection]
id=ens18
type=ethernet
interface-name=ens18
autoconnect=true

[ipv4]
method=manual
addresses=${NODE_IP}/24
gateway=10.10.10.1
dns=10.10.10.42;10.10.10.3;
dns-search=lab.local;

[ipv6]
method=disabled
EOF

sudo chmod 600 /etc/NetworkManager/system-connections/ens18.nmconnection

# 绕过企业代理对 rpm-ostree / coreos-installer 的拦截
export NO_PROXY="*"

sudo coreos-installer install \
--ignition-url=http://10.10.10.42:8080/ignition/master.ign \
--insecure-ignition \
--insecure \
--copy-network \
/dev/sda

112 / 113: SSH 至 core@10.10.10.45 / core@10.10.10.46,仅改 VMID

VMID SSH 目标 VMID 变量
111 core@10.10.10.44 111
112 core@10.10.10.45 112
113 core@10.10.10.46 113
现象 处理
connection refused / 代理超时 确认已 export NO_PROXY="*"
No such file: /dev/sda lsblk 确认磁盘设备名
安装较慢 正常,需从网络拉取 RHCOS 镜像
  • 三台均输出 Install complete

10.4 PVE 改 boot order(从磁盘启动)

PVE 的 --args 会让 VM 始终走 Live PXE,绕过磁盘。装盘后必须删除 args 并设 boot order=scsi0

1
2
3
4
5
6
7
8
9
10
11
12
# PVE 上执行
for VMID in 111 112 113; do
qm stop "$VMID"
qm set "$VMID" --delete args --boot order=scsi0
qm start "$VMID"
done

# 验证
for VMID in 111 112 113; do
echo "=== VM $VMID ==="
qm config "$VMID" | grep -E '^args|^boot'
done

预期:args: 行;boot: order=scsi0

10.5 验收 Master 加入集群

磁盘引导后约 15–30 分钟(Ignition → MCS api-int:22623 → 拉镜像 → kubelet)。此时不应存在 /run/ostree-live

1
2
3
4
5
6
# 工作站上执行(以 111 为例)
ssh -i deploy-key.pem core@10.10.10.44 '
test ! -e /run/ostree-live && echo "disk boot OK" || echo "WARN: still live"
systemctl is-active kubelet crio
sudo crictl pods 2>/dev/null | head -5
'
1
2
3
4
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc get nodes -o wide
oc get csr 2>/dev/null | grep -i pending || true

预期:

1
2
3
4
disk boot OK
active
active
(static pod 列表)
  • 三台 kubelet/crioactiveoc get nodes 出现 3 台 Master(INTERNAL-IP10.10.10.4x,可能先 NotReady
  • Pending CSR 需批准:oc adm certificate approve <name>
  • x509 证书问题:检查 kubelet kubeconfig 的 CA 链是否包含 kube-apiserver-lb-signer
  • kubelet 长期 inactivechrony-waitactive (waiting) → 先完成 §2.5 NTP 检查,再按 §12.7 下发 chrony MachineConfig;在生产环境默认禁用 chrony-wait

10.6 监控 Bootstrap 完成

1
2
3
# Helper 上执行(阻塞,约 20–40 分钟)
cd /root/ocp-install
openshift-install wait-for bootstrap-complete --dir=/root/ocp-install --log-level=info
  • 输出 It is now safe to remove the bootstrap resources

并行监控(另开终端,Helper 上):

1
2
3
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
watch -n15 'oc get nodes 2>/dev/null; echo; oc get csr 2>/dev/null | grep -i pending || true'
curl -sk https://api.lab-ocp.example.com:6443/healthz
  • API healthz 返回 ok;3 个 Master 逐步 Ready

10.7 bootstrap-complete 后更新 HAProxy

前置: §10.6 bootstrap-complete 已成功。

从 API/MCS 后端删除 Bootstrap(Red Hat 要求):

1
2
3
4
5
6
7
# Helper 上执行
sed -i '/server ocp-bootstrap/d' /etc/haproxy/haproxy.cfg
systemctl reload haproxy
grep bootstrap /etc/haproxy/haproxy.cfg || echo 'bootstrap removed: OK'

export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc get nodes
  • HAProxy 无 ocp-bootstrap 后端;3 个 Master 为 Ready

10.8 验收

1
2
3
4
5
6
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
CLUSTER=lab-ocp
echo "=== API healthz ===" && curl -sk https://api.${CLUSTER}.example.com:6443/healthz
echo "=== nodes ===" && oc get nodes
echo "=== HAProxy ===" && ! grep -q bootstrap /etc/haproxy/haproxy.cfg && echo 'bootstrap removed: OK'

预期输出:

1
2
3
4
5
6
7
8
9
10
=== API healthz ===
ok

=== nodes ===
master-111 Ready control-plane,master ...
master-112 Ready control-plane,master ...
master-113 Ready control-plane,master ...

=== HAProxy ===
bootstrap removed: OK
  • bootstrap-complete 已完成;3 Master ReadyINTERNAL-IP10.10.10.4x);HAProxy 无 bootstrap 后端

11. 阶段 9 — Worker

前置: §10.8 已完成(bootstrap-complete、3 Master Ready、HAProxy 已移除 bootstrap 后端)。Worker 与 Master 相同:Live PXE 仅作安装介质 → coreos-installer 写盘 → 磁盘启动(见 §10.3–§10.4)。

11.1 删除 Bootstrap VM

若 §10.7 已执行,本节可跳过,仅确认 VM 110 不存在。

1
2
3
4
# PVE 上执行
qm stop 110 2>/dev/null || true
qm destroy 110
qm list | grep 110 || echo 'OK: VM 110 gone'
  • Bootstrap VM 已删除;HAProxy 无 ocp-bootstrap 后端(§10.7)

11.2 Live PXE 引导 Worker(安装介质)

1
2
3
4
5
6
7
8
9
10
11
12
# PVE 上执行
KERNEL="/var/lib/vz/template/iso/rhcos-live-kernel-x86_64"
INITRD="/var/lib/vz/template/iso/rhcos-live-initramfs.x86_64.img"

for VMID in 114 115 116 117 118; do
qm stop "$VMID" 2>/dev/null || true
qm set "$VMID" --delete args 2>/dev/null || true
NODE_IP="10.10.10.$((VMID-67))"
APPEND="coreos.live.rootfs_url=http://10.10.10.42:8080/rhcos/rhcos-live-rootfs.x86_64.img ignition.config.url=http://10.10.10.42:8080/ignition/worker.ign ignition.firstboot ignition.platform.id=qemu rd.neednet=1 nameserver=10.10.10.42 nameserver=10.10.10.3 ip=${NODE_IP}::10.10.10.1:255.255.255.0::ens18:none console=tty0"
qm set "$VMID" --args "-kernel ${KERNEL} -initrd ${INITRD} -append \"${APPEND}\""
qm start "$VMID"
done
  • 五台 Live 网络正常(10.10.10.47.51

11.3 coreos-installer 安装到磁盘

每台 Worker 分别 SSH 执行。以 114 为例:

1
ssh -i deploy-key.pem core@10.10.10.47
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Worker 114 上执行
VMID=114
NODE_IP="10.10.10.$((VMID-67))"

sudo mkdir -p /etc/NetworkManager/system-connections/

sudo tee /etc/NetworkManager/system-connections/ens18.nmconnection << EOF
[connection]
id=ens18
type=ethernet
interface-name=ens18
autoconnect=true

[ipv4]
method=manual
addresses=${NODE_IP}/24
gateway=10.10.10.1
dns=10.10.10.42;10.10.10.3;
dns-search=lab.local;

[ipv6]
method=disabled
EOF

sudo chmod 600 /etc/NetworkManager/system-connections/ens18.nmconnection

export NO_PROXY="*"

sudo coreos-installer install \
--ignition-url=http://10.10.10.42:8080/ignition/worker.ign \
--insecure-ignition \
--insecure \
--copy-network \
/dev/sda

115–118: SSH 至 core@10.10.10.48.51VMID 改为 115118

VMID SSH 目标
114 core@10.10.10.47
115 core@10.10.10.48
116 core@10.10.10.49
117 core@10.10.10.50
118 core@10.10.10.51
  • 五台均输出 Install complete

11.4 PVE 改 boot order(从磁盘启动)

1
2
3
4
5
6
# PVE 上执行
for VMID in 114 115 116 117 118; do
qm stop "$VMID"
qm set "$VMID" --delete args --boot order=scsi0
qm start "$VMID"
done
  • 五台从磁盘启动;/run/ostree-live 不存在

11.5 批准 Worker CSR

磁盘引导后约 10–20 分钟,Worker 陆续注册;CSR 需人工批准:

1
2
3
4
5
6
7
8
9
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

while true; do
oc get csr -o json 2>/dev/null | jq -r '.items[] | select(.status == {}) | .metadata.name' | \
xargs -r oc adm certificate approve
sleep 10
done
# 五台 Worker 均 Ready 后 Ctrl+C
  • oc get nodes → 8 节点全部 Ready

11.6 验收

1
2
3
4
5
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
echo "=== nodes ===" && oc get nodes -o wide
echo "=== pending CSR ===" && oc get csr 2>/dev/null | grep -i pending || echo 'no pending CSR'
echo "=== bootstrap VM ===" && ssh -i deploy-key.pem root@10.10.10.41 '! qm list | grep -q 110 && echo EXISTS || echo gone'

预期:

1
2
3
4
5
6
7
8
9
=== nodes ===
master-111/112/113 Ready control-plane,master
worker-114–118 Ready worker (共 8 节点)

=== pending CSR ===
no pending CSR

=== bootstrap VM ===
gone
  • 8 节点 Ready;无 Pending CSR;Bootstrap VM 已删除

12. 阶段 10 — 完成安装

前置: §11.6 已完成(8 节点 Ready、无 Pending CSR)。本节在 Helper 上执行 install-complete 并做最终验收。

12.1 等待安装完成

1
2
3
# Helper 上执行(阻塞,约 10–30 分钟)
cd /root/ocp-install
openshift-install wait-for install-complete --dir=/root/ocp-install --log-level=info
  • 输出 Console URL 和 kubeadmin 密码(与 §12.2 文件中密码相同)

12.2 获取 kubeadmin 凭据

openshift-install create ignition-configs(§8.2)执行后即生成管理员凭据,不必等待 install-complete。§12.1 成功时终端会再次打印同一密码。

项目
用户名 kubeadmin(固定)
密码文件 /root/ocp-install/auth/kubeadmin-password
kubeconfig /root/ocp-install/auth/kubeconfig
Web Console https://console-openshift-console.apps.lab-ocp.example.com
API https://api.lab-ocp.example.com:6443
1
2
3
4
5
6
# Helper 上执行
cat /root/ocp-install/auth/kubeadmin-password

# 推荐:直接使用安装生成的 kubeconfig(已含客户端证书,无需 oc login)
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc whoami # 预期: system:admin 或 kube:admin
  • kubeadmin-password 文件存在且为非空单行字符串
  • oc whoami 有输出(system:admin / kube:admin 均可)

TLS 说明: Master pivot 后 API 证书由 kube-apiserver-lb-signer 签发。裸执行 oc login(不带 CA)会报 x509: certificate signed by unknown authority。Helper 上请优先用上面的 KUBECONFIG;若必须用 oc login -u kubeadmin,见 §12.4。

安全提示: kubeadmin 为临时管理员账号;生产环境应创建正式用户或对接 IdP 后停用 kubeadmin。凭据文件须纳入 §12.5 备份并限制访问权限。

12.3 集群健康检查

1
2
3
4
5
6
7
8
9
10
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

oc get nodes -o wide
oc get clusterversion
oc get clusteroperators
oc get co | grep -v -E 'True.*False.*False' || echo "All operators OK"

# 专项检查
oc get co ingress authentication

12.4 控制台访问验证

Helper 上(推荐):

1
2
3
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc whoami
curl -skI https://console-openshift-console.apps.lab-ocp.example.com | head -1

须以 kubeadmin 密码登录时(Helper 或工作站;须能解析 API FQDN):

1
2
3
4
5
6
7
8
9
10
11
# 1. 导出集群 CA 链(含 kube-apiserver-lb-signer;仅需执行一次)
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc get configmap kube-root-ca.crt -n openshift-config-managed \
-o jsonpath='{.data.ca\.crt}' > /root/ocp-install/auth/api-ca-bundle.crt

# 2. 带 CA 登录(勿省略 --certificate-authority)
oc login https://api.lab-ocp.example.com:6443 \
-u kubeadmin \
-p "$(cat /root/ocp-install/auth/kubeadmin-password)" \
--certificate-authority=/root/ocp-install/auth/api-ca-bundle.crt
oc whoami # 预期: kube:admin

工作站登录时,将 api-ca-bundle.crtkubeadmin-password 拷到本机,路径相应修改即可。
在生产环境长期使用 --insecure-skip-tls-verify(仅排障临时可用)。

  • Console 页面可打开(HTTP 200 / 302)
  • oc whoami 有输出

12.5 备份关键文件

auth/ 目录含 kubeconfigkubeadmin-password,丢失后无法从安装目录恢复 kubeadmin 密码。

1
2
3
4
5
6
# Helper 上执行
tar czf /root/ocp-install-backup-$(date +%Y%m%d).tar.gz \
/root/ocp-install/auth/ \
/root/ocp-install/install-config.yaml.bak \
/etc/haproxy/haproxy.cfg \
/etc/named.conf /var/named/lab-ocp.example.com.zone

12.6 验收

1
2
3
4
5
6
7
8
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
CLUSTER=lab-ocp
echo "=== clusterversion ===" && oc get clusterversion
echo "=== nodes ===" && oc get nodes
echo "=== operators ===" && oc get co | grep -v -E 'True.*False.*False' || echo 'all CO OK'
echo "=== API ===" && curl -sk https://api.${CLUSTER}.example.com:6443/healthz
echo "=== console ===" && curl -skI https://console-openshift-console.apps.${CLUSTER}.example.com | head -1

预期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== clusterversion ===
version 4.18.19 True ... Completed True

=== nodes ===
(8 节点均为 Ready)

=== operators ===
all CO OK

=== API ===
ok

=== console ===
HTTP/2 200 # 或 HTTP/1.1 200 / 302
  • ClusterVersion Completed;Console/API 可访问;备份已保存

12.7 配置 NTP 时间同步(chrony)

时机: API 可用后即可执行(§10.5 Master 加入后亦可提前做,避免 Worker 首次启动时 chrony 无法同步)。对已安装完成的集群,在 §12.6 之后执行本节即可。

方式: 通过 MCO 下发 /etc/chrony.conf,覆盖 RHCOS 默认公网 NTP pool,统一指向内网 NTP 服务器。

NTP 服务器(示例,与 §2.5 一致):

服务器 地址
a) 10.10.10.201
b) 10.10.10.202
c) 10.10.10.203

12.7.1 生成并应用 MachineConfig

Helper 上执行(Master / Worker 各一份):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

# 与集群现有 MachineConfig 的 Ignition 版本保持一致
IGN_VER=$(oc get machineconfig 00-worker -o jsonpath='{.spec.config.ignition.version}')
echo "ignition version: ${IGN_VER}"

CHRONY_B64=$(base64 -w0 <<'EOF'
server 10.10.10.201 iburst
server 10.10.10.202 iburst
server 10.10.10.203 iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
EOF
)

for ROLE in master worker; do
cat <<EOF | oc apply -f -
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: ${ROLE}
name: 99-${ROLE}-chrony-configuration
spec:
config:
ignition:
version: ${IGN_VER}
storage:
files:
- contents:
source: data:text/plain;charset=utf-8;base64,${CHRONY_B64}
mode: 420
path: /etc/chrony.conf
overwrite: true
EOF
done
  • oc get machineconfig | grep chrony 出现 99-master-chrony-configuration99-worker-chrony-configuration

12.7.2 等待 MCO 滚动完成

1
2
3
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

watch -n 10 'oc get mcp; echo; oc get machineconfigpool -o custom-columns=NAME:.metadata.name,UPDATED:.status.updated,UPDATING:.status.updating,DEGRADED:.status.degraded'

预期: master / worker 两个 MCP 均为 UPDATED=TrueUPDATING=FalseDEGRADED=False(每台节点滚动重启 chrony,约 10–20 分钟)。滚动期间节点可能出现 SchedulingDisabled,属正常现象。

12.7.3 节点验收

1
2
3
4
5
6
7
8
9
# 工作站上抽查 Master / Worker 各一台
for IP in 10.10.10.44 10.10.10.47; do
echo "=== ${IP} ==="
ssh -i deploy-key.pem core@${IP} '
timedatectl status | grep -E "synchronized|NTP service"
chronyc sources -v | grep -E "^\\^|Name/IP"
chronyc tracking | head -5
'
done

预期:

1
2
3
System clock synchronized: yes
NTP service: active
^* 10.10.10.201 ... Reach 377
1
2
3
4
# Helper 上确认无 chrony 相关告警
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc get nodes
oc get co | grep -i machine
  • timedatectl 显示 synchronized: yes
  • chronyc sources 中内网 NTP 至少一台带 ^*(当前选用源)
  • 全部节点仍为 Ready

12.7.4 排障

现象 处理
MCP 长期 UPDATING=True oc describe mcp worker 看卡住节点;oc get mcd -n openshift-machine-config-operator
chronyc sources 全为 ? 节点到 NTP 的 UDP/123 不通 → 回到 §2.5 查路由/防火墙
已手动 disable chrony-wait NTP 配好后 sudo systemctl enable --now chrony-wait 并 reboot 验证;或保持 disable 仅作临时手段
时间偏差 > 5 分钟 sudo chronyc makestep(节点上);检查 NTP 服务器 stratum 与上游同步状态

13. 阶段 11 — Ceph CSI

前置: §12.6 集群安装已完成;建议 §12.7 NTP 已配置且节点 timedatectlsynchronized: yes。PVE 上已部署 Ceph(ocp-pool,Monitor 在 10.10.10.41)。OCP 通过 Ceph CSI 消费外部 Ceph 存储。

可与核心安装分开执行;若暂不需要动态存储,可跳过本章。

前置检查(任意 Worker 上,须能连通 Ceph Monitor):

1
nc -zv 10.10.10.41 6789

13.1 在 PVE 上创建 Ceph 用户

1
2
3
4
# PVE 上执行
ceph auth get-or-create client.openshift mon 'profile rbd' osd 'profile rbd pool=ocp-pool' \
-o /etc/pve/ceph/openshift.client.openshift.keyring
ceph auth get client.openshift

记录 key 值,以及 Ceph FSID:

1
ceph fsid

13.2 创建 Ceph 密钥 Secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

CEPH_KEY="<client.openshift 的 key>"
FSID="<ceph fsid>"

cat <<EOF | oc apply -f -
apiVersion: v1
kind: Secret
metadata:
name: csi-ceph-secret
namespace: default
stringData:
userID: openshift
userKey: ${CEPH_KEY}
EOF

13.3 安装 Ceph CSI RBD Provisioner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

# 下载与 K8s 1.31 兼容的 ceph-csi 版本(示例 3.13)
curl -LO https://raw.githubusercontent.com/ceph/ceph-csi/v3.13.0/deploy/rbd/kubernetes/csi-provisioner-rbac.yaml
curl -LO https://raw.githubusercontent.com/ceph/ceph-csi/v3.13.0/deploy/rbd/kubernetes/csi-nodeplugin-rbac.yaml
curl -LO https://raw.githubusercontent.com/ceph/ceph-csi/v3.13.0/deploy/rbd/kubernetes/csi-rbdplugin-provisioner.yaml
curl -LO https://raw.githubusercontent.com/ceph/ceph-csi/v3.13.0/deploy/rbd/kubernetes/csi-rbdplugin.yaml

# 创建 ConfigMap
cat <<EOF | oc apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: ceph-csi-config
namespace: default
data:
config.json: |
[
{
"clusterID": "${FSID}",
"monitors": ["10.10.10.41:6789"],
"cephFS": {},
"rbd": {}
}
]
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ceph-config
namespace: default
data:
ceph.conf: |
[global]
auth_cluster_required = cephx
auth_service_required = cephx
auth_client_required = cephx
keyring: |
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ceph-csi-encryption-kms-config
namespace: default
data:
config.json: |-
{}
EOF

oc apply -f csi-provisioner-rbac.yaml
oc apply -f csi-nodeplugin-rbac.yaml
oc apply -f csi-rbdplugin.yaml
oc apply -f csi-rbdplugin-provisioner.yaml

13.4 创建 StorageClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

cat <<EOF | oc apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd-ocp
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: rbd.csi.ceph.com
parameters:
clusterID: ${FSID}
pool: ocp-pool
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: csi-ceph-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/controller-expand-secret-name: csi-ceph-secret
csi.storage.k8s.io/controller-expand-secret-namespace: default
csi.storage.k8s.io/node-stage-secret-name: csi-ceph-secret
csi.storage.k8s.io/node-stage-secret-namespace: default
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
EOF

13.5 验证 StorageClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig

oc get sc
oc get pods -n default | grep csi-rbd

cat <<EOF | oc apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
storageClassName: ceph-rbd-ocp
EOF

oc get pvc test-pvc
  • PVC 状态为 Bound
  • 测试完成后:oc delete pvc test-pvc

13.6 验收

1
2
3
4
5
# Helper 上执行
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
echo "=== StorageClass ===" && oc get sc ceph-rbd-ocp
echo "=== CSI pods ===" && oc get pods -n default -l app=csi-rbdplugin
echo "=== test PVC ===" && oc get pvc test-pvc

预期:

1
2
3
4
5
6
7
8
9
=== StorageClass ===
ceph-rbd-ocp rbd.csi.ceph.com ... (default)

=== CSI pods ===
csi-rbdplugin-... Running
csi-rbdplugin-provisioner-... Running

=== test PVC ===
test-pvc Bound ... ceph-rbd-ocp 5Gi
  • ceph-rbd-ocp 为默认 StorageClass;CSI Pod Running;测试 PVC Bound

14. 阶段 12 — 节点防火墙(可选)

时机: §12.6 集群 install-complete 且全部节点 Ready 之后。安装过程中不要启用。

目标:拒绝管理网段以外的入站流量,出站不限制(仍经 10.10.10.1 访问企业代理)。

注意: 仅允许 Helper(.42)入站不够——Master 间 etcd、节点间 kubelet/OVN 等仍需 10.10.10.43.51 互访。推荐入站白名单为整个 **10.10.10.0/24**(含 Helper、PVE、工作站管理 IP)。

在节点上验证(以 Master 为例):

1
2
3
4
5
ssh -i deploy-key.pem core@10.10.10.44
sudo iptables -I INPUT 1 -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -I INPUT 2 -i lo -j ACCEPT
sudo iptables -I INPUT 3 -s 10.10.10.0/24 -j ACCEPT
sudo iptables -A INPUT -j DROP

确认集群健康后,通过 MachineConfig 或配置管理固化到全部 Master/Worker(Master 与 Worker 各一份,role 标签分别对应)。

1
2
3
4
5
# Helper 上验收
export KUBECONFIG=/root/ocp-install/auth/kubeconfig
oc get nodes
oc get co network
curl -sk https://api.lab-ocp.example.com:6443/healthz
  • 集群仍全部 Ready;API healthzok
  • 网段外来源无法访问节点端口(可选:从非 10.10.10.0/24 主机探测)

Notice: 正常情况下,这里会有一个基于utteranc.es的留言系统,如果看不到,可能要想想办法才能看到。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2012 - 2026 tiaobug.com All Rights Reserved.

鲁ICP备2024124237号-1