基于Docker-CE与cri-dockerd部署Kubernetes 1.28高可用集群——从容器运行时到控制平面的全链路实战

摘要:Kubernetes 1.24 正式移除内置 dockershim 后,以 Docker 作为容器运行时的集群必须通过 cri-dockerd 适配器桥接 CRI 接口。本文以 K8S 1.28 + Docker-CE 24.x + cri-dockerd 0.3.x 为技术栈,从内核参数调优、容器运行时编译安装、镜像加速、kubeadm 引导初始化到多节点 join 全流程,深入剖析每一步的底层原理与工程细节,覆盖生产级部署中常见的坑点与调优策略。


一、技术背景与架构演进

1.1 dockershim 的移除与 CRI 架构

Kubernetes 自 1.5 引入 CRI(Container Runtime Interface) 规范,旨在解耦 kubelet 与具体容器运行时。然而早期 Docker 并未实现 CRI,Kubernetes 在 kubelet 内部维护了一个名为 dockershim 的垫片层:

┌─────────────────────────────────────────────────┐
│                   kubelet                       │
│  ┌───────────┐    ┌──────────────────────────┐  │
│  │  CRI API  │───>│  dockershim (内置垫片)    │  │
│  └───────────┘    └──────────┬───────────────┘  │
└──────────────────────────────┼──────────────────┘
                               │ Docker Engine API
                     ┌─────────▼─────────┐
                     │   Docker Daemon    │
                     │  ┌──────────────┐  │
                     │  │  containerd   │  │
                     │  │  ┌─────────┐  │  │
                     │  │  │  runc   │  │  │
                     │  │  └─────────┘  │  │
                     │  └──────────────┘  │
                     └───────────────────┘

这条调用链为 kubelet → dockershim → Docker Daemon → containerd → runc,存在两个核心问题:

  1. 维护成本:dockershim 约 2000 行代码嵌入 kubelet 主干,每次 Docker API 变更都需要同步适配
  2. 性能开销:请求经过 Docker Daemon 的额外序列化/反序列化,创建 Pod 的延迟增加 ~200ms

Kubernetes 1.20 发布弃用通知,1.24 正式移除 dockershim。此后若仍使用 Docker 作为运行时,必须部署外部适配器 cri-dockerd

1.2 cri-dockerd 的定位

cri-dockerd 是 Mirantis 维护的开源项目,其本质是将原 dockershim 代码从 kubelet 中剥离为独立的 gRPC 服务:

┌──────────┐     CRI gRPC      ┌──────────────┐     Docker API    ┌──────────────┐
│  kubelet │ ──────────────────>│  cri-dockerd  │ ────────────────>│ Docker Daemon│
└──────────┘  unix:///var/run/  └──────────────┘                   └──────┬───────┘
              cri-dockerd.sock                                            │
                                                                   ┌──────▼───────┐
                                                                   │  containerd   │
                                                                   └──────┬───────┘
                                                                   ┌──────▼───────┐
                                                                   │     runc      │
                                                                   └──────────────┘

为什么还要用 Docker? 在企业环境中,Docker 的 docker builddocker compose、镜像调试(docker exec)等开发者工具链仍有不可替代的地位。cri-dockerd 让团队在保留 Docker 工作流的同时接入 CRI 标准。

1.3 版本矩阵

组件 版本 说明
OS CentOS 7.9 / Ubuntu 22.04 内核 ≥ 5.4 推荐
Docker-CE 24.0.x 稳定长期支持
cri-dockerd 0.3.8+ 适配 CRI v1
Kubernetes 1.28.x 2023 LTS
Calico 3.26.x 网络插件(下篇)

二、环境规划与系统预配置

2.1 节点规划

+──────────────+───────────────+────────────+───────────────+
│    角色      │    主机名      │    IP      │    配置        │
+──────────────+───────────────+────────────+───────────────+
│ Master-01    │ k8s-master01  │ 10.0.0.10  │ 4C/8G/50G    │
│ Worker-01    │ k8s-worker01  │ 10.0.0.11  │ 4C/8G/50G    │
│ Worker-02    │ k8s-worker02  │ 10.0.0.12  │ 4C/8G/50G    │
+──────────────+───────────────+────────────+───────────────+

