Skip to content

02 — 容器运行时深度教材

理解容器运行时的分层架构(OCI → CRI → containerd/CRI-O → runc)是高级运维的基本功。本章从 OCI 规范到生产运维,完整覆盖 containerd 架构、CRI 接口、镜像管理、故障排查。


1. 容器运行时架构分层

┌─────────────────────────────────────────────────────────┐
│                    kubelet                               │
│  (通过 CRI gRPC 接口调用容器运行时)                        │
├─────────────────────────────────────────────────────────┤
│                 CRI (Container Runtime Interface)        │
│  gRPC 协议,定义 ImageService + RuntimeService           │
├──────────────────────┬──────────────────────────────────┤
│    containerd        │           CRI-O                   │
│  (shim 架构)         │     (轻量级,专为 K8s)             │
├──────────────────────┴──────────────────────────────────┤
│                   runc (OCI runtime)                     │
│  创建容器进程:clone() + namespace + cgroup + chroot      │
├─────────────────────────────────────────────────────────┤
│              OCI (Open Container Initiative)             │
│  定义镜像格式(image spec)和运行时规范(runtime spec)    │
└─────────────────────────────────────────────────────────┘

1.1 OCI 规范

OCI 两大规范:
1. Image Specification(镜像格式)
   - 定义镜像的 layer、config、manifest
   - Docker 镜像格式是 OCI 的超集
   
2. Runtime Specification(运行时规范)
   - 定义容器的创建、运行、销毁
   - 规定 config.json 格式(mounts、hooks、linux namespace/cgroup)
   
OCI 运行时参考实现:runc

1.2 containerd vs CRI-O 对比

特性containerdCRI-O
定位通用容器运行时K8s 专用运行时
支持 Docker CLI✅ 通过 ctr/docker
架构复杂度中等(shim 架构)轻量
K8s 默认✅ 1.24+ 默认OpenShift 默认
资源占用~50MB RSS~30MB RSS
社区活跃度高(CNCF 毕业项目)中(K8s SIG)

2. containerd 深度运维

2.1 架构与组件

containerd 架构:
┌──────────────┐
│   kubelet    │ ← CRI gRPC
├──────────────┤
│ containerd   │ ← 主进程(管理容器生命周期)
├──────────────┤
│ containerd-shim-runc-v2 │ ← 每个 Pod 一个 shim 进程
├──────────────┤
│    runc      │ ← 创建容器后退出(shim 接管)
└──────────────┘

关键设计:
- shim 进程在 containerd 重启时保持容器运行
- containerd 崩溃重启不影响已运行的容器
- 每个 Pod(含 pause + 业务容器)对应一个 shim

2.2 containerd 配置

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

# 关键配置项:
# [plugins."io.containerd.grpc.v1.cri"]
#   sandbox_image = "registry.k8s.io/pause:3.9"  # pause 镜像
#   [plugins."io.containerd.grpc.v1.cri".containerd]
#     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  # 使用 systemd 管理 cgroup(推荐)

# 镜像仓库配置(私有仓库 + mirror)
# [plugins."io.containerd.grpc.v1.cri".registry]
#   [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
#     [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
#       endpoint = ["https://mirror.ccs.tencentyun.com", "https://registry-1.docker.io"]
#     [plugins."io.containerd.grpc.v1.cri".registry.mirrors."harbor.example.com"]
#       endpoint = ["https://harbor.example.com"]

# 重启 containerd 使配置生效
systemctl restart containerd

2.3 crictl — K8s 容器运行时 CLI

bash
# === 容器管理 ===
crictl ps                              # 运行中的容器
crictl ps -a                           # 所有容器
crictl inspect <container-id>          # 容器详情
crictl logs <container-id>             # 容器日志
crictl logs -f <container-id>          # 实时跟踪
crictl exec -it <container-id> /bin/sh # 进入容器

# === Pod 管理 ===
crictl pods                            # 列出 Pod sandbox
crictl inspectp <pod-id>               # Pod 详情

# === 镜像管理 ===
crictl images                          # 列出镜像
crictl pull <image>                    # 拉取镜像
crictl rmi <image>                     # 删除镜像
crictl rmi --prune                     # 清理未使用镜像

