概述

Kubernetes v1.24 正式移除了 dockershim,这意味着 Docker Engine 不再作为 K8s 的容器运行时。取而代之的是符合 CRI(Container Runtime Interface)规范的运行时,其中 containerd 是事实上的标准选择。

很多运维人员对 containerd 的认知停留在"Docker 的替代品",但实际上 containerd 的架构、镜像管理、命令行工具都与 Docker 有显著差异。本文深入理解 CRI 规范、containerd 架构、日常运维和从 Docker 迁移的实践。

本文基于 containerd v2.0 和 Kubernetes v1.30。参考 containerd 官方文档

CRI 规范

什么是 CRI

CRI(Container Runtime Interface)是 K8s 定义的容器运行时接口规范。K8s 不再直接调用容器运行时,而是通过 gRPC 调用符合 CRI 规范的运行时:

kubelet → CRI gRPC → 容器运行时(containerd/CRI-O)→ 容器

CRI 接口定义

CRI 定义了两个核心服务:

服务功能关键方法
RuntimeService容器和 Pod 生命周期RunPodSandbox, CreateContainer, StartContainer, StopContainer
ImageService镜像管理ListImages, PullImage, RemoveImage, ImageStatus
// CRI 接口简化定义
service RuntimeService {
    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse);
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse);
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse);
    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse);
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse);
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse);
    rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse);
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse);
}

service ImageService {
    rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
    rpc PullImage(PullImageRequest) returns (PullImageResponse);
    rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse);
    rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse);
}

为什么移除 dockershim

Docker Engine 不原生支持 CRI,K8s 通过 dockershim 适配层调用 Docker。这带来几个问题:

问题说明
额外适配层dockershim 增加了一层调用,降低效率
维护负担K8s 维护 dockershim 代码,但 Docker 更新频繁
功能限制dockershim 无法利用 CRI 的高级特性
额外进程Docker Engine 包含 dockerd、containerd、docker-proxy 等多个进程

移除 dockershim 后,直接使用 containerd 去掉了中间层:

# 旧架构(Docker)
kubelet → dockershim → dockerd → containerd → containerd-shim → runc → 容器

# 新架构(containerd)
kubelet → CRI Plugin → containerd → containerd-shim → runc → 容器

Docker vs containerd vs CRI-O

三大运行时对比

特性Docker EnginecontainerdCRI-O
CRI 支持需 dockershim原生支持原生支持
架构多进程(dockerd + containerd)单进程单进程
镜像格式Docker ImageOCI ImageOCI Image
CLIdockernerdctl / ctrcrictl
镜像存储独立独立独立
网络管理docker-proxyCNICNI
构建能力docker buildnerdctl build / buildkitbuildah
适用场景开发环境K8s 生产OpenShift
资源开销

架构对比

# Docker Engine 架构
dockerd(HTTP API + 镜像构建 + 卷管理)
  └── containerd(容器生命周期)
        └── containerd-shim(容器进程管理)
              └── runc(OCI 运行时)

# containerd 架构
containerd(CRI + 容器生命周期 + 镜像管理)
  └── containerd-shim(容器进程管理)
        └── runc(OCI 运行时)

# CRI-O 架构
cri-o(CRI + 容器生命周期 + 镜像管理)
  └── conmon(容器进程监控)
        └── runc(OCI 运行时)

资源开销对比

指标DockercontainerdCRI-O
常驻内存~150MB~50MB~40MB
磁盘占用~200MB~80MB~70MB
启动时间~2s~0.5s~0.5s
镜像拉取速度基准快 10-15%快 10-15%

数据为大致参考值,实际取决于节点配置和负载。

containerd 架构

核心组件

