kubeadm安装K8s底层走Docker:cri-dockerd完整配置指南 看到“kubeadm 安装最新 k8s、底层走 docker”这类需求很多人的第一反应是Kubernetes 不是早在 1.24 版本就移除了 dockershim 吗Docker 不是已经被“踢出”K8s 了吗怎么还有人要把 K8s 跑在 Docker 上面这个疑问非常合理但它只说对了一半。K8s 确实不再通过内置的 dockershim 直接调用 Docker但这不等于“K8s 完全不能用 Docker”。只要补上一个名为cri-dockerd的适配层Docker 就能继续以容器运行时的身份为 K8s 提供底层容器能力。换句话说用 kubeadm 装 K8s、底层走 Docker完全可行但不能再像过去那样“装完 Docker 就让 kubelet 自己去连”必须显式安装并指定 cri-dockerd 的 CRI socket。这篇文章会把这条链路完整拆开。你会看到从节点规划、系统初始化、Docker 配置、cri-dockerd 安装、kubeadm init、CNI 网络插件到 Worker 节点加入和部署测试应用的整个过程。看完之后你不仅能亲手搭出一套“底层走 Docker”的 K8s 集群还能理解为什么这套方案在 1.24 之后的 K8s 版本里需要这样设计以及它到底适合什么场景。1. 为什么还要让 K8s 底层走 Docker先回答最核心的问题既然 K8s 官方已经不默认支持 Docker 作为运行时为什么还有人想“底层走 Docker”一个非常现实的原因是很多团队过去几年的容器化体系是围绕 Docker 建立的。镜像基于 Dockerfile 构建CI/CD 里到处是docker build、docker push运维同学已经习惯了用docker ps和docker logs排查问题。如果切换到 containerd虽然功能上完全没障碍但团队的学习成本、工具链迁移成本、心理成本都不小。另一个常见场景是测试环境。你可能只是想快速验证一下 K8s 集群中的业务逻辑不想把现有 Docker 主机上的资源重新迁移到另一套运行时上。这种情况下“直接让 K8s 复用我已有的 Docker”是性价比很高的选择。但这里有三个关键认知必须建立K8s 1.24 的 kubelet 不再内置 dockershim所以不能再用旧方式直接对接 Docker 守护进程。Docker 本身也不实现 CRIContainer Runtime Interface它是面向用户的容器引擎不是直接给 K8s 用的原子运行时。cri-dockerd 承担了翻译层角色把 kubelet 的 CRI 请求转成 Docker API 调用再由 Docker 引擎去创建容器。所以kubeadm 安装 K8s 时如果希望“底层走 Docker”本质上就是引入 cri-dockerd 这个中间层。这不是 hack也不是绕过了什么限制而是社区提供的一种标准适配方案。读到这里你应该已经能判断自己是否适合这套方案了。如果你手里有一套成熟的 Docker 工具链暂时不想迁移到 containerd或者你只是想在自己的测试机上快速拉起一个 K8s 环境并且希望直接用熟悉的docker命令查看容器状态那本文就是为你准备的。如果你要搭建的是大规模生产集群我更建议在读完后面的最佳实践后再做决定。2. 架构辨析kubelet、CRI 与 cri-dockerd 的配合过程要理解“底层走 Docker”这件事不能只照着命令敲还得理解 K8s 与容器运行时之间的协调机制。K8s 中负责创建、管理容器的组件是kubelet。kubelet 并不关心底下到底是什么运行时它只关心对方是否实现了 CRI 标准。CRI 就像一套“接口协议”kubelet 用这套协议告诉运行时“请帮我起一个容器”“请帮我停掉这个容器”“请告诉我这个容器的状态”。在 K8s 1.23 及更早版本里kubelet 内部内置了dockershim相当于自带了一个 Docker 适配器所以 kubelet 可以直接连 Docker。从 1.24 开始K8s 把这个内置适配器移除了强制 kubelet 通过标准 CRI 去连接运行时。这也是为什么 containerd、CRI-O 这些一开始就支持 CRI 的运行时成为了默认选择。那么 Docker 在这个体系里处于什么位置Docker 实际上并不是单一体它内部也依赖 containerd 和 runc。你可以把 Docker 看成是“面向开发者的工具链”它提供了镜像构建、镜像管理、命令行交互、日志等能力而 containerd 是“面向容器运行的底层引擎”。当 kubelet 通过 CRI 请求创建容器时cri-dockerd会把 CRI 请求转换成 Docker API 请求由 Docker daemon 接收再转发给它内部的 containerd 去真正启动容器。整个调用链是kubelet - CRI (unix:///var/run/cri-dockerd.sock) - cri-dockerd - Docker daemon (docker.sock) - docker 内置的 containerd - runc - 容器进程链路确实变长了但换来的是“Docker 工具链完全保留”。你可以在宿主机上继续使用docker ps、docker inspect、docker logs等命令查看 K8s 创建出来的容器。为了方便对比我把两种常见方案的差异整理成表格对比项containerd 方案Docker cri-dockerd 方案kubelet 连接方式直接通过 containerd.sock通过 cri-dockerd.sock调用路径kubelet - containerd - runckubelet - cri-dockerd - docker - containerd - runc额外中间层无cri-dockerd Docker daemon资源占用较低较高多出两个常驻进程运维工具ctr、crictlDocker 命令不可用docker 命令直接可用适用场景默认推荐、生产首选已有 Docker 工具链、迁移过渡期从表格可以明显看出K8s 走 Docker 的方案并非“更好”而是“更顺手”。它增加了一层适配也增加了一点资源开销但保留了 Docker 的完整操作体验。3. 环境准备节点规划与系统初始化无论使用哪种运行时kubeadm 安装 K8s 之前的环境准备工作都大同小异。本节按照 Ubuntu 22.04 为例演示CentOS/Rocky 用户只需要把包管理命令替换成 yum/dnf 即可。3.1 节点规划我建议至少准备 3 台机器一主两从角色主机名建议配置私有 IPControl Planek8s-master2C4G192.168.56.10Workerk8s-node12C4G192.168.56.11Workerk8s-node22C4G192.168.56.12最低配置可以只用单节点但在单节点上运行 K8s 还需要额外处理控制面组件不可调度的问题。为了让操作流程更接近生产环境这里以三节点为例展开。3.2 版本说明标题中写的是“最新 k8s 1.37.x”这里需要特别说明Kubernetes 的版本迭代速度非常快当你看到这篇文章时官方稳定版本可能已经变化。文章中的v1.37.x是一个版本线示例实际安装时请以官方仓库中已经发布的版本目录为准。如果官方还没有发布 v1.37只需要把相关命令中的v1.37替换成当前官方稳定版本目录即可整体安装流程完全一致。这一点非常重要否则你会在 apt 源阶段就遇到 404 错误。3.3 主机配置在所有节点上执行初始化操作。先设置主机名# 在 k8s-master 节点 hostnamectl set-hostname k8s-master # 在 k8s-node1 节点 hostnamectl set-hostname k8s-node1 # 在 k8s-node2 节点 hostnamectl set-hostname k8s-node2配置三台机器的/etc/hostscat /etc/hosts EOF 192.168.56.10 k8s-master 192.168.56.11 k8s-node1 192.168.56.12 k8s-node2 EOF关闭 swap。K8s 默认要求关闭 swap否则 kubelet 会启动失败swapoff -a sed -i / swap / s/^/#/ /etc/fstab加载必要内核模块并设置系统参数cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter 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这里有两个参数需要重点解释一下。net.bridge.bridge-nf-call-iptables用于保证经过 Linux 网桥的流量也能被 iptables 规则处理K8s 的 Service 和网络策略依赖这个行为。net.ipv4.ip_forward则是路由转发开关容器跨节点通信必须依靠内核 IP 转发。防火墙方面测试环境可以直接关闭或放行 K8s 常用端口。生产环境建议按端口放行不要直接把防火墙关掉。K8s 常用端口如下作用对象端口kube-apiserver6443etcd2379-2380kubelet10250调度与控制器10251、10252、10257、10259NodePort 服务30000-32767Flannel 网络8472、518204. 安装 Docker 并配置 systemd cgroup driver接下来就是本文的重点链路之一安装 Docker。4.1 安装 Docker最简单的安装方式是使用 Docker 官方脚本curl -fsSL https://get.docker.com | bash安装完成后启动并设置开机自启systemctl enable --now docker验证 Docker 是否正常运行docker info4.2 配置 daemon.json这一步非常关键很多 kubeadm 安装后节点 NotReady 的原因就在这里Docker 的 cgroup driver 和 kubelet 不一致。kubelet 默认使用systemd作为 cgroup driver而 Docker 默认使用cgroupfs。两者如果不一致kubelet 在启动时会报错导致节点状态异常。因此我们需要把 Docker 也配置成systemd。cat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m }, storage-driver: overlay2 } EOF systemctl restart docker验证配置生效docker info | grep Cgroup Driver如果输出结果是Cgroup Driver: systemd说明 Docker 的配置已经正确。4.3 理解 storage-driver 配置storage-driver配置为overlay2是当前 Linux 环境下的主流选择性能和稳定性都比较好。如果你的服务器使用的是老版本内核无法支持 overlay2可能会需要回退到vfs但那样性能会差很多。现代系统一般不需要担心这个问题。5. 安装 cri-dockerd 适配层Docker 本身不实现 CRI所以必须引入 cri-dockerd。这个项目由 Mirantis 维护负责把 kubelet 的 CRI 请求转换成 Docker API 调用。5.1 下载并安装 cri-dockerd从 GitHub Releases 页面下载对应的二进制包。版本号请以官方发布为准这里用v0.3.x作为示例wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.x/cri-dockerd-v0.3.x.amd64.tgz tar xvf cri-dockerd-v0.3.x.amd64.tgz install -m 755 cri-dockerd /usr/local/bin/如果你所在的网络无法直接访问 GitHub Releases可以考虑先下载到本地内网服务器再分发到各节点。5.2 配置 systemd 服务cri-dockerd 需要以 systemd 服务的形式运行这样 kubelet 才能稳定通过 socket 连接它。先下载官方提供的服务文件wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket install -Dm644 cri-docker.service /etc/systemd/system/ install -Dm644 cri-docker.socket /etc/systemd/system/然后重新加载 systemd 并启动 socketsystemctl daemon-reload systemctl enable --now cri-docker.socket这里优先启用cri-docker.socket而不是直接启用cri-docker.service。原因很简单socket 激活模式可以保证 cri-dockerd 在 kubelet 第一次发起 CRI 请求时才真正启动这和 kubelet 开机启动的顺序更匹配。5.3 验证 socket查看 socket 文件是否存在ls -l /var/run/cri-dockerd.sock如果能看到类似下面的输出说明 cri-dockerd 已经准备就绪srw-rw---- 1 root root 0 1月 21 10:00 /var/run/cri-dockerd.sock后面 kubeadm init 和 kubeadm join 时我们都需要显式指定这个 socket 路径unix:///var/run/cri-dockerd.sock。6. 安装 kubeadm、kubelet、kubectl接下来安装 K8s 三种关键命令行工具。这部分在三个节点上都要执行。6.1 配置官方 apt 源以 Ubuntu/Debian 为例使用 Kubernetes 官方软件源curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/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:/v1.37/deb/ / | tee /etc/apt/sources.list.d/kubernetes.list apt update需要注意这里使用了/v1.37的目录。如果执行apt update时提示 404说明你看到文章时官方还没发布 v1.37 的源目录请把v1.37替换成官方已经发布的稳定版本例如v1.31、v1.32等。6.2 安装并锁定版本apt install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectlapt-mark hold的作用是锁定版本防止后续执行apt upgrade时把三个工具升级成不一致的版本。K8s 集群中kubeadm、kubelet、kubectl 的版本最好保持一致跨多个版本的组合容易引发意外问题。6.3 验证版本kubeadm version kubelet --version kubectl version --client三个工具的版本号应该处于同一条版本线上。这里插一个实用技巧初始化之前可以先手动拉取 kubeadm 需要的镜像避免初始化时因为网络原因卡在镜像拉取阶段kubeadm config images pull \ --cri-socketunix:///var/run/cri-dockerd.sock \ --kubernetes-versionv1.37.x如果网络环境受限可以把--image-repository参数指向你所在地区的镜像仓库例如kubeadm config images pull \ --image-repositoryregistry.aliyuncs.com/google_containers \ --cri-socketunix:///var/run/cri-dockerd.sock注意指定国内镜像仓库后后续kubeadm init也需要使用相同参数否则会从默认的registry.k8s.io拉取镜像。7. 使用 kubeadm init 初始化 Master 节点在 Master 节点上执行初始化。这里最核心的一点是必须显式指定--cri-socket为 cri-dockerd 的 socket。如果机器上同时存在 containerd 的 socket 和 cri-dockerd 的 socket而你没有显式指定kubeadm 会提示找到多个 CRI socket 并终止运行。7.1 执行初始化kubeadm init \ --kubernetes-versionv1.37.x \ --control-plane-endpointk8s-master \ --apiserver-advertise-address192.168.56.10 \ --pod-network-cidr10.244.0.0/16 \ --cri-socketunix:///var/run/cri-dockerd.sock \ --image-repositoryregistry.k8s.io参数说明参数含义--kubernetes-version指定 K8s 版本需要替换成实际安装的小版本号--control-plane-endpoint控制面统一入口地址可以是主机名或 VIP--apiserver-advertise-addressAPI Server 对外通告地址通常填 Master 节点 IP--pod-network-cidrPod 网段不能与节点网段重叠--cri-socket显式指定 CRI socket本文必须指定 cri-dockerd--image-repository镜像仓库地址网络受限时可替换Pod 网段的选择要和后面安装的 CNI 插件匹配。本文示例使用 Flannel因此设为10.244.0.0/16。如果你打算用 Calico可以改成192.168.0.0/16具体以 CNI 插件文档为准。7.2 init 成功后的输出初始化成功后屏幕会打印三部分重要信息kubectl 配置命令join 命令含 token 和证书 hash后续操作提示先配置 kubectlmkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config验证 kubectl 能否访问集群kubectl get nodes这时你会看到 Master 节点状态是NotReady这是正常的因为网络插件还没有安装。接下来需要完成这一步。8. 安装 CNI 网络插件并让节点变为 ReadyK8s 集群的 Pod 网络不能直接使用 Docker 默认的 bridge 网络因为 Pod 的 IP 分配、跨节点通信、Service 转发都需要由 CNI 插件统一管理。8.1 Flannel 与 Calico 怎么选对比项FlannelCalico网络模型VXLAN 或 host-gwBGP 路由性能一般更好网络策略不支持支持部署复杂度低中适合场景快速验证、小型集群生产环境、需要网络策略的集群本文以 Flannel 为例因为它安装简单最适合第一次搭建。8.2 安装 Flannelkubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml执行后Flannel 会在每个节点上以 DaemonSet 的形式运行一个 Pod负责维护节点间 Pod 网络。等待一会儿后查看 Pod 状态kubectl get pods -n kube-flannel如果状态都是Running再查看节点状态kubectl get nodes此时控制面节点应该已经变为Ready。如果你需要安装 Calico可以去 Calico 官方文档获取最新的 operator 部署方式注意把 Pod 网段和--pod-network-cidr保持一致。9. 添加 Worker 节点kubeadm join 与常见坑Master 节点初始化完成后Worker 节点需要执行 join 命令加入集群。9.1 获取 join 命令如果初始化输出已经丢失可以在 Master 节点上重新生成kubeadm token create --print-join-command这条命令会打印出完整的kubeadm join命令包含 token 和 discovery-token-ca-cert-hash。9.2 在 Worker 节点执行 join再次强调join 时也必须显式指定--cri-socket。在你的 k8s-node1 和 k8s-node2 上执行kubeadm join k8s-master:6443 \ --token token \ --discovery-token-ca-cert-hash sha256:hash \ --cri-socketunix:///var/run/cri-dockerd.sock如果 join 时没有指定--cri-socketkubeadm 会自动探测节点上的 CRI socket。在同时存在 Docker 和 containerd 的主机上这个自动探测大概率会失败。9.3 验证节点状态回到 Master 节点执行kubectl get nodes输出类似NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane 5m v1.37.x k8s-node1 Ready none 2m v1.37.x k8s-node2 Ready none 2m v1.37.x如果某个节点长时间停留在NotReady优先检查这个节点上的 Flannel Pod 是否正常运行以及 Docker 与 kubelet 的 cgroup driver 是否一致。10. 部署测试应用验证流量确实经过 Docker集群就绪后最后一步是部署一个测试应用并验证它确实运行在 Docker 容器里。10.1 创建 Deployment 和 Service使用一个最简单的 nginx 服务apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo spec: replicas: 2 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-demo-svc spec: type: NodePort selector: app: nginx-demo ports: - port: 80 targetPort: 80 nodePort: 30080保存为nginx-demo.yaml然后执行kubectl apply -f nginx-demo.yaml查看 Pod 和 Servicekubectl get pods -o wide kubectl get svc10.2 在宿主机上验证 Docker 容器等到 Pod 进入Running状态后登录到任意一个 Worker 节点执行docker ps | grep nginx你会在输出中看到类似这样的容器CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b2c3d4e5f6 nginx ... ... Up 2 minutes k8s_nginx_nginx-demo-xxxx_..._0看到k8s_nginx_nginx-demo-...前缀的容器就说明这个 K8s Pod 中的业务容器确实由 Docker 创建并承载。10.3 通过 NodePort 访问服务在集群外部或 Master 节点上访问curl http://192.168.56.11:30080如果返回 nginx 的欢迎页 HTML说明整个链路已经打通kubelet - cri-dockerd - Docker - nginx 容器顺便多说一句标题里带到的 LNMP 场景也完全可以用同样的方式跑。PHP-FPM、MySQL 分别部署成 Deployment 和 Service再用 nginx Ingress 做统一入口编排思路和上面的 nginx-demo 一模一样只是每个应用都要考虑数据持久化和配置管理。11. 常见问题与排查思路kubeadm 安装过程中最容易出的问题都集中在 CRI socket、cgroup driver、镜像拉取和网络插件这几个方面。下面按排查频率排序。问题现象可能原因排查方式解决方案kubeadm init 报错 detected multiple CRI sockets节点上同时存在多个 CRI socket未显式指定查看报错信息中列出了哪些 socketinit 和 join 时都增加--cri-socketunix:///var/run/cri-dockerd.sockcri-dockerd.sock 不存在cri-docker 服务未启动systemctl status cri-docker.socketsystemctl enable --now cri-docker.socket节点一直 NotReadyCNI 未安装或 CNI Pod 未启动kubectl get pods -n kube-flannel安装 Flannel/Calico等待镜像拉取完成CoreDNS CrashLoopBackOff网络插件没就绪或 Pod 网段与宿主机冲突查看 CoreDNS Pod 日志检查 CNI 配置确认--pod-network-cidr未冲突kubelet 启动失败cgroup driver is cgroupfs but systemdDocker 和 kubelet 的 cgroup driver 不一致docker info | grep Cgroup Driver修改 daemon.json 为 systemd重启 Docker镜像拉取超时网络无法访问 registry.k8s.iokubeadm config images list查看具体镜像使用国内镜像仓库参数重新 initjoin 后节点状态长时间 Unknownkubelet 无法连接 Masterjournalctl -u kubelet -f检查 6443 端口连通性检查 token 是否过期这里重点强调一下“镜像拉取失败”的排查思路。K8s 跨越多个镜像仓库如果你使用了--image-repository自定义仓库join 时不需要重复指定但各节点上 kubelet 依然会从自定义仓库拉取 pause 等基础镜像。生产环境建议提前在所有节点执行kubeadm config images pull把镜像准备好再执行 init 或 join。12. 最佳实践与升级维护建议文末一部分我想从工程角度聊聊这套方案到底怎么用才稳妥。12.1 这套方案适合什么场景先说结论这套“Docker cri-dockerd kubeadm”的方案最适合测试环境和迁移过渡期。测试环境适合是因为部署速度快能用熟悉的 Docker 命令直接查看容器调试验证效率高。迁移过渡期适合是因为团队可以先用这套方案把 K8s 调度和编排跑起来后续再平滑切换到 containerd不需要一上来就面临大规模运行时迁移。但生产环境如果从零开始我仍然建议优先使用 containerd。原因有两个。第一调用链路更短kubelet 直接连接 containerd少了一层 cri-dockerd 和 Docker daemon故障面更小。第二K8s 社区对新版本的兼容性测试重点集中在 containerd 和 CRI-O 上Docker 方案虽然可用但并不是上游最优先保证的运行路径。12.2 版本升级要怎么操作K8s 组件版本更新比较频繁升级时要按 kubeadm 官方推荐的顺序来先升级 kubectl 和 kubeadm再升级 kubelet最后执行kubeadm upgrade plan查看可升级目标。每次只升级一个小版本不要跨多个版本跳级。由于我们将kubeadm kubelet kubectl用apt-mark hold锁定了版本升级前需要先解除锁定apt-mark unhold kubelet kubeadm kubectl apt update apt install -y kubeadm新版本 kubelet新版本 kubectl新版本 apt-mark hold kubelet kubeadm kubectl升级完成后记得重新拉取对应版本的组件镜像并检查 cri-dockerd 是否需要同步升级。12.3 生产环境还要注意什么数据备份etcd 是整个集群的“控制面数据库”集群创建完成后建议尽快配置 etcd 定时快照备份。监控与日志这套方案中容器日志由 Docker 的 json-file driver 管理建议配置好日志轮转否则/var/lib/docker/containers目录会快速增长。安全边界不要在宿主机上用 root 直接操作容器文件K8s 侧建议从最小权限开始配置 RBAC业务容器尽量以非 root 用户运行。资源规划docker info中的存储 driver 为 overlay2 时镜像层会占用宿主机的根分区建议把 Docker 数据目录放到独立、空间充裕的数据盘。保留 join 命令Master 初始化输出的 token 默认有效期是 24 小时节点扩容时如果 token 过期需要重新用kubeadm token create --print-join-command生成。12.4 后续如何迁移到 containerd如果你只是把 Docker 方案当作一个过渡方案后续想切到 containerd迁移思路大致是先在所有节点安装 containerd并生成默认配置。通过crictl测试 containerd 的 CRI 接口是否正常。逐个节点执行kubeadm reset再重新kubeadm join并指定 containerd 的 socket。验证节点上原有的 Pod 重建后是否正常。这个迁移过程会比一次全新安装复杂因为涉及存量容器、数据卷和网络插件状态。所以更稳妥的做法是如果还没真正上线就直接用 containerd 方案重新建集群如果已经跑了一段时间建议用一个测试集群模拟迁移后再操作生产环境。13. 总结与后续学习方向本文的核心链路可以浓缩成一句话kubelet - CRI - cri-dockerd - Docker - 容器看懂这条链路你就不会对“K8s 不是已经不用 Docker 了吗”产生困惑。K8s 不再内置 dockershim但不代表 Docker 不能继续作为底层运行时只是中间需要 cri-dockerd 这个适配层来补位。实际操作层面本文带你把 1.37.x 版本线下的 kubeadm 安装走了一遍系统初始化、Docker 安装与 cgroup driver 配置、cri-dockerd 安装、kubeadm init、CNI 插件、Worker 节点加入、测试应用部署。这些步骤在任何 1.2x 以上版本中几乎通用区别主要在版本号和官方源的目录路径。下一步建议你做三件事找三台虚拟机按照本文流程完整搭建一遍重点体验“不加--cri-socket时 init 与 join 的报错差别”。把其中的 nginx-demo 替换成一个带业务逻辑的服务比如 LNMP 下的 PHP-FPM MySQL体验 Deployment、Service、配置中心等更完整的编排场景。再装一个纯 containerd 版本的集群对比两者在命令习惯、日志查看、资源占用上的差异建立自己的判断。既然文章写到了这里这套命令清单建议你收藏备用。等到真的需要搭建集群时照着顺序操作能省掉不少踩坑时间。