Docker 曾被视为容器技术的代名词,但 Kubernetes 1.24 移除 Docker shim 后,containerd 成为主流。理解容器运行时从高层到底层的完整技术栈,是排查容器故障和优化集群性能的基础。
目录
- 1. 容器运行时技术栈概览
- 2. OCI 开放容器标准
- 3. runc:底层容器运行时
- 4. containerd:工业级容器引擎
- 5. CRI-O:为 K8s 而生的运行时
- 6. Containerd vs Docker
- 7. 安全容器
- 8. 运行时问题排查
1. 容器运行时技术栈概览
容器运行时分层模型:
┌─────────────────────────────────────────────────────┐
│ Kubernetes → CRI (gRPC) │ ← 容器编排层
├─────────────────────────────────────────────────────┤
│ containerd / CRI-O / Docker Engine │ ← 高层运行时 (Container Runtime)
│ - 镜像管理 │
│ - gRPC CRI 服务 │
│ - 容器生命周期管理 │
├─────────────────────────────────────────────────────┤
│ containerd-shim / conmon │ ← 容器 shim(为每个容器守护)
│ - 保持容器 STDIO 打开 │
│ - 转发信号 │
│ - 上报退出状态 │
├─────────────────────────────────────────────────────┤
│ runc / crun / youki │ ← 底层运行时 (OCI Runtime)
│ - 创建 Linux namespace │
│ - 配置 cgroups │
│ - 挂载 rootfs │
│ - 启动容器进程 │
├─────────────────────────────────────────────────────┤
│ Linux Kernel │ ← 内核层
│ - namespaces (隔离) │
│ - cgroups (资源限制) │
│ - capabilities (权限控制) │
│ - seccomp (系统调用过滤) │
└─────────────────────────────────────────────────────┘
| 层级 | 代表组件 | 职责 |
|---|---|---|
| 编排接口 | CRI | K8s 定义的标准接口 |
| 高层运行时 | containerd, CRI-O, Docker | 镜像管理、CRI 服务、容器管理 |
| 容器 shim | containerd-shim, conmon | 容器进程守护、IO 转发 |
| 底层运行时 | runc, crun, youki | 调用内核创建容器 |
| 内核 | Linux Kernel | 提供 namespace/cgroup/capability |
2. OCI 开放容器标准
OCI(Open Container Initiative)定义了容器的两个核心规范:
runtime-spec
定义容器运行时的标准接口,确保符合规范的运行时能够一致地创建和运行容器。
// config.json(OCI 运行时配置)
{
"ociVersion": "1.1.0",
"process": {
"terminal": false,
"user": {"uid": 0, "gid": 0},
"args": ["sh"],
"env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
"cwd": "/"
},
"root": {
"path": "rootfs",
"readonly": false
},
"hostname": "runc",
"linux": {
"namespaces": [
{"type": "pid"},
{"type": "network"},
{"type": "ipc"},
{"type": "uts"},
{"type": "mount"},
{"type": "cgroup"}
],
"cgroupsPath": "/kubepods/besteffort/pod-xxxxx/yyyyy",
"resources": {
"cpu": {"shares": 1024},
"memory": {"limit": 536870912}
},
"capabilities": {
"bounding": ["CAP_CHOWN", "CAP_DAC_OVERRIDE"],
"effective": ["CAP_CHOWN", "CAP_DAC_OVERRIDE"],
"permitted": ["CAP_CHOWN", "CAP_DAC_OVERRIDE"]
}
}
}
image-spec
定义容器镜像的格式,包括镜像层、配置、manifest:
镜像结构:
manifest.json
├── config.json # 镜像配置(入口点、环境变量、层列表)
├── layer-1.tar.gz # 底层(如 Ubuntu 基础系统)
├── layer-2.tar.gz # 中间层(如安装依赖)
└── layer-3.tar.gz # 顶层(如应用代码)
3. runc:底层容器运行时
runc 是 Docker 贡献给 OCI 的参考实现,直接操作 Linux 内核创建容器。现已独立为 containerd 的默认底层运行时。
手动使用 runc
# 1. 创建 rootfs
mkdir -p mycontainer/rootfs
# 使用 Docker 导出 busybox 的 rootfs
docker export $(docker create busybox) | tar -C mycontainer/rootfs -xvf -
# 2. 生成 OCI 配置
runc spec # 生成 config.json
# 3. 运行容器
runc run mycontainer
# 4. 管理容器
runc list
runc kill mycontainer SIGTERM
runc delete mycontainer
runc 的替代方案
| 运行时 | 特点 | 适用场景 |
|---|---|---|
| runc | OCI 参考实现,Go 编写 | 通用场景 |
| crun | C 编写,更快、更轻量 | 启动速度敏感 |
| youki | Rust 编写,内存安全 | 安全要求高的新系统 |
| railcar | Rust 编写 | 实验性 |
4. containerd:工业级容器引擎
containerd 最初是 Docker 的组件,现为 CNCF 毕业项目,是 Kubernetes 的默认容器运行时。
架构
containerd
├── API Layer
│ ├── gRPC (CRI for K8s)
│ └── Containerd API (ctr, nerdctl)
├── Core Services
│ ├── Metadata Service(metadata 存储)
│ ├── Content Service(镜像内容寻址)
│ ├── Snapshot Service(rootfs 快照)
│ ├── Diff Service(层间差异)
│ ├── Images Service(镜像管理)
│ ├── Containers Service(容器元数据)
│ └── Tasks Service(容器进程)
├── Runtime
│ └── containerd-shim → runc
└── Plugins
├── CRI Plugin
├── Snapshotters(overlayfs, zfs, btrfs)
└── Content Store
常用命令
# 查看容器(namespace: default 对应 k8s.io)
ctr -n k8s.io containers list
# 查看镜像
ctr -n k8s.io images list
# 查看任务(运行中的容器)
ctr -n k8s.io tasks list
# 查看快照
ctr -n k8s.io snapshots list
# nerdctl(Docker CLI 风格)
nerdctl -n k8s.io ps
nerdctl -n k8s.io images
nerdctl -n k8s.io logs <container>
Snapshotter
containerd 1.4+ 引入了可插拔的 Snapshotter 架构:
| Snapshotter | 原理 | 适用场景 |
|---|---|---|
| overlayfs | 默认,联合挂载 | 通用场景 |
| stargz | 延迟拉取(基于 eStargz) | 大镜像快速启动 |
| soci | AWS 延迟拉取 | ECR 镜像加速 |
| nydus | Dragonfly 镜像加速 | 阿里云环境 |
stargz 延迟拉取原理:
传统方式:拉取完整镜像(1GB)→ 启动容器(2分钟)
stargz: 拉取索引(1MB)→ 启动容器(5秒)→ 按需拉取层
5. CRI-O:为 K8s 而生的运行时
CRI-O 是一个轻量级容器运行时,专为 Kubernetes CRI 设计。
设计哲学
- 只做一件事:运行容器,不做镜像构建(由 Buildah/BuildKit 负责)
- 兼容 OCI:任何符合 OCI 的运行时(runc, crun, kata)都可以插入
- 轻量:比 containerd 更少的组件和依赖
containerd vs CRI-O
| 特性 | containerd | CRI-O |
|---|---|---|
| 维护方 | Docker/Containerd 社区 | Red Hat/OpenShift |
| 镜像构建 | 支持(nerdctl build) | 不支持(用 Buildah) |
| 镜像仓库 | 支持 | 不直接支持 |
| 默认发行版 | 通用 | OpenShift、RHEL |
| 资源占用 | 略高 | 更轻量 |
6. Containerd vs Docker
Docker 包含 containerd:Docker Engine 的上层组件(dockerd)通过 containerd 来实际管理容器。关系如下图所示:
Docker CLI → dockerd (Docker Daemon)
↓
containerd (container runtime)
↓
containerd-shim → runc
K8s 弃用 Docker:
- K8s 1.20 废弃 dockershim
- K8s 1.24 移除 dockershim
- 原因:dockershim 是 K8s 维护的 Docker 兼容层,增加了复杂度和维护负担
- 替代:直接使用 containerd(通过 CRI)
对开发者的影响:
- Docker Desktop 仍然可用,不受影响
- 服务器上 docker 命令可用,但 K8s 内部使用 containerd
- 使用
nerdctl替代docker命令操作 containerd
7. 安全容器
标准容器共享宿主机内核,攻击者可能通过内核漏洞逃逸。安全容器提供更强的隔离。
gVisor
Google 开源的用户态内核,用 Go 编写的 Sentry 进程拦截系统调用:
┌─────────────────────────────────────┐
│ Application │
└─────────────┬───────────────────────┘
│ syscall
┌─────────────▼───────────────────────┐
│ gVisor Sentry (User-space Kernel) │
│ - 实现大部分 Linux 系统调用 │
│ - 只有少量安全调用透传到 Host │
└─────────────┬───────────────────────┘
│ restricted syscall
┌─────────────▼───────────────────────┐
│ Host Kernel │
└─────────────────────────────────────┘
gVisor 运行模式:
| 模式 | 说明 | 性能 |
|---|---|---|
kvm | 使用 KVM 虚拟化,最强隔离 | 中等 |
ptrace | 用 ptrace 拦截 syscall | 较差 |
systrap | 混合模式,性能更好(默认) | 较好 |
配置:
runtimeClassName: gvisor # 在 Pod 中使用 gVisor
Kata Containers
Intel 开源的轻量级 VM 方案,每个容器运行在独立微型虚拟机中:
Host
├── QEMU/Kata (微型 VM)
│ ├── Guest Kernel
│ └── Container
│
├── QEMU/Kata (微型 VM)
│ ├── Guest Kernel
│ └── Container
特点:
- 每个容器有独立内核,隔离性接近 VM
- 启动时间 < 100ms(优化后)
- 支持多种 hypervisor:QEMU、Cloud Hypervisor、Firecracker
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
# 用法
spec:
runtimeClassName: kata
安全容器对比
| 方案 | 隔离级别 | 启动时间 | 性能损耗 | 适用场景 |
|---|---|---|---|---|
| runc | 进程级(共享内核) | ~100ms | 0% | 通用场景 |
| gVisor | 系统调用拦截 | ~200ms | 10-30% | 不可信代码 |
| Kata | 硬件虚拟化 | ~500ms | 20-40% | 强隔离需求 |
8. 运行时问题排查
容器无法启动
# 1. 查看 containerd 日志
journalctl -u containerd -f
# 2. 查看 Kubelet 日志
journalctl -u kubelet -f | grep -i "container"
# 3. 检查 CNI 插件
ls /opt/cni/bin/
cat /etc/cni/net.d/*.conflist
# 4. 检查 runc 状态
runc list --root /run/containerd/runc/k8s.io
常用诊断命令
# 查看容器内部进程
crictl ps
crictl inspect <container-id>
crictl exec -it <container-id> /bin/sh
# 查看镜像层
ctr -n k8s.io images mount docker.io/library/nginx:latest /mnt/nginx
ls /mnt/nginx
# 查看 cgroup 配置
cat /sys/fs/cgroup/kubepods/.../memory.max
cat /sys/fs/cgroup/kubepods/.../cpu.max
总结
| 层级 | 组件 | 核心职责 |
|---|---|---|
| 编排接口 | CRI | K8s 标准化接口 |
| 高层运行时 | containerd / CRI-O | 镜像管理、CRI 服务 |
| shim | containerd-shim | 容器守护、IO 转发 |
| 底层运行时 | runc / crun / youki | 创建 namespace/cgroup/进程 |
| 安全增强 | gVisor / Kata | 用户态内核 / 轻量 VM |
容器运行时的演进从 Docker 主导的"大而全"走向"小而专"的分层架构。理解每一层的作用和交互方式,是排查容器问题的关键。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。