Kubeadm安装Kubernetes 1.37:用cri-dockerd桥接Docker与CRI
2026/9/8 3:43:28 网站建设 项目流程

Kubernetes 与 Docker 的区别,可以先用一句话概括:Docker 负责单个容器的创建、启动和暂停,Kubernetes 负责一组服务器上的容器调度、服务发现、故障恢复和配置管理。真正执行 Kubernetes 集群安装时,kubeadm 只是搭建控制平面的骨架,真正的容器生命周期仍然由容器运行时承担。这里的困难点在于,从 Kubernetes 1.24 开始,官方已经移除了 dockershim,kubelet 不再直接把 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-dockerd,cri-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、Flannel

1.4 版本说明:1.37.x 按实际 stable 版本为准

Kubernetes 版本迭代非常快,文章中所有命令里的v1.37.x都表示“目标版本”,不一定代表当前官方 release 目录里已经存在。安装前先在官方 release 页面确认 1.37.x 是否已经进入 stable 仓库。如果还没有,就把v1.37替换成当前的稳定版本。下面的安装逻辑在 1.24 之后的版本上通用,差异主要集中在具体版本号和镜像仓库地址。

2. 环境准备:系统、内核参数与组件版本

2.1 主机规划

以一个控制平面节点加一个 worker 节点的最小集群为例。生产环境建议把控制平面节点和工作节点分离,测试环境可以直接复用单节点。

角色主机名建议配置示例 IP
control-planek8s-master2C4G 起,磁盘 30G192.168.56.11
workerk8s-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/fstab

Kubernetes 不支持把 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 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 \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

安装 Docker:

sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io

安装完成后,需要修改 Docker 的 cgroup driver。Docker 默认在 systemd 系统上可能采用cgroupfssystemd,但 kubeadm 生成的 kubelet 默认使用systemd。两者如果不一致,kubelet 启动时会报 cgroup driver 冲突。

修改/etc/docker/daemon.json

{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "storage-driver": "overlay2" }

重启 Docker:

sudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl enable docker

检查 cgroup driver:

docker info | grep -i cgroup

输出中应出现Cgroup Driver: systemd。这一步很关键,很多节点加入集群后显示 NotReady,就是因为 Docker 和 kubelet 的 cgroup driver 不一致。

2.4 安装 cri-dockerd

cri-dockerd 需要单独下载。示例使用 v0.3.x,实际安装时以 GitHub releases 页面里的版本为准。

CRI_DOCKERD_VERSION=0.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] Description=CRI Interface for Docker Application Container Engine Documentation=https://docs.mirantis.com After=network-online.target Wants=network-online.target [Service] Type=notify ExecStart=/usr/local/bin/cri-dockerd --container-runtime-endpoint unix:///var/run/cri-dockerd.sock Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF

启动并设置开机自启:

sudo systemctl daemon-reload sudo systemctl start cri-dockerd sudo systemctl enable cri-dockerd

确认 socket 出现:

ls -l /var/run/cri-dockerd.sock

cri-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 kubelet=1.37.x-* kubeadm=1.37.x-* kubectl=1.37.x-* sudo apt-mark hold kubelet kubeadm kubectl

apt-mark hold是为了防止 apt 自动升级这三个组件。Kubernetes 组件升级需要按顺序执行,不能任由包管理器随意升级。Docker 和 cri-dockerd 也需要在确定兼容版本后手动管理版本。

2.6 环境准备检查清单

检查项命令或位置预期结果
内核模块`lsmodgrep br_netfilter`
IPv4 转发sysctl net.ipv4.ip_forward1
swapswapon --show无输出
Docker cgroup driver`docker infogrep -i cgroup`
cri-dockerd socketls -l /var/run/cri-dockerd.sock文件存在
kubeadm 版本kubeadm version1.37.x
主机名hostnamectl set-hostname互相能解析

3. kubeadm init:让控制平面走 cri-dockerd

3.1 初始化参数说明

在控制平面节点上执行。如果准备使用 Calico 作为 CNI,这里可以把 Pod 网段设置为192.168.0.0/16

sudo kubeadm init \ --kubernetes-version=v1.37.x \ --apiserver-advertise-address=192.168.56.11 \ --pod-network-cidr=192.168.0.0/16 \ --cri-socket=unix:///var/run/cri-dockerd.sock

参数含义如下:

  • --kubernetes-version:显式指定版本,避免 kubeadm 使用默认版本与仓库版本不一致。
  • --apiserver-advertise-address:控制平面节点的 API Server 监听地址,多个网卡时必须指定。
  • --pod-network-cidr:Pod 网段,必须和 CNI 插件配置一致,这里匹配 Calico 默认网段。
  • --cri-socket:告诉 kubeadm 使用 cri-dockerd。不指定时,kubeadm 会按顺序探测可用的 CRI socket,容易误选 containerd。

