containerd 常用运维指令手册

运维指令手册 41.0 KB 原文

containerd 常用运维指令手册

适用版本:containerd 1.6 / 1.7 / 2.0 / 2.4(当前),runc 1.1+ / 1.4,nerdctl 2.x 配套:Kubernetes 1.35–1.37、Docker Engine 24–29 约定:# root 或 sudo,$ 普通用户,<xxx> 为占位符。ctr 与 crictl 的命令不要混用(namespace 不同,见 1.4)。 更新时间:2026-11(版本事实核对至 2026-11-05)


目录

  1. 定位与架构
  2. 版本与兼容矩阵
  3. 安装部署
  4. config.toml 配置详解
  5. 镜像仓库配置
  6. ctr 命令全集
  7. nerdctl
  8. crictl
  9. namespace 与内容存储
  10. snapshotter 与存储
  11. GC 与空间回收
  12. 服务运维与调试
  13. 与 Kubernetes 集成排障
  14. 升级与迁移
  15. 卸载与清理
  16. 故障速查
  17. 附录

1. 定位与架构

1.1 containerd 在哪一层

┌──────────────────────────────────────────────┐
│ Kubernetes (kubelet)      Docker (dockerd)   │   ← 消费者
└───────────┬──────────────────────┬───────────┘
            │ CRI (gRPC)           │ containerd API (gRPC)
            ▼                      ▼
┌──────────────────────────────────────────────┐
│                 containerd                    │   ← 镜像分发 + 容器执行
│  ┌─────────┐ ┌──────────┐ ┌────────────────┐ │
│  │ content │ │ snapshot │ │ runtime (task)  │ │
│  └─────────┘ └──────────┘ └───────┬────────┘ │
└───────────────────────────────────┼──────────┘
                                    ▼
                       ┌────────────────────────┐
                       │ containerd-shim-runc-v2│   ← 每容器一个
                       └───────────┬────────────┘
                                   ▼
                       ┌────────────────────────┐
                       │  runc → 业务进程       │   ← OCI 运行时
                       └────────────────────────┘

职责边界:

组件 负责 不负责
containerd 镜像拉取/解包、快照管理、任务生命周期、CRI 服务 网络配置(交给 CNI)、存储编排(交给 CSI)、调度
shim 容器的父进程,负责 stdio 转发、退出回收、让容器脱离 containerd 生命周期 业务逻辑
runc 创建 namespace/cgroup,exec 业务进程,然后退出 长期驻留

1.2 关键概念

概念 含义 常用命令
namespace 逻辑隔离的元数据空间(default / k8s.io / moby) ctr ns ls
content store 按 digest 存 blobs(层、配置、manifest) ctr content ls
image 指向 content 的命名引用 + 目标平台 ctr images ls
snapshot 容器/镜像的文件系统层(由 snapshotter 管理) ctr snapshots ls
container 元数据(spec),未运行 ctr containers ls
task 运行中的进程(真正占资源) ctr tasks ls
shim 每 task 一个的守护进程 ps | grep shim

重要:container 和 task 是分开的。ctr containers ls 有记录但 ctr tasks ls 没有,说明容器处于 created 但未 started 状态。

1.3 CRI 是什么

CRI(Container Runtime Interface)是 kubelet 与运行时之间的 gRPC 协议。containerd 内置 cri 插件实现它,所以 kubelet 可以直接用 containerd,不需要 Docker。

kubelet --container-runtime-endpoint=unix:///run/containerd/containerd.sock

版本差异:Kubernetes 1.24 移除了内置 dockershim,--container-runtime=docker 不再可用。若必须用 Docker Engine 作运行时,需装 Mirantis 维护的 cri-dockerd,但不推荐。

1.4 三个 CLI 操作的是不同世界(最高频误区)

工具 默认 namespace 看到的东西
ctr default 用 ctr 或 nerdctl 默认 ns 创建的镜像
nerdctl default 同上
crictl 自动读 CRI 配置(通常是 k8s.io) kubelet 创建的容器与镜像
ctr -n k8s.io k8s.io k8s 的镜像与容器(只读排查,别动手)
# 这三种输出经常完全不同,这就是"镜像明明有却拉取"的根因
ctr images ls | head
nerdctl images | head
crictl images | head
ctr -n k8s.io images ls | head

铁律:k8s 节点的日常排查一律用 crictl。ctr -n k8s.io 只用于读,用它 rm 容器会绕过 kubelet,导致状态不一致。


2. 版本与兼容矩阵

2.1 版本事实(2026-11)

项目 状态
当前版本 containerd 2.4.x
上个主线 2.0 / 2.1 / 2.2 / 2.3
已淘汰 1.x 自 Kubernetes 1.36 起不再受支持
配套 runc 1.4.x
配套 CNI 1.6+
内置 CRI 稳定,支持 cgroup v2

2.2 兼容矩阵(关键)

Kubernetes 支持的最低 containerd 说明
1.35 1.7.x / 2.x 1.x 最后一个可用版本
1.36 2.0+ 1.x 不再支持
1.37 2.x cgroup v1 默认失败
Docker Engine 内置 containerd
24.x 1.6.x
26.x 1.7.x
27.x 1.7.x
28.x 2.0.x
29.x 2.4.x(29.7+ 可选内嵌模式)

2.3 2.x 相对 1.x 的主要变化

变化 影响
配置文件 version = 3 老 version = 2 的插件段名不再兼容(io.containerd.grpc.v1.cri 仍保留)
移除 io.containerd.runtime.v1.linux 老 runtime 配置报错
snapshotter 默认仍是 overlayfs,但新增 erofs 只读镜像层可用 erofs,启动更快
CRI 内 pinned_images 替代 sandbox_image 老配置字段仍兼容但会告警
沙箱 API(sandbox controller) 支持 pod 级 sandbox 抽象(1.7 起实验,2.x 完善)
默认启用 cgropups v2 路径 cgroup v1 需显式配置
内置 transfer 服务 镜像拉取走 transfer,支持并发与重试控制
containerd --version
containerd config default | head -5      # 看 version = 3

3. 安装部署

3.1 二进制安装(最通用)

VER=2.4.2
ARCH=amd64

# 1) containerd
curl -fLO https://github.com/containerd/containerd/releases/download/v${VER}/containerd-${VER}-linux-${ARCH}.tar.gz
tar Cxzvf /usr/local containerd-${VER}-linux-${ARCH}.tar.gz
# 解出 /usr/local/bin/{containerd,containerd-shim-runc-v2,ctr,...}

