containerd 常用运维指令手册
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)
目录
- 定位与架构
- 版本与兼容矩阵
- 安装部署
- config.toml 配置详解
- 镜像仓库配置
- ctr 命令全集
- nerdctl
- crictl
- namespace 与内容存储
- snapshotter 与存储
- GC 与空间回收
- 服务运维与调试
- 与 Kubernetes 集成排障
- 升级与迁移
- 卸载与清理
- 故障速查
- 附录
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 侧的两种做法:
- 节点级限制:给
/var/lib/containerd单独分区 + kubelet 的evictionHard磁盘阈值。 - 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。