# === 运行时状态 ===
crictl info                            # containerd 状态
crictl stats                           # 容器资源使用

# === 调试技巧 ===
# 找到某个 Pod 的所有容器
crictl ps --pod <pod-id>

# 查看容器启动参数
crictl inspect <container-id> | jq .info.config.args

# 查看容器的 cgroup 路径
crictl inspect <container-id> | jq .info.runtimeSpec.linux.cgroupsPath

2.4 ctr — containerd 原生 CLI(调试用)

bash
# 注意:ctr 直接操作 containerd,绕过 CRI,生产慎用
ctr -n k8s.io images ls                # 列出镜像
ctr -n k8s.io containers ls             # 列出容器
ctr -n k8s.io tasks ls                  # 列出运行中的任务

# 查看镜像的 layer 信息
ctr -n k8s.io images info <image>

# 导出/导入镜像(离线场景)
ctr -n k8s.io images export /tmp/image.tar <image>
ctr -n k8s.io images import /tmp/image.tar

3. 镜像管理深度

3.1 镜像层与存储

bash
# 镜像层查看
ctr -n k8s.io images info docker.io/library/nginx:latest | jq .target

# 镜像存储位置
ls /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/

# 查看镜像占用空间
du -sh /var/lib/containerd/

# 镜像垃圾回收
ctr -n k8s.io images gc --help
# 配置自动 GC:
# [plugins."io.containerd.gc.v1.scheduler"]
#   deletion_threshold = 0
#   pause_threshold = 0.02

3.2 镜像拉取故障排查

bash
# 问题 1:证书错误
# 解决方案:配置 insecure-skip-verify
# [plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.local".tls]
#   insecure_skip_verify = true

# 问题 2:超时
# 检查网络和 DNS
nslookup registry-1.docker.io
curl -v https://registry-1.docker.io/v2/ 2>&1 | head -20

# 问题 3:镜像不存在
# 检查 imagePullPolicy 和 tag
crictl pull docker.io/library/nginx:latest

# 问题 4:认证失败
# 创建 imagePullSecret 或配置 containerd 认证
# crictl 不支持直接登录,需要在 K8s 中使用 imagePullSecret

4. 面试高频问题

Q: containerd 重启会杀死已运行的容器吗?

不会。containerd 使用 shim 架构:
1. 每个 Pod 对应一个 containerd-shim 进程
2. shim 由 init 进程(PID 1)接管,不依赖 containerd
3. containerd 重启时,shim 进程继续运行
4. containerd 重启后通过 shim 重新获取容器状态

验证:
  systemctl restart containerd
  crictl ps  # 容器仍然在运行
  systemctl status containerd-shim-*  # shim 进程仍在

Q: 如何排查 Pod 一直处于 ContainerCreating 状态?

bash
# 1. 查看 Pod 事件
kubectl describe pod <name> | tail -30

# 2. 常见原因
# a. 镜像拉取失败 → 检查 imagePullPolicy、镜像名、仓库认证
# b. Init Container 失败 → kubectl logs <pod> -c <init-container>
# c. 挂载卷失败 → 检查 PV/PVC、StorageClass、NFS/iSCSI 连通性
# d. 网络插件未就绪 → 检查 Calico/Cilium Pod 状态
# e. 资源不足 → kubectl describe node <node> | grep -A5 'Allocated'

# 3. containerd 层面排查
journalctl -u containerd --since "10 minutes ago" | grep -i error
crictl ps -a | grep <pod-name>
crictl inspect <container-id> 2>/dev/null

Q: Docker 和 containerd 的关系?为什么 K8s 弃用 Docker?

关系:
  Docker → containerd → runc
  Docker 是 containerd 的上层封装(增加了 build、compose 等功能)

弃用原因:
  1. K8s 只需要 CRI 接口,Docker 的额外功能是负担
  2. Docker 需要 dockershim 适配层(维护成本高)
  3. K8s 1.24 移除 dockershim,直接使用 containerd/CRI-O
  
影响:
  - 生产环境:用 containerd + crictl 替代 docker + docker CLI
  - 开发环境:Docker Desktop 仍可用(它内部也用 containerd)
  - 镜像构建:用 BuildKit/Kaniko 替代 docker build