# 2) runc
RUNC=1.4.3
curl -fLO https://github.com/opencontainers/runc/releases/download/v${RUNC}/runc.${ARCH}
install -m 755 runc.${ARCH} /usr/local/sbin/runc

# 3) systemd 单元
curl -fLo /etc/systemd/system/containerd.service \
  https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
# 或手工写(见 3.4)

systemctl daemon-reload
systemctl enable --now containerd
systemctl status containerd

# 4) 生成默认配置
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml

3.2 包管理器安装

# RHEL 系(Docker 官方仓库里有 containerd.io)
dnf install -y containerd.io
# 或 Docker CE 仓库
dnf install -y --repo docker-ce-stable containerd.io-2.4.2

# Ubuntu
apt install -y containerd.io

# 注意:包安装的路径与二进制安装不同
# 二进制:/usr/local/bin/containerd,配置 /etc/containerd/config.toml
# 包安装:/usr/bin/containerd,配置同上;systemd 单元由包提供

which containerd
containerd --version

3.3 nerdctl 与 CNI 插件

NERDCTL=2.2.0
curl -fLO https://github.com/containerd/nerdctl/releases/download/v${NERDCTL}/nerdctl-${NERDCTL}-linux-amd64.tar.gz
tar Cxzf /usr/local/bin nerdctl-${NERDCTL}-linux-amd64.tar.gz

# nerdctl 的 run 需要 CNI 插件(k8s 节点已有,不必重复装)
mkdir -p /opt/cni/bin
curl -fL https://github.com/containernetworking/plugins/releases/download/v1.6.2/cni-plugins-linux-amd64-v1.6.2.tgz \
  | tar -C /opt/cni/bin -xz

# nerdctl compose 需要 buildkit 才有 build
# 二进制方式
curl -fL -o /usr/local/bin/buildkitd https://github.com/moby/buildkit/releases/download/v0.18.0/buildkit-v0.18.0.linux-amd64.tar.gz
# 或用发行版包:dnf install -y buildkit

nerdctl version
nerdctl info

3.4 标准 systemd 单元(手工安装用)

# /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now containerd
journalctl -u containerd -f

3.5 安装后验证

containerd --version
ctr version
runc --version
ctr plugin ls | grep -E 'cri|overlayfs'
# 期望:io.containerd.grpc.v1.cri  ok
#       io.containerd.snapshotter.v1.overlayfs  ok

systemctl status containerd | head -5
ls -l /run/containerd/containerd.sock

# 冒烟测试
ctr images pull docker.io/library/hello-world:latest
ctr run --rm docker.io/library/hello-world:latest test-hello

4. config.toml 配置详解

4.1 生成与查看

# 不带参数 = 输出默认配置
containerd config default > /etc/containerd/config.toml

# 查看当前生效的完整配置(含默认值展开)
containerd config dump > /tmp/effective.toml

# 校验配置
containerd config dump >/dev/null && echo OK

# 修改后重启
systemctl restart containerd

重要:containerd config default 每次都会输出完整默认值,不要用 > 覆盖正在用的配置,先备份再对比。

4.2 顶层结构

version = 3
root  = "/var/lib/containerd"      # 持久数据(content、snapshot、metadata)
state = "/run/containerd"          # 运行态(socket、shim 通信)
# plugin_dir = "/opt/containerd/plugins"
# grpc 超时等
imports = ["/etc/containerd/conf.d/*.toml"]   # 分片配置,2.x 支持

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

[debug]
  address = "/run/containerd/debug.sock"
  uid = 0
  gid = 0
  level = "info"

[metrics]
  address = "127.0.0.1"
  grpc_histogram = false

[cgroup]
  path = ""

4.3 CRI 插件(k8s 节点最关键)

[plugins."io.containerd.grpc.v1.cri"]
  # 沙箱(pause)镜像 —— 必须与 k8s 版本匹配的 pause tag
  sandbox_image = "registry.k8s.io/pause:3.10"

  # 2.x 用 pinned_images(2.0+ 推荐)
  # [plugins."io.containerd.grpc.v1.cri".pinned_images]
  #   sandbox = "registry.k8s.io/pause:3.10"

  max_container_log_line_size = 16384
  max_concurrent_downloads = 3
  disable_tcp_service = true
  stream_server_address = "127.0.0.1"
  stream_server_port = "0"
  enable_selinux = false
  selinux_category_range = 1024
  # cgroup 驱动,k8s 节点必须 systemd
  # (实际在 runc 的 options 里设,见下)
  # 单次拉取镜像的并发层数
  image_pull_progress_timeout = "5m"
  image_pull_with_sync_fs = false

  # containerd 自身的 cgroup 路径
  containerd_max_container_log_line_size = 16384

  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "overlayfs"
    # 默认运行时
    default_runtime_name = "runc"
    # 禁止用默认 runtime 跑系统容器
    # disable_snapshot_annotations = true
    # 丢弃未使用的镜像层(GC 相关)
    discard_unpacked_layers = false

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
      # runc:标准配置
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
        runtime_type = "io.containerd.runc.v2"
        pod_annotations = ["io.kubernetes.cri.*"]

        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
          # ★ 必须 true,否则 kubelet 管理的 pod 会反复重启
          SystemdCgroup = true
          # 其余极少需要改
          # NoPivotRoot = false
          # ShimCgroup = ""

      # 示例:加一个 runsc(gVisor)运行时
      # [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
      #   runtime_type = "io.containerd.runsc.v1"

  # CNI 配置(kubelet 通常自己调 CNI,此项供 containerd 内建 CNI 使用)
  [plugins."io.containerd.grpc.v1.cri".cni]
    bin_dir = "/opt/cni/bin"
    conf_dir = "/etc/cni/net.d"
    max_conf_num = 1
    conf_template = ""
    # 2.x:ip_pref = "ipv4"

  # 镜像仓库配置入口(详见第 5 章)
  [plugins."io.containerd.grpc.v1.cri".registry]
    config_path = "/etc/containerd/certs.d"
    # 老写法(1.x 常见,仍兼容)
    # [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
    #   [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
    #     endpoint = ["https://docker.m.daocloud.io"]
    # [plugins."io.containerd.grpc.v1.cri".registry.configs]
    #   [plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.internal".tls]
    #     ca_file = "/etc/containerd/ca.crt"
    #   [plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.internal".auth]
    #     username = "robot"
    #     password = "xxx"

