从零到一:在 Proxmox VE 上构建企业级 K8s 高可用集群

1. 架构设计与资源规划

在 PVE 环境下,为了模拟生产环境的健壮性,采用 3 Master + 3 Worker + 2 LB 的 8 节点架构。

节点规格表

角色数量vCPURAMDisk说明
Load Balancer21核1 GiB20G运行 Keepalived + HAProxy,集群入口
Master 节点32核4 GiB40G形成 etcd 仲裁,保证控制平面高可用
Worker 节点34核10 GiB100G运行业务 Pod,预留较大空间
合计820核44G400G建议保留 20% 宿主机资源冗余

核心工具链

  • Infrastructure as Code (IaC): Terraform + Cloud-Init 快速分发虚拟机
  • 负载均衡: Keepalived + HAProxy
  • 容器运行时: Containerd
  • K8s 部署工具: Sealos (最终使用方案)

2. 虚拟机模板

虽然 PVE 的克隆功能非常强大,即便提前准备好了软件包,克隆出的虚拟机仍然需要进行大量的基础配置和环境调整。Cloud-Init 解决身份认证,克隆功能解决复杂预配制,Terraform 解决自动化部署虚拟机。

在 PVE 中制作一个包含所有基础依赖的 Ubuntu 24.04 模板是高效的前提。

PVE 模板制作

  1. 下载官方 Ubuntu 24.04 Cloud Image。

    wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img -o noble-server-cloudimg-amd64.img
  2. 使用 qm 创建虚拟机
    注意:这里直接使用 SCSI 作为系统盘接口,避免后续转换成模板时出现 IDE 转 SCSI 的麻烦。这主要也是因为 Ubuntu 24.04 默认支持 virtio-scsi-pci。现代环境更常用 virtio/virtio-scsi,IDE 已较少使用。
    STORAGE 是 PVE 中的存储池名称,通常是 local-lvm,我的这台机器安装时就组建了 zfs,所以此处名为 local-zfs。

    import subprocess
    
    # 配置参数
    VM_ID = "201"
    VM_NAME = "ubuntu-2404-scsi-template"
    IMG_PATH = "noble-server-cloudimg-amd64.img"
    STORAGE = "local-zfs" 
    
    def run_cmd(cmd):
        print(f"执行: {cmd}")
        subprocess.run(cmd, shell=True, check=True)
    
    try:
        # 1. 创建 VM,预设 SCSI 控制器为性能最好的 virtio-scsi-pci
        run_cmd(f"qm create {VM_ID} --name {VM_NAME} --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0")
        run_cmd(f"qm set {VM_ID} --scsihw virtio-scsi-pci")
        
        # 2. 导入并挂载系统盘 (scsi0)
        run_cmd(f"qm importdisk {VM_ID} {IMG_PATH} {STORAGE}")
        run_cmd(f"qm set {VM_ID} --scsi0 {STORAGE}:vm-{VM_ID}-disk-0")
        
        # 3. 将 Cloud-init 挂载到 scsi1 (替代之前的 ide2)
        run_cmd(f"qm set {VM_ID} --scsi1 {STORAGE}:cloudinit")
        
        # 4. 设置启动顺序、串口和显示
        run_cmd(f"qm set {VM_ID} --boot c --bootdisk scsi0 --serial0 socket --vga serial0")
        
        print(f"\n✅ 纯 SCSI 模板虚拟机 {VM_ID} 已就绪。")
        print("注意:在 PVE UI 的 Cloud-Init 界面配置好后再转模板。")
    
    except Exception as e:
        print(f"❌ 脚本执行失败: {e}")

基础初始化

# 更换内网镜像源/大陆镜像源
sudo sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
sudo sed -i 's|http://|https://|g' /etc/apt/sources.list
sudo apt-get update -y && sudo apt-get upgrade -y

# 必备工具
apt install -y qemu-guest-agent socat btop tcpdump curl jq nfs-common

K8s 环境预设 (模板关键点)

在模板中预加载内核模块,避免部署时报 modprobe 错误:

# 开启 IP 转发与网桥过滤
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

模板特征清理

如果不清理,克隆出的机器会有相同的 machine-id,导致 DHCP 分配相同 IP。

sudo truncate -s 0 /etc/machine-id
sudo rm /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
sudo cloud-init clean --logs
history -c && sudo shutdown -h now

Terraform 模板

部署过程中注意: 对于虚拟机的硬件配置修改,reboot/terraform apply 可能并不会生效,还是建议手动停止虚拟机,关机后再运行 terraform apply 来更新配置。

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.2-rc07"
    }
  }
}

# Proxmox API 认证配置
provider "proxmox" {
  pm_api_url      = "https://192.168.4.145:8006/api2/json"
  pm_api_token_id = "root@pam!terraform"
  pm_api_token_secret = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeee"
  pm_tls_insecure = true
}