生产建议:Master 至少 3 节点实现 etcd 多数派仲裁;Worker 根据业务规模横向扩展。

2.2 主机名与 hosts 解析

# 所有节点执行
hostnamectl set-hostname k8s-master01  # 各节点对应修改

cat >> /etc/hosts << 'EOF'
10.0.0.10  k8s-master01
10.0.0.11  k8s-worker01
10.0.0.12  k8s-worker02
EOF

2.3 关闭 swap

kubelet 默认拒绝在开启 swap 的节点上运行(可通过 --fail-swap-on=false 覆盖,但生产环境不推荐):

swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab

# 验证
free -h  # Swap 行应全部为 0

底层原因:Kubernetes 的 QoS 模型(Guaranteed / Burstable / BestEffort)依赖 cgroup 的内存限制精确控制。swap 的存在使得 OOM Killer 的触发时机不可预测,导致 Pod 驱逐策略失效。

2.4 关闭防火墙与 SELinux

# 防火墙
systemctl stop firewalld && systemctl disable firewalld

# SELinux
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config

生产替代方案:不关闭防火墙,而是精确放行所需端口:

  • Master:6443(API Server)、2379-2380(etcd)、10250-10252(kubelet/controller/scheduler)
  • Worker:10250(kubelet)、30000-32767(NodePort)

2.5 内核参数与模块加载

# 加载必要内核模块
cat > /etc/modules-load.d/k8s.conf << 'EOF'
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

# 网络桥接与转发参数
cat > /etc/sysctl.d/k8s.conf << 'EOF'
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

sysctl --system

参数解析

参数 作用
overlay 为 Docker 的 overlay2 存储驱动提供联合文件系统支持
br_netfilter 使桥接流量经过 iptables/netfilter 处理,Pod 跨节点通信必需
bridge-nf-call-iptables 二层桥接帧经过三层 iptables 规则,Service 的 DNAT/SNAT 依赖此项
ip_forward 允许内核在不同网络接口间转发数据包,Pod 网络路由必需

2.6 时间同步

yum install -y chrony    # CentOS
# apt install -y chrony  # Ubuntu

systemctl enable chronyd --now
chronyc sources -v

节点间时钟偏差超过数秒将导致 etcd 选举异常、证书验证失败。


三、Docker-CE 安装与配置

3.1 配置 Docker 仓库

# CentOS
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# Ubuntu
apt-get update
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
apt-get update

3.2 安装指定版本

# 查看可用版本
yum list docker-ce --showduplicates | sort -r | head -20

# 安装 Docker-CE 24.0
yum install -y docker-ce-24.0.7-1.el7 docker-ce-cli-24.0.7-1.el7 containerd.io

# Ubuntu
# apt-get install -y docker-ce=5:24.0.7-1~ubuntu.22.04~jammy docker-ce-cli=5:24.0.7-1~ubuntu.22.04~jammy containerd.io

3.3 配置 Docker Daemon

mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "registry-mirrors": [
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ],
  "data-root": "/data/docker",
  "live-restore": true
}
EOF

关键配置深度解析

配置项 原理说明
cgroupdriver=systemd systemd K8S 1.22+ 强制要求 kubelet 与运行时使用同一 cgroup 驱动。systemd 作为 PID 1 进程管理者,由它管理 cgroup 层级树可避免两套 cgroup 管理器竞争导致的资源泄漏
overlay2 存储驱动 基于 OverlayFS 的 CoW 文件系统,相比 aufs 性能更优(顺序读提升 ~20%),且为内核原生支持
live-restore true Docker Daemon 重启时不中断运行中容器,对于 Daemon 升级场景至关重要
data-root /data/docker 分离数据目录至独立磁盘,避免镜像/容器数据撑满系统盘
log-opts 100m × 3 单容器日志滚动上限 300MB,防止日志洪泛导致磁盘耗尽(生产中曾有容器日志单文件达 TB 级别的事故)

3.4 启动与验证

systemctl daemon-reload
systemctl enable docker --now

# 验证
docker info | grep -E "Cgroup Driver|Storage Driver|Docker Root Dir"
# 期望输出:
# Cgroup Driver: systemd
# Storage Driver: overlay2
# Docker Root Dir: /data/docker

