containerd 从入门到精通实战手册

containerd 从入门到精通实战手册

版本基线:containerd 1.7.x(LTS)与 2.x、CRI(Kubernetes 运行时)、nerdctl、crictl


目录


第 1 章 containerd 是什么

1.1 背景:为什么需要 containerd

containerd(读作 “container-dee”)是一个工业级容器运行时,由 Docker 公司在 2017 年捐献给 CNCF,2019 年 2 月成为 CNCF 毕业项目,与 Kubernetes、Prometheus 同级。

它是目前云原生生态的事实标准底层运行时

  • Docker Engine 的容器执行底层就是 containerd。
  • Kubernetes 通过 CRI 接口对接 containerd(自 K8s 1.24 移除 dockershim 后,containerd 是主流默认运行时)。
  • 它只做”运行容器”这一件事,把镜像构建(Build)、编排(Orchestration)等留给上层。

1.2 containerd 在整个技术栈中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-------------------------------------------------------------+
| Docker CLI / Docker Compose | kubectl / kubelet |
+------------------+-------------+--------------+--------------+
| Docker Engine | | CRI (K8s) | |
| (docker daemon) | | | |
+------------------+ +--------------+ |
| containerd(统一底座) |
| +------------+ +-----------+ +----------+ +------------+ |
| | 镜像管理 | | 容器生命周期 | | 快照存储 | | 命名空间 | |
| +------------+ +-----------+ +----------+ +------------+ |
| containerd-shim(每个容器一个) |
| runc / runsc / kata(OCI 运行时) |
+--------------------------------------------------------------+
| Linux 内核(namespace/cgroup/overlayfs) |
+--------------------------------------------------------------+

一句话总结:containerd 是”操作系统的容器子系统”,上层无论是 Docker 还是 Kubernetes,最终都通过它来拉镜像、建容器、跑进程。

1.3 核心组件与进程

安装 containerd 后,主要二进制与进程:

进程/二进制 作用
containerd 守护进程,暴露 gRPC API,管理镜像、容器、快照、元数据
containerd-shim-runc-v2 每个容器一个的垫片进程,隔离 containerd 与容器进程
runc OCI 运行时,真正创建/运行容器进程
ctr containerd 自带 CLI(调试/运维用)
crictl CRI 客户端(K8s 视角的运维工具)
nerdctl Docker 兼容 CLI(社区推荐)

shim 的作用(重要):

1
2
3
containerd 重启/升级 -> 容器不受影响(shim 还在)
containerd 崩溃 -> 运行中的容器继续跑
容器退出 -> shim 回收资源并上报状态

shim 让”containerd 重启不杀容器”成为可能,这是它相比”单守护进程架构”的核心优势(对应 Docker 的 live-restore 是更上层的近似实现)。

1.4 containerd 与 Docker 的对比

维度 Docker Engine containerd
定位 完整容器平台(build+run+compose) 纯运行时(run only)
镜像构建 内置(buildkit) 不支持(需 buildkit/nerdctl build)
编排 Compose / Swarm 无(对接 K8s)
网络 内置 bridge/host/overlay 不管理网络(CRI 场景靠 CNI)
端口映射 内置 无(nerdctl 通过 CNI 实现)
CLI docker ctr(原始)/ crictl(CRI)/ nerdctl(类 docker)
重启策略 restart 参数 无(上层负责,如 K8s/kubelet)
数据卷 volume/bind 无内置(nerdctl 支持)
使用场景 单机开发/小规模 K8s 节点运行时、嵌入式/裁剪场景
资源占用 较大(多一层 daemon) 更小、更稳

结论:Docker 适合单机开发与交付;生产 K8s 节点直接用 containerd,去掉多余组件,减少攻击面与资源占用。

1.5 版本与演进

版本 状态 关键变化
1.6.x EOL 大量 K8s 版本默认搭载
1.7.x LTS(长期支持) 支持镜像签名验证、改进快照器、Windows 容器增强
2.0.x 当前主线 默认启用镜像存储(image store)、配置语法更新、runc 升级、移除旧兼容层
1
2
3
# 查看版本
containerd --version
ctr version

生产选择:K8s 节点用 containerd 1.7 LTS(稳定、文档多),新集群可评估 2.x;升级前先看官方”升级到 2.x”的兼容性说明(配置、插件、API 变化)。


第 2 章 安装与基础配置

2.1 系统要求

项目 要求
内核 Linux ≥ 4.x(推荐 5.x+),需支持 overlayfs、cgroup v2(推荐)
架构 x86_64 / arm64 / ppc64le / s390x 等
磁盘 视镜像量而定,建议 ≥ 20GB 给 /var/lib/containerd
K8s 节点 与 kubelet/kubeadm 版本匹配(见官方兼容矩阵)
1
2
3
uname -r
cat /etc/os-release
grep -c cgroup /proc/filesystems # 确认 cgroup 支持

2.2 通过发行版包安装(推荐)

Ubuntu / Debian:

1
2
3
4
5
6
7
8
9
10
# 方法A:docker-ce 源里带 containerd.io(与 Docker 共用)
sudo apt-get update
sudo apt-get install -y containerd.io

# 方法B:官方 containerd 仓库
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/containerd.list
sudo apt-get update
sudo apt-get install -y containerd.io

CentOS / Rocky / AlmaLinux:

1
2
3
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y containerd.io

发行版包会:创建 containerd 用户与组、安装 systemd unit、默认配置 /etc/containerd/config.toml、自动启动。

2.3 官方二进制安装(手动,可控性最强)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 以 containerd 1.7.22 为例(去 GitHub releases 查最新版)
wget https://github.com/containerd/containerd/releases/download/v1.7.22/containerd-1.7.22-linux-amd64.tar.gz
tar -C /usr/local -xzf containerd-1.7.22-linux-amd64.tar.gz

# 安装 runc(OCI 运行时)
wget https://github.com/opencontainers/runc/releases/download/v1.1.14/runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc

# 安装 CNI 插件(CRI 网络,见第 7 章)
wget https://github.com/containernetworking/plugins/releases/download/v1.5.0/cni-plugins-linux-amd64-v1.5.0.tgz
mkdir -p /opt/cni/bin
tar -C /opt/cni/bin -xzf cni-plugins-linux-amd64-v1.5.0.tgz