containerd
├── Containerd API    (gRPC API,供外部调用)
├── Containerd Runtime
│   ├── Tasks Manager   (容器任务管理)
│   ├── Runtime Shim    (OCI 运行时 shim)
│   └── runc            (OCI 低级运行时)
├── Containerd Images
│   ├── Pull / Push     (镜像拉取/推送)
│   ├── Image Store     (镜像存储)
│   └── Content Store   (镜像内容存储)
├── Containerd Snapshots
│   └── Snapshotter     (快照管理,overlayfs/btrfs)
├── Containerd Metadata
│   └── BoltDB          (元数据存储)
└── Containerd Events   (事件系统)

containerd 配置文件

# /etc/containerd/config.toml
version = 2

root = "/var/lib/containerd"
state = "/run/containerd"
oom_score = 0

[grpc]
  address = "/run/containerd/containerd.sock"
  uid = 0
  gid = 0

[metrics]
  address = "127.0.0.1:8080"
  grpc_histogram = false

[debug]
  address = ""
  uid = 0
  gid = 0
  level = "info"

# CRI 插件配置
[plugins."io.containerd.grpc.v1.cri"]
  # sandbox_image = "registry.k8s.io/pause:3.9"
  sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9"

  # 容器运行时
  [plugins."io.containerd.grpc.v1.cri".containerd]
    default_runtime_name = "runc"
    snapshotter = "overlayfs"

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
      runtime_type = "io.containerd.runc.v2"
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
        SystemdCgroup = true        # 使用 systemd cgroup

  # 镜像拉取配置
  [plugins."io.containerd.grpc.v1.cri".registry]
    config_path = "/etc/containerd/certs.d"   # 镜像仓库配置目录

  # 镜像 GC 策略
  [plugins."io.containerd.grpc.v1.cri".image_decryption]
    key_model = "node"

  # 日志配置
  [plugins."io.containerd.grpc.v1.cri".containerd.log]
    max_size = "100MB"
    max_files = 3

systemd cgroup 驱动

K8s v1.26+ 要求容器运行时使用 systemd cgroup 驱动:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true

如果不配置,kubelet 和 containerd 的 cgroup 驱动不一致会导致不稳定。

镜像管理

镜像拉取加速配置

# 方式一:config.toml 中配置镜像仓库(旧方式,已废弃)
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
  endpoint = ["https://mirror.ccs.tencentyun.com", "https://registry-1.docker.io"]

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
  endpoint = ["https://registry.cn-hangzhou.aliyuncs.com/google_containers"]
# 方式二:hosts.toml 方式(containerd 1.7+,推荐)
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml << 'EOF'
server = "https://registry-1.docker.io"

[host."https://mirror.ccs.tencentyun.com"]
  capabilities = ["pull", "resolve"]

[host."https://docker.1panel.live"]
  capabilities = ["pull", "resolve"]
EOF

# k8s.io 镜像
mkdir -p /etc/containerd/certs.d/registry.k8s.io
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml << 'EOF'
server = "https://registry.k8s.io"

[host."https://registry.cn-hangzhou.aliyuncs.com/google_containers"]
  capabilities = ["pull", "resolve"]
EOF

# 私有仓库认证
mkdir -p /etc/containerd/certs.d/harbor.example.com
cat > /etc/containerd/certs.d/harbor.example.com/hosts.toml << 'EOF'
server = "https://harbor.example.com"

[host."https://harbor.example.com"]
  capabilities = ["pull", "resolve"]
  ca = "/etc/containerd/certs.d/harbor.example.com/ca.crt"
  # 或跳过 TLS 验证(不推荐生产使用)
  # skip_verify = true
