入行这些年我最大的感悟是装 Kubernetes 这事儿本身一点不玄乎真正费时间的往往是那些反反复复的“手动重复劳动”——关交换分区、装运行时、对版本、改配置、等镜像然后踩一遍别人早就踩过的坑。尤其是跑到 1.31 这个版本Docker 时代遗留的那套“先装 Docker 再装 K8s”的旧习惯已经彻底过时了官方默认的容器运行时就是 Containerd。这篇博文我把整个部署过程收敛成一个可以直接跑的一键式脚本你只要改几个环境变量执行完基本就是一个能用的 Kubernetes 1.31 集群。适合想快速搭一套环境做验证、学 kubeadm 流程、或者带新同事入手的场景。1. 整体设计与思路拆解为什么这套方案值得直接抄作业动手写脚本之前我先把设计逻辑说清楚。这套部署方案不是把网上零散的教程拼在一起而是有一个明确的取舍思路尽可能贴近 kubeadm 的官方默认路径同时把那些容易反复出错的“脏活”全部脚本化。1.1 为什么运行时选 Containerd 而不是 Docker很多刚接触 Kubernetes 的人会惯性以为“先装 Docker 再装 K8s”是唯一正道因为网上老教程太多这个习惯一直延续到现在。但 Kubernetes 从 1.24 版本开始就把 dockershim 从代码里彻底移除了换句话说K8s 官方已经不再内置对 Docker 的直接支持。现在的标准做法是Kubernetes 通过 CRIContainer Runtime Interface直接对接 Containerd中间少了一层 Docker Daemon 的转发。用一张简单的对比表就能看清差别项目DockerContainerd与 K8s 关系需要额外适配层已被官方移除K8s 直接通过 CRI 对接进程模型Docker Daemon 守护进程轻量守护进程面向容器运行时镜像构建支持不支持构建交给 buildkit 等命令行工具dockerctr、crictl、nerdctl资源占用相对较重更轻生产环境首选打个比方Docker 是一台带全套厨具的餐车Containerd 是专门负责“出餐”的后厨。K8s 只需要有人把菜端出来就行不需要餐车的其他功能。所以 1.31 时代老老实实用 Containerd既省资源又少出兼容性问题。1.2 为什么选择 Kubernetes 1.31 这个版本版本选择也是门学问。1.31 属于当前维护期内的稳定版本它在 1.30 的基础上补齐了一批问题生态兼容性已经很成熟。对于想正经搭环境的人来说选太新的版本比如刚发布的 RC 版容易碰到某些组件没跟上导致的小毛病选太老的版本又要面对支持周期快到期的焦虑。1.31 处于“新但已被广泛验证”的区间是快速部署和学习的合适选择。另外从实用角度看我要用 kubeadm 部署就必须保证 kubeadm、kubelet、kubectl 三者版本一致。如果三者版本错开kubelet 和 kubeadm 之间偶发的不兼容问题会让你排查到怀疑人生。所以我的脚本里把K8S_VERSION1.31.0设成一个变量三个组件统一装同一个小版本。1.3 一键式部署脚本的定位能跑但更要能看懂我也看到过很多“一键脚本”是跑到最后没人知道干了什么出问题时两眼一抹黑。所以我这个脚本做了折中所有关键参数都放在文件顶部中间每个阶段都加注释和输出提示。你直接执行没问题但我也建议你至少完整读一遍脚本知道每个步骤在干什么出问题才能按提示去查日志。这套方案的目标是新机器从零开始一条命令完成基础环境和 K8s 组件安装初始化控制平面装好网络插件打印出 worker 节点加入集群的命令不覆盖额外的高可用方案不引入复杂的生产级配置一句话适合快速得到一个“能用”的集群不适合直接当生产高可用标准来抄。2. 部署前的环境准备这几个参数搞错后面全白搭写脚本之前先看一下需要满足的机器条件。很多人喜欢跳过这一步直接跑脚本结果跑到一半发现磁盘不够或者内核模块没启用反而更费时间。2.1 硬件与操作系统要求硬件方面没有想象中那么夸张但也不要太抠门。我列一个对照表你可以按自己的用途对号入座用途CPU内存磁盘节点数单机学习2 核4G40G1基础实验2 核4G50G2-3稍微正式的测试环境4 核8G100G3操作系统优先推荐 Ubuntu 22.04/24.04 或 Debian 12我的主脚本也是基于 Debian 系写的。如果你用的是 Rocky Linux / AlmaLinux / CentOS Stream可以用 RPM 系的软件源做法后面我会单独给替换示例。最重要的一点是三台机器的系统版本尽量一致内核版本差异太大会让排错变得复杂。2.2 主机规划IP 地址、主机名、端口集群通信是走端口的这一步千万别偷懒。假设我给三台机器做规划节点角色主机名IP 地址控制平面k8s-master192.168.1.10Worker 节点k8s-node01192.168.1.11Worker 节点k8s-node02192.168.1.12主机名不要带下划线不要有大写Kubernetes 对主机名的要求比较严格解析不干净会引发 kubelet 注册失败。端口方面控制平面至少要保证这些端口可通信6443kube-apiserver所有客户端入口2379/2380etcd 客户端和集群通信10250kubelet 健康检查和日志获取10257kube-controller-manager10259kube-scheduler30000-32767NodePort 服务端口段如果你用的是云厂商的安全组直接放行这些端口如果是本机测试又懒得搞防火墙规则临时关掉防火墙跑通流程但正式环境一定要按最小放行原则配置。2.3 系统基础项关交换分区、加载内核模块、同步时间Kubernetes 的 kubelet 强烈建议关闭 swap原因很直接Pod 的资源申请和限制是基于内存做的如果系统出现 swap 交换内存资源隔离就没意义了。脚本里会执行swapoff -a并注释掉/etc/fstab中 swap 相关行保证重启后也不会自动挂载。内核模块方面需要加载overlay和br_netfilter。overlay是容器镜像分层存储的基础br_netfilter让 iptables 能处理桥接流量这是 Kubernetes 网络通信正常工作的前提。然后再设置一组 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 sysctl --system最后是时间同步。etcd 对时钟偏移极度敏感如果节点间时间差超过几百毫秒集群可能会出现各种莫名其妙的间歇性故障。Ubuntu 上装个 chrony 或者直接用 systemd-timesyncd只要保证节点时间一致即可。3. 一键式部署脚本的实现从零到集群可用这一节是整篇的核心。我把完整脚本贴在下面并且用注释标明每个阶段在做什么。先声明脚本按 Ubuntu/Debian 系写的你在 Rocky/CentOS 上跑需要把包管理器部分替换成 dnf/yum 版我后面会专门给出差异说明。3.1 完整脚本内容#!/usr/bin/env bash set -euo pipefail # # 快速搭建 Kubernetes 1.31 Containerd 一键式脚本 # 适用系统Ubuntu 22.04 / 24.04、Debian 12 # 用法 # 1. 修改下方 MASTER_IP、POD_CIDR 等变量 # 2. 以 root 或 sudo 权限执行本脚本 # # ---------- 按需修改的参数 ---------- K8S_VERSION1.31.0 MASTER_IP192.168.1.10 POD_CIDR10.244.0.0/16 SERVICE_CIDR10.96.0.0/12 # 如果你的网络环境访问 registry.k8s.io 较慢 # 可以把 IMAGE_REPO 改成你本地网络更容易访问的镜像仓库。 IMAGE_REPOregistry.k8s.io # ---------- 全局变量 ---------- LOG_PREFIX[K8s-Deploy] log() { echo $(date %F %T) ${LOG_PREFIX} $* } # ---------- 1. 系统基础配置 ---------- log 关闭 swap... swapoff -a sed -i / swap / s/^/#/ /etc/fstab log 加载内核模块... cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter log 配置内核参数... cat EOF | 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 sysctl --system /dev/null log 设置主机名可选当前主机名$(hostname)... # 如果你需要统一主机名取消下面这行并改成你的节点名称 # hostnamectl set-hostname k8s-master # ---------- 2. 安装 Containerd ---------- log 安装 Containerd... apt-get update -y apt-get install -y ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod ar /etc/apt/keyrings/docker.gpg echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ tee /etc/apt/sources.list.d/docker.list /dev/null apt-get update -y apt-get install -y containerd.io log 生成 Containerd 默认配置... mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml /dev/null log 开启 SystemdCgroup... sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml log 重启 Containerd... systemctl enable containerd systemctl restart containerd # ---------- 3. 安装 kubeadm / kubelet / kubectl ---------- log 添加 Kubernetes apt 源... curl -fsSL https://pkgs.k8s.io/core:/stable:/v${K8S_VERSION}/deb/Release.key | \ 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:/v${K8S_VERSION}/deb/ / | \ tee /etc/apt/sources.list.d/kubernetes.list /dev/null apt-get update -y apt-get install -y kubelet${K8S_VERSION}-* kubeadm${K8S_VERSION}-* kubectl${K8S_VERSION}-* apt-mark hold kubelet kubeadm kubectl log 启动 kubelet初始化前可能处于异常状态正常现象... systemctl enable kubelet # ---------- 4. 拉取镜像并初始化控制平面 ---------- log 拉取 Kubernetes 组件镜像... kubeadm config images pull --image-repository${IMAGE_REPO} log 初始化控制平面... kubeadm init \ --kubernetes-version${K8S_VERSION} \ --control-plane-endpoint${MASTER_IP} \ --pod-network-cidr${POD_CIDR} \ --service-cidr${SERVICE_CIDR} \ --image-repository${IMAGE_REPO} \ --v5 log 配置 kubectl... mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config # ---------- 5. 安装网络插件 Flannel ---------- log 安装 Flannel CNI... kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml log 等待控制平面 Pod 就绪... kubectl wait --forconditionReady pods --all -n kube-system --timeout300s || true log 部署完成当前节点状态 kubectl get nodes log 如果你要加入 Worker 节点请复制下面的命令 kubeadm token create --print-join-command3.2 脚本里最容易写错的两个地方第一个是SystemdCgroup。Containerd 默认生成的配置里这一项是false而 kubelet 如果通过 systemd 管理kubeadm 默认就是两边 cgroup 驱动不一致节点会一直卡在 NotReady。脚本里用 sed 直接把它改成true这步漏掉后面必然翻车。第二个是--pod-network-cidr和 CNI 插件的关系。我用的是 Flannel它默认使用的网段就是10.244.0.0/16。如果你用 Calico通常会把网段设置成10.244.0.0/16或者192.168.0.0/16具体以你安装的插件为准。这里一旦不一致Pod 能创建出来但分配到的 IP 和路由对不上网络直接不通。3.3 在 Rocky Linux / AlmaLinux 上怎么改如果你用的是 RPM 系系统脚本的第三部分要换成这样cat EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://pkgs.k8s.io/core:/stable:/v1.31/rpm/ enabled1 gpgcheck1 gpgkeyhttps://pkgs.k8s.io/core:/stable:/v1.31/rpm/repodata/repomd.xml.key EOF dnf install -y kubelet kubeadm kubectl --disableexcludeskubernetesContainerd 的安装也可以直接用系统源里的containerd.io或containerd包但一定要确认版本不要太老。我建议 RPM 系直接参考 Docker 官方仓库的安装步骤或者使用系统自带的容器工具包前提是版本至少为 1.7.x。版本太老的 Containerd 和 K8s 1.31 在 CRI 适配上有概率出现兼容问题。3.4 脚本的实际执行流程执行方式很简单chmod x k8s-deploy.sh sudo ./k8s-deploy.sh整个流程跑完大概需要 5-10 分钟具体看网络速度和机器配置。看到最后kubectl get nodes输出里 master 节点是Ready状态就说明控制平面起来了。注意如果最后一步kubectl wait超时不要急着重启一切先看 CNI 插件是否装好再看 kube-system 命名空间里的 Pod 状态。这属于常见问题我在第 5 节专门讲。4. 集群初始化后的验证与基础配置脚本跑完不等于万事大吉至少要做一轮验证确保新节点能正常调度 Pod。下面是我每次部署完都会走的检查清单。4.1 检查控制平面核心组件先看节点状态kubectl get nodes期望输出大概是NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane 10m v1.31.0如果 STATUS 是NotReady先排查 kube-system 里的 Podkubectl get pods -n kube-system重点看 coredns 和 flannel 是否 Running。如果这两个组件都正常节点从 NotReady 到 Ready 一般只是时间问题如果一直 Pending 或 CrashLoopBackOff问题大概率出在镜像拉取或 CNI 配置上。4.2 kubectl 权限配置与自动补全kubeadm init 完成后默认情况下面向普通用户需要用admin.conf。脚本里已经帮你 copy 到了$HOME/.kube/config。如果你是 root 跑的要记得给普通用户也配置相同内容。顺便配置一下补全省得后面敲命令太痛苦echo source (kubectl completion bash) ~/.bashrc source ~/.bashrc4.3 用一条命令验证集群调度能力光看节点 Ready 还不够我习惯起一个临时 Pod 做冒烟测试kubectl create deployment smoke-test --imagenginx:alpine kubectl expose deployment smoke-test --port80 --typeNodePort kubectl get pods -o wide如果 Pod 顺利 Running说明从镜像拉取、调度到网络分配整条链路都没问题。测试完删掉这两个资源就行kubectl delete service smoke-test kubectl delete deployment smoke-test4.4 把 Worker 节点加进集群如果脚本是在所有节点上跑的那么你只需要在 Worker 节点上执行之前打印出来的 join 命令就能把节点加进来kubeadm join 192.168.1.10:6443 --token token \ --discovery-token-ca-cert-hash sha256:hashtoken 默认有效期是 24 小时如果你跑完脚本忘了保存 join 命令可以在 master 上重新生成kubeadm token create --print-join-commandWorker 节点加入后回到 master 上kubectl get nodes就能看到新节点上线。5. 实际部署中最容易翻车的几个问题这部分是我最想写给你们的全是实操中踩过的坑。我按出现频率从高到低排列。5.1 镜像拉取失败或超时症状kubeadm init卡在拉镜像阶段或者kube-system里的 Pod 频繁ImagePullBackOff。排查思路就三步# 1. 手动拉一下确认仓库可访问 kubeadm config images pull --image-repositoryregistry.k8s.io # 2. 如果超时就换成网络环境容易访问的镜像仓库再重新 init kubeadm init --image-repository可访问的镜像仓库 ... # 3. 查看运行时里到底有没有镜像 crictl images这里我要强调一句改镜像仓库不是让你绕过网络限制而是换一个更近、更快的源。部署前最好先确认自己所在网络到达registry.k8s.io的通畅程度不通就直接把 IMAGE_REPO 替换成你网络环境更容易访问的镜像仓库然后重新执行脚本。5.2 cgroup 驱动不一致症状kubelet 一直在CrashLoopBackOffjournalctl -u kubelet里能看到类似failed to run Kubelet: failed to create kubelet component ... cgroup driver cgroupfs的报错。这个问题的根源我在前面提过Containerd 默认是cgroupfskubeadm 默认把 kubelet 配成systemd。两边不一致kubelet 直接起不来。解决办法就是保证两边统一在/etc/containerd/config.toml中确认[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true改完重启 containerd再重启 kubeletsystemctl restart containerd systemctl restart kubelet5.3 kubelet 无法启动日志没有任何有用信息有些时候journalctl -u kubelet打出来的日志很少拼命刷content of /var/lib/kubelet/config.yaml之类。这种情况第一个要看的文件是/var/lib/kubelet/kubeadm-flags.env确认 kubelet 启动参数是否正确。另外还要检查 kubelet 的证书目录/etc/kubernetes/pki如果 kubeadm init 因为前面某步失败导致残留文件不完整再跑 init 时会卡在证书校验。老练一点的处理方法kubeadm reset -f rm -rf /etc/cni/net.d rm -rf /var/lib/kubelet/*然后再重新执行一次脚本干干净净重来比花半小时抠日志更快。5.4 Flannel Pod 一直 CrashLoopBackOff这个问题我遇到好几次原因通常是旧的 flannel.1 网卡残留。当你重复执行脚本或者切换过 CNI 插件时节点上可能残留老网卡和路由导致新的 flannel 起不来。处理方式ifconfig flannel.1 down ip link delete flannel.1 # 清理 CNI 配置 rm -rf /etc/cni/net.d/*然后重新 apply Flannel 清单。如果还是不行检查节点之间的内网是否放行了UDP 8472端口这是 Flannel VXLAN 模式用的端口。5.5 常见问题速查表症状大概率原因处理方式node 一直是 NotReadyCNI 没装好或 cgroup 不一致检查 kube-system Pod 状态确认 SystemdCgrouptruecoredns PendingCNI 没就绪或节点有污点先装 flannel/calicokubectl describe pod看事件镜像拉取失败仓库不可达换镜像仓库后 kubeadm reset 重来join 命令失效token 过期在 master 上重新生成 join 命令Pod 之间 ping 不通网段与 CNI 不匹配检查 POD_CIDR 与 flannel/calico 配置kubectl 命令提示拒绝连接KUBECONFIG 没配置export KUBECONFIG/etc/kubernetes/admin.conf6. 写在最后的几句话如果你是个新手我强烈建议拿到脚本之后先不要急着闭眼跑而是打开脚本对着 kubeadm 官网文档一段一段读一遍。自动化的价值不是让你放弃理解而是让你在理解之后免于重复劳动。我第一次搭集群全程手动敲整整折腾了大半天后来把所有步骤沉淀成脚本后面每次重建环境基本都能在十分钟内搞定这才是“一键式脚本”真正值钱的地方。这套部署方案后续还有很多可以往外扩展的地方比如把单控制平面改成高可用给 etcd 加证书备份用 Ansible 把脚本改造成批量执行甚至把 Worker 节点初始化做成独立 playbook。但不管怎么扩展底层核心还是这篇文章里这些东西。把它们吃透了后面再怎么变你都有一条清晰的排查路径而不是遇到问题只会重装系统。