# 生成默认配置并放置
mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# systemd unit(手动安装时通常自己写)
cat > /etc/systemd/system/containerd.service <<'EOF'
[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
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
LimitNOFILE=1048576
TasksMax=infinity

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now containerd

2.4 验证安装

1
2
3
4
5
6
7
8
9
10
11
systemctl status containerd
containerd --version
ctr version

# 查看 socket 与目录
ls -l /run/containerd/containerd.sock
ls /var/lib/containerd/

# 拉一个镜像并运行测试容器(需要 root)
ctr image pull docker.io/library/hello-world:latest
ctr run --rm docker.io/library/hello-world:latest hello

安装检查清单:

  • containerd --version 正常输出
  • systemd 服务 active (running)
  • socket /run/containerd/containerd.sock 存在
  • ctr image pull + ctr run 成功
  • 生产节点已配置镜像加速(见 4.4)
  • cgroup v2 正常(stat -fc %T /sys/fs/cgroup 输出 cgroup2fs

2.5 目录与文件布局

1
2
3
4
5
6
7
8
9
10
11
12
/var/lib/containerd/           # 数据根目录(重要数据,需备份/监控)
├── io.containerd.content.v1.content/ # 内容存储(镜像 blob)
├── io.containerd.metadata.v1.bolt/ # 元数据(bolt 数据库)
├── io.containerd.snapshotter.v1.overlayfs/ # 快照(镜像层/容器层)
├── io.containerd.image.v1.containerd/ # 镜像存储(2.x 默认)
└── tmp/

/run/containerd/ # 运行时状态(socket、shim 目录)
├── containerd.sock
└── containerd.sock.ttrpc

/etc/containerd/config.toml # 配置文件

磁盘规划/var/lib/containerd 与系统盘分离(独立分区/数据盘),并设置容量监控告警。

2.6 配置文件 config.toml

1
2
3
4
5
# 生成默认配置(首次安装必做)
containerd config default > /etc/containerd/config.toml

# 校验配置
containerd config check

核心配置块解读(1.7 风格):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 根目录与状态目录
root = "/var/lib/containerd"
state = "/run/containerd"

# gRPC 监听地址
grpc.address = "/run/containerd/containerd.sock"
grpc.uid = 0
grpc.gid = 0

# 命名空间
disabled_plugins = []
# 常用内置插件:
# io.containerd.grpc.v1.cri # CRI(K8s 用,必须启用)
# io.containerd.snapshotter.v1.overlayfs
# io.containerd.content.v1.content
# io.containerd.metrics.v1.metrics # metrics
# io.containerd.runtime.v2.task # shim v2 运行时

# 快照器(存储驱动)
snapshotter = "overlayfs"

# 运行时(OCI)
[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 # K8s 节点必须 true(cgroup 驱动 systemd)

# 镜像加速(见 4.4)
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://docker.m.daocloud.io"]

# metrics 端口
[metrics]
address = "127.0.0.1:1338"
1
2
3
# 修改后重启
sudo systemctl restart containerd
sudo containerd config check

2.7 Windows 上的 containerd(简述)

  • 官方提供 containerd-windows-amd64 压缩包与 MSI(2.x 提供 MSI)。
  • 服务:sc.exe create containerd ... 或用 MSI 一键安装。
  • 运行容器依赖 Windows 容器特性(Hyper-V 隔离/进程隔离),与 Linux 容器机制不同。
  • K8s Windows 节点同样用 containerd 作为运行时。

第 3 章 命令行工具:ctr / crictl / nerdctl

3.1 三个 CLI 的定位

工具 接口 面向 特点
ctr containerd 原生 gRPC API 运维调试 containerd 本身 原始、命令少、需理解命名空间
crictl CRI(Kubernetes 运行时接口) K8s 节点排障 看 Pod/Sandbox 视角,K8s 标配
nerdctl containerd 原生 API(模拟 docker) 日常开发/单机 命令与 docker 几乎一致,含 build/网络/卷

生产经验:K8s 节点优先用 crictl(与 kubelet 视角一致);单机测试/开发用 nerdctlctr 用来做底层诊断。

3.2 命名空间(Namespace)

containerd 用命名空间逻辑隔离数据(镜像/容器/快照互不可见):

命名空间 使用者
default ctr 默认
moby Docker Engine
k8s.io Kubernetes(kubelet/CRI)
1
2
3
4
5
6
7
8
9
10
11
# 查看命名空间
ctr namespace ls
ctr ns ls

# 创建/删除
ctr ns create staging
ctr ns remove staging

# 指定命名空间操作(未指定时默认 default)
ctr -n k8s.io images ls
ctr -n moby images ls

运维提示:排查”镜像看不到/容器找不到”,先确认命名空间对不对。K8s 的一切都在 k8s.io 下。

3.3 ctr 常用命令(原生视图)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 镜像
ctr images ls # 列出镜像(default 命名空间)
ctr -n k8s.io images ls # 查看 K8s 使用的镜像
ctr images pull docker.io/library/nginx:alpine
ctr images push registry.example.com/app:v1
ctr images rm docker.io/library/nginx:alpine
ctr images import app.tar # 导入镜像归档
ctr images export app.tar app:v1
ctr images check # 检查本地镜像是否存在(含内容校验)
ctr image ls # 等价命令
ctr image pull --platform linux/amd64 nginx:alpine # 指定平台

# 容器与任务
ctr containers ls # 列出容器(仅元数据,不一定在运行)
ctr run --rm -t docker.io/library/alpine:latest shell /bin/sh # 运行容器并进 shell
ctr run -d --env TZ=Asia/Shanghai docker.io/library/redis:7-alpine redis
ctr tasks ls # 列出运行中的任务
ctr tasks kill -s SIGTERM redis # 向容器主进程发信号
ctr tasks exec -t --exec-id sh1 redis /bin/sh # 进入运行中容器
ctr containers rm redis # 删除容器
ctr tasks rm redis # 删除任务(强制先 kill)

# 快照
ctr snapshots ls # 查看快照(镜像层/容器层)

# 内容(blob)
ctr content ls # 查看内容存储中的 blob
ctr content rm sha256:xxxx # 删除 blob

# 事件与插件
ctr events # 实时事件流(创建/删除容器等)
ctr plugins ls # 列出已加载插件

ctr run 语法:

1
2
3
4
5
6
7
8
9
10
11
12
ctr run [options] <image-ref> <container-name> <command> [args...]

# 常用参数
ctr run -d # 后台运行(detach)
ctr run --rm # 退出即删容器
ctr run -t # 分配 TTY
ctr run --env KEY=VAL # 环境变量
ctr run --cwd /app # 工作目录
ctr run --snapshotter overlayfs # 指定快照器
ctr run --runtime io.containerd.runc.v2 # 指定运行时
ctr run --mount type=bind,src=/data,dst=/data,options=rbind:ro # 挂载
ctr run --net-host # 使用宿主机网络(不依赖 CNI)

ctr 不管理网络(除非 --net-host),不提供端口映射/重启策略,这些由上层(CRI/CNI/nerdctl)负责。

3.4 crictl(CRI 视角,K8s 运维首选)

配置(/etc/crictl.yaml):

1
2
3
4
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
1
2
3
4
5
6
7
# 安装
# Ubuntu:apt install cri-tools
# 手动:
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.30.0/crictl-v1.30.0-linux-amd64.tar.gz
tar -C /usr/local/bin -xzf crictl-v1.30.0-linux-amd64.tar.gz

crictl version

常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
crictl ps                          # 查看 Pod 内容器(含运行状态)
crictl ps -a # 全部(含已退出)
crictl pods # 查看 sandbox(Pod 沙箱)
crictl pods -q | xargs -I{} crictl inspectp {}

crictl images # 节点上镜像
crictl pull nginx:alpine
crictl rmi nginx:alpine

crictl exec -it <container-id> sh # 进入容器
crictl logs -f <container-id> # 查看日志
crictl stats # 资源占用(类似 docker stats)
crictl top <container-id> # 容器内进程

crictl inspect <container-id> # 详情
crictl inspectp <pod-id> # Pod 详情
crictl start/stop/rm <id> # 生命周期
crictl events # 事件流

crictl vs kubelet 注意事项:

  • crictl ps 的字段是 CRI 语义(CONTAINER / SANDBOX)。
  • crictl rmi 只删镜像引用,不回收层;磁盘回收主要靠 kubelet 的镜像 GC(--image-gc-high-threshold=85)。
  • 不要用 crictl 乱删 K8s 的 Pod 容器——kubelet 会自动重建,除非你在排障。
  • 端口转发:crictl portforward <pod-id> 8080:80(调试用)。

3.5 nerdctl(docker 兼容 CLI,强烈推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 安装 nerdctl
wget https://github.com/containerd/nerdctl/releases/download/v1.7.6/nerdctl-1.7.6-linux-amd64.tar.gz
tar -C /usr/local/bin -xzf nerdctl-1.7.6-linux-amd64.tar.gz

# 基本使用(命令与 docker 对齐)
nerdctl ps
nerdctl images
nerdctl run -d -p 8080:80 --name web nginx:alpine
nerdctl exec -it web sh
nerdctl logs -f web
nerdctl stop web && nerdctl rm web

# 构建镜像(默认用 buildkit,需先启动 buildkitd)
nerdctl build -t myapp:v1 .
nerdctl compose up -d # 支持 docker-compose 文件(需 CNI 网络)

# 网络与卷
nerdctl network create app-net
nerdctl volume create data-vol

前提条件:

  • 网络功能依赖 CNI 插件(/opt/cni/bin)与 nerdctl 的 CNI 配置。
  • 端口映射需要 rootlesskit(rootless 模式)或 root 权限。
  • build 功能需要 buildkit:containerd + buildkitd 两个 daemon。

3.6 三工具对照表(docker 习惯迁移)

操作 docker crictl nerdctl ctr
列容器 docker ps crictl ps nerdctl ps ctr containers ls
运行容器 docker run -d nginx (由 K8s 管理) nerdctl run -d nginx ctr run -d nginx n1
进容器 docker exec -it c sh crictl exec -it c sh nerdctl exec -it c sh ctr tasks exec -t --exec-id e c sh
看日志 docker logs -f c crictl logs -f c nerdctl logs -f c 无(需日志驱动/文件)
列镜像 docker images crictl images nerdctl images ctr images ls
拉镜像 docker pull crictl pull nerdctl pull ctr images pull
资源占用 docker stats crictl stats nerdctl stats
网络 内置 CNI(kubelet 配置) CNI(自带配置) 无(–net-host)

第 4 章 镜像管理

4.1 containerd 镜像模型

containerd 对镜像的处理与 Docker 一致(OCI 标准),但存储更”原始”:

1
2
3
4
5
6
镜像 = 配置(manifest/config) + 层列表(blobs)

/var/lib/containerd
├── io.containerd.content.v1.content/ # blob 二进制(压缩/未压缩层)
└── io.containerd.snapshotter.v1.overlayfs/
└── snapshots/ # 解压后的只读快照(按需解压)
  • content store:保存所有 blob(镜像层、配置、清单),以 digest(sha256)寻址,天然去重。
  • snapshotter:把 blob 解压成挂载可用的快照链,容器可写层基于此。
  • 元数据:bolt 数据库记录镜像名 → digest 的映射(1.7 之前);2.x 引入独立的 image store(io.containerd.image.v1.containerd),镜像按名/摘要管理,删除更彻底、支持多引用。

2.x 默认启用 image store 后,ctr images ls/crictl images 的语义更接近 Docker:镜像删除会真正回收未引用内容。

4.2 镜像拉取与列出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ctr
ctr images pull docker.io/library/nginx:1.27-alpine
ctr images pull --platform linux/arm64 nginx:alpine # 指定架构
ctr images ls --quiet
ctr -n k8s.io images ls -q

# crictl(K8s 节点)
crictl pull nginx:1.27-alpine
crictl images
crictl images --digests

# nerdctl
nerdctl pull nginx:1.27-alpine
nerdctl images

镜像全名格式(containerd 需要完整 repo 名):

1
2
3
docker.io/library/nginx:1.27-alpine
registry.example.com/team/app:v1.2.3
docker.io/library/nginx@sha256:xxxx... # 按 digest 引用(最安全)

注意:ctr 里 nginx 简写可能解析失败,建议始终写全 docker.io/library/nginx。CRI/crictl 支持简写(内部会补全)。

4.3 镜像导入导出(离线分发)

1
2
3
4
5
6
7
8
9
10
11
# ctr
ctr images export nginx.tar docker.io/library/nginx:1.27-alpine
ctr images import nginx.tar

# 支持 OCI 归档与 Docker 归档
ctr images export --platform linux/amd64 out.tar app:v1
ctr images import --no-unpack app.tar # 只导入元数据,不预解压

# nerdctl(与 docker save/load 完全一致)
nerdctl save -o nginx.tar nginx:1.27-alpine
nerdctl load -i nginx.tar

断网机房/离线交付:nerdctl save → 拷贝 → nerdctl load,或直接走私有 Registry。

4.4 镜像加速与私有仓库配置

containerd 的镜像加速配置在 CRI 插件的 registry.mirrors 下(不是 registry-mirrors 那种 Docker 顶层键)。

1.7 风格(config.toml):

1
2
3
4
5
6
7
8
9
10
11
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d" # 2.x 推荐用 certs.d 目录

[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = [
"https://docker.m.daocloud.io",
"https://dockerproxy.com"
]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.example.com"]
endpoint = ["https://registry.example.com"]

2.x / 推荐做法(certs.d 目录,声明式):

1
2
3
4
5
6
7
8
9
10
11
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<'EOF'
server = "https://docker.io"

[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]

[host."https://registry.example.com"]
capabilities = ["pull", "push", "resolve"]
ca = "/etc/containerd/certs.d/registry.example.com/ca.crt"
EOF
1
2
3
4
5
6
7
8
9
10
# 私有仓库认证
mkdir -p /etc/containerd/certs.d/registry.example.com
cat > /etc/containerd/certs.d/registry.example.com/hosts.toml <<'EOF'
server = "https://registry.example.com"

[host."https://registry.example.com"]
ca = "/etc/containerd/certs.d/registry.example.com/ca.crt"
[host."https://registry.example.com".header]
authorization = "Basic BASE64(user:password)"
EOF

更推荐的认证方式:K8s 用 imagePullSecrets;containerd 侧也可用 crictl pull --creds user:pass / nerdctl login(登录凭据存入 containerd auth 文件,由 config_path 指定)。

4.5 镜像校验与安全

1
2
3
4
5
6
7
8
9
10
11
# 查看 digest 与元数据
ctr images ls --digests
ctr images check --ref docker.io/library/nginx:1.27-alpine # 校验内容是否完整
ctr content ls | grep sha256

# CRI 侧
crictl images --digests

# 按 digest 拉取(不可变引用,防篡改)
crictl pull docker.io/library/nginx@sha256:2f23b580b8ba9d2b4f0...
ctr images pull docker.io/library/nginx@sha256:2f23b580b8ba9d2b4f0...

镜像签名(Notation,containerd 1.7+ 实验、2.x 可用):

  • 用 Notation 对镜像签名,containerd 在拉取时可校验签名(需启用 verify_image_signatures 相关配置/插件)。
  • Harbor 2.x 原生支持 Notation/Cosign 签名与验证,是生产首选路径。

4.6 镜像 GC 与磁盘回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# K8s 节点:镜像 GC 由 kubelet 负责
# kubelet 参数:
# --image-gc-high-threshold=85 磁盘使用超 85% 触发回收
# --image-gc-low-threshold=80 回收到 80% 以下停止

# 手动清理(K8s 节点用 crictl 删除未用镜像)
crictl rmi <image-id>

# containerd 内容级 GC
ctr content prune # 清理未被引用的 blob(1.7.7+)
ctr images ls -q | xargs -I{} sh -c 'echo {}'

# nerdctl 清理(类似 docker system prune)
nerdctl system prune -a -f
nerdctl image prune -f

血泪教训:K8s 节点 /var/lib/containerd 爆满的经典原因 = 日志不轮转 + 镜像 GC 阈值太高 + 悬空 blob。见第 9 章排障。

4.7 镜像平台与多架构

1
2
3
4
5
6
7
8
9
# 查看镜像清单(manifest list / index)
ctr images ls --digests
ctr content get <manifest-digest> | jq .

# 拉取指定架构
crictl pull --platform linux/arm64 ubuntu:24.04
ctr images pull --platform linux/arm64 ubuntu:24.04

# K8s 多架构节点:kubelet 会自动按节点架构拉取(镜像需支持多架构)

手动装镜像到节点时注意架构匹配,否则报 exec format error(见附录报错表)。


第 5 章 容器与任务

5.1 容器(Container)与任务(Task)的区别

containerd 把”容器”拆成两个概念:

概念 含义 类比
Container 静态描述:镜像、rootfs 快照、配置(元数据) docker 的容器配置
Task 实际运行中的进程实例(有 PID、状态) 进程本身
1
2
3
4
5
6
ctr containers create(只创建配置)
|
v
ctr task start(启动进程)-> Running
|
+-> 停止/退出 -> Stopped(进程没了,容器配置还在)
  • 一个 Container 可以多次创建/启动 Task(每次新进程)。
  • ctr run = containers create + tasks start 的便捷组合。

5.2 创建并运行容器(ctr)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 一步到位(run)
ctr run -d \
--env TZ=Asia/Shanghai \
--env MYSQL_ROOT_PASSWORD=Root123! \
--mount type=bind,src=/data/mysql,dst=/var/lib/mysql,options=rbind:rw \
docker.io/library/mysql:8.0 mysql

# 分步(create + start)
ctr containers create docker.io/library/redis:7-alpine redis
ctr tasks start redis
ctr tasks ls

# 前台交互运行
ctr run --rm -t docker.io/library/alpine:3.20 shell /bin/sh

# 指定运行时与快照器
ctr run --runtime io.containerd.runc.v2 --snapshotter overlayfs app:v1 myapp

生命周期命令:

1
2
3
4
5
6
7
8
ctr tasks ls                     # 运行中的任务
ctr tasks kill -s SIGTERM redis # 发信号
ctr tasks kill --all -s SIGKILL redis
ctr tasks pause redis # 暂停(freeze)
ctr tasks resume redis # 恢复
ctr tasks rm -f redis # 删除任务(强制)
ctr containers rm redis # 删除容器元数据
ctr containers rm -f redis # 连任务一起删

5.3 快照(Snapshotter)机制

快照是 containerd 管理 rootfs 的核心抽象:

1
2
3
4
Active 快照(可写,容器层)
|
v commit
Committed 快照(只读,镜像层)<-- 可被多个容器共享
1
2
3
ctr snapshots ls
ctr snapshots info <snapshot-key>
# overlayfs 实现:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/

常用快照器对比:

快照器 说明 适用
overlayfs(默认) 基于内核 overlayfs,性能好、省磁盘 Linux 通用,首选
native 直接目录拷贝,无层共享 老内核/特殊文件系统
devmapper 基于 Device Mapper(thin provisioning) overlayfs 不可用时
btrfs / zfs 基于文件系统快照 对应文件系统环境
nydus 容器镜像加速(按需加载) 大规模镜像分发优化
stargz 懒加载镜像(lazy pulling) 启动加速场景
1
2
3
4
# 指定快照器(需该快照器插件已启用)
ctr run --snapshotter devmapper ...
# config.toml 全局设置
# snapshotter = "overlayfs"

5.4 挂载(Mount)

1
2
3
4
5
6
7
8
9
10
11
12
# bind mount(类似 docker -v)
ctr run -d \
--mount type=bind,src=/srv/config,dst=/etc/app,options=rbind:ro \
app:v1 app

# tmpfs
ctr run -d \
--mount type=tmpfs,dst=/tmp,options=size=128m \
app:v1 app

# 只读根文件系统
ctr run -d --rootfs-ro app:v1 app

注意:ctr 没有 docker 的”命名卷”概念;持久化靠 bind mount 或上层(nerdctl volume / K8s volume)。

5.5 nerdctl 视角(贴近 docker 体验)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nerdctl run -d --name web \
-p 8080:80 \
-v /srv/www:/usr/share/nginx/html:ro \
-e TZ=Asia/Shanghai \
--restart unless-stopped \
nginx:1.27-alpine

nerdctl ps -a
nerdctl inspect web
nerdctl exec -it web sh
nerdctl logs -f web
nerdctl stop web && nerdctl rm web
nerdctl volume create data-vol
nerdctl network create app-net
nerdctl run -d --network app-net --name api myapi:v1

nerdctl 与 docker 的差异提醒:

  • --restart 由 nerdctl 自己实现(复刻策略),不是 containerd 原生能力。
  • 端口映射依赖 CNI + iptables,rootless 模式依赖 rootlesskit。
  • nerdctl build 需要 buildkitd 在跑。

5.6 运行时(Runtime)概念

containerd 支持多 OCI 运行时,通过 shim 调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[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

# 沙箱运行时 gVisor
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runc.v2"
runtime_path = "/usr/local/bin/runsc"

# Kata 安全容器
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
1
2
# ctr 指定运行时运行
ctr run --runtime io.containerd.runc.v2 alpine:3.20 shell /bin/sh

K8s 中通过 RuntimeClass 选择运行时(runtimeClassName: runsc)。


第 6 章 CRI 集成与 Kubernetes

6.1 什么是 CRI

CRI(Container Runtime Interface) 是 Kubernetes 定义的、kubelet 与容器运行时之间的标准接口(gRPC + protobuf),包含两组服务:

1
2
3
4
5
6
7
8
9
10
kubelet
|
|-- RuntimeService(管理 Pod sandbox 与容器生命周期)
| RunPodSandbox / CreateContainer / StartContainer / StopPodSandbox ...
|
|-- ImageService(管理镜像)
PullImage / ListImages / RemoveImage ...
|
v
containerd(CRI 插件 io.containerd.grpc.v1.cri)
  • kubelet 不关心底层是 Docker/containerd/CRI-O,只认 CRI。
  • containerd 的 CRI 插件把 CRI 请求翻译成 containerd 原生 API。
  • K8s 1.24 移除 dockershim 后,containerd 成为默认/主流运行时。

6.2 节点准备(kubeadm 部署前)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 安装 containerd(见第 2 章),确认 CRI 插件启用
containerd config default | grep -A3 'cri'
# 应看到:disabled_plugins 里没有 io.containerd.grpc.v1.cri

# 2. 生成默认配置
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# 3. 关键:K8s 使用 systemd cgroup 驱动(与 kubelet 一致)
# 修改 config.toml:
# SystemdCgroup = true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 4. 配置镜像加速(docker.io 与 K8s 镜像源,见 4.4)
# 5. 配置 pause 镜像(sandbox 镜像)
# 6. 重启
sudo systemctl restart containerd

# 7. 验证
crictl version
crictl pods

核心配置项(CRI 相关):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version = 2
root = "/var/lib/containerd"
state = "/run/containerd"

[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.10" # sandbox(pause)镜像
enable_selinux = false
enable_unprivileged_ports = true
enable_unprivileged_icmp = true

[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".registry]
config_path = "/etc/containerd/certs.d"

6.3 Pod 与 Sandbox(pause)机制

K8s 的”Pod”在运行时层面 = sandbox(pause 容器) + 业务容器:

1
2
3
4
Pod sandbox(pause 容器)
├── 持有网络命名空间 / 共享网络栈
├── 持有 IPC、UTS namespace
└── 业务容器(app1、sidecar...)加入 sandbox 的命名空间
  • pause 镜像默认 registry.k8s.io/pause:3.10(不同 K8s 版本要求不同版本)。
  • 国内环境必须把 sandbox_image 换成可访问的镜像源(如 registry.aliyuncs.com/google_containers/pause:3.10)。
1
2
3
4
5
6
# 查看 sandbox
crictl pods
crictl inspectp <sandbox-id>

# 手动拉 sandbox 镜像
crictl pull registry.k8s.io/pause:3.10

高频故障:节点无法拉取 pause 镜像 → Pod 一直 ContainerCreating(Error: ErrImagePull)。处理:配置镜像加速/换源,或预拉 pause 镜像到节点。

6.4 kubelet 对接

1
2
3
4
# kubelet 配置(/var/lib/kubelet/config.yaml 或启动参数)
--container-runtime-endpoint=unix:///run/containerd/containerd.sock
--kubelet-cgroups=/systemd/system.slice
--cgroup-driver=systemd # 必须与 containerd 的 SystemdCgroup 一致

kubeadm 初始化:

1
2
# 生成配置
kubeadm config print init-defaults --component-configs KubeletConfiguration > kubeadm-init.yaml
1
2
3
4
5
6
7
# kubeadm-init.yaml 关键片段
kind: KubeletConfiguration
cgroupDriver: systemd
---
kind: InitConfiguration
---
kind: ClusterConfiguration
1
2
3
kubeadm init --config kubeadm-init.yaml
kubectl get nodes
kubectl get pods -A

kubelet + containerd 健康自检:

1
2
3
4
crictl version
crictl pods # 能看到 sandbox
journalctl -u kubelet -n 100
crictl logs <container-id>

6.5 K8s 节点日常排障(crictl 实战)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 定位异常 Pod
kubectl get pods -A -o wide
kubectl describe pod <pod> -n <ns>

# 2. 到节点上看容器
crictl ps -a
crictl inspect <container-id> | grep -A5 -i state

# 3. 看日志/进入容器
crictl logs -f <container-id>
crictl exec -it <container-id> sh

# 4. 看资源
crictl stats

# 5. 看镜像(拉取是否成功)
crictl images
crictl pull <image>

# 6. 事件
crictl events

常见 CRI 问题对照:

现象 常见原因 处理
Pod 卡 ContainerCreating pause 镜像拉不到 配加速/换 sandbox_image
ErrImagePull / ImagePullBackOff 镜像不存在/仓库认证失败 crictl pull 复现;配 imagePullSecrets
CrashLoopBackOff 应用启动失败 crictl logs 看退出原因
节点 NotReady containerd 挂了/cgroup 驱动不一致 systemctl status containerd;检查 SystemdCgroup
sandbox 泄漏(大量 exited sandbox) 异常删除 Pod 重启 kubelet/containerd 清理
failed to create containerd task 快照/存储异常、权限 看 containerd 日志、检查磁盘
exec format error 架构不匹配 换对应架构镜像

6.6 卸载/更换运行时(Docker → containerd 迁移)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 节点 drain 后操作(kubeadm 场景)
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

# 1. 停用并移除 docker
sudo systemctl stop docker
sudo apt-get purge -y docker-ce docker-ce-cli containerd.io # 注意:这也会删 containerd.io!
# 迁移场景推荐保留 containerd.io,只卸载 docker-ce 相关:
sudo apt-get purge -y docker-ce docker-ce-cli docker-buildx-plugin docker-compose-plugin

# 2. 重新安装/确认 containerd(见第 2 章)
# 3. 配置 SystemdCgroup=true + 镜像加速 + sandbox_image
# 4. kubeadm 更新 kubelet 参数(--container-runtime-endpoint)
# 5. 恢复节点
kubectl uncordon <node>

重要containerd.io 与 Docker Engine 是同一发行渠道;迁移时只移除 docker-ce,保留 containerd.io 即可无缝切换。


第 7 章 网络与存储

7.1 containerd 的网络模型

核心事实:containerd 自己不管理 Pod 网络。

  • CRI 场景:kubelet 调用 CNI 插件(calico/flannel/cilium 等)为 sandbox 配置网络。
  • nerdctl 场景:nerdctl 自带 CNI 配置(bridge/portmap 等插件)实现网络与端口映射。
  • ctr 场景:默认无网络(只有 --net-host)。
1
2
3
4
Pod sandbox 创建
-> kubelet 调用 CNI(/opt/cni/bin + /etc/cni/net.d 配置)
-> 插件创建 veth、分配 IP、配置路由
-> 业务容器加入 sandbox 网络命名空间
1
2
3
4
5
6
# CNI 目录
ls /opt/cni/bin # 插件二进制
ls /etc/cni/net.d/ # 网络配置(由网络方案生成)

# 验证 CNI 插件安装
/opt/cni/bin/bridge --version 2>/dev/null || echo "bridge not found"

常用 CNI 插件:

插件 特点 适用
bridge + portmap 默认/最简 单机、nerdctl
flannel VXLAN 覆盖网络,简单 小集群
calico BGP/overlay、NetworkPolicy、性能好 生产主流
cilium eBPF、服务网格能力、高性能 大规模/云原生高级场景

7.2 存储(快照与数据)

  • 镜像层:snapshotter 管理(见 5.3)。
  • 容器可写层:默认 overlayfs,限制与 Docker 相同(写敏感应用建议挂载)。
  • K8s 数据卷:由 CSI(Container Storage Interface)插件负责,containerd 不参与。
  • 日志:K8s 场景由 kubelet 写到 /var/log/pods;containerd 不直接管理容器日志。

生产存储建议:

  • /var/lib/containerd 放独立数据盘,监控容量。
  • 高 IO 的数据库类工作负载:避免 overlayfs 可写层,用宿主机裸盘/CSI 本地卷 + 数据目录挂载。
  • 大批量镜像分发:评估 nydus/stargz 懒加载方案降低启动时间。

7.3 nerdctl 网络实战(单机)

1
2
3
4
5
6
7
8
9
# 依赖 CNI 插件
nerdctl network ls
nerdctl network create --subnet 10.10.0.0/24 app-net
nerdctl run -d --network app-net --name api myapi:v1
nerdctl run -d --network app-net -p 8080:80 --name web nginx:alpine

# 容器间用容器名互访
nerdctl exec web getent hosts api
nerdctl network inspect app-net

7.4 镜像存储与 GC 调优(K8s 生产)

1
2
3
4
5
6
7
# kubelet 镜像 GC 参数(/var/lib/kubelet/config.yaml)
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80

# 节点级手动清理(谨慎)
crictl rmi --prune # 删除未被使用镜像(crictl v1.26+)
nerdctl -n k8s.io image prune -a -f # 需 nerdctl 与 k8s.io 命名空间权限

不要直接删 /var/lib/containerd 下的文件——绕过 API 会导致元数据不一致,只能用 GC 工具。


第 8 章 安全加固

8.1 安全面梳理

层面 风险 措施
守护进程 socket 未授权访问、远程 gRPC 裸奔 收紧权限、不开放 TCP、认证
镜像供应链 恶意/带漏洞镜像、被投毒镜像 私有仓库 + 签名验证 + 扫描
运行时 容器逃逸、内核漏洞 非 root、seccomp/AppArmor、最小能力
配置 敏感信息入镜像、root 运行 规范 Dockerfile/CRI 配置
节点 K8s 攻击面(kubelet 未认证等) 加固 OS + RBAC + 审计

8.2 守护进程安全

1
2
3
4
5
6
7
8
9
10
# 1. socket 权限
ls -l /run/containerd/containerd.sock
# srw-rw---- 1 root containerd -> 默认 root:containerd,非授权用户不可访问

# 2. 不要开 TCP 监听(默认只监听 unix socket)
grep -A3 'grpc' /etc/containerd/config.toml
# address = "/run/containerd/containerd.sock" <- 保持 unix socket

# 3. 审计
auditctl -w /var/lib/containerd -k containerd

containerd 官方不支持无认证的远程 gRPC 暴露;远程管理一律走受控通道(SSH、K8s API、堡垒机)。

8.3 镜像安全

1
2
3
4
5
6
7
8
9
10
# 1. 只从可信仓库拉取:默认禁止 docker.io 之外的匿名源(可在 CRI 配置中白名单)
# 2. 镜像扫描(Trivy 示例)
trivy image registry.example.com/app:v1.2.3 --severity HIGH,CRITICAL --ignore-unfixed

# 3. 镜像签名验证(Notation)
notation cert add --type ca --store mytrust ca.crt
notation sign --key mykey registry.example.com/app:v1.2.3
# containerd 侧启用校验(config.toml)
# [plugins."io.containerd.grpc.v1.cri".image_decryption]
# key_model = "node"

供应链安全基线:

  • 镜像锁定 digest(image@sha256:...)。
  • CI 中加入漏洞扫描门禁(HIGH/CRITICAL 阻断发布)。
  • Harbor/私有仓库开启内容信任与签名策略。
  • 定期更新基础镜像与运行时(runc 升级尤其重要,历史多次逃逸 CVE 出在 runc)。

8.4 运行时安全(容器内)

1
2
3
4
5
6
7
8
9
10
11
# 1. 非 root 运行(镜像 USER 指令,或 CRI 的 securityContext)
# K8s PodSecurityContext 示例:
# runAsNonRoot: true
# runAsUser: 1001

# 2. seccomp(默认开启:CRI 配置 enable_seccomp = true)
# 3. AppArmor / SELinux
# 4. 最小能力
# K8s:securityContext.capabilities.drop: ["ALL"]
# 5. 只读根文件系统:readOnlyRootFilesystem: true
# 6. 禁止 privileged:PodSecurity admission(restricted 策略)

CRI 配置示例(config.toml):

1
2
3
4
5
6
7
8
9
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
discard_unpacked_layers = true # 解压后丢弃中间层,省磁盘

[plugins."io.containerd.grpc.v1.cri"]
enable_selinux = true
enable_unprivileged_ports = true
enable_unprivileged_icmp = true
enable_seccomp = true

K8s 安全上下文示例:

1
2
3
4
5
6
7
8
9
securityContext:
runAsNonRoot: true
runAsUser: 1001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault

8.5 Rootless 模式

containerd 支持 rootless(非 root 运行),配合用户命名空间:

1
2
3
4
5
6
7
8
9
10
# 前置:用户命名空间支持
sudo sysctl kernel.unprivileged_userns_clone=1 # 老内核可能需要

# rootless containerd + nerdctl
containerd-rootless-setuptool.sh install
containerd-rootless-setuptool.sh install-buildkit
containerd-rootless-setuptool.sh install-kubectl

# 使用(无需 root)
nerdctl run -d -p 8080:80 nginx:alpine

rootless 依赖:fuse-overlayfs、slirp4netns/rootlesskit;性能与功能有取舍(如部分网络能力受限)。适合开发与多租户场景,生产 K8s 节点通常仍用 root 模式 + 纵深防御。

8.6 加固检查清单

  • socket 属主 root:containerd,权限 660
  • 未开放 TCP gRPC 监听
  • 镜像来自受信仓库且过漏洞扫描
  • 生产启用镜像签名验证(Notation/Cosign)
  • seccomp 开启,AppArmor/SELinux 按发行版启用
  • 容器非 root 运行、能力最小化、只读 rootfs
  • runc/containerd/内核保持最新补丁
  • K8s 启用 PodSecurity(restricted)与 NetworkPolicy
  • /var/lib/containerd 独立分区并监控
  • 审计日志开启(auditd)

第 9 章 运维与故障排查

9.1 日志体系

1
2
3
4
5
6
7
8
containerd daemon 日志(systemd journal)
journalctl -u containerd -n 200 -f

容器标准输出(K8s 场景由 kubelet 接管)
/var/log/pods/<ns>_<pod>_<uid>/<container>/0.log

shim 日志(每个容器)
/run/containerd/io.containerd.runtime.v2.task/<ns>/<container>/log.json
1
2
3
journalctl -u containerd -n 100 --no-pager
journalctl -u containerd --since "10 minutes ago"
journalctl -u kubelet -n 100 --no-pager

containerd 本身不负责容器日志持久化;单机用 nerdctl 时日志在 shim 的 log.json,可配日志轮转。

9.2 磁盘爆满(第一高频事故)

排查顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 确认占用
df -h /var/lib/containerd
du -sh /var/lib/containerd/* | sort -h

# 2. 容器可写层/日志
du -sh /var/log/pods 2>/dev/null | sort -h
du -sh /run/containerd/io.containerd.runtime.v2.task/* 2>/dev/null | sort -h

# 3. 镜像与 blob
crictl images
ctr -n k8s.io content ls | head -20

# 4. 清理
crictl rmi --prune # 删未使用镜像(K8s 节点)
crictl images | grep '<none>' # 悬空镜像
ctr -n k8s.io content prune # 清未引用 blob(低版本无此命令则跳过)
nerdctl system prune -a -f # 单机场景

# 5. 预防
# - kubelet 调低 image-gc-high-threshold(85 -> 70)
# - 容器日志轮转(containerd 配置 max_size/max_file 或 kubelet 容器日志策略)
# - 定期巡检脚本 + 告警

containerd 日志轮转(CRI 配置):

1
2
3
4
5
6
7
[plugins."io.containerd.grpc.v1.cri".containerd]
max_container_log_line_size = 16384

# 或 daemon 层面
[plugins."io.containerd.grpc.v1.cri".containerd.logs]
max_size = 52428800 # 50MB
max_files = 5

9.3 常见故障速查表

现象 原因 处置
failed to connect to /run/containerd/containerd.sock containerd 未启动/崩溃 systemctl status containerd,看 journal
Pod 一直 ContainerCreating pause 镜像拉不到/CNI 故障 crictl pods + crictl pull pause
ErrImagePull 镜像不存在/无权限 crictl pull 复现;检查认证与地址
容器启动报 permission denied 非 root 用户无权限/AppArmor 检查镜像 USER、挂载属主
exec format error 架构不匹配 拉取对应 --platform 镜像
节点 NotReady containerd 或网络插件异常 journal 定位;重启 containerd/calico
大量 exited sandbox 异常 Pod 清理不彻底 重启 kubelet;crictl rmp -f 清理
failed to reserve sandbox name 同名 sandbox 残留 crictl rmp 删除残留沙箱
磁盘持续增长 镜像 GC 不回收/日志不轮转 见 9.2
unexpected EOF 拉取中断 网络/仓库不稳定 重试、配加速、加代理
no such image(ctr) 命名空间/名称写错 -n k8s.io;写全镜像名

9.4 性能调优

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# config.toml 生产建议
version = 2
root = "/var/lib/containerd"
state = "/run/containerd"

# 并发下载/上传
max_concurrent_downloads = 10
max_concurrent_uploads = 5

# 流媒体/大镜像
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = true

# 内核参数(节点级)
net.ipv4.ip_local_port_range = 1024 65535
net.core.somaxconn = 1024
fs.file-max = 1000000
1
sysctl -w vm.max_map_count=262144        # 部分应用(ES 等)需要

性能要点:

  • 使用 overlayfs 快照器(默认),避免 native。
  • 镜像构建阶段就尽量精简(层数、大小),减少拉取/解压时间。
  • 大批量镜像预热:提前 crictl pull 或使用懒加载(nydus)。
  • 高并发创建容器:关注 shim 数量与 PID 限制(TasksMax=infinity)。
  • 基准:ctr 创建容器毫秒级;对比 Docker 多一层 daemon 开销更小。

9.5 备份与迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 停止 containerd(保证数据一致)
sudo systemctl stop containerd

# 2. 打包数据目录
sudo tar -czf /backup/containerd-$(date +%F).tar.gz /var/lib/containerd

# 3. 恢复(新机器)
sudo mkdir -p /var/lib/containerd
sudo tar -xzf /backup/containerd-xxxx.tar.gz -C /
sudo systemctl start containerd

# 4. 验证
ctr -n k8s.io images ls | head

备份注意:跨 containerd 大版本恢复不一定兼容(bolt 元数据格式变化),建议用镜像仓库做制品备份/var/lib/containerd 只做节点级灾备。数据库等有状态数据务必走应用自身备份。

9.6 事件与指标

1
2
3
4
5
6
7
8
9
# 事件
ctr events --namespace k8s.io
crictl events

# metrics(Prometheus)
# config.toml:
# [metrics]
# address = "127.0.0.1:1338"
curl -s http://127.0.0.1:1338/metrics | grep -E '^container|^containerd'

关键指标:

  • containerd_containers_total / containerd_tasks_running
  • containerd_events_total
  • container_tasks_state
  • containerd_snapshotter_*(快照使用)

Grafana 面板建议: node-exporter(主机)+ cAdvisor(容器)+ containerd metrics(运行时)。

9.7 巡检脚本骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
# 每 5 分钟巡检,异常告警
set -euo pipefail

# 1. 服务状态
systemctl is-active containerd >/dev/null || echo "ALERT: containerd down"

# 2. 磁盘
USAGE=$(df -h /var/lib/containerd | awk 'NR==2{print $5}' | tr -d '%')
[ "$USAGE" -gt 80 ] && echo "ALERT: disk usage ${USAGE}%"

# 3. 节点容器异常数量
ABNORMAL=$(crictl ps -a --state exited 2>/dev/null | wc -l)
[ "$ABNORMAL" -gt 50 ] && echo "ALERT: ${ABNORMAL} exited containers"

# 4. socket 可达性
crictl version >/dev/null 2>&1 || echo "ALERT: cri endpoint unreachable"

第 10 章 进阶与生态

10.1 插件体系

containerd 由插件构成,全部通过 gRPC 接口注册:

1
2
3
4
5
6
7
8
ctr plugins ls
# 常见插件:
# io.containerd.content.v1.content
# io.containerd.metadata.v1.bolt
# io.containerd.snapshotter.v1.overlayfs
# io.containerd.grpc.v1.cri
# io.containerd.runtime.v2.task
# io.containerd.metrics.v1.metrics

config.toml 中启用/禁用插件:

1
2
3
4
disabled_plugins = []
required_plugins = []
# 例:禁用未使用的插件减少攻击面
# disabled_plugins = ["io.containerd.grpc.v1.healthcheck"]

10.2 内容校验、租约(Lease)与 GC

  • Content 按 digest 寻址:天然防篡改、自动去重。
  • Lease:给内容打”引用标记”,防止 GC 误删(如”正在解压”的内容)。
  • GC:containerd 定期清理未被引用的 content/snapshots;也可手动 ctr content prune
1
2
3
4
5
# 查看 lease
ctr leases list

# 手动触发 GC(低版本无内置命令时)
# 依赖 API 调用;日常交给 containerd 自动 GC + kubelet 镜像 GC

10.3 Go 客户端编程(简介)

containerd 官方 Go 客户端是上层工具(nerdctl、kubelet CRI 插件)的基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"context"
"log"

"github.com/containerd/containerd"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
)

func main() {
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
log.Fatal(err)
}
defer client.Close()

ctx := namespaces.WithNamespace(context.Background(), "default")

// 拉镜像
image, err := client.Pull(ctx, "docker.io/library/alpine:3.20")
if err != nil {
log.Fatal(err)
}

// 创建容器
container, err := client.NewContainer(
ctx,
"demo",
containerd.WithImage(image),
containerd.WithNewSnapshot("demo-snap", image),
containerd.WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("/bin/sh", "-c", "echo hello; sleep 5")),
)
if err != nil {
log.Fatal(err)
}
defer container.Delete(ctx, containerd.WithSnapshotCleanup)

log.Println("container created:", container.ID())
}
1
2
# 依赖
go get github.com/containerd/containerd/v2

一般运维不需要写 Go;了解 API 分层有助于理解 nerdctl/kubelet 行为。

10.4 nerdctl 完整能力(开发/单机主力)

1
2
3
4
5
6
nerdctl run / ps / exec / logs / inspect / stats   # 容器
nerdctl build / commit / tag / push / pull # 镜像
nerdctl compose up -d # Compose
nerdctl network / volume / namespace # 网络/卷/命名空间
nerdctl image prune / system prune # 清理
nerdctl info

与 Docker CLI 的细微差异:

  • nerdctl run -d 默认不分配 TTY;-it 行为一致。
  • 端口映射依赖 CNI portmap 插件。
  • nerdctl build 依赖 buildkitd(systemctl start buildkitnerdctl builder)。
  • 支持 rootless 全链路。

10.5 与生态的完整关系图

1
2
3
4
5
6
7
8
9
10
11
12
开发者视角:
Docker CLI / docker compose / nerdctl
| |
v v
BuildKit(构建) containerd(运行)
| |
+--------+---------+
v
Kubernetes(CRI + CNI + CSI)
|
v
containerd(每节点运行时)

什么时候选什么:

场景 推荐
开发机/单机容器体验 Docker Desktop 或 nerdctl
生产 K8s 集群 containerd(每节点)+ CRI
边缘/嵌入式 containerd 精简部署
离线内网 containerd + Harbor + crictl/nerdctl
多租户开发隔离 rootless containerd + nerdctl

10.6 containerd 2.x 升级要点

1
2
3
4
5
6
7
8
9
10
11
# 升级前
containerd --version
ctr version
containerd config dump > /tmp/config-backup.toml

# 停服务 -> 替换二进制 -> 校验配置 -> 启动
sudo systemctl stop containerd
sudo tar -C /usr/local -xzf containerd-2.0.x-linux-amd64.tar.gz
sudo containerd config check
sudo systemctl start containerd
crictl version && crictl pods

2.x 主要变化(迁移注意):

  • 默认启用独立镜像存储(image store),镜像管理行为更接近 Docker。
  • 配置语法/字段变化:用 containerd config default 重新生成并合并自己的修改。
  • 移除部分废弃 API/插件(旧 snapshotters、旧客户端版本)。
  • runc 升级到 v3 运行时接口(runc 版本需配套)。
  • 更多细节以官方升级文档为准。

升级原则:测试环境先验证 → 灰度节点 → 全量;K8s 集群升级 containerd 前确认与 kubelet 版本兼容矩阵。


第 11 章 常用命令速查表

11.1 ctr(原生)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ctr version
ctr namespace ls / ns create / ns remove

# 镜像
ctr images pull docker.io/library/nginx:alpine
ctr images push registry.example.com/app:v1
ctr images ls [-q]
ctr images rm <image>
ctr images import app.tar
ctr images export app.tar <image>
ctr images check
ctr images prune

# 内容
ctr content ls
ctr content rm sha256:xxxx

# 容器与任务
ctr containers ls
ctr run -d --env A=B --mount type=bind,src=/x,dst=/y img name
ctr run --rm -t img sh
ctr tasks ls
ctr tasks exec -t --exec-id e1 name sh
ctr tasks kill -s SIGTERM name
ctr tasks pause/resume name
ctr tasks rm -f name
ctr containers rm -f name

# 快照
ctr snapshots ls

# 事件/插件/租约
ctr events
ctr plugins ls
ctr leases list

11.2 crictl(CRI / K8s 节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
crictl version
crictl ps [-a]
crictl pods
crictl images
crictl pull nginx:alpine
crictl rmi <image>
crictl exec -it <id> sh
crictl logs -f <id>
crictl stats
crictl top <id>
crictl inspect <id>
crictl inspectp <pod-id>
crictl start/stop/rm/rmp <id>
crictl events
crictl portforward <pod-id> 8080:80
crictl rmi --prune

11.3 nerdctl(docker 兼容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nerdctl version
nerdctl run -d -p 8080:80 --name web nginx:alpine
nerdctl ps -a
nerdctl exec -it web sh
nerdctl logs -f web
nerdctl stop/start/restart/rm web
nerdctl images / pull / push / tag / rmi
nerdctl save -o x.tar img
nerdctl load -i x.tar
nerdctl build -t app:v1 .
nerdctl compose up -d
nerdctl network ls/create/rm
nerdctl volume ls/create/rm
nerdctl stats
nerdctl system prune -a -f
nerdctl image prune -f
nerdctl info

11.4 系统级

1
2
3
4
5
6
7
systemctl status/start/stop/restart containerd
journalctl -u containerd -n 200 -f
containerd config default > /etc/containerd/config.toml
containerd config check
crictl version
ss -l | grep containerd
df -h /var/lib/containerd

附录

A. 术语表

术语 含义
OCI 开放容器标准(image/runtime 规范)
CRI Kubernetes 容器运行时接口
CNI 容器网络接口(K8s Pod 网络)
CSI 容器存储接口(K8s 卷)
Sandbox CRI 中的 Pod 沙箱(pause 容器)
Shim 每容器一个的 containerd 垫片进程
Snapshotter rootfs 快照管理(存储驱动)
Content Store 镜像 blob 内容存储
Namespace containerd 数据逻辑隔离空间
Task 运行中的容器进程实例
Lease 内容引用标记(防 GC)
RuntimeClass K8s 选择容器运行时的机制
Rootless 非 root 运行容器

B. 官方与推荐资料

C. 常见报错对照表

报错 含义 处理
failed to connect to containerd daemon 不可达 启动 containerd;检查 socket
connection refused socket 未监听/权限 ss -lx;确认用户
permission denied(socket) 非 root/非 containerd 组 sudo 或加组
not found: image ... 镜像不存在/命名空间错 -n k8s.io;写全名
failed to resolve reference 拉取失败(网络/认证) 配加速/仓库认证
image is not used 删除失败 镜像仍被容器引用 删容器后再删镜像
exec format error 架构不匹配 --platform 镜像
failed to create shim task 运行时/快照/权限问题 journal 定位;查 runc 版本
sandbox name already reserved sandbox 残留 crictl rmp -f 清理
no space left on device 磁盘满 见 9.2 清理流程
snapshot already exists 同名快照冲突 删旧容器/快照再试
unexpected EOF 拉取中断 重试/换源/加代理

D. 进阶自测清单

  • 能解释 containerd、shim、runc 三者的职责边界
  • 会用 ctr/crictl/nerdctl 完成镜像与容器全生命周期操作
  • 理解 k8s.io / moby / default 命名空间的区别
  • 能配置镜像加速、私有仓库认证、sandbox 镜像
  • 会排查 Pod ContainerCreating / ErrImagePull / 节点 NotReady
  • 掌握磁盘爆满的处置流程与镜像 GC 原理
  • 会配置 SystemdCgroup 并理解 cgroup 驱动一致性的坑
  • 能完成 Docker 到 containerd 的迁移
  • 理解 CRI/CNI/CSI 三大接口的分工
  • 知道 rootless、seccomp、签名验证等安全手段
  • 能编写基于 ctr/crictl 的巡检脚本
  • 能独立规划 containerd 节点的存储与容量监控

学完本手册,你就掌握了云原生”运行时底座”。下一步建议深入 Kubernetes 控制面(kubelet、CRI、CNI 插件)与镜像供应链安全(签名、扫描、策略)。


containerd 从入门到精通实战手册
https://blog.t-ao.cn/2026/08/03/containerd从入门到精通实战手册/
作者
TAO
发布于
2026年8月3日
许可协议