1. 大多数人搭 K8s 集群失败,不是因为命令敲错
先说个反直觉的现象:kubeadm init跑完报错,九成以上不是网络问题,就是镜像拉不下来,但真正让新手心态崩掉的,往往是“明明照着教程敲了,怎么结果不一样”。原因就一个——Kubernetes 的版本迭代太快,教程之间相差三个月,行为就可能完全不同,而你很可能拿了一篇老教程配新版本,或者反过来。
这篇文章存在的意义很简单:我把自己从零搭建一套可用 K8s 集群的完整过程、每一步背后的原理、以及踩过的坑,全部摊开来讲。无论你是刚接触容器编排的运维新人,还是想在公司内部落地一套测试环境的后端开发,都可以照着这份指南一步步操作。文章会覆盖环境规划、系统初始化、容器运行时安装、控制平面初始化、工作节点接入、网络插件部署、配置验证、以及后续日常管理中最容易翻车的几个环节。
整份指南基于Ubuntu 22.04 LTS + Containerd + kubeadm + Calico这套组合,是目前社区最主流、资料最全、坑相对最少的搭配。如果你用的是 CentOS 7 或别的发行版,原理完全一样,只是个别包管理器命令和内核参数略有差异,我会在对应位置做补充说明。
2. 动手之前,先把集群拓扑和版本策略定死
2.1 我的环境规划(3 节点最小可用集群)
K8s 最少可以跑单节点,但生产或测试至少要 3 个节点才有意义——1 个控制平面 + 2 个工作节点。因为控制平面挂了,集群还能撑一阵,但连控制平面都只剩一个的话,整个集群实际上处于“能跑但不可管”的状态。
我这次用的是一套 3 节点虚拟机:
| 角色 | 主机名 | 配置 | IP |
|---|---|---|---|
| 控制平面 | k8s-master | 4C / 8G / 50G | 192.168.1.10 |
| 工作节点 | k8s-node1 | 4C / 8G / 50G | 192.168.1.11 |
| 工作节点 | k8s-node2 | 4C / 8G / 50G | 192.168.1.12 |
关于配置我要多说两句。网上很多教程说 2C2G 也能跑,确实能跑,但你会非常痛苦——etcd 本身就吃内存,控制平面的几个组件(kube-apiserver、kube-controller-manager、kube-scheduler)加在一起轻松吃掉 2~3G,再跑几个业务 Pod 内存基本就告急。如果你是拿自己电脑开虚拟机练手,建议至少 4G 内存起步,磁盘 50G 是最低配置,因为镜像、容器日志、 etcd 数据都会持续增长。
2.2 版本选择:不要一味求新,要选“稳定且互相兼容”的组合
这一步我单独拎出来讲,因为它决定了你后面 90% 的坑会不会出现。
Kubernetes 的版本号规则是 v1.xx.y,其中 xx 是次要版本。官方支持最近三个次要版本,也就是说 v1.28 发布后,v1.25 就停止维护了。但“支持”和“推荐”是两回事,我个人的建议是:选择比最新版本落后 1~2 个次要版本,比如最新是 v1.31,你就选 v1.29 或 v1.30。为什么?因为新版本发布后,生态组件(CNI、CSI、ingress-controller)的兼容性需要一段时间才能跟上,你等老鸟们先把坑踩完,再上不迟。
更关键的是,控制平面和工作节点的版本不能差太多,最多允许一个次要版本的偏差。比如 master 是 v1.29,node 最高只能到 v1.30,否则 kubelet 和 kube-apiserver 之间的 API 协商会出问题。
我这次使用的版本组合:
- Kubernetes:v1.29.x
- Containerd:1.7.x
- Calico:v3.28.x
- 操作系统:Ubuntu 22.04(内核 5.15)
为什么选 Containerd 而不是 Docker?K8s 在 v1.24 版本之后彻底移除了对 Docker 作为运行时(dockershim)的支持,虽然 Docker 仍然可以通过 cri-dockerd 这个适配层来用,但官方推荐的默认运行时就是 Containerd。你如果坚持用 Docker,等于多引入了一层转换,性能损耗且不说,排错链路也变长了。听我一句劝,新集群直接上 Containerd,别纠结。
2.3 先想清楚你的网络模型
这是搭建之前最容易被忽略、但影响最深远的一个决策。K8s 本身只定义了一套网络模型(CNI,Container Network Interface),但具体实现要你自己选。
常见的 CNI 方案有 Flannel、Calico、Cilium、Weave Net,它们的核心区别在于网络策略(NetworkPolicy)的支持程度和转发性能。Flannel 只做连通性,不支持策略;Calico 基于 BGP,天然支持网络策略,性能也不错;Cilium 基于 eBPF,性能最好,但学习曲线陡。
我这个系列用 Calico,原因很简单:它既支持 NetworkPolicy,又不依赖特定的内核版本(Cilium 要求内核 5.8+,Ubuntu 22.04 默认 5.15 没问题,但如果你想在 CentOS 7 上跑,内核 3.10 就尴尬了),而且 BGP 模式在中小规模集群里性能完全够用。
3. 系统初始化:把所有节点的环境“拉齐”
3.1 主机名、hosts、以及关闭 swap 的真相
这一步看起来基础,但很多人就是在这一步偷懒,导致后面 kubeadm init 时各种莫名其妙的问题。
首先,给每个节点设置唯一的主机名:
# master 节点 sudo hostnamectl set-hostname k8s-master # node1 节点 sudo hostnamectl set-hostname k8s-node1 # node2 节点 sudo hostnamectl set-hostname k8s-node2然后,修改每个节点的 /etc/hosts,让所有节点能通过主机名互通:
sudo tee -a /etc/hosts <<EOF 192.168.1.10 k8s-master 192.168.1.11 k8s-node1 192.168.1.12 k8s-node2 EOF接着是关闭 swap。这一步不是“建议”,而是“必须”。K8s 要求禁用 swap 的原因是:当节点内存不足时,如果 swap 还在,kubelet 会认为内存还有余量,不会及时触发 Pod 驱逐(eviction),结果就是整个节点卡死。v1.22 之后 K8s 引入了对 swap 的实验性支持,但默认仍然要求关闭,别去踩这个坑。
sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab注意swapoff -a只是临时关闭,重启就失效了,所以必须同时修改 /etc/fstab 用注释方式永久关闭。
3.2 内核模块与系统参数:iptables 的桥接流量是关键
K8s 的 Service 和 Pod 之间的通信依赖 iptables(或 IPVS)来做转发,而 iptables 处理桥接流量需要特殊的配置。这里有两项:内核模块 br_netfilter 必须加载,net.bridge.bridge-nf-call-iptables 必须设为 1。
# 加载 br_netfilter 模块 cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # 设置必要的 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如果不做这一步,最典型的报错是 kubeadm init 时提示 “WARNING: bridge-nf-call-iptables is disabled”,或者 Pod 间网络时通时不通。我见过太多人卡在这里——明明容器都起来了,但 Service 就是访问不了,结果一查是 bridge-nf-call-iptables 没开。
3.3 同步时间:比你想的更重要
这个很容易被忽略,但重要程度不亚于关闭 swap。K8s 集群里证书的签发与校验、日志的时序分析、etcd 的 raft 选举,全都依赖节点时间的一致性。节点间时钟偏差超过几秒,轻则证书校验失败,重则 etcd 集群脑裂。
sudo apt install -y chrony sudo systemctl enable chrony sudo systemctl start chrony # 验证时间同步状态 chronyc sources -v如果你用的是虚拟机,建议同时关闭 time sync 相关的选项,以避免宿主机的时间同步和客户机冲突。VMware 里可以在 VM Options 里关掉 “Time synchronization”,VirtualBox 则在设置里关闭 “Guest Additions” 的时间同步。
4. 安装 Containerd:别直接 apt install,要配置 systemd cgroup 驱动
4.1 从 Docker 官方源安装,或者用发行版自带源
Containerd 的安装有两种常见方式:一种是从 Docker 官方 apt 源装,另一种是用发行版自带的源。我推荐用 Docker 官方 apt 源,原因很简单:版本更新快,而且和 K8s 社区的兼容性测试同步。
sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install -y containerd.io4.2 关键配置:cgroup 驱动必须是 systemd
这是新手翻车率最高的一个环节。K8s 在 v1.24 之后默认使用 systemd 作为 kubelet 的 cgroup 驱动,而 Containerd 默认配置是 cgroupfs,两者不一致就会导致 kubelet 无法启动,报错信息是 kubelet cgroup driver 不匹配。
安装完 Containerd 后,需要先导出默认配置,再修改:
mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml然后编辑 /etc/containerd/config.toml,找到[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]这一段,把SystemdCgroup从false改为true:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true顺手还要处理镜像加速的问题。由于众所周知的网络原因,直接拉取registry.k8s.io的镜像大概率会失败。有两种加速方式:一种是通过 systemd 配置镜像加速器,让 Containerd 在拉取镜像时自动替换 registry 地址;另一种是使用国内镜像站手动拉取镜像后重新 tag。我使用的是前者,在 config.toml 中配置 mirror,把registry.k8s.io指向一个可用的加速器地址,这样 kubeadm init 时的镜像检查就能顺利通过。
改完配置后重启 Containerd:
sudo systemctl restart containerd systemctl status containerd确认服务状态是 running 且没有报错,再进行下一步。
5. kubeadm 安装与集群初始化:成功的人各有各的顺利,失败的人卡在同一处
5.1 kubeadm、kubelet、kubectl 的安装
这里有一个版本锁定的问题。如果你直接用apt install kubelet kubeadm kubectl,大概率装的是最新版。对于学习和测试来说没问题,但如果你想复现我这份指南,建议明确锁定版本:
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/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.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet=1.29.* kubeadm=1.29.* kubectl=1.29.* sudo apt-mark hold kubelet kubeadm kubectlapt-mark hold非常重要,它防止apt upgrade时把 K8s 组件静默升级到更高版本,导致集群版本不一致。我在生产环境里见过不止一次,因为没 hold 版本,一次系统更新后 kubelet 和 apiserver 版本差了两个次要版本,整个集群直接失控。
5.2 提前解决镜像问题:kubeadm config images pull 检查
在正式执行kubeadm init之前,先手动拉一遍镜像,确认网络和镜像源都没问题:
kubeadm config images list这一步会列出所有需要拉取的镜像清单,包括 kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、pause、etcd、coredns。
确认清单无误后,执行:
sudo kubeadm config images pull如果你看到所有的 image 都显示 pulled successfully,说明基础镜像这一关已经过了。如果这里报错,千万别继续往后走——回去检查 Containerd 的 mirror 配置,或者用 crictl 手动拉镜像后再重新打 tag 的方式解决。
5.3 kubeadm init:最重要的几个参数
执行如下命令(注意替换为你自己的 IP 和 Pod 网段):
sudo kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --kubernetes-version=v1.29.0 \ --control-plane-endpoint=k8s-master参数含义:
--apiserver-advertise-address:APIServer 对外宣告的 IP,通常是控制平面节点的 IP。如果节点有多网卡,必须显式指定,否则 kubeadm 可能选错网卡。--pod-network-cidr:Pod 网段,这个网段必须和后续 CNI 插件配置一致。我用 10.244.0.0/16 是因为 Calico 的默认配置里包含这个网段,如果你用 Flannel,默认也是 10.244.0.0/16。如果你用了其他网段,必须调整 CNI 配置。--service-cidr:Service 网段,默认 10.96.0.0/12 即可。--kubernetes-version:显式指定版本,避免 kubeadm 拉取默认版本的镜像。--control-plane-endpoint:控制平面统一入口。单控制平面可以填主机名或 IP,但在搭建高可用(HA)集群时,这个值是负载均衡器的地址,非常重要。我在这一步直接写主机名,可以让 kubeconfig 里记录的地址是主机名,在某些场景下比裸 IP 更方便。
kubeadm init执行成功后,末尾会输出三块关键信息:
- kubectl 的配置方法(就是把 admin.conf 拷到 ~/.kube/config)
- 工作节点加入集群的完整命令(kubeadm join 加上 token)
- token 过期时间(默认 24 小时,过期后需要重新生成)
先按提示配置 kubectl:
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 网络插件还没部署呢。
5.4 Pod 网段冲突与调试思路
在实际操作中,最容易忽略的一个坑是:Pod 网段和现有网络冲突。比如你的公司内网是 10.0.0.0/8,你再把 Pod 网段设成 10.244.0.0/16,这里的 10.244 网段虽然不冲突,但如果你用了 10.10.0.0/16 这种,内网设备很可能会访问不到集群里的 Pod。这个在设计初期就得想清楚,网络规划不平衡,集群搭建完了再改 Pod 网段是非常痛苦的事。
另外补充一个调试技巧:等到 Pod 一直处于 ContainerCreating,不要只盯着 kubectl get pods 看,用kubectl describe pod xxx -n kube-system来看事件(Events),大部分情况下 Event 里会直接把真正原因写清楚(镜像拉取失败、挂载失败、CNI 冲突等)。会看 describe 出来的 Events,比会敲命令重要得多。
6. 部署 Calico 网络插件:Pod 间通信的“高速公路”
6.1 下载并修改 Calico 清单
Calico 的安装很简单,官方提供了一份完整的 YAML 清单:
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/calico.yaml默认配置里已经包含了很多内容,包括 CRD、RBAC、DaemonSet、Service 等。我们需要关注的是 CALICO_IPV4POOL_CIDR 这个环境变量:
grep -n "CALICO_IPV4POOL_CIDR" calico.yaml默认值通常是192.168.0.0/16,如果你的--pod-network-cidr用的不是这个网段,必须改掉,否则 Calico 分配的 Pod IP 和 apiserver 规划的网段不匹配,CNI 会一直报错。
kubectl apply -f calico.yaml然后观察状态:
kubectl get pods -n kube-system等所有 pod 都是 Running 后,再看节点状态:
kubectl get nodes这时候 master 应该已经是 Ready 状态了。
6.2 为什么选 BGP 模式而不是 VXLAN 模式
Calico 支持两种主要的数据面模式:BGP 和 VXLAN。BGP 模式直接通过节点的三层网络交换路由,性能最好,延迟低,适合网络环境简单、所有节点在同一个二层域的场景;VXLAN 是基于 UDP 的隧道封装,适合跨子网、跨机房的场景,但会有额外的封装开销和延迟。
如果是同机房、同网段的节点,直接用默认的 BGP 模式,不用改。如果你的节点跨网段,才需要考虑 VXLAN。怎么改?
在 calico.yaml 中,找到环境变量CALICO_IPV4POOL_VXLAN,设置为Always或CrossSubnet。这个配置不当容易导致跨节点 Pod 通信不通,排查起来最耗费时间。
6.3 部署后验证网络
我常用的验证方式,是启动一个 busybox Pod,然后从它去 ping 任意节点的 Pod IP:
kubectl run test-pod --image=busybox --restart=Never -- sleep 3600 kubectl exec -it test-pod -- ping <某个Pod的IP>能通说明 CNI 转发这条链路是完好的。这里补充一句:ping通只是最基础的网络连通性验证,但 K8s 集群中持续在用的核心通信对象是 Service 的 ClusterIP。
7. 工作节点加入集群:这个环节最容易出的问题
7.1 执行 kubeadm join
master 初始化完成后,工作节点上需要执行kubeadm join命令。如果 token 过期(24小时),或者你忘记保存 join 命令了,可以通过如下方式重新生成:
# 在 master 上重新生成 token kubeadm token create --print-join-command然后在 node1 和 node2 上执行(注意替换成你自己的 token 和 hash):
sudo kubeadm join 192.168.1.10:6443 \ --token <token> \ --discovery-token-ca-cert-hash sha256:<hash>执行成功后会看到输出:This node has joined the cluster。
回 master 执行:
kubectl get nodes最尴尬的场景出现了:节点状态是 NotReady。怎么办?
7.2 节点 NotReady 的完整排查链路
先别急着到处搜答案,按这个顺序一步步查:
第一步,确认 kubelet 状态
systemctl status kubelet # 或 journalctl -u kubelet -f如果 kubelet 没起来,大概率是配置文件有问题或 cgroup 驱动不匹配,需要重点观察启动日志中是否出现failed to run Kubelet或cgroup driver等字样。
第二步,查看 kubelet 日志里的关键错误
journalctl -u kubelet --no-pager | grep -i error | tail -20如果看到Failed to connect to apiserver,检查节点到 master 的 6443 端口是否通:nc -vz 192.168.1.10 6443。如果不通,先解决网络连通性。
第三步,检查 node 的注册信息与 CNI 是否报错
kubectl describe node k8s-node1看 Conditions 字段,重点看Ready是 True 还是 False,以及报错信息里提到 Ready=False 的原因。如果报CNI plugin not initialized,则是 CNI 相关的问题,去节点上查看 kubelet 日志。
这一套链路排查下来,95% 的问题都能定位到。
7.3 如果 join 后怎么都加不上:从节点上彻底重置
当你试了很多办法还是不行,或者你改了配置想重新加入节点时,最干净的方式是在节点上执行:
sudo kubeadm reset sudo rm -rf $HOME/.kube /etc/cni/net.d然后在 master 上删除旧节点(如果是同一个节点重新加入,通常 master 端会自动清理,但保险起见先 delete 一次):
kubectl delete node k8s-node1再重新执行 join。这个方法对“节点死活加不上”有奇效,但属于“最后手段”,先按 7.2 的链路排查完再用。
8. 集群验证与第一份工作负载:验证 Pod、Service、Ingress
到这里,恭喜你,你的集群已经可以正常工作了。但“能跑”和“能用”是两回事,我习惯把 K8s 集群的验证动作做完整,确认这套集群不只是表面 Ready,而是真正可以承载业务。
8.1 部署一个 Nginx 应用并暴露为 NodePort
先创建一个简单的 Deployment:
kubectl create deployment nginx-demo --image=nginx:1.25 kubectl scale deployment nginx-demo --replicas=3查看 Pod 分布:
kubectl get pods -o wide你会看到 3 个 Pod 被调度到不同节点上,IP 是 Calico 分配的 10.244.x.x 网段。这时候用curl 10.244.x.x可以直接访问 Nginx 首页(如果通了,说明 CNI 正常)。
接着创建 Service:
kubectl expose deployment nginx-demo --port=80 --type=NodePort kubectl get svcService 会分配一个 ClusterIP(10.96.x.x)和一个 NodePort(30000~32767 之间随机)。这时候从任意节点执行curl 10.96.x.x能通,从外部用curl http://任意节点IP:NodePort也能通。
8.2 验证 DNS 解析与负载均衡
K8s 内置的 CoreDNS 负责集群内域名解析。在一个 Pod 里测试 Service 的域名解析:
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- sh # 在容器内 curl nginx-demo.default.svc.cluster.local能通就说明 Service 的 DNS 解析、kube-proxy 转发链路都正常。
小集群下,kube-proxy 默认走 iptables 模式,IPVS 模式需要额外在 kube-proxy 的 ConfigMap 中显式开启并确保节点上安装了 ipvsadm。但即便用默认的 iptables 模式,也能做到多副本随机轮询,你多发几次 curl,观察几个 Pod 的访问日志,能看出请求基本要轮流分配,这就算负载均衡验证过了。
8.3 crictl vs docker:容器运行时排错命令
既然用的是 Containerd,日常排错就不再是docker ps了,而是 crictl:
crictl ps crictl images crictl logs <container-id>前面我提到拉取 K8s 组件镜像时遇到问题,也可以用 crictl 手动 pull 镜像:
crictl pull registry.k8s.io/kube-apiserver:v1.29.0crictl 和 kubelet 共用同一个 containerd.sock,所以在节点上观察容器状态,它比 docker 命令更贴近 K8s 的真实视角。习惯用 docker 的同学,建议尽早切换到 crictl,否则后面排错会走弯路。
9. 踩坑实录与运维提示:我能帮你省下的每一小时
9.1 三个反复出现的经典问题
问题一:Pod 一直 ContainerCreating,卡在 ContainerCreating
排查命令:
kubectl describe pod <pod-name> -n <namespace> kubectl logs <pod-name> -n <namespace> --previous最常见原因是镜像拉取失败、存储卷挂载失败、或者 CNI 没有就绪。我见过一个典型场景是用了 hostPath 卷但没有提前创建宿主机目录,导致 Pod 一直 ContainerCreating 并在 Event 里直接给出挂载报错。解决方法是先建目录,或者换用 PV/PVC。
问题二:节点 Ready 但 Pod 之间跨节点不通
这大概率是 Calico BGP peer 没有建立成功。在节点上执行:
calicoctl node status如果没有输出 BGP peer 信息,检查节点防火墙(ufw)是否放行了 BGP 端口 179。另外还要确认 Calico 的 IP 池网段和你 kubeadm init 时配置的 pod-network-cidr 保持一致。
问题三:kubelet 起不来,日志提示 kubelet cgroup driver: "cgroupfs" is different from kubeadm cgroup driver: "systemd"
这个错误信息非常精准,说明你的容器运行时(Containerd)配置的还是 cgroupfs,回到第 4 节做修改,然后重启 Containerd,再重启 kubelet,这个问题就没了。
9.2 关于 CoreDNS 一直 Pending(或 CrashLoopBackOff)
如果你是严格按照我的步骤来做的,CoreDNS 大概率不会出问题。但如果你用的是某些特殊网络或汇总配置,CoreDNS 可能会一直 Pending 或 CrashLoopBackOff。Pending 通常是节点资源不足或调度约束导致,确认一下节点有没有污点即可;CrashLoopBackOff 则和 CoreDNS 的配置文件有关——如果你改过 ClusterIP 网段但没有同步调 CoreDNS 的上游 DNS 配置,CoreDNS 就会反复重启。
排查的时候,先看日志:
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=509.3 集群日常管理里的几个实用技巧
隔三差五看看事件与资源用量,别等告警才知道:
kubectl get events --sort-by=.lastTimestamp | tail -20 kubectl top node kubectl top pod -A在 master 上拿命令控全局:
我强烈建议日常只在 master 节点上使用 kubectl,不要随意去 node 操作。master 上的 kubeconfig 默认是 admin 权限,别随手让人拿去用了。
知识库组件升级时,先读 release notes:
升级集群不是一件小事。每次 K8s 的次版本升级都可能有破坏性的变更,比如 v1.24 移除 dockershim、v1.25 开始不再默认启用 PodSecurityPolicy 等。不管在什么环境,升级前都要先备份 etcd,并对关键配置做 snapshot。
10. 生产集群还远远不止这一步
到这里,你已经拥有了一套可以运行、可以调度 Pod 的 Kubernetes 集群。但这套集群离生产环境要求还很远——控制平面是单点、etcd 没有独立部署、没有日志监控体系、没有 ingress 控制器、没有持久化存储方案、没有命名空间隔离策略。作为起步,这个状态足够你学习和验证核心概念;作为学习路线图,你下一步很自然地会去研究高可用控制平面(kubeadm 支持多 master + 外部负载均衡)、ingress-nginx、metrics-server + Prometheus、以及基于 NFS 或云盘的 CSI 存储方案。
我最初搭第一套 K8s 集群的时候,前后折腾接近两周,大量时间花在版本不匹配和网络插件选型上。现在是2025年,社区资料比我那时丰富太多了,照着这份流程,配合耐心读日志,一套测试集群配下来三四个小时是比较正常的节奏。真踩到坑了,不要慌,先用 describe 和 journalctl 把日志完整看一遍,再动手改东西,很多“玄学”问题到最后都会发现是自己某个配置细节没对。
希望这份指南对你有实质性的帮助。练手过程中遇到任何问题,欢迎在评论区一起讨论,我看到都会回复。