容器运行时深度解析:从runc到containerd到安全容器

深入解析容器运行时技术栈:OCI 规范、runc、containerd、CRI-O、shim 架构、rootfs 与 UnionFS,以及 gVisor 和 Kata Containers 安全容器方案。

Docker 曾被视为容器技术的代名词,但 Kubernetes 1.24 移除 Docker shim 后,containerd 成为主流。理解容器运行时从高层到底层的完整技术栈,是排查容器故障和优化集群性能的基础。


目录


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 (系统调用过滤)                              │
└─────────────────────────────────────────────────────┘
层级代表组件职责
编排接口CRIK8s 定义的标准接口
高层运行时containerd, CRI-O, Docker镜像管理、CRI 服务、容器管理
容器 shimcontainerd-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 的替代方案

运行时特点适用场景
runcOCI 参考实现,Go 编写通用场景
crunC 编写,更快、更轻量启动速度敏感
youkiRust 编写,内存安全安全要求高的新系统
railcarRust 编写实验性

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)大镜像快速启动
sociAWS 延迟拉取ECR 镜像加速
nydusDragonfly 镜像加速阿里云环境

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

特性containerdCRI-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进程级(共享内核)~100ms0%通用场景
gVisor系统调用拦截~200ms10-30%不可信代码
Kata硬件虚拟化~500ms20-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

总结

层级组件核心职责
编排接口CRIK8s 标准化接口
高层运行时containerd / CRI-O镜像管理、CRI 服务
shimcontainerd-shim容器守护、IO 转发
底层运行时runc / crun / youki创建 namespace/cgroup/进程
安全增强gVisor / Kata用户态内核 / 轻量 VM

容器运行时的演进从 Docker 主导的"大而全"走向"小而专"的分层架构。理解每一层的作用和交互方式,是排查容器问题的关键。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「云原生」更多文章

  1. Kubernetes多集群联邦:Karmada、Crossplane与Istio多集群实战
  2. CNCF云原生技术全景图:从毕业项目到前沿方向
  3. GitOps与ArgoCD实践:声明式持续交付