4.4 snapshotter 插件

[plugins."io.containerd.snapshotter.v1.overlayfs"]
  root_path = ""            # 默认 <root>/io.containerd.snapshotter.v1.overlayfs
  # 上层目录最大并发挂载
  sync_remove = false
  # 慢设备上可加
  # upperdir_label = false

# 2.x 新增 erofs(只读层,启动快、省空间)
# [plugins."io.containerd.snapshotter.v1.erofs"]
#   root_path = ""

# native:不用 overlay,每层存目录(慢,排障用)
# [plugins."io.containerd.snapshotter.v1.native"]
#   root_path = ""

4.5 其他常用插件

# 镜像加速(transfer 服务,2.x)
[plugins."io.containerd.transfer.v1.local"]
  max_concurrent_downloads = 3
  max_concurrent_uploads = 3

# 运行时 v2(底层 task 服务)
[plugins."io.containerd.runtime.v2.task"]
  platforms = ["linux/amd64"]

# CRI 的 stream server(exec/port-forward 用)
[plugins."io.containerd.grpc.v1.cri".stream_server]
  # 默认自动选端口

4.6 配置校验与回滚

cp /etc/containerd/config.toml /etc/containerd/config.toml.bak-$(date +%F)

# 改完先校验(能 dump 说明语法 OK)
containerd config dump >/dev/null && echo "syntax ok"

systemctl restart containerd
systemctl status containerd --no-pager | head -10
journalctl -u containerd -n 30 --no-pager

# 回滚
cp /etc/containerd/config.toml.bak-2026-11-05 /etc/containerd/config.toml
systemctl restart containerd

5. 镜像仓库配置

5.1 两种配置方式