docker run --rm hello-world

四、cri-dockerd 编译安装与配置

4.1 安装 Go 编译环境

cri-dockerd 使用 Go 语言开发,需要 Go 1.20+ 编译:

wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz

cat >> /etc/profile.d/go.sh << 'EOF'
export GOROOT=/usr/local/go
export GOPATH=/opt/gopath
export PATH=$GOROOT/bin:$GOPATH/bin:$PATH
export GOPROXY=https://goproxy.cn,direct
EOF

source /etc/profile.d/go.sh
go version  # go version go1.21.5 linux/amd64

4.2 编译 cri-dockerd

cd /opt
git clone https://github.com/Mirantis/cri-dockerd.git
cd cri-dockerd
git checkout v0.3.8

# 编译
make cri-dockerd
# 产物位于 cri-dockerd/cri-dockerd

# 安装二进制
install -o root -g root -m 0755 cri-dockerd /usr/local/bin/cri-dockerd

# 验证
cri-dockerd --version
# cri-dockerd 0.3.8 (commit-hash)

无法科学上网时的替代方案:从 GitHub Releases 页面直接下载预编译二进制:

wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.8/cri-dockerd-0.3.8.amd64.tgz
tar xf cri-dockerd-0.3.8.amd64.tgz
install -o root -g root -m 0755 cri-dockerd/cri-dockerd /usr/local/bin/

4.3 配置 systemd 服务

cat > /usr/lib/systemd/system/cri-docker.service << 'EOF'
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target firewalld.service docker.service
Wants=network-online.target
Requires=docker.service

[Service]
Type=notify
ExecStart=/usr/local/bin/cri-dockerd \
  --container-runtime-endpoint=unix:///var/run/cri-dockerd.sock \
  --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9 \
  --network-plugin=cni \
  --cni-bin-dir=/opt/cni/bin \
  --cni-conf-dir=/etc/cni/net.d
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF

cat > /usr/lib/systemd/system/cri-docker.socket << 'EOF'
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service

[Socket]
ListenStream=/var/run/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF

关键参数解析

  • --pod-infra-container-image:指定 pause 容器镜像(国内需替换为阿里云镜像)。pause 容器是 Pod 中所有容器的"父容器",它创建并持有 Pod 的网络命名空间(Network Namespace),其他容器通过 --net=container:<pause-id> 共享该命名空间实现 Pod 内容器间 localhost 通信
  • --network-plugin=cni:声明使用 CNI 插件管理网络(Calico/Flannel),而非 Docker 的原生桥接网络
  • Requires=docker.service:确保 Docker Daemon 先于 cri-dockerd 启动,构成强依赖链

4.4 启动服务

systemctl daemon-reload
systemctl enable cri-docker --now

# 验证 socket 存在
ls -la /var/run/cri-dockerd.sock
# srw-rw---- 1 root docker 0 ... /var/run/cri-dockerd.sock

# 验证服务状态
systemctl status cri-docker
# Active: active (running)

# 功能验证:通过 crictl 连接
cat > /etc/crictl.yaml << 'EOF'
runtime-endpoint: unix:///var/run/cri-dockerd.sock
image-endpoint: unix:///var/run/cri-dockerd.sock
timeout: 10
debug: false
EOF

crictl info  # 应返回运行时信息

五、Kubernetes 1.28 组件安装

5.1 配置 K8S 仓库

# CentOS
cat > /etc/yum.repos.d/kubernetes.repo << 'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/rpm/repodata/repomd.xml.key
EOF

# Ubuntu
curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/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://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/deb/ /" > /etc/apt/sources.list.d/kubernetes.list
apt-get update

5.2 安装 kubeadm / kubelet / kubectl

# CentOS
yum install -y kubelet-1.28.2 kubeadm-1.28.2 kubectl-1.28.2 --disableexcludes=kubernetes

# Ubuntu
# apt-get install -y kubelet=1.28.2-1.1 kubeadm=1.28.2-1.1 kubectl=1.28.2-1.1
# apt-mark hold kubelet kubeadm kubectl

