最近搭了一套全新的测试环境,从裸机到 Kubernetes 1.33.7 集群的完整安装部署,全程走 kubeadm 路线。1.33.7 算目前比较新的补丁版本,网上很多教程还停留在 v1.26、v1.28 的旧节奏里,直接照抄容易在版本兼容性上报一堆错。这篇文章把整个过程拆开写清楚:先讲选型和规划,再给出可复制的初始化配置、网络插件安装、Worker 节点加入方法,最后梳理几个我实际踩过的坑。适合第一次用 kubeadm 搭集群的新手,也适合从旧版本迁移过来、想快速验证新版本行为的运维老手。
1. 部署前的思路与方案选型
1.1 为什么我仍然选择 kubeadm
接触过集群部署的人都知道,业界能选的工具确实不少。二进制部署要手动生成证书、手写 systemd unit、自己组 etcd 集群,一套流程非常容易出错;kubespray、kops 这类项目适合批量交付场景,但对使用者要求高,遇到问题不好定位;Rancher 则引入了产品层依赖,反而把简单问题复杂化。
kubeadm 是官方维护的项目,最大的优势是“可预期”:证书自动生成、etcd 以 static pod 方式托管、控制面组件全部编排到 manifest 里,升级路径经过官方校验。生产集群能跑,测试环境更是标配。很多人的顾虑是担心 kubeadm 不够“底层”,实际用下来,它的日志和配置都足够透明,出问题能看到是哪一个组件在报错,排障效率比黑盒脚本高得多。
1.2 环境规划与版本组合
我这次准备了三台 Ubuntu 22.04 LTS 虚拟机,网络互通,规格如下:
| 角色 | 主机名 | 配置 | IP |
|---|---|---|---|
| Master | master01 | 2C4G / 50G | 192.168.1.11 |
| Worker | node01 | 4C8G / 100G | 192.168.1.12 |
| Worker | node02 | 4C8G / 100G | 192.168.1.13 |
版本组合是整套部署的灵魂。装之前先列清楚:
| 组件 | 版本 | 说明 |
|---|---|---|
| Kubernetes | v1.33.7 | kubeadm / kubelet / kubectl 三者严格同版本 |
| containerd | 2.0.x | K8s 1.33 不再内置 Docker 支持,统一走 CRI |
| Calico | v3.32.x | CNI 插件,需要查对 1.33 的兼容矩阵 |
| etcd | 3.5.x | 由 kubeadm 自动部署,不需要单独手动装 |
Kubernetes 1.33 版本里,CRI 只认 containerd 这类标准实现,Docker 本身已经被踢出主流程。如果还停留在“K8s 装 Docker”的老观念里,第一关就过不去。CNI 插件的版本也要注意,不能随手选个最新版,得先看官方 support matrix。
1.3 提前定好的三个网络决策
网络规划一定要在初始化之前确定,因为podSubnet和serviceSubnet一旦初始化完成,后期再改只能重新建集群,非常痛苦。我的选择:
- Pod 网段:
10.244.0.0/16,和 Calico 的默认配置对齐 - Service 网段:
10.96.0.0/12 - kube-proxy 模式:IPVS,而不是默认的 iptables
选择 IPVS 的原因很简单:当集群里 Service 数量变多、规则条目膨胀时,iptables 的链式遍历性能会肉眼可见地下降,IPVS 基于哈希表转发,规则再多也能保持稳定。这个选择对后续的维护收益很大,值得在初始化前就写好。
2. 基础环境准备
2.1 三台机器的统一初始化
无论 Master 还是 Worker,前三步完全一致:改主机名、配 hosts、关 swap。
改主机名比较简单:
sudo hostnamectl set-hostname master01 # worker 节点改成 node01/node02hosts 解析也顺手加上:
cat <<EOF | sudo tee -a /etc/hosts 192.168.1.11 master01 192.168.1.12 node01 192.168.1.13 node02 EOF然后关 swap。虽然 K8s 新版本里已经提供了 NodeSwap 特性,但默认行为仍然是禁用,与其去折腾特性开关,不如直接关掉:
sudo swapoff -a sudo sed -i '/swap/s/^/#/' /etc/fstab注意:
swapoff -a只对当前会话生效,不改/etc/fstab的话重启后又会挂载回来,所以两行必须一起执行。
时间同步容易被忽略,但它直接影响证书校验和 etcd 选主。我在这三台机器上都装了 chrony:
sudo apt install -y chrony sudo systemctl enable --now chrony测试环境的防火墙我直接关了,生产环境只需要放行必要端口,包括 6443、10250、2379/2380 等,不需要全开。
2.2 内核模块与网络栈参数
K8s 的 Pod 网络依赖 Linux 内核的网络桥接功能,两个模块必须加载:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF然后是 sysctl 参数:
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这两个配置背后的原理值得说一下。net.bridge.bridge-nf-call-iptables=1的意思是:经过 Linux 网桥的流量也要经过 iptables 规则过滤。如果没有这一项,Pod 之间的流量在跨节点通信时会出现 DNS 解析超时、Service 访问不通等诡异问题。net.ipv4.ip_forward=1则是让数据包能在节点之间转发,相当于给集群流量打开了路由开关。
2.3 containerd 安装与两处关键配置
containerd 的安装直接用 apt 就好:
sudo apt install -y containerd装完先不要急着启动,配置需要调整两个关键点。生成默认配置:
sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml第一处,SystemdCgroup改成true:
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml这个改动怎么强调都不为过。kubelet 默认的 cgroup 驱动是systemd,而 containerd 生成的默认配置是cgroupfs,两者不一致会导致 kubelet 启动时报failed to validate kubelet flags,节点永远进不了 Ready 状态。
第二处,是sandbox_image。先用下面的命令确认当前版本的 pause 镜像具体是哪个版本:
kubeadm config images list --kubernetes-version v1.33.7里面会有一行形如registry.k8s.io/pause:3.x的镜像地址。如果你的网络访问registry.k8s.io偏慢或受限,就把config.toml里的sandbox_image改成可访问的镜像仓库地址,仓库路径保持到pause:3.x。改完记得:
sudo systemctl restart containerd我用crictl info验证过一次配置是否生效,比直接开跑 kubeadm init 更稳妥。
2.4 安装 kubeadm、kubelet、kubectl 并锁版本
三个二进制必须同版本,这是整篇文章里最重要的经验之一。用官方pkgs.k8s.io源安装:
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt update先看源里有哪几个精确版本:
sudo apt-cache madison kubeadm然后手动指定 1.33.7 那个版本号安装,不要用apt install kubeadm默认拉最新:
sudo apt install -y kubelet=1.33.7-* kubeadm=1.33.7-* kubectl=1.33.7-* sudo systemctl enable kubelet注意:kubelet 装完只需要
enable,不需要start。集群还没初始化时,kubelet 启动会因为没有 kubeconfig 而反复报错,这是正常的,等 init 之后它自然会被拉起来。
3. 使用 kubeadm 初始化 Master 节点
3.1 初始化配置文件解析
我不太推荐直接敲一长串 kubeadm init 参数,写一份配置文件可以保留存档,后续维护和升级都比命令行清楚。新建kubeadm-config.yaml:
apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.11 bindPort: 6443 --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: v1.33.7 imageRepository: registry.k8s.io # 网络受限环境改成可用镜像仓库 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs每个字段都说一下。advertiseAddress填的是本机在集群内网里的地址,千万别填公网 IP;podSubnet必须和 CNI 插件期望的网段一致,我这里选 10.244.0.0/16 就是 Calico 默认值;serviceSubnet保持默认 10.96.0.0/12 即可。kubeadm.k8s.io/v1beta4是当前 1.33 支持的 API 版本,老博客里的v1beta2、v1beta3在旧版本里能用,新版会直接拒绝。
3.2 预拉镜像与正式 init
配置写好后,先把镜像拉全,这一步能提前暴露网络问题,不用等到 init 中途才发现:
kubeadm config images pull --config kubeadm-config.yaml -v=3拉到镜像后,执行初始化:
sudo kubeadm init --config kubeadm-config.yaml如果只需要单控制面,这里不用加--upload-certs。只有打算后续扩容多个控制面时才需要,它会额外生成一份证书加密密钥。
初始化成功的标志是终端打印出这段:
Your Kubernetes control-plane has been initialized successfully!同时会输出两段关键信息:为普通用户配置 kubectl 的命令,以及给 Worker 节点用的kubeadm join命令。这两段内容建议复制保存,后面一定会用到。
3.3 当前用户配置 kubectl
初始化过程会生成/etc/kubernetes/admin.conf,这是集群的管理员 kubeconfig 文件。普通用户要操作集群,需要把它放到用户目录:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config然后验证:
kubectl get nodes这时候 Master 节点会出现,但状态大概率是NotReady,别慌,不是集群坏了,是 CNI 还没装。admin.conf这个文件就是集群的钥匙,丢了或者证书过期,都要通过kubeadm certs renew all重新生成。
3.4 单控制面与高可用之间的选择
我这次为了演示,先用了单控制面。生产环境里至少需要 3 个控制面节点,配合外部负载均衡器做流量入口。单控制面重启 Master 会导致整个集群不可用,测试环境无所谓,生产这样干就是事故。
真正要扩容控制面时,集群初始化阶段就需要在ClusterConfiguration里指定controlPlaneEndpoint,也就是负载均衡器的虚拟 IP,然后额外部署外部负载均衡器。这一步不建议新手一上来就做,先把单控制面的网络、证书、调度逻辑弄明白,再上 HA 会更顺。
4. 部署 CNI 网络插件
4.1 不装网络插件时你是看不到 Ready 的
很多人第一次装集群,卡在 Master 和 Worker 都是NotReady的状态,第一反应是 kubelet 出了问题。其实这一步大概率只是缺了 CNI 插件。K8s 本身不会负责 Pod 网络互通,它把网络能力交给了第三方插件。没有 CNI,节点上的 Pod 网络根本建不起来,自然也就无法上报 Ready。
你可以看到kube-system空间里的 CoreDNS 一直Pending,这也是同一个原因:Pod 没有网络,调度器把它放在那里却起不来。
4.2 选型对比与 Calico 安装
CNI 的选择会影响集群的网络策略和安全模型。常见的有三种:
| 插件 | 性能 | NetworkPolicy | 典型场景 |
|---|---|---|---|
| Calico | 中高 | 完整支持 | 通用部署,资料多,兼容性好 |
| Cilium | 高 | 完整支持 | 偏云原生网络与可观测性场景 |
| Flannel | 中低 | 不支持 | 只要求全连通的最小集群 |
我选了 Calico,因为它对 iptables 和 BGP 的方式支持成熟,部署资料多,遇到问题容易找到答案。K8s 1.33 下建议选 Calico v3.32.x,装之前先到官方兼容矩阵确认。
部署 Calico 我习惯用 Operator 方式:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml然后准备custom-resources.yaml,把里面的cidr改成我们规划好的 Pod 网段:
apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - name: default-ipv4-ippool blockSize: 26 cidr: 10.244.0.0/16 encapsulation: VXLAN natOutgoing: true执行:
kubectl apply -f custom-resources.yaml4.3 验证网络
安装后观察 Calico 组件状态:
kubectl get pods -n calico-system等calico-node都变成Running,再回头看节点:
kubectl get nodesMaster 应该已经是Ready状态了,CoreDNS 也会从Pending变成Running。为了确认 DNS 解析真的可用,我放了个测试 Pod:
kubectl run test-dns --image=busybox --rm -it --restart=Never -- nslookup kubernetes.default能正确解析出10.96.0.1这个 Service 地址,说明集群网络全链路通了。
5. 扩容 Worker 节点
5.1 Worker 节点上的重复准备工作
Worker 和 Master 在基础环境上没有任何区别,把第 2 章的操作原样跑一遍:改主机名、关 swap、加载内核参数、装 containerd、装 kubeadm/kubelet/kubectl。唯一不需要做的是kubeadm init。
如果你希望 Worker 也提前把镜像准备好,可以执行kubeadm config images pull,但是 Worker 节点真正需要的镜像会在 join 时自动拉取,这里不是必须操作。
5.2 生成并执行 join
在 Master 上生成一条带 token 的 join 命令:
kubeadm token create --print-join-command --ttl 24h输出会类似这样:
kubeadm join 192.168.1.11:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxx把整条命令复制到 node01、node02 上执行。这个 token 默认 24 小时过期,过期后再生成一次就行。
join 成功后,在 Master 上看节点状态:
kubectl get nodes -o wide新节点在部署并启动好 CNI 插件后,会从NotReady自动转成Ready。这里有个细节:Worker 节点不需要单独装 Calico,因为 CNI 插件会以 DaemonSet 方式自动调度到所有节点上。
5.3 用 nginx 做一次端到端验证
节点加进来了,不代表调度链路完全没问题。我通常会用 Deployment 验证一下跨节点调度和 Service 转发:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test spec: replicas: 2 selector: matchLabels: app: nginx-test template: metadata: labels: app: nginx-test spec: containers: - name: nginx image: nginx:1.27 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-test spec: type: NodePort selector: app: nginx-test ports: - port: 80 targetPort: 80 nodePort: 30080应用后确认两个副本落在不同节点上:
kubectl get pods -o wide再用任意一台节点的 IP 加 NodePort 访问:
curl http://192.168.1.12:30080能返回 nginx 欢迎页,说明从 Pod 创建、跨节点调度、kube-proxy 转发到 NodePort 端口映射,整条链路都通了。
6. 常见问题与排查技巧实录
6.1 kubelet 起不来,journalctl 刷屏
初始化过程中最常见的问题就是 kubelet 起不来。遇到这种情况,先看日志:
sudo journalctl -u kubelet -f --no-pager最常出现的几类错误我整理了一张速查表:
| 报错关键字 | 原因 | 处理 |
|---|---|---|
failed to load kubeconfig | kubelet 还没被 init/join 初始化 | 确认是否执行过 kubeadm init/join |
unknown service runtime.v1alpha2 | kubelet 找错 CRI socket | 确认 containerd 已启动,socket 路径正确 |
cgroup driver is not set to systemd | containerd 的 SystemdCgroup 没改成 true | 检查 config.toml 并重启 containerd |
version of kubelet is not equal to kubeadm | kubelet 和 kubeadm 版本不一致 | 用kubeadm version/kubelet --version对比后重新装 |
版本不一致这个坑我踩过一次:apt 安装时没锁版本,kubelet 被拉到了更新补丁版,kubeadm 还是旧的,kubeadm init时直接报版本校验错误,只能卸载重装对齐版本。
6.2 kubeadm init 卡在拉镜像
kubeadm init卡在downloading images这一步,基本就是网络访问registry.k8s.io太慢或被限了。两个解法:
- 提前执行
kubeadm config images pull --config kubeadm-config.yaml,看它卡在哪一步 - 把
imageRepository换成可用的镜像仓库地址,同步修改 containerd 的sandbox_image
有一点要留意:拉镜像问题不会影响已有配置,但如果你改了 containerd 的sandbox_image,改完必须重启 containerd,否则新的 pause 镜像不会生效。
6.3 CoreDNS 一直 Pending
CoreDNS 一直 Pending,绝大多数情况是 CNI 没装或者装错了。按这个顺序排查:
kubectl get pods -n kube-system | grep coredns kubectl describe pod -n kube-system coredns-xxxxx如果事件里全是FailedScheduling,去看节点上的 CNI 配置目录:
ls /etc/cni/net.d/目录为空,说明网络插件没装成功,回到第 4 章重新部署。如果 CNI 装了但 CoreDNS 还是起不来,看日志:
kubectl logs -n kube-system coredns-xxxxx常见报错是 IP 池不匹配,也就是 Calico 的cidr和初始化时的podSubnet不一致。这个只能重装 Calico 并保证配置一致。
6.4 节点反复 NotReady
节点状态在 Ready 和 NotReady 之间反复横跳,通常是下面几种情况。用kubectl describe node <name>看 Conditions 一栏,能直接看到是哪条告警。
| Condition 类型 | 常见原因 | 处理 |
|---|---|---|
MemoryPressure | 节点内存不足 | 清理无用 Pod 或扩容 |
DiskPressure | 镜像占满根分区 | 用crictl rmi --prune-all清理 /var/lib/containerd |
PIDPressure | 进程数超限 | 检查异常进程 |
Ready反复切换 | kubelet 心跳上报失败 | 排查网络,检查 chronyd 时间同步 |
时间偏移这个问题容易被人忽略。节点时钟和 apiserver 偏差太大时,kubelet 上报的证书验证会失败,节点直接被标记为 NotReady。装好 chrony 是最省事的预防手段。
6.5 kubeadm reset 的正确打开方式
初始化搞砸了不要急着重装系统,kubeadm reset可以把环境恢复到一个相对干净的状态:
sudo kubeadm reset --force sudo rm -rf /etc/cni/net.d ~/.kube sudo iptables -F sudo iptables -t nat -Frm -rf /etc/cni/net.d是为了清掉 CNI 插件的残留配置,iptables清空是为了防止旧规则影响下一次初始化。如果只执行kubeadm reset不清 iptables,第二次 init 之后可能会出现 Service IP 不通的诡异问题,排查半天才发现是残留规则在作怪。
我个人在整套环境搭下来后最大的体会是:版本一致、网段一致、镜像源一致,这三个“一致”做到了,kubeadm 基本能一次过。版本一致是指 kubeadm/kubelet/kubectl 严格同码同版本;网段一致是指初始化配置和 CNI 配置里的 Pod 网段完全匹配;镜像源一致是指 containerd 拉取镜像的仓库地址在 Master 和 Worker 上保持统一。这三点里任何一点没对齐,后面就是各种看起来毫不相关的玄学故障。
另外建议把kubeadm-config.yaml这种初始化配置纳入版本管理,别放在 root 目录下吃灰。集群出了问题时,你手里有一份可追溯的初始配置,排障会轻松很多。希望这份部署记录能让你少熬几个夜。