手头有一台 PVE(Proxmox VE)宿主机,希望在其上创建 3 台 Ubuntu 虚拟机,搭建一个 1 控制面 + 2 工作节点的 Kubernetes 集群,用于学习和测试。
国内环境的主要障碍是网络:k8s 官方 apt 源(pkgs.k8s.io)、镜像仓库(registry.k8s.io、quay.io、docker.io)以及 GitHub raw 均无法直接稳定访问,需要全程替换为国内镜像。本文使用的替代方案:
| 资源 | 官方地址(不可达) | 国内替代 |
|---|---|---|
| Ubuntu ISO / apt 源 | archive.ubuntu.com | mirrors.tuna.tsinghua.edu.cn / mirrors.aliyun.com |
| k8s apt 源 | pkgs.k8s.io | mirrors.aliyun.com/kubernetes-new |
| k8s 组件镜像 | registry.k8s.io | registry.aliyuncs.com/google_containers |
| docker.io / quay.io / gcr.io 镜像 | docker.io / quay.io / gcr.io | docker.m.daocloud.io / quay.m.daocloud.io / gcr.m.daocloud.io |
| GitHub raw / releases 文件 | raw.githubusercontent.com / github.com | jsdelivr,或自备代理 |
软件版本(本文撰写时验证过):
- PVE 8.x
- Ubuntu Server 24.04 LTS
- containerd 2.2.x(使用 Ubuntu 官方仓库版本)
- Kubernetes v1.36.x(kubeadm 部署)
- Cilium 作为 CNI
1. 总体规划
1.1 集群拓扑
| 主机名 | IP | 角色 | 最低规格 |
|---|---|---|---|
| k8s-cp | 10.8.8.180 | control-plane | 2C / 4G / 40G |
| k8s-node1 | 10.8.8.181 | worker | 2C / 4G / 40G |
| k8s-node2 | 10.8.8.182 | worker | 2C / 4G / 40G |
1.2 网络规划
| 网络 | 网段 | 说明 |
|---|---|---|
| 节点网络 | 10.8.8.0/24 | 虚拟机桥接 vmbr0,与 PVE 同网段;网关 10.8.8.1,DNS 10.8.8.66 / 8.8.8.8 |
| Pod 网络 | 10.244.0.0/16 | 由 Cilium 分配使用,不得与节点网络重叠 |
| Service 网络 | 10.96.0.0/12 | 不得与节点/Pod 网络重叠 |
2. PVE 上准备 Ubuntu 虚拟机
2.1 下载 Ubuntu Server 镜像
在 PVE 节点上下载 ISO(用清华镜像站,国内访问较快):
| |
具体文件名以 https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/24.04/ 列表为准。
2.2 创建第一台虚拟机
PVE Web 界面「创建 VM」,主要选项:
- 常规:名称
ubuntu-k8s-tpl(之后转为模板) - 操作系统:选择刚下载的 ISO,Guest OS 类型 Linux 6.x
- 系统:机器类型 q35,SCSI 控制器 VirtIO SCSI single,BIOS 默认 SeaBIOS
- 磁盘:VirtIO Block,40 GB
- CPU:2 核以上,类型 host
- 内存:4096 MB 以上
- 网络:桥接 vmbr0,模型 VirtIO(半虚拟化)
2.3 安装 Ubuntu Server
启动虚拟机进入安装程序,注意:
- 语言建议选 English(避免控制台中文乱码)
- 网络先保持 DHCP,静态 IP 留到克隆后逐台配置
- 存储使用整块磁盘即可(默认 LVM 方案无妨)
- 勾选
Install OpenSSH server - Ubuntu Server 默认不创建 swap 分区,无需额外处理
安装完成后重启,SSH 登录进行基础配置。
2.4 安装后基础配置(在模板机内执行)
Ubuntu 24.04 的 apt 源为 DEB822 格式,替换为国内镜像:
| |
安装常用工具和 qemu-guest-agent(PVE 感知虚拟机状态、安全关机所必需):
| |
同时在 PVE 端该虚拟机的「选项 → QEMU Guest Agent」勾选启用。
注意 qemu-guest-agent 不需要手动 systemctl enable:它的 unit 没有 [Install] 段(执行 enable 会提示 “no installation config”,属正常现象),PVE 端勾选后虚拟机内会出现 /dev/virtio-ports/org.qemu.guest_agent.0 设备,udev 检测到后会自动拉起服务。验证:
| |
服务正常后,PVE 的虚拟机摘要页面会显示该虚拟机的 IP 地址。
清理模板,避免克隆出的机器互相冲突:
| |
2.5 转为模板并克隆
在 PVE Web 界面右键该虚拟机「转换成模板」,然后从模板 完整克隆(Full Clone) 出 3 台:
k8s-cpk8s-node1k8s-node2
2.6 克隆后逐台修改主机名和静态 IP
以 k8s-cp 为例(k8s-node1 用 10.8.8.181、k8s-node2 用 10.8.8.182,其余相同):
| |
编辑 netplan(文件名以实际为准,通常是 /etc/netplan/00-installer-config.yaml):
| |
| |
3. 所有节点:系统初始化
以下操作在 3 台节点上都要执行。
配置 hosts 互相解析(可选但推荐):
| |
时区与时间同步(节点间时间偏差会导致证书校验失败):
| |
关闭 swap(kubelet 要求):
| |
加载内核模块并开启 iptables 桥接转发:
| |
防火墙:Ubuntu 默认 ufw 未启用、PVE 虚拟机防火墙默认关闭,则无需处理。如果启用过防火墙,控制面需放行 6443、2379-2380、10250、10251、10252、10257、10259,工作节点需放行 10250、30000-32767。
4. 所有节点:安装 containerd
直接使用 Ubuntu 官方仓库的 containerd(24.04 仓库当前为 2.2.x),省去额外配置第三方源:
| |
生成完整默认配置并修改:
| |
需要改四处:
| |
| |
注意:以上写法对应 containerd 2.x 的 v3 配置格式(CRI 插件路径为
io.containerd.cri.v1.images,配置文件开头version = 3),已在 Ubuntu 24.04 仓库的 containerd 2.2.1 上实际验证。如果用的是 containerd 1.7,插件路径是io.containerd.grpc.v1.cri,且没有 config_path 互斥问题。如果之前按不匹配的版本改过配置,直接重新执行containerd config default | sudo tee /etc/containerd/config.toml从头再来即可。
配置 crictl(排查容器问题时用)。crictl 由 cri-tools 包提供:Ubuntu 仓库里它在 universe 组件中,第 5 节的 kubernetes-new 源也会提供,第 5 节会显式安装。所以这里只写配置文件即可,crictl 验证留到第 5 节之后:
| |
重启并验证(containerd 没有 config check 子命令,用 config dump 输出解析后的实际配置来检查,语法错误会直接报出来):
| |
如果最后一条 grep 有输出(常见为
mirrors cannot be set when config_path is provided或 cri 被 disabled_plugins 禁用),说明 CRI 插件没有加载,kubeadm 会报unknown service runtime.v1.RuntimeService,按输出提示回头检查配置。
待第 5 节装好 cri-tools 后,还可以实测拉取:
| |
5. 所有节点:安装 kubeadm / kubelet / kubectl
使用阿里云的 kubernetes-new 源(社区 pkgs.k8s.io 的镜像。注意旧源 mirrors.aliyun.com/kubernetes/apt 停留在 1.28,不要再用)。
如需其他小版本,把下面 URL 中的 v1.36 换掉即可(可用版本列表见 https://mirrors.aliyun.com/kubernetes-new/core/stable/):
| |
查看可用版本并安装(3 台节点版本必须一致):
| |
6. 控制面:kubeadm init
在 k8s-cp 上编写初始化配置 kubeadm-init.yaml:
| |
说明:
- 必须显式写
kubernetesVersion,否则 kubeadm 会去 dl.k8s.io 查询最新版本,国内可能超时。 - 必须显式配置
imageRepository,否则默认从 registry.k8s.io 拉取组件镜像,国内无法直接拉取。 - v1beta3 已在 kubeadm 1.34 中移除,1.34 及以上必须使用 v1beta4;如果安装的是 1.33 及更早版本,把 apiVersion 改回
kubeadm.k8s.io/v1beta3即可,本例用到的字段在两个版本中位置相同。也可执行kubeadm config print init-defaults查看当前 kubeadm 的默认配置格式。
先预拉镜像验证镜像源可用,再执行初始化:
| |
成功后按输出提示配置 kubectl:
| |
保存 init 输出末尾的 kubeadm join ... 命令,第 8 步要用。如果丢失可重新生成:
| |
7. 控制面:安装 CNI 插件(Cilium)
不装 CNI 的话节点会一直停留在 NotReady。目前主流 CNI 是 Cilium、Calico、Flannel 三家。Cilium 基于 eBPF,2023 年从 CNCF 毕业,被 GKE、Azure 等托管 k8s 服务采用,支持 L3-L7 NetworkPolicy、Hubble 流量可观测,还可完全替代 kube-proxy;Calico 以 BGP 路由和网络策略见长,企业集群常见;Flannel 最轻量,但不支持 NetworkPolicy。本文选用 Cilium。
7.1 安装 Cilium
Cilium 对内核的要求是 4.19+(5.10+ 可解锁全部特性),Ubuntu 24.04 自带的 6.8 内核完全满足。
下载 cilium-cli(GitHub releases 国内不可直达,请自行通过代理下载,或找能出网的机器下载后 scp 过来):
| |
安装:
| |
说明:
clusterPoolIPv4PodCIDRList必须与kubeadm-init.yaml中的podSubnet一致。- 默认保留 kube-proxy,对 kubeadm 集群开箱即用。
- Cilium 镜像托管在 quay.io,第 4 步配置的 quay.m.daocloud.io 镜像加速自动生效,无需改任何镜像地址。
可选:验证集群网络连通性(会额外拉取 quay.io 上的测试镜像,同样走镜像加速):
| |
注意其中的外网连通用例(访问 1.1.1.1)在国内无法连通,失败属预期,不影响对集群内网络功能的判断。个别用例(如 L7 负载均衡的 l7-lb)的镜像若拉取失败(ImagePullBackOff),可用 kubectl -n cilium-test-1 describe pod <pod> 在 Events 里看具体镜像和原因,一般是该镜像在镜像加速服务上不可用,可忽略该用例。测试资源都创建在 cilium-test-1 namespace 中,验证完删除该 namespace 即清理干净:
| |
进阶:Cilium 可以完全替代 kube-proxy(kubeProxyReplacement=true,需在 kubeadm init 时跳过 kube-proxy 插件或事后移除),并可启用 Hubble Relay/UI 做流量可观测,按需参考官方文档开启。
8. worker 节点:加入集群
在 k8s-node1、k8s-node2 上分别执行第 6 步保存的 join 命令,形如:
| |
注意 init 输出的 join 命令原文不带 sudo,直接复制执行会报 ERROR IsPrivilegedUser,记得补上。
worker 的组件镜像同样来自 registry.aliyuncs.com/google_containers(join 时 kubeadm 只拉 pause 和 kube-proxy, kubeadm 会沿用镜像仓库设置;如拉取失败,可先 sudo kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers)。
worker 加入后,Cilium agent 会以 DaemonSet 形式自动在新节点上启动,无需额外操作。
9. 验证集群
在控制面上:
| |
部署一个测试应用(nginx 镜像经 docker.m.daocloud.io 加速拉取):
| |
如需在控制面节点上调度普通 Pod(测试集群常见需求):
| |
10. 安装 helm(可选)
helm 是 k8s 的包管理工具,后续安装 Hubble UI、ingress-controller 等 chart 会用到。它是纯客户端工具,装在操作集群的机器上即可(如 k8s-cp),直接使用 ~/.kube/config。
方式一:官方安装脚本(自动安装最新版):
| |
脚本会从 get.helm.sh(Azure CDN,国内一般可直连)下载 helm 二进制。
方式二:手动下载指定版本:
| |
验证:
| |
11. 常见问题
- kubeadm init 卡在拉镜像:检查是否忘了
imageRepository;用kubeadm config images pull --config kubeadm-init.yaml单独验证拉取。 - kubeadm 报
unknown service runtime.v1.RuntimeService:containerd 的 CRI 插件没有加载。用sudo journalctl -u containerd -b --no-pager | grep 'failed to load plugin'看具体原因——最常见两种:一是 cri registry 段的config_path非空与内联mirrors互斥(Ubuntu containerd 2.2 的默认配置即如此,按第 4 节置空即可);二是配置里disabled_plugins包含了 “cri”。修复并systemctl restart containerd后,sudo crictl version能列出 RuntimeVersion 即恢复。 - init 报 cgroup driver 不匹配:确认 containerd 的
SystemdCgroup = true且 init 配置里cgroupDriver: systemd,两边一致后sudo systemctl restart containerd重试。 - 节点一直 NotReady:多半是 CNI 插件未就绪。
kubectl describe node <节点>看 Conditions;Cilium 用cilium status和kubectl -n kube-system logs -l k8s-app=cilium排查。镜像拉取失败时检查 quay 加速配置。 - kubelet 启动失败 / 反复重启:
journalctl -u kubelet -f看日志,常见原因是 swap 未完全关闭或配置文件错误。 - 克隆的虚拟机注册冲突:machine-id 重复会导致 kubelet 无法正常注册,重新生成:
sudo rm /etc/machine-id && sudo systemd-machine-id-setup,再重启 kubelet。 - join token 过期(默认 24 小时):在控制面执行
kubeadm token create --print-join-command重新生成。 - GitHub 直连失败:raw 文件和 releases 下载在国内不可直达时,请自行使用代理,或找一台能出网的机器下载好文件再 scp 过来。
12. 附录:镜像代理的配置位置与流程
本文涉及"拉镜像"的代理一共分布在 3 个位置,分两类机制,另有 1 类容易混淆的非镜像代理。
配置位置
1. containerd registry mirrors(/etc/containerd/config.toml,第 4 节)—— 透明代理,覆盖名字固定的第三方镜像
| 原镜像仓库 | 代理 endpoint | 兜底 |
|---|---|---|
| docker.io | docker.m.daocloud.io | registry-1.docker.io |
| quay.io | quay.m.daocloud.io | quay.io |
| gcr.io | gcr.m.daocloud.io | gcr.io |
| registry.k8s.io | k8s.m.daocloud.io | registry.k8s.io |
生效对象:cilium 等组件清单里写死的 quay.io 镜像、手动部署的 nginx (docker.io)、connectivity test 的测试镜像(gcr.io)等。好处是无需修改镜像清单本身。前提是把 config_path 置空(containerd 2.2 默认启用的 certs.d 目录方式与内联 mirrors 互斥,否则 CRI 插件加载失败,见第 4 节)。
2. containerd pause 沙箱镜像(同文件 pinned_images 的 sandbox)—— 改名换源
把 registry.k8s.io/pause 改为 registry.aliyuncs.com/google_containers/pause。这不是代理,是直接把镜像名指到国内可达的地址。
3. kubeadm 的 imageRepository(kubeadm-init.yaml,第 6 节)——改名换源
registry.aliyuncs.com/google_containers 作用于 kubeadm 负责拉取的全部控制面组件:kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、 etcd、coredns、pause。kubeadm 拼镜像名时用它替换默认的 registry.k8s.io 前缀。
(区分)非镜像代理:apt 包走清华/阿里云源、cilium-cli 等 GitHub 文件下载需自备代理——这些是文件/包下载,与 containerd 镜像代理是两条独立的线。
代理流程
以 kubelet 在某节点启动一个 cilium Pod(镜像 quay.io/cilium/cilium:vX.Y.Z)为例:
- kubelet 通过 CRI 调 containerd 的
PullImage - containerd 的 CRI 插件解析镜像引用,提取 registry 主机名
quay.io - 查 registry 配置:命中 mirrors,endpoint 列表第一个是
https://quay.m.daocloud.io - 向该 endpoint 发标准 OCI registry 请求(先 Head/GET manifest,再 GET blob),镜像路径保持原样(
/cilium/cilium/...);daocloud 作为缓存代理回源 quay.io 取数返回 - 第一个 endpoint 失败则按列表顺序回退到下一个
- 成功则 unpack 到 snapshotter,镜像入库,Pod 创建
而 kubeadm init 拉控制面镜像走的是另一条路径:镜像名已被拼成 registry.aliyuncs.com/google_containers/kube-apiserver:vX.Y.Z,主机名是 registry.aliyuncs.com,不命中任何 mirrors 规则,直连阿里云(国内可达,无需代理)。
两者的分工:mirrors 透明代理管第三方镜像(yaml 里名字固定的), imageRepository / pause 改名换源管 k8s 官方组件——前者在 resolver 层替换 endpoint,后者在生成镜像名时直接换掉前缀。