systemctl enable kubelet
# 注意:此时 kubelet 会反复重启(CrashLoop),这是正常行为——它在等待 kubeadm init 提供配置

三组件职责

组件 职责
kubeadm 集群引导工具,负责生成 CA 证书、静态 Pod 清单、bootstrap token 等
kubelet 节点代理,通过 CRI 接口管理 Pod 生命周期,向 API Server 上报节点状态
kubectl 命令行客户端,与 API Server 交互的主要工具

5.3 配置 kubelet 使用 cri-dockerd

cat > /etc/sysconfig/kubelet << 'EOF'
KUBELET_EXTRA_ARGS=--container-runtime-endpoint=unix:///var/run/cri-dockerd.sock
EOF

此配置确保 kubelet 连接 cri-dockerd 的 socket 而非默认的 containerd socket。


六、kubeadm 初始化 Master 节点

6.1 生成初始化配置文件

kubeadm config print init-defaults > kubeadm-config.yaml

编辑关键字段:

apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
  criSocket: unix:///var/run/cri-dockerd.sock   # 指定 CRI socket
  name: k8s-master01
localAPIEndpoint:
  advertiseAddress: 10.0.0.10                    # Master IP
  bindPort: 6443
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.2
controlPlaneEndpoint: "10.0.0.10:6443"           # HA 场景替换为 VIP
imageRepository: registry.aliyuncs.com/google_containers  # 国内镜像
networking:
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.244.0.0/16                       # 与 Calico 配置一致
  dnsDomain: cluster.local
etcd:
  local:
    dataDir: /var/lib/etcd
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd                             # 与 Docker 一致

6.2 预拉取镜像

kubeadm config images pull \
  --config kubeadm-config.yaml \
  --cri-socket unix:///var/run/cri-dockerd.sock

# 验证
docker images | grep -E "kube-|etcd|coredns|pause"

6.3 执行初始化

kubeadm init --config kubeadm-config.yaml --upload-certs --v=5

初始化流程深度解析(kubeadm init 的 13 个阶段):

[init]           → 版本校验、配置验证
[preflight]      → 系统预检(端口、swap、cgroup、CRI 连通性)
[certs]          → 生成 PKI 证书(CA、API Server、etcd、front-proxy 等共 ~10 对)
[kubeconfig]     → 生成 kubeconfig(admin、controller-manager、scheduler、kubelet)
[kubelet-start]  → 写入 kubelet 配置并启动
[control-plane]  → 生成静态 Pod 清单(/etc/kubernetes/manifests/)
[etcd]           → 生成 etcd 静态 Pod 清单
[upload-config]  → 将配置存入 ConfigMap(kube-system/kubeadm-config)
[upload-certs]   → 加密上传证书到 Secret(HA join 使用)
[mark-control]   → 给 Master 节点打 taint(NoSchedule)
[bootstrap-token] → 创建 bootstrap token(Worker join 使用)
[kubelet-finalize] → 更新 kubelet 的客户端证书轮转配置
[addon]          → 安装 CoreDNS 和 kube-proxy 附加组件

6.4 配置 kubectl

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 验证
kubectl get nodes
# NAME           STATUS     ROLES           AGE   VERSION
# k8s-master01   NotReady   control-plane   60s   v1.28.2
# NotReady 是正常的——尚未安装网络插件

6.5 证书体系详解

kubeadm 生成的证书位于 /etc/kubernetes/pki/

/etc/kubernetes/pki/
├── ca.crt / ca.key                    # 集群根 CA
├── apiserver.crt / apiserver.key      # API Server 服务端证书
├── apiserver-kubelet-client.crt/key   # API Server → kubelet 客户端证书
├── apiserver-etcd-client.crt/key      # API Server → etcd 客户端证书
├── front-proxy-ca.crt/key             # 聚合 API 层 CA
├── front-proxy-client.crt/key         # 聚合 API 客户端证书
├── etcd/
│   ├── ca.crt / ca.key                # etcd 独立 CA
│   ├── server.crt / server.key        # etcd 服务端证书
│   ├── peer.crt / peer.key            # etcd 节点间通信证书
│   └── healthcheck-client.crt/key     # etcd 健康检查证书
└── sa.key / sa.pub                    # ServiceAccount 签发密钥对