EOF
# config.toml 中指向 certs.d 目录
[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"

私有仓库认证

# 方式一:K8s Secret(Pod 拉取镜像时使用)
kubectl create secret docker-registry harbor-secret \
  --docker-server=harbor.example.com \
  --docker-username=admin \
  --docker-password=Harbor12345 \
  --docker-email=admin@example.com \
  -n production

# Pod 中引用
# imagePullSecrets:
# - name: harbor-secret
# 方式二:节点级认证(所有 Pod 共享)
# 创建认证目录
mkdir -p /etc/containerd/certs.d/harbor.example.com

# 登录生成认证文件
crictl login harbor.example.com -u admin -p Harbor12345

# 或手动写入
cat > /var/lib/kubelet/config.json << 'EOF'
{
  "auths": {
    "harbor.example.com": {
      "auth": "YWRtaW46SGFyYm9yMTIzNDU="
    }
  }
}
EOF

命名空间

containerd 命名空间

containerd 使用命名空间隔离不同客户端的容器和镜像:

命名空间使用者说明
k8s.iokubeletK8s 管理的容器和镜像
mobyDockerDocker Engine 管理的容器(如果安装了 Docker)
defaultnerdctl / ctr默认命名空间
buildkitbuildkitd构建过程中使用的镜像
# 查看所有命名空间
ctr namespaces list

# 在指定命名空间操作
ctr -n k8s.io images list
ctr -n k8s.io containers list

重要:K8s 管理的容器和镜像在 k8s.io 命名空间下。用 ctr 操作时要加 -n k8s.io,否则看不到 K8s 的资源。

CLI 工具

ctr:containerd 原生 CLI

ctr 是 containerd 自带的命令行工具,功能最全但不够友好:

# 镜像操作
ctr -n k8s.io images list                          # 列出镜像
ctr -n k8s.io images pull docker.io/library/nginx:1.25-alpine  # 拉取镜像
ctr -n k8s.io images push harbor.example.com/myapp:v1          # 推送镜像
ctr -n k8s.io images delete myapp:v1                            # 删除镜像
ctr -n k8s.io images export app.tar myapp:v1                    # 导出镜像
ctr -n k8s.io images import app.tar                             # 导入镜像

# 容器操作
ctr -n k8s.io containers list                       # 列出容器
ctr -n k8s.io tasks list                            # 列出运行中的任务

crictl:K8s 容器调试工具

crictl 是 K8s 社区维护的 CRI 调试工具,与 K8s 概念对齐:

# 查看 Pod
crictl pods

# 查看容器
crictl ps
crictl ps -a                          # 包括已停止的
crictl ps --name nginx                # 按名称过滤

# 查看镜像
crictl images
crictl images --digests               # 显示摘要

# 查看容器日志
crictl logs <container-id>
crictl logs -f <container-id>         # 跟踪日志

# 在容器中执行命令
crictl exec -it <container-id> sh

# 查看容器状态
crictl inspect <container-id>
crictl stats

# 拉取镜像
crictl pull nginx:1.25-alpine
# /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false

nerdctl:Docker 兼容 CLI

nerdctl 是 containerd 社区开发的 Docker 兼容 CLI,推荐作为 Docker CLI 的替代:

# 基本与 docker 命令兼容
nerdctl ps                             # 列出容器
nerdctl images                         # 列出镜像
nerdctl pull nginx:1.25-alpine         # 拉取镜像
nerdctl run -d --name nginx -p 80:80 nginx:1.25-alpine  # 运行容器
nerdctl exec -it nginx sh              # 进入容器
nerdctl logs -f nginx                  # 查看日志
nerdctl stop nginx                     # 停止容器
nerdctl rm nginx                       # 删除容器
nerdctl rmi nginx:1.25-alpine          # 删除镜像

# 构建镜像(需要 buildkitd)
nerdctl build -t myapp:v1 .
nerdctl build -t myapp:v1 --platform linux/amd64,linux/arm64 .

# compose 支持
nerdctl compose up -d
nerdctl compose down

# 指定命名空间
nerdctl --namespace=k8s.io ps
nerdctl --namespace=default ps

工具对比

功能ctrcrictlnerdctl
镜像管理支持支持支持
容器管理支持只读支持
容器创建支持不支持支持
镜像构建不支持不支持支持
compose不支持不支持支持
日志查看不支持支持支持
exec不支持支持支持
K8s 对齐
Docker 兼容

推荐:日常运维用 crictl(与 K8s 概念一致),替代 Docker CLI 用 nerdctl,底层调试用 ctr

snapshotter

快照器的作用

snapshotter 负责管理容器文件系统的层叠。每拉取一个镜像层,snapshotter 创建一个快照;运行容器时,在最上层创建可读写层。

支持的 snapshotter

snapshotter说明适用场景
overlayfs默认,基于 OverlayFS通用(推荐)
btrfs基于 Btrfs 文件系统需要 Btrfs 支持
zfs基于 ZFS 文件系统需要 ZFS 支持
native简单的文件拷贝测试环境
stargz懒加载镜像大镜像加速启动
nydus镜像加速(蚂蚁开源)大镜像加速启动

overlayfs 配置

[plugins."io.containerd.grpc.v1.cri".containerd]
  snapshotter = "overlayfs"
# 查看 overlayfs 挂载
mount | grep overlay

# 查看镜像层
ctr -n k8s.io snapshots list

Stargz / Nydus 懒加载

传统镜像拉取是全量的,大镜像(GB 级)拉取慢。Stargz 和 Nydus 通过懒加载解决:

# 启用 stargz snapshotter
[plugins."io.containerd.snapshotter.v1.stargz"]
  root_path = "/var/lib/containerd-stargz-grpc"
# 传统镜像拉取:全量下载 → 启动容器(慢)
# 懒加载镜像:元数据下载 → 启动容器 → 按需下载(快)

性能对比

镜像拉取性能

# 测试条件:1GB 镜像,百兆内网
Docker:        ~85s  (包含 dockerd 调度开销)
containerd:    ~72s  (直接 CRI 调用)
CRI-O:         ~73s  (直接 CRI 调用)
containerd + stargz: ~15s(懒加载,按需拉取)

容器启动性能

# 测试条件:nginx:alpine,100 个并发容器
Docker:        ~8s
containerd:    ~5s
CRI-O:         ~5s

内存占用

# 空载状态下
Docker(dockerd + containerd): ~150MB
containerd:                   ~50MB
CRI-O:                        ~40MB

从 Docker 迁移到 containerd

迁移评估

检查项说明
镜像构建Docker Build 替换为 BuildKit / nerdctl build
CLI 工具docker 命令替换为 crictl / nerdctl
镜像仓库认证配置方式不同
监控脚本依赖 docker CLI 的脚本需更新
日志驱动containerd 不支持 Docker 的日志驱动
docker.sock依赖 Docker socket 的应用需改造

迁移步骤

# 1. 安装 containerd
apt-get update && apt-get install -y containerd

# 2. 生成默认配置
containerd config default > /etc/containerd/config.toml

# 3. 修改配置(SystemdCgroup + 镜像加速)
# 编辑 /etc/containerd/config.toml,确保:
# SystemdCgroup = true
# config_path = "/etc/containerd/certs.d"

# 4. 配置镜像加速
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml << 'EOF'
server = "https://registry-1.docker.io"
[host."https://mirror.ccs.tencentyun.com"]
  capabilities = ["pull", "resolve"]
EOF

# 5. 重启 containerd
systemctl restart containerd
systemctl enable containerd

# 6. 修改 kubelet 配置
# /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock"

# 7. 重启 kubelet
systemctl restart kubelet

# 8. 验证
kubectl get nodes -o wide
# 验证 CONTAINER-RUNTIME 列显示 containerd://

镜像迁移

# 导出 Docker 镜像
docker save -o myapp.tar myapp:v1

# 导入到 containerd
ctr -n k8s.io images import myapp.tar

# 或用 nerdctl
nerdctl -n k8s.io load -i myapp.tar

脚本迁移

# Docker 命令 → containerd 等价命令对照

# 列出容器
docker ps                    → crictl ps
# 列出镜像
docker images                → crictl images
# 拉取镜像
docker pull nginx            → crictl pull nginx
# 查看日志
docker logs <id>             → crictl logs <id>
# 进入容器
docker exec -it <id> sh      → crictl exec -it <id> sh
# 查看容器详情
docker inspect <id>          → crictl inspect <id>
# 查看容器资源
docker stats                 → crictl stats
# 构建镜像
docker build -t app .        → nerdctl build -t app .
# 运行容器
docker run -d nginx          → nerdctl run -d nginx
# 删除镜像
docker rmi nginx             → crictl rmi nginx

日常运维

镜像 GC 配置

# K8s 镜像 GC 通过 kubelet 管理,不在 containerd 配置
# /var/lib/kubelet/config.yaml
imageGCHighThresholdPercent: 85    # 磁盘使用率超过85%触发GC
imageGCLowThresholdPercent: 80     # GC到80%停止
imageMinimumGCAge: 2m              # 镜像至少存在2分钟才可被GC

日志管理

containerd 不像 Docker 有丰富的日志驱动。容器日志通过 kubelet 管理,输出到 /var/log/pods/

# 查看容器日志
crictl logs <container-id>

# K8s Pod 日志路径
ls /var/log/pods/<namespace>_<pod-name>_<pod-uid>/

# 日志轮转配置(kubelet)
# /var/lib/kubelet/config.yaml
containerLogMaxSize: "100Mi"
containerLogMaxFiles: 5

性能调优

# containerd 性能调优
version = 2

# 提高 gRPC 并发
[grpc]
  max_concurrent_streams = 1000

# 调整镜像拉取并发
[plugins."io.containerd.grpc.v1.cri"]
  max_concurrent_downloads = 5       # 镜像并发下载数(默认3)

# overlayfs 性能
[plugins."io.containerd.snapshotter.v1.overlayfs"]
  upperdir_capability = true

故障排查

# 查看 containerd 状态
systemctl status containerd

# 查看 containerd 日志
journalctl -u containerd -f

# 查看 CRI 状态
crictl info

# 查看 containerd metrics
curl -s http://localhost:8080/metrics | grep containerd

# 常见问题
# 1. Pod 创建失败 - ImagePullBackOff
crictl images | grep <image>       # 检查镜像是否存在
ctr -n k8s.io images pull <image>  # 手动拉取测试

# 2. 容器启动失败
crictl inspect <container-id>      # 查看容器详情
journalctl -u containerd | grep <container-id>  # 查看日志

# 3. 镜像拉取慢
cat /etc/containerd/certs.d/*/hosts.toml  # 检查镜像加速配置

总结

containerd 已成为 K8s 的事实标准容器运行时,核心要点:

  1. CRI 是规范,containerd 是实现:理解 CRI 规范有助于理解 K8s 与运行时的交互方式。
  2. 三个 CLI 各有定位crictl 用于 K8s 运维(只读操作为主),nerdctl 作为 Docker CLI 替代,ctr 用于底层调试。
  3. 镜像加速必须配:国内环境必须配置镜像加速,使用 certs.d 目录方式(hosts.toml),旧版配置方式已废弃。
  4. SystemdCgroup 必须开:K8s 要求 cgroup 驱动一致,不配置会导致不稳定。
  5. 命名空间隔离:K8s 的容器和镜像在 k8s.io 命名空间,操作时要加 -n k8s.io
  6. 迁移要有计划:从 Docker 迁移到 containerd 需要评估依赖 Docker socket 的应用、监控脚本等,不能直接切换。
  7. 大镜像用懒加载:Stargz / Nydus 可以显著加速大镜像的启动,适合 CI/CD 场景。

containerd 的设计比 Docker 更简洁高效,但也意味着一些 Docker 的便利功能(如 docker build、丰富的日志驱动)需要用替代方案。理解这些差异是平滑迁移的关键。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. containerd 官方文档 — Containerd,参考了containerd 官方文档相关内容