方式 位置 优点 缺点
certs.d(推荐) /etc/containerd/certs.d/<host>/hosts.toml 每仓库独立、可热加载、迁移简单 需 config_path 开启
config.toml 内 registry.mirrors /etc/containerd/config.toml 单文件、老版本兼容 改一次重启一次、易写错
# config.toml 中开启 certs.d
[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"

5.2 certs.d 实战

# 目录结构:一个 registry 一个目录,里面放 hosts.toml + ca.crt + client cert
/etc/containerd/certs.d/
├── docker.io/
│   └── hosts.toml
├── registry.k8s.io/
│   └── hosts.toml
└── harbor.internal/
    ├── hosts.toml
    ├── ca.crt
    └── client.cert / client.key

镜像加速(docker.io)

# /etc/containerd/certs.d/docker.io/hosts.toml
server = "https://docker.io"

[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
  # 覆盖(默认不覆盖,写 true 会优先用镜像站的 manifest)
  override_path = false

k8s 镜像加速

# /etc/containerd/certs.d/registry.k8s.io/hosts.toml
server = "https://registry.k8s.io"

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

自签 Harbor(HTTPS)

# /etc/containerd/certs.d/harbor.internal/hosts.toml
server = "https://harbor.internal"

[host."https://harbor.internal"]
  capabilities = ["pull", "resolve", "push"]
  ca = "/etc/containerd/certs.d/harbor.internal/ca.crt"
  # 客户端证书(双向 TLS 时)
  # client = [["/etc/containerd/certs.d/harbor.internal/client.cert",
  #            "/etc/containerd/certs.d/harbor.internal/client.key"]]
  # skip_verify = false

HTTP 仓库(内网 80 端口)

# /etc/containerd/certs.d/harbor.internal:80/hosts.toml
server = "http://harbor.internal:80"

[host."http://harbor.internal:80"]
  capabilities = ["pull", "resolve", "push"]
  skip_verify = true

注意:目录名必须与镜像引用中的 host 完全一致(含端口)。harbor.internal:80 与 harbor.internal 是两个不同目录,这是"配了不生效"的头号原因。

5.3 凭据配置

# 方式一:hosts.toml 内嵌(简单,但明文)
[host."https://harbor.internal"]
  capabilities = ["pull","push"]
  [host."https://harbor.internal".header]
    authorization = "Basic <base64>"

# 方式二:与 Docker 共用凭据文件(推荐)
mkdir -p /root/.docker
cat >/root/.docker/config.json <<'EOF'
{
  "auths": {
    "harbor.internal": { "auth": "<base64(user:pass)>" }
  }
}
EOF

# 方式三:k8s 场景用 imagePullSecrets(最规范)
kubectl create secret docker-registry harbor-cred \
  --docker-server=harbor.internal \
  --docker-username='robot$ci' --docker-password='xxx' -n prod

5.4 验证

# 用 crictl 测(走 CRI 路径)
crictl pull harbor.internal/library/nginx:1.25

# 用 ctr 测(走 containerd 直连,不读 certs.d 的镜像加速?会读)
ctr -n k8s.io images pull harbor.internal/library/nginx:1.25 --hosts-dir /etc/containerd/certs.d

# 排障:看 containerd 日志里的 registry 请求
journalctl -u containerd -f | grep -iE 'registry|pull|tls|x509'

# 手工验证 TLS 链
openssl s_client -connect harbor.internal:443 -servername harbor.internal </dev/null 2>/dev/null | \
  openssl x509 -noout -subject -issuer -dates

5.5 常见错误

报错 原因 处理
x509: certificate signed by unknown authority 未配 CA ca = ... 或系统信任
http: server gave HTTP response to HTTPS client 仓库是 HTTP,按 HTTPS 访问 hosts.toml 用 http:// + skip_verify
no such host DNS 解析失败 检查 /etc/hosts 或 DNS
401 Unauthorized 未登录 配凭据或 imagePullSecrets
403 Forbidden 账号无 push 权限 Harbor 项目成员权限
配了 mirror 仍走原站 目录名不匹配 核对 host:port 拼写
failed to resolve reference 镜像名缺 tag/registry 写全名

6. ctr 命令全集

说明:ctr 是 containerd 的原生调试工具,功能强但危险(绕过 kubelet/docker 的状态管理)。生产 k8s 节点上只读使用。

6.1 namespace

ctr namespace ls
ctr ns ls                       # 简写
ctr namespace create myns
ctr namespace rm myns
ctr -n k8s.io namespace ls      # 指定 ns 执行任意命令

6.2 镜像

ctr -n k8s.io images ls
ctr -n k8s.io images ls -q      # 只输出名字
ctr images pull docker.io/library/nginx:1.25
ctr images pull --platform linux/arm64 nginx:1.25
ctr images pull --hosts-dir /etc/containerd/certs.d harbor.internal/app:1.0
ctr images pull --user admin:pwd harbor.internal/app:1.0
ctr images export nginx.tar docker.io/library/nginx:1.25
ctr images import nginx.tar
ctr images import --all-platforms nginx.tar
ctr images tag docker.io/library/nginx:1.25 harbor.internal/mirror/nginx:1.25
ctr images push harbor.internal/mirror/nginx:1.25
ctr images rm docker.io/library/nginx:1.25
ctr images check                  # 检查 content 完整性
ctr images check --quiet

6.3 容器与任务

# container = 元数据,task = 运行实例
ctr containers ls
ctr containers create docker.io/library/nginx:1.25 web
ctr containers rm web
ctr containers info web

ctr tasks ls
ctr run -d docker.io/library/nginx:1.25 web          # create + start
ctr run --rm -t docker.io/library/alpine:3.20 sh
ctr run --mount type=bind,src=/data,dst=/data,options=rbind:rw alpine ls /data
ctr run --env FOO=bar alpine env
ctr run --with-ns network:/proc/1234/ns/net alpine ip addr   # 加入已有 netns

ctr tasks start web
ctr tasks kill web
ctr tasks kill -s SIGKILL web
ctr tasks pause web
ctr tasks resume web
ctr tasks ps web
ctr tasks delete web
ctr tasks exec --exec-id t1 -t web sh
ctr tasks metrics web             # 资源指标
ctr tasks ls --quiet

6.4 内容与快照

ctr content ls
ctr content ls -q | head
ctr content fetch docker.io/library/nginx:1.25
ctr content prune references        # 清理无引用 blob
ctr content prune references --dry-run

ctr snapshots ls
ctr snapshots ls -s                # 显示大小
ctr snapshots tree <key>
ctr snapshots usage <key>
ctr snapshots rm <key>
ctr snapshots commit <key> <name>

6.5 插件与调试

ctr plugin ls
ctr plugin ls | grep -v ok          # 只看异常插件
ctr plugin info io.containerd.grpc.v1.cri
ctr version
ctr pprof heap > /tmp/heap.prof
ctr pprof goroutines
ctr pprof metrics
ctr events                          # 事件流
ctr namespaces ls

7. nerdctl

7.1 定位

nerdctl = containerd 的 docker 风格 CLI,命令与 docker 高度一致,同时补足 containerd 缺失的 compose/build。

nerdctl run -d --name web -p 8080:80 nginx:1.25
nerdctl ps
nerdctl images
nerdctl logs -f web
nerdctl exec -it web sh
nerdctl stop web && nerdctl rm web
nerdctl pull nginx:1.25
nerdctl push harbor.internal/app:1.0
nerdctl login harbor.internal
nerdctl network ls
nerdctl volume ls
nerdctl stats
nerdctl system df
nerdctl system prune

7.2 与 k8s 交互

# 默认 namespace 是 default,看 k8s 的要显式指定
nerdctl --namespace k8s.io ps
nerdctl -n k8s.io images ls
nerdctl -n k8s.io logs <container>
nerdctl -n k8s.io exec -it <container> sh

# 全局默认改 k8s.io(不推荐,容易误操作)
# /etc/nerdctl/nerdctl.toml: namespace = "k8s.io"

7.3 build 与 compose

# build 需要 buildkitd
systemctl enable --now buildkit
nerdctl build -t myapp:1.0.0 .
nerdctl build --build-arg VERSION=1.0 --no-cache .
nerdctl build --platform linux/amd64,linux/arm64 -t app:1.0 --push .

# compose
nerdctl compose up -d
nerdctl compose -f app.yaml up -d
nerdctl compose down
nerdctl compose ps
nerdctl compose logs -f api
# /etc/systemd/system/buildkit.service(若用二进制)
[Unit]
Description=BuildKit
After=network.target
[Service]
ExecStart=/usr/local/bin/buildkitd --oci-worker-no-process-sandbox
[Install]
WantedBy=multi-user.target

7.4 nerdctl 特有

# 直接操作任意 snapshotter
nerdctl --snapshotter native run --rm alpine echo hi

# 不落盘运行(只读 rootfs)
nerdctl run --rm --rootfs /path/to/rootfs ls /

# 命名空间隔离
nerdctl -n ci run --rm alpine echo ci

# 与 ctr 互通验证
nerdctl images | head
ctr -n default images ls | head

8. crictl

8.1 定位

crictl 是 k8s 官方提供的 CRI 调试工具,是节点排障的正确入口。它通过 CRI gRPC 与运行时通信,所以看到的状态与 kubelet 完全一致。

8.2 配置

cat >/etc/crictl.yaml <<'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF

若节点同时装了 Docker 与 containerd,务必显式配 runtime-endpoint,否则可能连到 docker 的 CRI(若存在)。

8.3 命令全集

crictl version
crictl info
crictl info | jq '.config.criConfig'      # 看 containerd 的 CRI 配置

# 镜像
crictl images
crictl images -q
crictl pull harbor.internal/app:1.0
crictl rmi <image-id>
crictl rmi --prune                        # 清理无引用镜像(安全)
crictl inspecti <image-id>

# pod(sandbox)
crictl pods
crictl pods --name <pod-name>
crictl pods --namespace prod --state Ready
crictl inspectp <pod-id>

# 容器
crictl ps
crictl ps -a
crictl ps --name <container>
crictl ps --pod <pod-id>
crictl inspect <container-id>
crictl logs -f <container-id>
crictl logs --tail 100 <container-id>
crictl logs --since 10m <container-id>
crictl exec -it <container-id> sh
crictl exec -it -u 0 <container-id> sh
crictl stats
crictl stats <container-id>
crictl stop <container-id>
crictl rm <container-id>
crictl rmp <pod-id>

# 运行时信息
crictl runtimeversion
crictl status
crictl config

8.4 crictl 与 kubectl 的对应

需求 kubectl crictl
列 pod get pods pods
列容器 get pods -o wide ps
日志 logs <pod> logs <container-id>
进入 exec -it <pod> exec -it <container-id>
镜像 get nodes -o wide 间接 images
重启容器 delete pod 不推荐(绕过 kubelet)
# 从 pod 名找容器 ID
POD=web-7d9f8c5b6-abcde
NS=prod
CID=$(crictl ps --name "$POD" -q | head -1)
crictl logs -f "$CID"
crictl inspect "$CID" | jq '.status.imageRef'

# 从容器找 pod
crictl inspect "$CID" | jq '.status.labels["io.kubernetes.pod.name"]'

铁律:crictl stop/rm 会绕过 kubelet,kubelet 会在下个同步周期重建 pod,表现为"删了又回来"。要停 pod,用 kubectl delete pod 或 kubectl scale。


9. namespace 与内容存储

9.1 namespace 的本质

containerd 的 namespace 是元数据隔离,不是内核 namespace。每个 namespace 有独立的 image/container/task 列表,但共享同一个 content store 与 snapshotter 存储。

ctr ns ls
# NAME    LABELS
# default
# k8s.io

# 同一镜像在不同 ns 需要分别 pull(除非共享 content)
ctr images ls -q | head
ctr -n k8s.io images ls -q | head
场景 namespace
Docker 用 containerd moby
Kubernetes k8s.io
nerdctl / ctr 手工 default
自建 CI 隔离 自定义
# 跨 ns 复用镜像:先 export 再 import
ctr -n default images export /tmp/app.tar harbor.internal/app:1.0
ctr -n k8s.io images import /tmp/app.tar

9.2 content store 结构

content store 按 digest 存储,天然去重:

ROOT=$(containerd config dump | grep -E '^\s*root\s*=' | head -1 | awk -F'"' '{print $2}')
ls "$ROOT/io.containerd.content.v1.content/blobs/sha256" | head
du -sh "$ROOT/io.containerd.content.v1.content"
/var/lib/containerd/io.containerd.content.v1.content/
├── blobs/sha256/<digest>      # 层、config、manifest 全在这
└── ingest/                    # 拉取中的临时文件
ctr content ls | head
ctr content ls -q | wc -l
ctr content prune references --dry-run    # 看能清多少

9.3 ingest 残留(拉取中断的坑)

拉取被中断会在 ingest/ 留下临时文件,占空间但不被 GC 清:

ROOT=/var/lib/containerd/io.containerd.content.v1.content
du -sh $ROOT/ingest
ls -la $ROOT/ingest | head

# 确认 containerd 空闲后清理
systemctl stop containerd
rm -rf $ROOT/ingest/*
systemctl start containerd

10. snapshotter 与存储

10.1 什么是 snapshot

snapshotter 负责管理容器的文件系统层。overlayfs snapshotter 把每个镜像层映射为一个 snapshot,容器的可写层是另一个 snapshot,通过 overlay 联合挂载。

镜像层 1 (sha256:a) → snapshot 1 → overlay2/1/diff
镜像层 2 (sha256:b) → snapshot 2 → overlay2/2/diff
容器可写层          → snapshot N → overlay2/N/diff  ← 容器写入落这里
                        ↓
                   overlay2/N/fs  ← 容器内看到的 /
# 看目录
ls /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ | head
tree -L 2 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1

# 查看某个容器的 snapshot
ctr -n k8s.io containers info <container> | jq '.snapshotter, .snapshotKey'
ctr -n k8s.io snapshots ls -s | head

10.2 各 snapshotter 对比

snapshotter 特点 适用
overlayfs 默认,CoW,性能好 通用(内核 ≥ 4.0,xfs 需 ftype=1)
native 每层完整目录,无 CoW 排障、不支持 overlay 的文件系统
fuse-overlayfs 用户态 overlay rootless、老内核
btrfs 子卷快照 宿主已用 btrfs
devmapper 块设备快照 需要块隔离(已较少用)
erofs 只读镜像层 + overlay 可写 2.x 新特性,启动快、省空间
# 当前用的 snapshotter
containerd config dump | grep -A2 'snapshotter ='
crictl info | jq '.config.criConfig.containerdConfig.snapshotter'

坑点:切换 snapshotter 不会迁移已有镜像,切换后所有镜像"消失",需要重新拉取。生产环境不要在运行中的节点切换。

10.3 空间占用分析

# 总占用
du -sh /var/lib/containerd
du -sh /var/lib/containerd/* | sort -h

# 明细
du -sh /var/lib/containerd/io.containerd.content.v1.content/blobs/sha256 | sort -h
du -sh /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/* | sort -h | tail -10

# 容器可写层大小
ctr -n k8s.io snapshots ls -s | sort -k3 -h | tail -10

# 挂载点(未释放的 overlay 挂载会占用空间)
mount | grep -c overlay

10.4 磁盘用量限制

containerd 本身不支持给单容器限制可写层大小(这点与 Docker 的 --storage-opt size= 不同)。k8s 侧的两种做法:

  1. 节点级限制:给 /var/lib/containerd 单独分区 + kubelet 的 evictionHard 磁盘阈值。
  2. emptyDir sizeLimit:限制临时目录,而非整层。
# kubelet 配置
evictionHard:
  nodefs.available: "10%"
  imagefs.available: "15%"
evictionMinimumReclaim:
  imagefs.available: "5%"

11. GC 与空间回收

11.1 GC 机制

containerd 用引用计数 + 标记清除:定期扫描所有 image/container/snapshot,删除无引用的 content 和 snapshot。

# config.toml
[plugins."io.containerd.gc.v1.scheduler"]
  pause_threshold = 0.02
  deletion_threshold = 0
  mutation_threshold = 100
  schedule_delay = "0s"
  startup_delay = "100ms"
参数 作用
pause_threshold 写入放大超过该比例时暂停 GC 相关操作
mutation_threshold 累积多少次变更触发一次 GC
schedule_delay 两次 GC 的间隔
deletion_threshold 删除操作阈值
# 手动触发(无直接命令,用 content prune)
ctr content prune references

# 看 GC 状态
journalctl -u containerd | grep -i gc | tail -20

11.2 k8s 侧镜像回收

kubelet 负责镜像回收,不要手工 ctr images rm:

# kubelet 配置
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m
# 手工触发(安全方式)
crictl rmi --prune

# 看 kubelet 的回收日志
journalctl -u kubelet | grep -i 'imageGC\|Image garbage'

11.3 空间回收实践

# 顺序:先安全、后激进
# 1) 清无引用镜像
crictl rmi --prune

# 2) 清 content 无引用 blob
ctr content prune references

# 3) 清 ingest 残留(需停服务)
systemctl stop containerd && rm -rf /var/lib/containerd/io.containerd.content.v1.content/ingest/* && systemctl start containerd

# 4) 清未释放的 overlay 挂载
mount | grep overlay | awk '{print $3}' | xargs -r umount

# 5) 兜底(危险:删所有镜像,k8s 会重新拉)
# crictl rmi --all

11.4 磁盘告警处理流程

# 1) 定位
df -h /var/lib/containerd
du -sh /var/lib/containerd/*
crictl images | wc -l

# 2) 判断是内容还是快照
du -sh /var/lib/containerd/io.containerd.content.v1.content
du -sh /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs

# 3) 快照占大头 → 有容器写了大量数据
ctr -n k8s.io snapshots ls -s | sort -k3 -h | tail -5
# 找到对应容器:kubectl get pod -o wide | grep <node>

# 4) 内容占大头 → 镜像多
crictl images --digests | head -20
crictl rmi --prune

# 5) 仍不降 → 检查已删文件句柄
lsof +L1 | grep containerd

12. 服务运维与调试

12.1 启停与日志

systemctl start containerd
systemctl stop containerd         # 会杀掉所有容器(shim 一起退出)
systemctl restart containerd      # 生产慎用,见 12.2
systemctl reload containerd       # 部分配置
systemctl status containerd -l
journalctl -u containerd -f
journalctl -u containerd --since "10 min ago" | grep -iE 'error|warn|panic'

12.2 restart 会不会杀容器

默认会。containerd.service 的 KillMode=process 只杀主进程,但 shim 是独立进程树的一部分,restart 时 systemd 会清理整个 cgroup。

# 验证:容器 PID 是否变化
crictl ps -q | head -1
crictl inspect <id> | jq '.info.pid'
systemctl restart containerd
crictl ps -q | head -1

生产做法:升级 containerd 前先 kubectl drain 节点。containerd 没有 Docker 那样的 live-restore。

12.3 日志级别

[debug]
  level = "debug"        # 临时排障用,日志量很大
systemctl restart containerd
journalctl -u containerd -f | grep -i '<你要查的关键字>'
# 排障完记得改回 info

12.4 指标与 pprof

[metrics]
  address = "0.0.0.0:1338"
  grpc_histogram = false
curl -s localhost:1338/metrics | grep -E '^containerd_'
curl -s localhost:1338/metrics | grep -E 'grpc_server_handled_total|snapshot'

# pprof(通过 debug socket)
ctr pprof heap > /tmp/heap.prof
ctr pprof goroutines | head -50
ctr pprof metrics
go tool pprof -top /tmp/heap.prof        # 需本地有 go

12.5 事件

ctr events
ctr events --filter type=task
# 或看 journald 里的 CRI 事件
journalctl -u containerd | grep -i 'TaskExit'

12.6 健康检查脚本

#!/usr/bin/env bash
set -uo pipefail
echo "===== containerd check $(date '+%F %T') ====="
systemctl is-active containerd || echo "!! containerd 未运行"
ctr version
echo "--- 异常插件"; ctr plugin ls | grep -v '\bok\b' || echo "全部 ok"
echo "--- CRI 可用性"; crictl version >/dev/null 2>&1 && echo ok || echo "!! crictl 失败"
echo "--- 容器数"; crictl ps -q | wc -l
echo "--- 镜像数"; crictl images -q | wc -l
echo "--- 存储"; du -sh /var/lib/containerd
echo "--- ingest 残留"; du -sh /var/lib/containerd/io.containerd.content.v1.content/ingest 2>/dev/null
echo "--- overlay 挂载数"; mount | grep -c overlay
echo "--- 磁盘"; df -h /var/lib/containerd | tail -1
echo "--- 近期错误"; journalctl -u containerd --since '1 hour ago' | grep -icE 'error|panic' || true

13. 与 Kubernetes 集成排障

13.1 节点 NotReady 的运行时侧排查

# 1) 运行时是否活着
systemctl status containerd
crictl version

# 2) CRI 插件是否 ok
ctr plugin ls | grep cri

# 3) kubelet 连不上运行时?
journalctl -u kubelet -n 100 | grep -iE 'runtime|containerd|cri'
# 常见:failed to get runtime status: rpc error: code = Unavailable

# 4) socket 是否存在
ls -l /run/containerd/containerd.sock
ss -lx | grep containerd

# 5) kubelet 配置的 endpoint
ps -ef | grep kubelet | tr ' ' '\n' | grep container-runtime-endpoint
cat /var/lib/kubelet/kubeadm-flags.env 2>/dev/null

13.2 Pod 创建失败(sandbox 相关)

# 现象:Pod 停在 ContainerCreating,事件里报
# "failed to create pod sandbox: rpc error: ..."
# "failed to get sandbox image ... pause"

# 1) pause 镜像是否存在
crictl images | grep pause
crictl pull registry.k8s.io/pause:3.10

# 2) sandbox_image 配置是否与实际 tag 一致
containerd config dump | grep -i sandbox_image
crictl images | grep pause

# 3) CNI 配置
ls /etc/cni/net.d/
cat /etc/cni/net.d/*.conf* | head -30
ls /opt/cni/bin/

# 4) containerd 日志看具体错误
journalctl -u containerd -n 200 | grep -iE 'sandbox|cni|pause'
报错关键字 原因 处理
failed to get sandbox image pause 镜像缺失/名字错 拉取或改 sandbox_image
failed to setup network for sandbox CNI 插件缺失/配置错 检查 /opt/cni/bin、/etc/cni/net.d
cni plugin not initialized CNI 配置未加载 确认 CNI 已 apply,重启 containerd
failed to create containerd task: ... cgroup cgroup 配置错 检查 SystemdCgroup = true
no such file or directory: unknown runtime 二进制缺失 which runc
OCIruntime error: ... permission denied SELinux/AppArmor 看 audit 日志

13.3 cgroup 驱动不一致(经典坑)

# kubelet 用 systemd,containerd 用 cgroupfs → 双层 cgroup,pod 反复重启
ps -ef | grep kubelet | grep -o 'cgroup-driver=[a-z]*'
containerd config dump | grep -i SystemdCgroup

必须两处一致:

# containerd
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true
# kubelet(KubeletConfiguration)
cgroupDriver: systemd

13.4 镜像拉取慢/失败

# 1) 节点上直接测
crictl pull harbor.internal/app:1.0

# 2) 看 containerd 日志的 registry 交互
journalctl -u containerd -f | grep -iE 'pull|registry|resolve'

# 3) 验证 certs.d 是否被读到
ls /etc/containerd/certs.d/
containerd config dump | grep -A1 registry

# 4) 手工验证网络
curl -vk https://harbor.internal/v2/
openssl s_client -connect harbor.internal:443 </dev/null

# 5) 检查 DNS
getent hosts harbor.internal

# 6) 镜像是否真的存在
crictl pull harbor.internal/app:1.0 2>&1 | tail -5

13.5 磁盘压力驱逐

# 节点 ImageFs 不足 → kubelet 驱逐 pod
kubectl describe node <node> | grep -A5 Conditions
df -h /var/lib/containerd

# 快速缓解
crictl rmi --prune
ctr content prune references

# 根治
# 1) 给 /var/lib/containerd 单独分区
# 2) 配 kubelet 的 imageGCHighThresholdPercent
# 3) 控制镜像 tag 数量(CI 不要无限推送)

13.6 kubelet 与 containerd 版本兼容

kubectl version --short
crictl version
containerd --version
runc --version

# 对照第 2 章的矩阵
# k8s 1.36+ 必须 containerd 2.x

14. 升级与迁移

14.1 升级前检查

# 1) 版本兼容(见第 2 章)
containerd --version
kubectl version --short

# 2) 配置备份
cp /etc/containerd/config.toml /etc/containerd/config.toml.bak-$(date +%F)

# 3) 记录现场
crictl ps -a > /tmp/crictl-ps-before.txt
crictl images > /tmp/crictl-images-before.txt
du -sh /var/lib/containerd

# 4) 节点排空(最重要)
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

14.2 升级步骤

# 方式一:包管理器
dnf update -y containerd.io
systemctl restart containerd
containerd --version

# 方式二:二进制替换
systemctl stop containerd
mv /usr/local/bin/containerd /usr/local/bin/containerd.old
tar Cxzvf /usr/local containerd-2.4.2-linux-amd64.tar.gz
systemctl start containerd

注意:2.x 的 shim 名字仍是 containerd-shim-runc-v2,但二进制版本要配套。升级 containerd 时把 shim 一起替换。

14.3 配置迁移(1.x → 2.x)

# 1) 备份旧配置
cp /etc/containerd/config.toml /etc/containerd/config.toml.v1

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

# 3) 逐项对比迁移(重点:cri 段、registry、snapshotter)
diff /etc/containerd/config.toml.v1 /etc/containerd/config.toml.v2

# 4) 迁移后必须检查的两项
grep -n 'SystemdCgroup' /etc/containerd/config.toml.v2
grep -n 'sandbox_image\|pinned_images' /etc/containerd/config.toml.v2

# 5) 应用
cp /etc/containerd/config.toml.v2 /etc/containerd/config.toml
containerd config dump >/dev/null && echo ok
systemctl restart containerd

# 6) 验证
crictl version
crictl ps
crictl pull registry.k8s.io/pause:3.10

14.4 验证与恢复

kubectl uncordon <node>
kubectl get nodes
kubectl get pods -A --field-selector spec.nodeName=<node>
crictl ps | wc -l

# 有问题时回滚
systemctl stop containerd
cp /etc/containerd/config.toml.bak-2026-11-05 /etc/containerd/config.toml
dnf downgrade -y containerd.io-<old-version>
systemctl start containerd
kubectl uncordon <node>

14.5 从 Docker 迁移到 containerd(k8s 节点)

# 1) 移除 dockershim 依赖(k8s 1.24+ 已无)
kubectl drain <node> --ignore-daemonsets

# 2) 停 docker
systemctl disable --now docker docker.socket

# 3) 装 containerd 并配 cri
dnf install -y containerd.io
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd

# 4) 告知 kubelet 换运行时(改 kubeadm-flags.env 或用 kubeadm 重配)
# /var/lib/kubelet/kubeadm-flags.env:
# --container-runtime-endpoint=unix:///run/containerd/containerd.sock

systemctl restart kubelet
kubectl uncordon <node>
kubectl get nodes

15. 卸载与清理

卸载前必须:① 节点已 drain;② 该节点的 pod 已迁移;③ 若节点是控制面,先确认 etcd 健康。

15.1 k8s 节点卸载 containerd

# 1) 排空并删除节点
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --force
kubectl delete node <node>

# 2) 停服务与容器
crictl ps -q | xargs -r crictl stop
systemctl stop kubelet containerd
systemctl disable kubelet containerd

# 3) 卸载包
dnf remove -y containerd.io
# 或二进制
rm -f /usr/local/bin/containerd /usr/local/bin/containerd-shim* /usr/local/bin/ctr
rm -f /usr/local/sbin/runc
rm -f /etc/systemd/system/containerd.service /usr/local/lib/systemd/system/containerd.service
systemctl daemon-reload

# 4) 清数据
rm -rf /var/lib/containerd
rm -rf /run/containerd
rm -rf /etc/containerd
rm -f /etc/crictl.yaml

# 5) 清挂载(关键,否则目录删不掉)
mount | grep -E 'overlay|/var/lib/containerd' 
cat /proc/mounts | grep -E 'overlay|containerd' | awk '{print $2}' | xargs -r umount -l
rm -rf /var/lib/containerd

# 6) 清残留 socket 与进程
pkill -9 containerd-shim 2>/dev/null
rm -f /run/containerd/containerd.sock /run/containerd/containerd.pid
ls /run/containerd 2>/dev/null

# 7) 验证
which containerd ctr || echo "已卸载"
ps -ef | grep -E 'containerd|shim' | grep -v grep
ls /etc/containerd 2>/dev/null

15.2 非 k8s 环境卸载

# 停所有 task
ctr tasks ls -q | xargs -r ctr tasks kill
ctr tasks ls -q | xargs -r ctr tasks delete

# 删所有容器与镜像
ctr containers ls -q | xargs -r ctr containers rm
ctr images ls -q | xargs -r ctr images rm

systemctl disable --now containerd
rm -rf /var/lib/containerd /etc/containerd /run/containerd
rm -f /usr/local/bin/{containerd,containerd-shim-runc-v2,ctr}
rm -f /etc/systemd/system/containerd.service
systemctl daemon-reload

15.3 只重置数据(保留程序)

systemctl stop containerd
crictl ps -aq | xargs -r crictl stop 2>/dev/null
rm -rf /var/lib/containerd/*
systemctl start containerd

# 注意:所有镜像与容器丢失,k8s 节点会重新拉取镜像

15.4 卸载后验证

检查 命令 期望
进程 ps -ef | grep -E 'containerd|shim' 无
socket ls /run/containerd 不存在
目录 ls /var/lib/containerd /etc/containerd 不存在
挂载 mount | grep -c overlay 0
服务 systemctl list-unit-files | grep containerd 无
二进制 which containerd ctr runc 无

16. 故障速查

16.1 服务启动

现象 排查 处理
containerd: unit not found 未装单元文件 装 service 或手写
启动失败,failed to load plugin config.toml 段名/字段错 containerd config dump 校验
address already in use socket 残留 rm -f /run/containerd/containerd.sock
failed to mount ... invalid argument 内核不支持 overlay 换 native/fuse-overlayfs
plugin "cri" failed sandbox_image 无效 / CNI 缺失 检查镜像与 CNI
反复 restart 看 journalctl -u containerd -n 100 按首条 error 定位

16.2 镜像

现象 排查 处理
ctr images ls 有、crictl images 无 namespace 不同 ctr -n k8s.io images ls
x509: unknown authority CA 未配 certs.d 里加 ca
401 Unauthorized 未认证 配凭据 / imagePullSecrets
拉取卡住 网络/registry 慢 image_pull_progress_timeout;检查 mirror
failed to resolve reference 镜像名不全 写全 registry/repo:tag
镜像存在但容器起不来 架构不匹配 检查 --platform

16.3 容器/任务

现象 排查 处理
ctr containers ls 有但 tasks ls 无 未 start ctr tasks start
failed to create task cgroup/权限/runtime 看 containerd 日志;查 SystemdCgroup
OCI runtime create failed runc 参数/镜像问题 runc --version;看 spec
task 卡 running 但进程没了 shim 异常 ctr tasks kill 后重建
context deadline exceeded 操作超时 增大 timeout;看 debug 日志
容器无法 exec shim 通信问题 重启容器(pod)

16.4 存储

现象 排查 处理
no space left on device du -sh /var/lib/containerd/* crictl rmi --prune
空间删了不释放 lsof +L1 重启持有句柄的容器
failed to mount overlay 内核/文件系统 查 ftype、dmesg
snapshot does not exist 数据损坏 ctr content prune;必要时重建
ingest 占大量空间 拉取中断 停服务清 ingest/
overlay 挂载泄漏 mount | grep overlay umount

16.5 k8s 集成

现象 排查 处理
节点 NotReady crictl version 运行时挂了
pod 停在 ContainerCreating journalctl -u containerd | grep sandbox pause 镜像/CNI
pod 反复重启 crictl inspect 看 cgroup SystemdCgroup 不一致
镜像拉取失败 crictl pull 手工测 certs.d / 凭据
rpc error: Unavailable socket 不通 检查 endpoint 与服务
kubelet 报 unknown runtime 版本不兼容 见第 2 章矩阵

16.6 通用排障命令

systemctl status containerd -l
journalctl -u containerd -n 100 --no-pager
ctr plugin ls | grep -v '\bok\b'
crictl info | head -40
ctr version
runc --version
df -h /var/lib/containerd
mount | grep -c overlay
ps -ef | grep -c 'containerd-shim'

17. 附录

17.1 关键路径

路径 内容
/etc/containerd/config.toml 主配置
/etc/containerd/certs.d/<host>/hosts.toml 仓库配置(mirror/CA/认证)
/etc/crictl.yaml crictl 端点
/etc/cni/net.d/ CNI 配置
/opt/cni/bin/ CNI 插件二进制
/var/lib/containerd 数据根(root)
.../io.containerd.content.v1.content/blobs/sha256 内容存储
.../io.containerd.content.v1.content/ingest 拉取临时文件
.../io.containerd.snapshotter.v1.overlayfs/snapshots 快照
/run/containerd/containerd.sock CRI/API socket
/run/containerd/debug.sock debug/pprof socket
/run/containerd/io.containerd.runtime.v2.task task 运行态

17.2 端口与 socket

项 值
CRI socket unix:///run/containerd/containerd.sock
API socket 同上(同一个)
debug socket /run/containerd/debug.sock
metrics 默认关闭,配 [metrics] address
容器内 DNS 由 CNI 决定

17.3 三条命令的等价关系

目的 ctr nerdctl crictl
列镜像 ctr -n k8s.io images ls nerdctl -n k8s.io images crictl images
列容器 ctr -n k8s.io containers ls nerdctl -n k8s.io ps -a crictl ps -a
列运行任务 ctr -n k8s.io tasks ls nerdctl -n k8s.io ps crictl ps
拉镜像 ctr -n k8s.io images pull nerdctl -n k8s.io pull crictl pull
日志 无 nerdctl -n k8s.io logs crictl logs
进入 ctr tasks exec nerdctl -n k8s.io exec crictl exec
删镜像 ctr images rm nerdctl rmi crictl rmi
删容器 ctr containers rm nerdctl rm crictl rm

17.4 最简可用 CRI 配置(k8s 节点)

version = 3
root = "/var/lib/containerd"
state = "/run/containerd"

[grpc]
  address = "/run/containerd/containerd.sock"

[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.k8s.io/pause:3.10"

  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "overlayfs"
    default_runtime_name = "runc"

    [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

  [plugins."io.containerd.grpc.v1.cri".cni]
    bin_dir = "/opt/cni/bin"
    conf_dir = "/etc/cni/net.d"

  [plugins."io.containerd.grpc.v1.cri".registry]
    config_path = "/etc/containerd/certs.d"

17.5 与其他手册的配合

  • Docker 与 containerd 的关系、daemon 侧配置:Docker-常用运维指令手册.md
  • k8s 侧 pod/节点排障:Kubernetes-常用运维指令手册.md
  • 镜像仓库:Harbor-镜像仓库运维指令手册.md
  • 宿主内核参数、cgroup、SELinux:CentOS-7-10-常规运维指令手册.md

一句话总结:containerd 的坑集中在三点——namespace 搞混(ctr vs crictl)、SystemdCgroup 没设对、certs.d 目录名与 registry host 不一致。把这三处校准,剩下的排障基本靠 journalctl -u containerd 加 crictl inspect。