# 虚拟机规格定义
locals {
  vm_list = {
    "lb-1"     = { vmid = 301, cpu = 1, mem = 1024, disk = "20G",  ip = "192.168.4.110" }
    "lb-2"     = { vmid = 302, cpu = 1, mem = 1024, disk = "20G",  ip = "192.168.4.111" }
    "master-1" = { vmid = 311, cpu = 2, mem = 4096, disk = "40G",  ip = "192.168.4.112" }
    "master-2" = { vmid = 312, cpu = 2, mem = 4096, disk = "40G",  ip = "192.168.4.113" }
    "master-3" = { vmid = 313, cpu = 2, mem = 4096, disk = "40G",  ip = "192.168.4.114" }
    "worker-1" = { vmid = 321, cpu = 4, mem = 10240, disk = "100G", ip = "192.168.4.115" }
    "worker-2" = { vmid = 322, cpu = 4, mem = 10240, disk = "100G", ip = "192.168.4.116" }
    "worker-3" = { vmid = 323, cpu = 4, mem = 10240, disk = "100G", ip = "192.168.4.117" }
  }
}

# 使用 Cloud-Init 克隆模板并创建虚拟机
resource "proxmox_vm_qemu" "k8s_node" {
  for_each    = local.vm_list
  name        = each.key
  vmid        = each.value.vmid
  target_node = "pve-bigc"
  clone       = "Ubuntu-template-modify"
  full_clone  = true
  os_type = "cloud-init"
  agent = 1
  
  cpu {
    cores   = each.value.cpu
    type    = "host"
  }
  network {
    id     = 0
    model  = "virtio"
    bridge = "vmbr0" # 确保这是你 PVE 上的网桥名称
  }
  serial {
    id   = 0
    type = "socket"
  }
  vga {
    type = "serial0"
  }
  memory  = each.value.mem
  scsihw  = "virtio-scsi-pci"

  disks {
    scsi {
      scsi0 {
        disk {
          size    = each.value.disk
          storage = "local-zfs"
        }
      }
      scsi1 {
        cloudinit {
          storage = "local-zfs"
        }
      }
    }
  }

  # Cloud-init 配置:自动注入 IP 和 SSH Key
  ciuser     = "iris"
  ipconfig0 = "ip=${each.value.ip}/24,gw=192.168.4.1"
  sshkeys = <<EOF
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG21JF/tUKdRTShlvIGb+aHiABgSpWpf0I1sk7mWyKmB ed25519 256-20260302
EOF
}

3. 集群入口:Keepalived + HAProxy 高可用

在 Master 节点部署前,必须先建立 VIP (192.168.4.100),这将作为 K8s API Server 的统一入口。Keepalived 负责 VIP 的高可用,HAProxy 负责流量分发。

VIP

安装 Keepalived 和 HAProxy,详细步骤不再赘述,核心配置如下:

sudo apt update && sudo apt install -y keepalived haproxy
# /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state MASTER
    interface eth0      # 确认这是正确的网卡名,用 ip addr 查一下
    virtual_router_id 51
    priority 100           # 优先级,MASTER 高于 BACKUP
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.4.100      # VIP
    }
}
# /etc/keepalived/keepalived.conf

vrrp_instance VI_1 {
    state BACKUP           # 改为 BACKUP
    interface eth0
    virtual_router_id 51
    priority 90            # 优先级比 100 小即可
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.4.100
    }
}

错误路径:网卡名对不上

现象:Keepalived 启动失败,日志报错 interface enp0s18 doesn't exist解决:PVE 的网卡名在不同内核下可能从 eth0 变成 ens18。务必先 ip addr 确认,并在 keepalived.conf 中修正,altname enp0s18 并非总是能正确兼容,还是用 ip addr 查到的名字最可靠。

HAProxy 配置

# /etc/haproxy/haproxy.cfg
frontend k8s-api
    bind *:6443
    mode tcp
    option tcplog
    default_backend k8s-masters

backend k8s-masters
    mode tcp
    option tcplog
    option tcp-check
    balance roundrobin
    server master-1 192.168.4.112:6443 check
    server master-2 192.168.4.113:6443 check
    server master-3 192.168.4.114:6443 check

预期配置

  • Keepalived: 负责监控 LB 存活,确保 VIP 始终在线。
  • HAProxy: 负责将 6443 端口的请求轮询分发给 3 台 Master,这个端口是 K8s API Server 的默认端口。

4. K8s 部署:从 KubeKey 切换到 Sealos

手动部署 K8s 既不方便,也不标准,后期若是出了问题,还不知道该怎么查。 所幸社区有很多自动化工具,这将提供极大的便利。

对于国内用户来说,部署工具的选择往往受限于镜像拉取问题。尝试了多个方案,最终选择了 Sealos。KK 没有被选择的原因主要是它在某些硬编码逻辑中仍然依赖于外部镜像路径,加上内网并没有合适的镜像仓库,导致部署失败,而 Sealos 则通过在控制服务器上通过 http 代理下载了所需镜像,再通过内网 SCP 分发二进制包,完全绕过了镜像拉取的瓶颈。

路径 A:KubeKey (KK) —— 因镜像问题折戟