初始化输出末尾会给出两段重要信息:

  1. 配置 kubectl 的命令。
  2. 加入 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 CNI

Calico 是 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 \ --for=condition=Ready pod -l app=calico

3.4 检查控制平面状态

网络插件安装完成后,检查节点状态:

kubectl get nodes kubectl get pods -n kube-system

正常情况下,控制平面节点应该为 Ready,coredns、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 确认节点 Ready

kubectl get nodes -o wide

输出中STATUS为 Ready,INTERNAL-IP为预期 IP。节点加入时间较长时,可以先看 kubelet 状态:

sudo systemctl status kubelet journalctl -u kubelet -f

4.2 确认 kubelet 使用的 CRI endpoint

kubeadm 会把 kubelet 的启动参数写进/var/lib/kubelet/kubeadm-flags.env

cat /var/lib/kubelet/kubeadm-flags.env

这里应该能看到--container-runtime-endpoint=unix:///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 也不影响使用。

需要重复的步骤包括:

  1. 配置 br_netfilter 和 sysctl。
  2. 关闭 swap。
  3. 安装 Docker 并设置native.cgroupdriver=systemd
  4. 安装 cri-dockerd 并启动。
  5. 安装 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-socket=unix:///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,可以在集群里创建一个测试 Deployment:

kubectl create deployment nginx-test --image=nginx:1.27 kubectl expose deployment nginx-test --port=80 --type=NodePort

查看 Pod 运行在哪个节点:

kubectl get pods -o wide

然后访问 NodePort 服务验证网络链路是否正常。如果流量不通,优先检查 Calico 的 Pod 状态和节点 iptables 规则,而不是急着重启节点。

6. 常见问题排查:运行时、CNI 与 cgroup

6.1 报错:CRI v1 runtime API is not implemented

现象:kubelet 不断重启,日志中提示类似CRI v1 runtime API is not implementedfailed 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或者systemdcgroupfs冲突。

成因:Docker、kubelet、cri-dockerd 三者的 cgroup driver 不一致。

处理方式:

docker info | grep -i cgroup

如果显示不是systemd,修改/etc/docker/daemon.json中的exec-opts并重启 Docker:

sudo systemctl restart docker sudo systemctl restart cri-dockerd sudo systemctl restart kubelet

6.3 报错:节点 NotReady 且 CNI 未就绪

现象:kubectl get nodes显示 NotReady,kubectl 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 app=calico -f

如果日志提示 IPAM 冲突或网段不匹配,检查kubeadm init时的--pod-network-cidr与 Calico 配置是否一致。如果想完全重置网络,可以清空/etc/cni/net.d后重新安装 Calico,但生产环境不要随意删除目录。

6.4 报错:crictl 连接不上

现象:执行crictl ps时报failed to connectno 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 --system
kubelet 找不到 runtimecri-dockerd 未启动systemctl status cri-dockerd启动并设置开机自启
worker join 后节点 NotReadyjoin 时没指定 cri socket查看/var/lib/kubelet/kubeadm-flags.env重新执行带--cri-socket的 join
Calico Pod Pending镜像拉取失败或网段冲突kubectl logs -n kube-system -l app=calico核对 Pod CIDR 和镜像仓库地址
Pod 启动后被不断重启误删 Docker 容器或资源不足kubectl describe pod用 kubectl 和 crictl 排查,避免直接操作 docker
Docker 和 kubelet cgroup 不一致daemon.json 配置缺失`docker infogrep -i cgroup`

7. 学习环境与生产环境的取舍

7.1 学习环境如何快速重来

单节点学习时,如果集群环境弄乱了,最快的方式是重置 kubeadm,而不是反复补配置。

sudo kubeadm reset --cri-socket=unix:///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 扩展方向

这套流程跑通后,你可以继续往下走几条线:

  1. 高可用控制平面:为 API Server 增加负载均衡,并配置多个控制平面节点。
  2. Ingress 和证书管理:部署 Ingress Controller 并通过证书管理工具自动签发证书。
  3. 可观测性:部署 Prometheus 和 Loki,把节点、容器和 kubelet 日志统一起来。
  4. 应用部署流程:从 kubectl apply 逐步演进到 Helm Chart 和 GitOps。

刚开始练习时,最重要的是把“kubelet 通过 CRI 与运行时通信”这条链路记清楚。很多生产环境中的网络和容器问题,只要先确认 kubelet 的 runtime endpoint、cgroup driver 和 CNI 配置三者一致,就已经能排除掉一半的嫌疑点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询