安全要点ca.keysa.keyetcd/ca.key 是集群最高权限密钥,泄露等同于集群完全失控。生产环境应严格管控其访问权限(chmod 0600)并考虑硬件安全模块(HSM)存储。


七、Worker 节点加入集群

7.1 在 Worker 节点执行 join

Master 初始化成功后输出的 join 命令:

kubeadm join 10.0.0.10:6443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --cri-socket unix:///var/run/cri-dockerd.sock

关键:必须追加 --cri-socket 参数指向 cri-dockerd,否则 kubeadm 会尝试连接默认的 containerd socket 导致失败。

7.2 Token 过期处理

Token 默认 24 小时过期。若需重新生成:

# Master 上执行
kubeadm token create --print-join-command

# 获取 ca-cert-hash
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
  openssl rsa -pubin -outform der 2>/dev/null | \
  openssl dgst -sha256 -hex | sed 's/^.* //'

7.3 验证集群状态

kubectl get nodes -o wide
# NAME           STATUS     ROLES           AGE     VERSION   INTERNAL-IP   OS-IMAGE
# k8s-master01   NotReady   control-plane   5m      v1.28.2   10.0.0.10     CentOS 7.9
# k8s-worker01   NotReady   <none>          2m      v1.28.2   10.0.0.11     CentOS 7.9
# k8s-worker02   NotReady   <none>          1m      v1.28.2   10.0.0.12     CentOS 7.9

所有节点显示 NotReady 是预期行为——需要安装 CNI 网络插件(Calico),这将在下一篇文章中详细介绍。


八、常见问题与排查

8.1 kubelet 启动失败

journalctl -xeu kubelet | tail -50
错误信息 原因 解决
failed to run Kubelet: running with swap on is not supported swap 未关闭 swapoff -a
Error getting node...connection refused API Server 未就绪 检查静态 Pod:crictl ps
failed to get sandbox image... pause 镜像拉取失败 配置 cri-dockerd 的 --pod-infra-container-image
cgroup driver "cgroupfs" != "systemd" cgroup 驱动不一致 Docker daemon.json 和 kubelet 配置统一为 systemd

8.2 cri-dockerd 连接失败

# 检查 socket 是否存在
ls -la /var/run/cri-dockerd.sock

# 检查服务日志
journalctl -u cri-docker -f

# 手动测试 CRI 连通性
crictl --runtime-endpoint unix:///var/run/cri-dockerd.sock info

8.3 镜像拉取超时

# 检查 Docker 镜像加速是否生效
docker info | grep -A 5 "Registry Mirrors"

# 手动预拉取并重命名
docker pull registry.aliyuncs.com/google_containers/kube-apiserver:v1.28.2
docker tag registry.aliyuncs.com/google_containers/kube-apiserver:v1.28.2 \
  registry.k8s.io/kube-apiserver:v1.28.2

8.4 kubeadm reset(重置重来)

kubeadm reset --cri-socket unix:///var/run/cri-dockerd.sock -f
rm -rf /etc/kubernetes/ /var/lib/etcd/ /var/lib/kubelet/ $HOME/.kube
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X

九、总结

本文完整覆盖了基于 Docker-CE + cri-dockerd 部署 Kubernetes 1.28 集群的全流程:

  1. 架构理解:厘清 dockershim 移除后的 CRI 架构演进,理解 cri-dockerd 的桥接角色
  2. 系统预配置:内核参数、cgroup 驱动、网络模块——每项配置都有其 Kubernetes 网络模型层面的必然性
  3. Docker-CE:overlay2 存储驱动、systemd cgroup 驱动、日志滚动策略的生产级配置
  4. cri-dockerd:从源码编译到 systemd 服务配置,深入 pause 容器与 CNI 的关联
  5. kubeadm 引导:13 个初始化阶段的逐步解析,PKI 证书体系的安全要点
  6. Worker 加入:token 管理与 CRI socket 指定

下一篇将介绍 Calico 网络插件的部署与集群可用性全面验证,包括 Pod 跨节点通信、Service 负载均衡、DNS 解析、网络策略等内容。


参考资料

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