在 Proxmox VE 上以 UPI + platform: none 安装 OpenShift 4.18:自建 Helper(DNS/LB/引导)、PVE Kernel 引导 RHCOS、集群装完后通过 Ceph CSI 对接 PVE 上的外部 Ceph 存储。
架构一览 1 2 3 4 5 6 用户 → DNS(api/*.apps) → Helper 外网 IP → HAProxy ├─ 6443/22623 → Master(安装期含 Bootstrap) └─ 80/443 → Worker OCP 节点(192.168.100.x)→ PVE NAT → HTTP 代理 → quay.io 拉镜像 OCP PVC → Ceph CSI → PVE Ceph Monitor :6789 → ocp-pool
节点
生命周期
作用
Helper(RHEL)
永久
BIND、HAProxy、Nginx、安装器、出网代理
Bootstrap
临时
临时 control plane,装完删除
Master ×3
永久
API、etcd
Worker ×N
永久
业务 Pod、Ingress
PVE Ceph
永久
外部 RBD 存储(ocp-pool)
OCP 节点只需内网 IP;容器镜像由各节点从 quay.io 拉取,不是 Bootstrap 分发。
参数模板(安装前填写)
变量
示例
CLUSTER_NAME
lab-ocp
BASE_DOMAIN
example.com
PVE_IP
10.0.0.41
HELPER_EXT
10.0.0.42(外网,双网卡)
HELPER_INT
192.168.100.2
PVE_INT_GW
192.168.100.1
HTTP_PROXY
http://proxy.example.com:8080
CEPH_POOL
ocp-pool
OCP 版本
4.18.19
域名规则 :api.<CLUSTER_NAME>.<BASE_DOMAIN>,*.apps.<CLUSTER_NAME>.<BASE_DOMAIN> → 解析到 Helper 外网 IP 。
前置条件 :PVE 上 Ceph 已部署,ocp-pool 为 HEALTH_OK(参见 PVE 部署 MOP)。
Step 1 — 下载软件 从 Red Hat 官网获取(版本保持一致):
文件
用途
openshift-install-linux-4.18.19.tar.gz
安装器
openshift-client-linux-4.18.19.tar.gz
oc 客户端
rhel-9.x-boot.iso
Helper 安装
rhcos-live-kernel-x86_64
RHCOS 内核
rhcos-live-initramfs.x86_64.img
RHCOS initrd
rhcos-live-rootfs.x86_64.img
RHCOS rootfs
上传到 PVE:/var/lib/vz/template/iso/
验证
1 2 ls -lh /var/lib/vz/template/iso/
Step 2 — PVE 准备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cat >> /etc/network/interfaces << 'EOF' auto vmbr1 iface vmbr1 inet static address 192.168.100.1/24 bridge_ports none bridge_stp off bridge_fd 0 EOF ifup vmbr1 iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o vmbr0 -j MASQUERADE echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-ocp-nat.confsysctl -p /etc/sysctl.d/99-ocp-nat.conf apt install -y iptables-persistent 2>/dev/null || true
验证
1 2 3 ip addr show vmbr1 sysctl net.ipv4.ip_forward iptables -t nat -L POSTROUTING -n | grep 192.168.100
Step 3 — DNS 在公网/内网 DNS 注册(指向 Helper 外网 IP):
api.<CLUSTER_NAME>.<BASE_DOMAIN>
*.apps.<CLUSTER_NAME>.<BASE_DOMAIN>(或至少 console、oauth)
验证
1 2 dig +short api.<CLUSTER_NAME>.<BASE_DOMAIN>
Step 4 — 创建 VM
VMID
角色
vCPU
内存
磁盘
网卡
内网 IP
100
Helper
4
16G
120G
vmbr0+vmbr1
.2
110
Bootstrap
8
16G
100G
vmbr1
.3
101-103
Master
8
16G
80G
vmbr1
.101-.103
104+
Worker
16
32G
160G
vmbr1
.104+
存储:vm-storage
Helper 挂 RHEL ISO
Bootstrap/Master/Worker 勿 qm start(空白盘会 iPXE 循环)
SCSI 控制器选 VirtIO SCSI single ,磁盘 SSD emulation + Discard
验证
1 2 3 4 qm list qm config 101 | grep -E 'scsi|net|memory|cores'
Step 5 — 安装 Helper(RHEL 9 Minimal)
项
值
外网(ens192 等)
<HELPER_EXT>,网关、DNS
内网(ens224 等)
192.168.100.2/24,无网关
SSH
公钥登录,禁用密码
1 2 3 4 5 tar xzf openshift-install-*.tar.gz -C /usr/local/bin/ tar xzf openshift-client-*.tar.gz -C /usr/local/bin/ mkdir -p /var/www/html/{rhcos,ignition}
验证
1 2 3 4 5 openshift-install version oc version --client curl -I http://127.0.0.1/rhcos/rhcos-live-kernel-x86_64 ping -c 2 192.168.100.1 curl -x <HTTP_PROXY> -I https://quay.io
Step 6 — Helper 服务 1 2 dnf install -y bind bind-utils haproxy nginx squid firewalld jq systemctl enable --now firewalld
BIND(集群内 DNS) zone 名:<CLUSTER_NAME>.<BASE_DOMAIN>
记录
类型
值
api
A
192.168.100.2
*.apps
A
192.168.100.2
api-int
A
192.168.100.2
1 2 named-checkconf && named-checkzone <CLUSTER_NAME>.<BASE_DOMAIN> /var/named/<zone-file> systemctl enable --now named
HAProxy 安装期 API/MCS 必须含 Bootstrap 192.168.100.3:
1 2 3 4 5 6 7 8 frontend api bind *:6443 default_backend api_back backend api_back server bootstrap 192.168.100.3:6443 check server master101 192.168.100.101:6443 check server master102 192.168.100.102:6443 check server master103 192.168.100.103:6443 check
22623(MCS)后端同样包含 bootstrap + master。80/443 指向 worker(未启动时 DOWN 正常)。
1 systemctl enable --now haproxy
Nginx + Squid
Nginx 192.168.100.2:8080 → /var/www/html/rhcos/、/ignition/
Squid 192.168.100.2:3128 → 转发 HTTP_PROXY
可选 Registry Proxy :5000(解决 Go 客户端经企业代理拉镜像问题)
防火墙 1 2 3 4 for p in 53/tcp 53/udp 6443 22623 80 443 8080 3128 5000; do firewall-cmd --permanent --add-port=${p} /tcp 2>/dev/null done firewall-cmd --reload
验证
1 2 3 4 dig api.<CLUSTER_NAME>.<BASE_DOMAIN> @192.168.100.2 +short ss -tlnp | grep -E '6443|8080|3128' firewall-cmd --list-ports curl -I http://192.168.100.2:8080/rhcos/rhcos-live-kernel-x86_64
将 RHCOS kernel/initrd 复制到 PVE:/var/lib/vz/template/iso/
Step 7 — install-config 与 Ignition 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 apiVersion: v1 baseDomain: <BASE_DOMAIN> metadata: name: <CLUSTER_NAME> platform: none: {} controlPlane: name: master replicas: 3 compute: - name: worker replicas: <N> networking: clusterNetwork: - cidr: 172.28 .0 .0 /16 hostPrefix: 23 serviceNetwork: - 172.29 .0 .0 /16 machineNetwork: - cidr: 192.168 .100 .0 /24 networkType: OVNKubernetes pullSecret: '<console.redhat.com 获取>' sshKey: '<与 OCP 节点登录用的公钥>' proxy: httpProxy: <HTTP_PROXY> httpsProxy: <HTTP_PROXY> noProxy: .<BASE_DOMAIN>,192.168.100.0/24,<外网段>,172.28.0.0/16,172.29.0.0/16,.svc,.cluster.local additionalTrustBundle: | -----BEGIN CERTIFICATE----- ...(代理自签 CA,如有)... -----END CERTIFICATE-----
不要写 publish: 字段。metadata.name + baseDomain 必须等于 DNS 中的集群域名。
1 2 3 4 5 6 mkdir -p /root/ocp-install && cd /root/ocp-installvi install-config.yaml cp install-config.yaml install-config.yaml.bak openshift-install create ignition-configs cp *.ign /var/www/html/ignition/chmod 644 /var/www/html/ignition/*.ign
验证
1 2 3 4 5 6 7 ls -la /root/ocp-install/curl -s -o /dev/null -w '%{http_code}' http://192.168.100.2:8080/ignition/bootstrap.ign grep -E 'baseDomain|name:' install-config.yaml.bak
Step 8 — Kernel 引导 RHCOS(PVE)
网卡名用 **ens18**;必须带 ip= 静态地址,否则 nm-wait-online 卡住。
1 2 3 4 5 6 7 8 9 10 KERNEL=/var/lib/vz/template/iso/rhcos-live-kernel-x86_64 INITRD=/var/lib/vz/template/iso/rhcos-live-initramfs.x86_64.img IGN=http://192.168.100.2:8080/ignition ROOTFS=http://192.168.100.2:8080/rhcos/rhcos-live-rootfs.x86_64.img GW=192.168.100.1 APPEND="coreos.live.rootfs_url=${ROOTFS} ignition.config.url=${IGN} /bootstrap.ign ip=192.168.100.3::${GW} :255.255.255.0::ens18:none console=tty0" qm set 110 --args "-kernel ${KERNEL} -initrd ${INITRD} -append \"${APPEND} \"" qm start 110
Bootstrap 上(企业网环境)
配置 crio/bootkube 代理环境变量
registries.conf mirror 到 Registry Proxy :5000
必要时在 bootkube.sh 硬编码 VERSION="4.18.19"
验证
1 2 3 4 5 6 7 8 sudo crictl pods curl -k https://192.168.100.3:6443/healthz
Step 9 — 启动 Master,等待 bootstrap-complete 1 2 3 4 5 6 7 8 9 for id ip in "101 192.168.100.101" "102 192.168.100.102" "103 192.168.100.103" ; do set -- $id $ip APPEND="coreos.live.rootfs_url=${ROOTFS} ignition.config.url=${IGN} /master.ign ip=$2 ::${GW} :255.255.255.0::ens18:none console=tty0" qm set $1 --args "-kernel ${KERNEL} -initrd ${INITRD} -append \"${APPEND} \"" qm start $1 done openshift-install wait-for bootstrap-complete --dir =/root/ocp-install --log-level=debug
验证
1 2 3 4 curl -k https://api.<CLUSTER_NAME>.<BASE_DOMAIN>:6443/healthz export KUBECONFIG=/root/ocp-install/auth/kubeconfigoc get nodes oc get clusteroperators | grep -v True
bootstrap-complete 后 :从 haproxy.cfg 删除 server bootstrap 行 → systemctl reload haproxy
1 2 grep bootstrap /etc/haproxy/haproxy.cfg
Step 10 — 删 Bootstrap,启动 Worker 1 2 3 4 5 6 7 8 qm stop 110 && qm destroy 110 APPEND="coreos.live.rootfs_url=${ROOTFS} ignition.config.url=${IGN} /worker.ign ip=192.168.100.104::${GW} :255.255.255.0::ens18:none console=tty0" qm set 104 --args "-kernel ${KERNEL} -initrd ${INITRD} -append \"${APPEND} \"" qm start 104
1 2 3 4 5 6 7 while true ; do PENDING=$(oc get csr -o json | jq -r '.items[] | select(.status == {}) | .metadata.name' ) [ -z "$PENDING " ] && break echo "$PENDING " | xargs -r oc adm certificate approve sleep 10 done
验证
1 2 3 4 5 oc get nodes oc get csr | grep Pending
Step 11 — 完成安装 1 2 3 4 5 6 openshift-install wait-for install-complete --dir =/root/ocp-install export KUBECONFIG=/root/ocp-install/auth/kubeconfigoc get clusterversion oc get clusteroperators oc whoami
验证
Step 12 — 配置 Ceph 存储(CSI) PVE 上已有 Ceph(ocp-pool,Monitor 在 <PVE_IP>:6789)。OCP 通过 Ceph CSI RBD 动态供给 PVC。
12.1 网络连通性 OCP Worker 经 PVE NAT 需能访问 Ceph Monitor:
若不通,在 PVE 放行 6789,或确认 192.168.100.0/24 到 PVE 管理网路由/NAT 正常。
12.2 在 PVE 上创建 Ceph 用户 1 2 3 4 5 6 7 8 ceph auth get-or-create client.openshift \ mon 'profile rbd' \ osd 'profile rbd pool=<CEPH_POOL>' \ -o /etc/pve/ceph/openshift.client.openshift.keyring ceph auth get client.openshift ceph fsid
记录 key 和 fsid 。
验证
1 2 ceph auth get client.openshift | grep caps
12.3 创建 Secret 与 ConfigMap 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 export KUBECONFIG=/root/ocp-install/auth/kubeconfigCEPH_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 cat <<EOF | oc apply -f - apiVersion: v1 kind: ConfigMap metadata: name: ceph-csi-config namespace: default data: config.json: | [ { "clusterID": "${FSID}", "monitors": ["<PVE_IP>:6789"], "cephFS": {}, "rbd": {} } ] EOF
验证
1 2 oc get secret csi-ceph-secret -o jsonpath='{.data.userID}' | base64 -d
12.4 安装 Ceph CSI 选用与集群 K8s 版本兼容的 ceph-csi(OCP 4.18 对应 K8s 1.31,示例 v3.13.0 ):
1 2 3 4 5 6 7 8 9 CEPH_CSI_VER=v3.13.0 for f in csi-provisioner-rbac csi-nodeplugin-rbac csi-rbdplugin csi-rbdplugin-provisioner; do curl -LO https://raw.githubusercontent.com/ceph/ceph-csi/${CEPH_CSI_VER} /deploy/rbd/kubernetes/${f} .yaml done 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
验证
1 2 3 4 5 oc get pods -A | grep csi-rbd oc logs -l app=csi-rbdplugin-provisioner -c csi-provisioner --tail =20
12.5 创建 StorageClass 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cat <<EOF | oc apply -f - apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ceph-rbd annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: rbd.csi.ceph.com parameters: clusterID: ${FSID} pool: <CEPH_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
验证
12.6 PVC 功能测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cat <<EOF | oc apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: [ReadWriteOnce] resources: requests: storage: 5Gi storageClassName: ceph-rbd EOF oc get pvc test-pvc -w oc get pv
在 PVE 上交叉验证:
测试完成后清理:
验证清单
SSH 登录提示 Helper 与 OCP 节点可能使用不同 SSH 密钥:
1 2 3 ssh -i <ocp-key.pem> -o IdentitiesOnly=yes \ -o ProxyCommand="ssh -i <helper-key.pem> -o IdentitiesOnly=yes -W %h:%p root@<HELPER_EXT>" \ core@192.168.100.101
-i + -J 单命令要求两跳同一密钥;两把密钥时先登 Helper 再 ssh core@...。
节点调试(API 不可用时) 1 2 3 sudo crictl pods && sudo crictl ps -a sudo crictl logs <container-id> sudo journalctl -u kubelet -u bootkube -u release-image -n 50
踩坑速查
现象
处理
iPXE 无限重启
用 kernel --args 引导,勿直接 qm start
nm-wait-online 卡住
ip=...ens18:none
curl :8080 No route to host
Helper 防火墙放行 8080
bootstrap 超时
HAProxy 含 bootstrap 后端;核对 API 域名
Permission denied SSH
install-config sshKey 与登录私钥不一致
镜像 403/timeout
真实 pullSecret;Registry Proxy + registries.conf
PVC Pending
Worker 到 <PVE_IP>:6789 不通;检查 Ceph 用户权限和 fsid
CSI pod CrashLoop
oc logs 查 monitor 地址、secret key、pool 名称