遭遇挑战

  1. Sandbox 镜像拉取失败registry.k8s.io/pause:3.8 无法访问,这并非简单的网络问题,而是因为 KK 的某些组件在部署过程中硬编码了这个路径,导致整个部署流程卡死在拉取这个镜像的阶段,官方提供的解决方案是,使用 manifasest 功能制作离线的部署包。
  2. 镜像拉平失败:尝试通过 namespaceOverride 将路径拉平至私有仓库,但 KK 在部分硬编码逻辑中仍寻找原路径,在折腾了许久之后,认定按照 KK 的设计思路,国内用户很难绕过这个问题,除非自己搭建一个完全兼容的镜像仓库,这点的复杂度和维护成本都过高。

路径 B:Sealos

核心优势:Sealos 将 K8s 视为一个“操作系统镜像”,通过内网 SCP 分发二进制包,极大减少了对外部镜像仓库的依赖。

export https_proxy=http://proxy.example.com:8080
export http_proxy=http://proxy.example.com:8080
export no_proxy=192.168.4.0/24,localhost,127.0.0.1
sudo -E sealos run \
  labring/kubernetes:v1.29.0 \
  labring/helm:v3.14.0 \
  labring/calico:v3.27.4 \
  --masters 192.168.4.112,192.168.4.113,192.168.4.114 \
  --nodes 192.168.4.115,192.168.4.116,192.168.4.117 \
  --user iris \
  --pk /home/iris/k8s-infra/k8s \
  --env controlPlaneEndpoint=192.168.4.100:6443 \
  --force

可能遇到的问题:

  1. Containerd 冲突 错误:Sealos 报错 containerd is installed... please uninstall it,但 apt 却说没装。 原因:系统 $PATH 中残留了手动下载的二进制文件。 解决sudo rm -f /usr/bin/containerd*,物理抹除后再运行。

  2. Sudo 环境变量丢失 错误:设置了 https_proxysudo sealos run 依然报错 500/404。 原因:普通 sudo 会重置环境变量,导致代理失效。 解决:使用 sudo -E sealos run ...,通过 -E 强制保留当前用户的代理设置。


5. 收官之战:解决 TLS 证书“死锁”

当看到下面的 ASCI 图标时,恭喜,集群已经 Ready 了!但还差一点: VIP 服务还没有用得上呢,当前的 master 节点的 API Server 仍然绑定在它们各自的本地 IP 上,而不是 VIP 上。这就导致了后续访问 VIP 时的证书错误。

      ___           ___           ___           ___       ___           ___
     /\  \         /\  \         /\  \         /\__\     /\  \         /\  \
    /::\  \       /::\  \       /::\  \       /:/  /    /::\  \       /::\  \
   /:/\ \  \     /:/\:\  \     /:/\:\  \     /:/  /    /:/\:\  \     /:/\ \  \
  _\:\~\ \  \   /::\~\:\  \   /::\~\:\  \   /:/  /    /:/  \:\  \   _\:\~\ \  \
 /\ \:\ \ \__\ /:/\:\ \:\__\ /:/\:\ \:\__\ /:/__/    /:/__/ \:\__\ /\ \:\ \ \__\
 \:\ \:\ \/__/ \:\~\:\ \/__/ \/__\:\/:/  / \:\  \    \:\  \ /:/  / \:\ \:\ \/__/
  \:\ \:\__\    \:\ \:\__\        \::/  /   \:\  \    \:\  /:/  /   \:\ \:\__\
   \:\/:/  /     \:\ \/__/        /:/  /     \:\  \    \:\/:/  /     \:\/:/  /
    \::/  /       \:\__\         /:/  /       \:\__\    \::/  /       \::/  /
     \/__/         \/__/         \/__/         \/__/     \/__/         \/__/

                  Website: https://sealos.io/
                  Address: github.com/labring/sealos
                  Version: 5.1.1-1e312ad2c

现象

在任意 master-node 上运行 kubectl get nodes 报错:certificate is valid for 192.168.4.112... not 192.168.4.100

解决方案

  1. 修正解析:检查 /etc/hosts,确保 apiserver.cluster.local 指向 VIP 而非节点本地 IP。
grep apiserver /etc/hosts
  1. 导出配置
kubectl --insecure-skip-tls-verify \
  -n kube-system get configmap kubeadm-config \
  -o jsonpath='{.data.ClusterConfiguration}' > kubeadm-config.yaml
  1. 注入 SANs:在 apiServer.certSANs 下手动添加 192.168.4.100
  2. 重签证书:删除 /etc/kubernetes/pki/apiserver.crt,运行 kubeadm init phase certs apiserver --config kubeadm-config.yaml
  3. 重启服务:杀死 kube-apiserver 进程让 Kubelet 自动拉起。

此时 VIP 上的 API Server 已经启用,访问 https://VIP:6443/healthz 应该能看到正常的响应了。


6. 总结

通过在 Proxmox VE 上构建企业级 K8s 集群,经历了从基础架构设计、虚拟机模板制作、负载均衡配置,到最终的 K8s 部署和证书调整的完整流程。 下面是这个项目的 GitHub 仓库地址,欢迎 star 和 fork:KagurazakaIris/k8s-infra