Kubernetes 与 Docker 的区别可以先用一句话概括Docker 负责单个容器的创建、启动和暂停Kubernetes 负责一组服务器上的容器调度、服务发现、故障恢复和配置管理。真正执行 Kubernetes 集群安装时kubeadm 只是搭建控制平面的骨架真正的容器生命周期仍然由容器运行时承担。这里的困难点在于从 Kubernetes 1.24 开始官方已经移除了 dockershimkubelet 不再直接把 Docker 当作容器运行时来调用。因此“kubeadm 安装最新 Kubernetes 1.37.x底层走 Docker 容器”这个需求必须通过 cri-dockerd 作为 CRI 桥接组件来实现。下面以 Kubernetes 1.37.x 为目标版本完整走一遍 kubeadm 安装过程。这个过程与 1.24 之后的版本基本一致但第一次操作时容易在 CRI socket、cgroup driver、CNI 网络三处翻车。本文会先把架构讲清楚再给出完整命令、验证方式和排错清单。1. 理解 kubeadm、kubelet、Docker 和 cri-dockerd 的分工1.1 kubeadm 只负责把集群“骨架”立起来kubeadm 是 Kubernetes 官方提供的集群初始化工具它的职责包括生成证书、创建 etcd、启动 API Server、调度器和控制器管理器以及生成 kubelet 拉取镜像时需要的 bootstrap token。它不负责直接运行容器也不会自己创建 Pod。kubeadm 初始化完成后集群里会运行很多静态 Pod 和 DaemonSet。这些 Pod 真正由 kubelet 拉起而 kubelet 不直接调用容器命令它通过 CRI 接口和容器运行时通信。Kubernetes 只定义了一组 CRI 接口容器运行时只要实现这组接口就可以被 kubelet 使用。1.2 Docker 与 Kubernetes 的 CRI 链路Docker 本身不是 CRI 实现。在比较老的 Kubernetes 版本里kubelet 内部有一块 dockershim 代码把 CRI 请求翻译成 Docker API。这个方案维护成本高官方便在 1.20 中宣布弃用1.24 正式移除。官方移除 dockershim 之后如果你仍然要求“底层走 Docker 容器”就需要把 cri-dockerd 装到节点上。cri-dockerd 是 Mirantis 维护的桥接组件它本身实现 CRI 接口可以接收 kubelet 发来的 CRI 请求再通过 Docker Engine API 调用 Docker 来创建容器。这样从调用链上看容器进程仍然是 Docker 拉起但 Kubernetes 版本的演进不会直接影响这一层。实际链路为kubectl - kubelet - CRI - cri-dockerd - Docker Engine - containerd - runc这里的“底层走 Docker 容器”不是说 kubelet 直接访问/var/run/docker.sock而是指 kubelet 把请求交给 cri-dockerdcri-dockerd 再把请求转交给 Docker Engine。少了 cri-dockerd 这一层kubelet 就找不到可用的 CRI 后端。1.3 组件分工速查组件作用说明kubeadm初始化集群、生成证书、生成 join 命令侧重控制平面搭建kubelet在节点上运行管理 Pod 生命周期通过 CRI 与运行时通信kubectl操作集群的客户端工具可以装在你的工作站上Docker Engine实际创建和管理容器进程不直接给 kubelet 使用cri-dockerd把 CRI 请求转成 Docker API节点上必须常驻运行containerdDocker 内部负责容器运行时的组件通常在 docker-ce 安装时一并安装CNI 插件实现 Pod 网络常见选择是 Calico、Flannel1.4 版本说明1.37.x 按实际 stable 版本为准Kubernetes 版本迭代非常快文章中所有命令里的v1.37.x都表示“目标版本”不一定代表当前官方 release 目录里已经存在。安装前先在官方 release 页面确认 1.37.x 是否已经进入 stable 仓库。如果还没有就把v1.37替换成当前的稳定版本。下面的安装逻辑在 1.24 之后的版本上通用差异主要集中在具体版本号和镜像仓库地址。2. 环境准备系统、内核参数与组件版本2.1 主机规划以一个控制平面节点加一个 worker 节点的最小集群为例。生产环境建议把控制平面节点和工作节点分离测试环境可以直接复用单节点。角色主机名建议配置示例 IPcontrol-planek8s-master2C4G 起磁盘 30G192.168.56.11workerk8s-node12C4G 起磁盘 30G192.168.56.12操作系统推荐 Ubuntu 22.04 LTS内核版本不低于 5.4。如果使用 CentOS 或 RHEL 系列需要额外处理 SELinux 和 firewalld。下面的命令默认操作系统是 Ubuntu。2.2 内核模块和 sysctl 配置Kubernetes 节点要求开启 IPv4 转发并要求 iptables 能看到 bridge 流量。否则 kubeadm 预检会直接失败Pod 之间的网络也可能出现问题。先加载 br_netfilter 模块cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter再写入内核参数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检查是否生效sysctl net.bridge.bridge-nf-call-iptables sysctl net.ipv4.ip_forward这里要注意不要只执行modprobe却不写/etc/modules-load.d否则服务器重启后模块不会自动加载节点可能重启后变为 NotReady。关闭 swap并注释掉/etc/fstab里的 swap 行sudo swapoff -a sudo sed -i / swap / s/^/#/ /etc/fstabKubernetes 不支持把 swap 作为可用的内存调度资源kubelet 默认也在检测到 swap 时拒绝正常运行。2.3 安装 Docker Engine 并设置 systemd cgroup先安装依赖sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg添加 Docker 官方仓库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 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 | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null安装 Dockersudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io安装完成后需要修改 Docker 的 cgroup driver。Docker 默认在 systemd 系统上可能采用cgroupfs或systemd但 kubeadm 生成的 kubelet 默认使用systemd。两者如果不一致kubelet 启动时会报 cgroup driver 冲突。修改/etc/docker/daemon.json{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2 }重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl enable docker检查 cgroup driverdocker info | grep -i cgroup输出中应出现Cgroup Driver: systemd。这一步很关键很多节点加入集群后显示 NotReady就是因为 Docker 和 kubelet 的 cgroup driver 不一致。2.4 安装 cri-dockerdcri-dockerd 需要单独下载。示例使用 v0.3.x实际安装时以 GitHub releases 页面里的版本为准。CRI_DOCKERD_VERSION0.3.18 wget https://github.com/Mirantis/cri-dockerd/releases/download/v${CRI_DOCKERD_VERSION}/cri-dockerd-${CRI_DOCKERD_VERSION}.amd64.tgz tar xvf cri-dockerd-${CRI_DOCKERD_VERSION}.amd64.tgz sudo install -m 0755 cri-dockerd /usr/local/bin/cri-dockerd cri-dockerd --version创建 systemd service 文件sudo tee /etc/systemd/system/cri-dockerd.service /dev/null EOF [Unit] DescriptionCRI Interface for Docker Application Container Engine Documentationhttps://docs.mirantis.com Afternetwork-online.target Wantsnetwork-online.target [Service] Typenotify ExecStart/usr/local/bin/cri-dockerd --container-runtime-endpoint unix:///var/run/cri-dockerd.sock Restartalways RestartSec10 [Install] WantedBymulti-user.target EOF启动并设置开机自启sudo systemctl daemon-reload sudo systemctl start cri-dockerd sudo systemctl enable cri-dockerd确认 socket 出现ls -l /var/run/cri-dockerd.sockcri-dockerd 默认监听在unix:///var/run/cri-dockerd.sock。后面 kubeadm init 和 kubeadm join 都必须显式指定这个 socket否则 kubeadm 可能检测到 containerd 的 socket导致集群最终使用 containerd 而不是 Docker。2.5 安装 kubeadm、kubelet、kubectl这里以 v1.37 仓库为例。如果该版本还没进入 stable先把v1.37改成实际存在的版本。curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key | \ sudo gpg --dearmor -o /etc/apt/keyrings/k8s.gpg echo deb [signed-by/etc/apt/keyrings/k8s.gpg] https://pkgs.k8s.io/core:/stable:/v1.37/deb/ / | \ sudo tee /etc/apt/sources.list.d/kubernetes.list安装sudo apt-get update sudo apt-get install -y kubelet1.37.x-* kubeadm1.37.x-* kubectl1.37.x-* sudo apt-mark hold kubelet kubeadm kubectlapt-mark hold是为了防止 apt 自动升级这三个组件。Kubernetes 组件升级需要按顺序执行不能任由包管理器随意升级。Docker 和 cri-dockerd 也需要在确定兼容版本后手动管理版本。2.6 环境准备检查清单检查项命令或位置预期结果内核模块lsmodgrep br_netfilterIPv4 转发sysctl net.ipv4.ip_forward1swapswapon --show无输出Docker cgroup driverdocker infogrep -i cgroupcri-dockerd socketls -l /var/run/cri-dockerd.sock文件存在kubeadm 版本kubeadm version1.37.x主机名hostnamectl set-hostname互相能解析3. kubeadm init让控制平面走 cri-dockerd3.1 初始化参数说明在控制平面节点上执行。如果准备使用 Calico 作为 CNI这里可以把 Pod 网段设置为192.168.0.0/16。sudo kubeadm init \ --kubernetes-versionv1.37.x \ --apiserver-advertise-address192.168.56.11 \ --pod-network-cidr192.168.0.0/16 \ --cri-socketunix:///var/run/cri-dockerd.sock参数含义如下--kubernetes-version显式指定版本避免 kubeadm 使用默认版本与仓库版本不一致。--apiserver-advertise-address控制平面节点的 API Server 监听地址多个网卡时必须指定。--pod-network-cidrPod 网段必须和 CNI 插件配置一致这里匹配 Calico 默认网段。--cri-socket告诉 kubeadm 使用 cri-dockerd。不指定时kubeadm 会按顺序探测可用的 CRI socket容易误选 containerd。初始化输出末尾会给出两段重要信息配置 kubectl 的命令。加入 worker 节点时使用的kubeadm join命令。如果输出没有 join token可以稍后用kubeadm token create --print-join-command重新生成。3.2 配置 kubectl控制平面节点上按 kubeadm 输出的提示操作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此时节点可能显示 NotReady原因通常是 CNI 网络插件还没安装。3.3 安装 Calico CNICalico 是 Kubernetes 常用的 CNI 插件。执行kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/calico.yaml如果服务器无法直接访问 raw.githubusercontent.com可以先下载到本地或者放到公司内部文件服务器后同步到节点再执行。生产环境更应该先评审清单内容再手动 apply。等待 kube-system 命名空间里的 Pod 就绪kubectl wait -n kube-system \ --forconditionReady pod -l appcalico3.4 检查控制平面状态网络插件安装完成后检查节点状态kubectl get nodes kubectl get pods -n kube-system正常情况下控制平面节点应该为 Readycoredns、etcd、kube-apiserver、kube-controller-manager、kube-scheduler 等 Pod 均为 Running 或 Completed。3.5 单节点测试时允许调度业务 Pod默认控制平面节点带有node-role.kubernetes.io/control-plane:NoSchedule污点业务 Pod 不会调度上来。如果你想在单节点学习环境中跑业务可以去掉这个污点kubectl taint nodes --all node-role.kubernetes.io/control-plane-这只适合学习和临时测试。生产环境不要把控制平面节点作为普通业务节点否则控制平面负载和业务负载互相影响出现问题后排查范围也会扩大。4. crictl、Docker 与 kubectl 三层验证4.1 确认节点 Readykubectl get nodes -o wide输出中STATUS为 ReadyINTERNAL-IP为预期 IP。节点加入时间较长时可以先看 kubelet 状态sudo systemctl status kubelet journalctl -u kubelet -f4.2 确认 kubelet 使用的 CRI endpointkubeadm 会把 kubelet 的启动参数写进/var/lib/kubelet/kubeadm-flags.envcat /var/lib/kubelet/kubeadm-flags.env这里应该能看到--container-runtime-endpointunix:///var/run/cri-dockerd.sock。如果这里变成 containerd socket说明 init 时没有指定或指定错误。4.3 用 crictl 看容器运行时视图crictl 是 CRI 视角的容器查看工具。由于机器上可能同时存在 containerd 和 cri-dockerd 两个 socket必须显式指定 endpoint。先安装 crictl 并配置cat EOF | sudo tee /etc/crictl.yaml runtime-endpoint: unix:///var/run/cri-dockerd.sock image-endpoint: unix:///var/run/cri-dockerd.sock timeout: 10 debug: false EOF然后执行crictl ps crictl pods可以看到与 kubectl 对应的 sandbox 容器和业务容器。crictl 的输出不以 k8s 的“Pod”为单位而是以容器和 Pod sandbox 为单位这是正常的。4.4 用 docker ps 看宿主机视图同一时刻执行docker ps | grep k8s_会看到 cri-dockerd 通过 Docker Engine 创建出来的容器。容器名通常带有k8s_前缀这是 cri-dockerd 的命名规范。4.5 对比两种视图工具看到的内容适用场景kubectlPod、Service、Deployment 等资源日常业务操作crictlPod sandbox、容器运行时状态排查 kubelet 与运行时一致性问题docker ps宿主机上的 Docker 容器进程验证是否真的走了 Docker注意在 Kubernetes 集群中优先使用 kubectl 和 crictl 排查容器不要直接用 docker stop、docker rm 去删容器。Kubernetes 会把期望状态和实际状态比较直接操作 Docker 很可能造成一个容器被删了另一个又被自动拉起来最终更难排查。5. worker 节点加入集群5.1 worker 节点环境准备worker 节点需要安装 Docker、cri-dockerd、kubeadm、kubelet操作步骤和控制平面节点基本相同。worker 可以只安装 kubeadm 和 kubelet不安装 kubectl但安装 kubectl 也不影响使用。需要重复的步骤包括配置 br_netfilter 和 sysctl。关闭 swap。安装 Docker 并设置native.cgroupdriversystemd。安装 cri-dockerd 并启动。安装 kubeadm、kubelet 并apt-mark hold。建议先在 worker 节点上执行环境准备检查清单再执行 join避免加入失败后反复查看日志。5.2 生成新的 join 命令控制平面节点上执行kubeadm token create --print-join-command这条命令适合 token 过期或丢失的情况。它输出的命令通常不包含--cri-socket需要手动追加。5.3 worker 节点 join 时指定 cri socket在 worker 节点上执行sudo kubeadm join 192.168.56.11:6443 \ --token token \ --discovery-token-ca-cert-hash sha256:hash \ --cri-socketunix:///var/run/cri-dockerd.sock这里最容易犯的错是直接复制 kubeadm init 输出中的 join 命令而不加--cri-socket。不加时 kubeadm 可能检测到 containerd结果 worker 节点变成 containerd 运行时而控制平面是 cri-dockerd 运行时虽然都能工作但节点环境不一致会让后续维护更困难。5.4 验证工作负载调度回到控制平面节点kubectl get nodes kubectl get pods -n kube-system -o wide如果看到 worker 节点 Ready可以在集群里创建一个测试 Deploymentkubectl create deployment nginx-test --imagenginx:1.27 kubectl expose deployment nginx-test --port80 --typeNodePort查看 Pod 运行在哪个节点kubectl get pods -o wide然后访问 NodePort 服务验证网络链路是否正常。如果流量不通优先检查 Calico 的 Pod 状态和节点 iptables 规则而不是急着重启节点。6. 常见问题排查运行时、CNI 与 cgroup6.1 报错CRI v1 runtime API is not implemented现象kubelet 不断重启日志中提示类似CRI v1 runtime API is not implemented或failed to get runtime endpoint。成因kubelet 和 cri-dockerd 的 CRI 版本不匹配或者 cri-dockerd 版本过旧不认识当前 kubelet 请求的 CRI 版本。处理方式sudo systemctl status cri-dockerd cri-dockerd --version如果 cri-dockerd 版本太老升级到最新 0.3.x。升级后分别重启 docker 和 cri-dockerd。6.2 报错cgroup driver 与 kubelet 不一致现象kubelet 日志出现failed to run Kubelet: failed to get cgroup driver for docker或者systemd与cgroupfs冲突。成因Docker、kubelet、cri-dockerd 三者的 cgroup driver 不一致。处理方式docker info | grep -i cgroup如果显示不是systemd修改/etc/docker/daemon.json中的exec-opts并重启 Dockersudo systemctl restart docker sudo systemctl restart cri-dockerd sudo systemctl restart kubelet6.3 报错节点 NotReady 且 CNI 未就绪现象kubectl get nodes显示 NotReadykubectl get pods -n kube-system中的 Calico Pod 一直 Pending 或 CrashLoopBackOff。成因Calico 无法下载镜像、Pod 网段不一致、节点 IP 没有正确检测或/etc/cni/net.d目录下没有 CNI 配置。处理方式先查看 Calico Pod 日志kubectl logs -n kube-system -l appcalico -f如果日志提示 IPAM 冲突或网段不匹配检查kubeadm init时的--pod-network-cidr与 Calico 配置是否一致。如果想完全重置网络可以清空/etc/cni/net.d后重新安装 Calico但生产环境不要随意删除目录。6.4 报错crictl 连接不上现象执行crictl ps时报failed to connect或no such file or directory。成因crictl 默认尝试连接 containerd socket但节点实际使用的是 cri-dockerd socket。处理方式crictl --runtime-endpoint unix:///var/run/cri-dockerd.sock ps也可以直接把/etc/crictl.yaml里的 runtime-endpoint 改成 cri-dockerd这样就不需要每次传参。6.5 问题速查表问题现象常见原因检查方式处理建议kubeadm preflight 报 bridge-nf 错误没有配置 sysctlsysctl net.bridge.bridge-nf-call-iptables加载 br_netfilter 并执行sysctl --systemkubelet 找不到 runtimecri-dockerd 未启动systemctl status cri-dockerd启动并设置开机自启worker join 后节点 NotReadyjoin 时没指定 cri socket查看/var/lib/kubelet/kubeadm-flags.env重新执行带--cri-socket的 joinCalico Pod Pending镜像拉取失败或网段冲突kubectl logs -n kube-system -l appcalico核对 Pod CIDR 和镜像仓库地址Pod 启动后被不断重启误删 Docker 容器或资源不足kubectl describe pod用 kubectl 和 crictl 排查避免直接操作 dockerDocker 和 kubelet cgroup 不一致daemon.json 配置缺失docker infogrep -i cgroup7. 学习环境与生产环境的取舍7.1 学习环境如何快速重来单节点学习时如果集群环境弄乱了最快的方式是重置 kubeadm而不是反复补配置。sudo kubeadm reset --cri-socketunix:///var/run/cri-dockerd.sock然后清理配置sudo rm -rf /etc/cni/net.d $HOME/.kube /var/lib/kubelet再次安装时需要重新执行kubeadm init。kubeadm reset 并不清理 Docker 镜像如果需要彻底清理再手动删除 Docker 中的数据。7.2 生产环境为什么优先 containerd如果你的团队没有强需求必须使用 Docker CLI 或 Docker API生产环境更推荐直接使用 containerd。原因是 containerd 本身就是 CRI 原生实现少一层 cri-dockerd 桥接组件越少越好排查。kubeadm 官方文档默认也是推荐 containerd。但如果你明确要求“底层走 Docker 容器”例如现有监控、安全扫描、构建工具都绑定在 Docker API 上那么 cri-dockerd 是必要选择。这种情况下要做好维护预案确认 cri-dockerd 与 Kubernetes 版本都有同步升级计划。7.3 必须保留 cri-dockerd 时的最佳实践把 Docker、kubelet、kubeadm、kubectl、cri-dockerd 的版本固定下来不要使用latest。在/etc/docker/daemon.json中配置日志轮转避免容器日志长期堆满磁盘。使用 overlay2 存储驱动避免 devicemapper 等旧驱动带来的性能问题。不要在业务节点上开放 Docker API 给外部访问Docker socket 权限等于宿主机 root 权限。每次升级 Kubernetes 前先确认 cri-dockerd 是否已经兼容目标 kubelet 版本。监控/var/run/cri-dockerd.sock的连通性并用进程守护保证 cri-dockerd 异常后自动拉起。7.4 扩展方向这套流程跑通后你可以继续往下走几条线高可用控制平面为 API Server 增加负载均衡并配置多个控制平面节点。Ingress 和证书管理部署 Ingress Controller 并通过证书管理工具自动签发证书。可观测性部署 Prometheus 和 Loki把节点、容器和 kubelet 日志统一起来。应用部署流程从 kubectl apply 逐步演进到 Helm Chart 和 GitOps。刚开始练习时最重要的是把“kubelet 通过 CRI 与运行时通信”这条链路记清楚。很多生产环境中的网络和容器问题只要先确认 kubelet 的 runtime endpoint、cgroup driver 和 CNI 配置三者一致就已经能排除掉一半的嫌疑点。