容器运行时深度剖析

概述

容器运行时(Container Runtime)是 Kubernetes 系统中负责管理和运行容器的底层组件。从 Kubernetes 1.24 开始,dockershim 被移除,containerd 和 cri-o 成为主流选择。本文档深入剖析容器运行时的架构、实现原理和关键技术。

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
42
43
44
45
46
47
┌─────────────────────────────────────────────────────────────────────────┐
│ 容器运行时架构图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Kubernetes │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ kubelet │ │kubectl │ │controller│ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │
│ └───────┼─────────────┼─────────────┼───────────────────────────────┘ │
│ │ │ │ │
│ │ gRPC (CRI) │ │
│ └─────────────┼─────────────┘ │
│ │ │
│ ┌─────────────────────┼───────────────────────────────────────────┐ │
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ CRI │ │ │
│ │ │(Container │ │ │
│ │ │Runtime │ │ │
│ │ │Interface) │ │ │
│ │ └─────┬─────┘ │ │
│ │ │ │ │
│ │ ┌────────────────┼────────────────┐ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ ┌──▼───┐ ┌─────▼─────┐ ┌─────▼─────┐ │ │
│ │ │cri- │ │ containerd │ │ cri-o │ │ │
│ │ │dockershim│ │ │ │ │ │ │
│ │ └───────┘ └─────┬─────┘ └─────┬─────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌───────────┐ ┌───────────┐ │ │
│ │ │ OCI │ │ OCI │ │ │
│ │ │ runtime │ │ runtime │ │ │
│ │ │ (runc) │ │ (runc, │ │ │
│ │ └─────┬─────┘ │ crun) │ │ │
│ │ │ └─────┬─────┘ │ │
│ │ ▼ ▼ │ │
│ │ ┌───────────────────────────────────┐ │ │
│ │ │ 操作系统 (Linux Namespace) │ │ │
│ │ │ cgroups, SELinux, AppArmor │ │ │
│ │ └───────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1. CRI 容器运行时接口

1.1 CRI 概述

CRI(Container Runtime Interface)是 Kubernetes 定义的容器运行时接口标准,使 kubelet 可以与不同的容器运行时交互。

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
42
43
44
45
46
47
48
49
50
51
52
53
┌─────────────────────────────────────────────────────────────────────────┐
│ CRI 接口定义 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ RuntimeService │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ // Pod Sandbox 操作 │ │
│ │ RunPodSandbox(config) -> podSandboxID │ │
│ │ StopPodSandbox(podSandboxID) │ │
│ │ RemovePodSandbox(podSandboxID) │ │
│ │ PodSandboxStatus(podSandboxID) -> PodSandboxStatus │ │
│ │ ListPodSandbox(filter) -> []PodSandbox │ │
│ │ │ │
│ │ // 容器操作 │ │
│ │ CreateContainer(config, sandboxConfig) -> containerID │ │
│ │ StartContainer(containerID) │ │
│ │ StopContainer(containerID, timeout) │ │
│ │ RemoveContainer(containerID) │ │
│ │ ListContainers(filter) -> []Container │ │
│ │ ContainerStatus(containerID) -> ContainerStatus │ │
│ │ │ │
│ │ // 容器执行 │ │
│ │ ExecSync(containerID, cmd, timeout) -> ExecSyncResponse │ │
│ │ Exec(containerID, cmd) -> ExecResponse │ │
│ │ Attach(containerID) -> AttachResponse │ │
│ │ PortForward(podSandboxID, port) │ │
│ │ │ │
│ │ // 镜像操作 │ │
│ │ PullImage(image, auth) -> imageRef │ │
│ │ RemoveImage(image) │ │
│ │ ListImages(filter) -> []Image │ │
│ │ ImageStatus(image) -> Image │ │
│ │ │ │
│ │ // 运行时状态 │ │
│ │ Status() -> StatusResponse │ │
│ │ UpdateRuntimeConfig(config) │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ImageService │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ ListImages(filter) -> []Image │ │
│ │ ImageStatus(image) -> Image │ │
│ │ PullImage(image, auth) -> imageRef │ │
│ │ RemoveImage(image) │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1.2 CRI gRPC 定义

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
42
43
// staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto
syntax = "proto3";

package runtime.v1;

service RuntimeService {
// Sandbox 操作
rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse);
rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse);
rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse);
rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse);
rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse);

// 容器操作
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
rpc StopContainer(StopContainerRequest) returns (StopContainerResponse);
rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse);
rpc ListContainers(ListContainersRequest) returns (ListContainersResponse);
rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse);

// 执行操作
rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse);
rpc Exec(ExecRequest) returns (ExecResponse);
rpc Attach(AttachRequest) returns (AttachResponse);
rpc PortForward(PortForwardRequest) returns (PortForwardResponse);

// 镜像操作
rpc PullImage(PullImageRequest) returns (PullImageResponse);
rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse);
rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse);

// 运行时状态
rpc Status(StatusRequest) returns (StatusResponse);
}

service ImageService {
rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse);
rpc PullImage(PullImageRequest) returns (PullImageResponse);
rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse);
}

2. containerd 架构

2.1 containerd 组件

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
42
43
44
45
46
┌─────────────────────────────────────────────────────────────────────────┐
│ containerd 架构图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ containerd │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ API Layer │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ GRPC │ │ Metadata │ │ Content │ │ │ │
│ │ │ │ Server │ │ Store │ │ Store │ │ │ │
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Service Layer │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ Runtime │ │ Image │ │ Snapshot │ │ │ │
│ │ │ │ Service │ │ Service │ │ Service │ │ │ │
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ Task │ │ Diff │ │ GC │ │ │ │
│ │ │ │ Service │ │ Service │ │ Service │ │ │ │
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ OCI Layer │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ runc │ │ crun │ │ gvisor │ │ │ │
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────┼─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 操作系统层 │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Linux │ │ cgroup │ │ Overlay │ │ SELinux │ │ │
│ │ │ Namespace │ │ v2 │ │ fs │ │ │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

2.2 containerd 核心组件

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
// Containerd 组件结构

// 1. Client - 客户端 SDK
type Client struct {
defaultRuntime string
runtimePath string
checkpoint *CheckpointAction
}

// 2. Container - 容器管理
type Container struct {
id string
info meta.ContainerInfo
}

// 3. Task - 运行中的任务
type Task struct {
container *Container
pid uint32
status TaskStatus
}

// 4. Snapshot - 快照管理
type Snapshot struct {
id string
parent string
mounts []Mount
}

2.3 containerd 与 kubelet 交互

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
// kubelet 与 containerd CRI 插件交互
// pkg/kubelet/crichandler/implementation.go

type CRIImageService = runtimeapi.ImageServiceClient
type CRIRuntimeService = runtimeapi.RuntimeServiceClient

func NewCRIClient(endpoint string) (*CRIClient, error) {
// 1. 连接到 containerd CRI 插件
conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
if err != nil {
return nil, err
}

// 2. 创建客户端
return &CRIClient{
runtime: runtimeapi.NewRuntimeServiceClient(conn),
image: runtimeapi.NewImageServiceClient(conn),
}, nil
}

// 创建容器
func (c *CRIClient) CreateContainer(ctx context.Context, config *ContainerConfig) (string, error) {
// 1. 调用 CRI 创建容器
req := &runtimeapi.CreateContainerRequest{
PodSandboxId: sandboxID,
Config: config,
}

resp, err := c.runtime.CreateContainer(ctx, req)
if err != nil {
return "", err
}

return resp.ContainerId, nil
}

3. Pod Sandbox 与容器创建

3.1 Pod Sandbox 创建流程

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
┌─────────────────────────────────────────────────────────────────────────┐
│ Pod Sandbox 创建流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Kubelet containerd OCI Runtime │
│ │ │ │ │
│ │ RunPodSandbox() │ │ │
│ │ ───────────────────►│ │ │
│ │ │ │ │
│ │ │ 1. 创建网络命名空间 │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ │ 2. 调用 CNI 配置网络 │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ │ 3. 创建 rootfs (snapshot)│ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ │ 4. 创建 shim 进程 │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ sandboxID │ │ │
│ │ ◄────────────────── │ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

3.2 容器创建流程

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
┌─────────────────────────────────────────────────────────────────────────┐
│ 容器创建完整流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Kubelet containerd OCI Runtime │
│ │ │ │ │
│ │ CreateContainer() │ │ │
│ │ ───────────────────►│ │ │
│ │ │ │ │
│ │ │ 1. 准备 rootfs │ │
│ │ │ (overlay snapshot) │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ │ 2. 创建容器配置文件 │ │
│ │ │ (config.json) │ │
│ │ │ │ │
│ │ │ 3. 创建容器进程 │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ │ 4. containerd-shim 启动 │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ containerID │ │ │
│ │ ◄────────────────── │ │ │
│ │ │ │ │
│ │ StartContainer() │ │ │
│ │ ───────────────────►│ │ │
│ │ │ │ │
│ │ │ 5. 启动容器进程 │ │
│ │ │ ────────────────────────► │ │
│ │ │ │ │
│ │ 成功 │ │ │
│ │ ◄────────────────── │ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

3.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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// containerd/container create 流程
// github.com/containerd/containerd/services/containers/service.go

func (s *service) Create(ctx context.Context, req *api.CreateContainerRequest) (*api.CreateContainerResponse, error) {
// 1. 创建容器元数据
container := containers.Container{
ID: id,
Runtime: req.Config.Runtime.Name,
Spec: req.Config.Spec,
RootFS: req.Config.Rootfs,
Image: req.Image,
Labels: req.Labels,
}

// 2. 写入元数据存储
if err := s.Store.Create(ctx, container); err != nil {
return nil, err
}

// 3. 创建 snapshot
snapshot, err := s.SnapshotService.Prepare(ctx, id, req.SnapshotKey)
if err != nil {
return nil, err
}

// 4. 准备 rootfs
if err := s.prepareRootfs(ctx, snapshot, container); err != nil {
return nil, err
}

return &api.CreateContainerResponse{ContainerId: id}, nil
}

// containerd/task create 流程
// github.com/containerd/containerd/services/tasks/service.go

func (s *service) Create(ctx context.Context, req *api.CreateTaskRequest) (*api.CreateTaskResponse, error) {
// 1. 创建 shim 客户端
shim, err := s.getShim(ctx, container)
if err != nil {
return nil, err
}

// 2. 创建 task
task, err := s.createTask(ctx, shim, container, req)
if err != nil {
return nil, err
}

// 3. 启动 task
if err := task.Start(ctx); err != nil {
return nil, err
}

return &api.CreateTaskResponse{Pid: task.Pid()}, nil
}

4. OCI 运行时

4.1 OCI 规范

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
┌─────────────────────────────────────────────────────────────────────────┐
│ OCI 运行时规范 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ OCI Runtime Spec │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ { │ │
│ │ "ociVersion": "1.0.2", │ │
│ │ "process": { │ │
│ │ "terminal": true, │ │
│ │ "user": { "uid": 0, "gid": 0 }, │ │
│ │ "args": ["sh"], │ │
│ │ "env": ["PATH=/usr/local/sbin:/usr/local/bin"], │ │
│ │ "cwd": "/" │ │
│ │ }, │ │
│ │ "root": { "path": "rootfs", "readonly": true }, │ │
│ │ "hostname": "runc", │ │
│ │ "mounts": [ │ │
│ │ { "destination": "/proc", "type": "proc", ... }, │ │
│ │ { "destination": "/dev", "type": "tmpfs", ... }, │ │
│ │ ... │ │
│ │ ], │ │
│ │ "linux": { │ │
│ │ "namespaces": [ │ │
│ │ { "type": "pid" }, │ │
│ │ { "type": "network" }, │ │
│ │ { "type": "mount" }, │ │
│ │ { "type": "ipc" }, │ │
│ │ { "type": "uts" }, │ │
│ │ { "type": "user" } │ │
│ │ ], │ │
│ │ "cgroupsPath": "pod-123/container-456", │ │
│ │ "resources": { ... } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

4.2 runc 工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# runc 创建容器的基本步骤

# 1. 创建容器(新进程命名空间)
runc create \
--pid-file /run/containerd/runc/default/redis/redis.pid \
--console-socket /run/containerd/runc/default/redis/pty.sock \
redis

# 2. 启动容器进程
runc start redis

# 3. 列出容器
runc list

# 4. 停止容器
runc kill redis

# 5. 删除容器
runc delete redis

4.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
┌─────────────────────────────────────────────────────────────────────────┐
│ Linux 命名空间隔离 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Host (物理机) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Container A │ │ │
│ │ │ PID: 1001, Network: net-A, Mount: /var/lib/A │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Container B │ │ │
│ │ │ PID: 2001, Network: net-B, Mount: /var/lib/B │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Host (物理机) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Container A │ │ │
│ │ │ UTS: hostname-A, User: uid 1000 │ │ │
│ │ │ IPC: semaphore set A │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Namespace 类型: │
│ - pid: 进程隔离 │
│ - net: 网络隔离 │
│ - ipc: 进程间通信隔离 │
│ - mnt: 挂载点隔离 │
│ - uts: 主机名隔离 │
│ - user: 用户隔离 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

5. 镜像管理

5.1 镜像拉取流程

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
┌─────────────────────────────────────────────────────────────────────────┐
│ 镜像拉取流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Kubelet containerd Registry │
│ │ │ │ │
│ │ PullImage() │ │ │
│ │ ──────────────────►│ │ │
│ │ │ │ │
│ │ │ 1. 检查本地镜像缓存 │ │
│ │ │ │ │
│ │ │ 2. Docker Registry API │ │
│ │ │ 获取 Manifest │ │
│ │ │ ──────────────────────────►│ │
│ │ │ │ │
│ │ │ 3. 拉取 Layer (tar.gz) │ │
│ │ │ ──────────────────────────► │ │
│ │ │ │ │
│ │ │ 4. 拉取 Config (JSON) │ │
│ │ │ ──────────────────────────►│ │
│ │ │ │ │
│ │ │ 5. 解压 Layer 到 snapshot │ │
│ │ │ │ │
│ │ │ 6. 存储到 content store │ │
│ │ │ │ │
│ │ imageRef │ │ │
│ │ ◄─────────────────│ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

5.2 镜像存储结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/var/lib/containerd/
├── io.containerd.content.v1.content/
│ └── blobs/
│ ├── sha256/
│ │ ├── a3ed95caeb... # Image Config
│ │ ├── 9b2a28b8c5... # Layer 1
│ │ ├── 7a044e0dea... # Layer 2
│ │ └── ...

├── io.containerd.metadata.v1.bolt/
│ └── meta.db # 元数据数据库

├── snapshots/
│ ├── overlayfs/
│ │ ├── sha256/ # Active snapshot
│ │ └── ...
│ └── ...

└── runtimes/
└── runc/
└── ...

6. 资源限制

6.1 Cgroup v2 架构

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
┌─────────────────────────────────────────────────────────────────────────┐
│ Cgroup v2 资源控制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ /sys/fs/cgroup/ │
│ └── unified/ │
│ └── kubernetes/ │
│ ├── pod-123/ │
│ │ ├── redis/ <- 容器 cgroup │
│ │ │ ├── cpu.max │
│ │ │ ├── memory.max │
│ │ │ └── pids.max │
│ │ │ │
│ │ └── nginx/ <- 另一个容器 │
│ │ ├── cpu.max │
│ │ ├── memory.max │
│ │ └── pids.max │
│ │ │
│ └── system.slice/ │
│ └── containerd.service/ │
│ │
│ 资源类型: │
│ - cpu: CPU 时间片限制 │
│ - memory: 内存限制 │
│ - io: I/O 限制 │
│ - pids: 进程数限制 │
│ -hugetlb: 大页内存 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

6.2 容器资源配置

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
// Linux 资源配置
type LinuxResources struct {
CPU *LinuxCPUResources // CPU 限制
Memory *LinuxMemoryResources // 内存限制
I/O *LinuxIOResources // I/O 限制
Pids *LinuxPidsResources // 进程数限制
HugepageLimits []HugepageLimit
}

type LinuxCPUResources struct {
Limit *LinuxCPUAmount // CPU limit
Request *LinuxCPUAmount // CPU request
Cpus string // CPU set
Shares *uint64 // CPU shares
}

// 示例配置
{
"cpu": {
"limit": "2", // 2 个 CPU
"request": "1", // 1 个 CPU
"cpus": "0-1" // 使用 CPU 0-1
},
"memory": {
"limit": "512Mi",
"request": "256Mi",
"swap": "512Mi"
}
}

7. 网络管理

7.1 CNI 插件交互

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
┌─────────────────────────────────────────────────────────────────────────┐
│ CNI 网络配置流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ containerd CNI Plugins 主机网络 │
│ │ │ │ │
│ │ 1. 创建 pause 容器 │ │ │
│ │ ──────────────────────►│ │ │
│ │ │ │ │
│ │ │ 2. 调用 CNI ADD │ │
│ │ │ ─────────────────────► │ │
│ │ │ │ │
│ │ │ 3. 创建网络命名空间 │ │
│ │ │ ─────────────────────► │ │
│ │ │ │ │
│ │ │ 4. 配置 veth pair │ │
│ │ │ ─────────────────────► │ │
│ │ │ │ │
│ │ │ 5. 分配 IP 地址 │ │
│ │ │ ─────────────────────► │ │
│ │ │ │ │
│ │ │ 6. 配置 iptables │ │
│ │ │ ─────────────────────► │ │
│ │ │ │ │
│ │ │ 7. 返回结果 │ │
│ │ │ ◄──────────────────── │ │
│ │ │ │ │
│ │ 8. 网络配置完成 │ │ │
│ │ ◄─────────────────────│ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘

7.2 Pause 容器

Pause 容器是 Pod 的基础架构容器:

  • 持有 Pod 的网络命名空间
  • 持有 Pod 的 PID 命名空间(可选)
  • 生命周期与 Pod 一致
1
2
3
4
5
6
7
8
9
// Pause 容器配置
{
"image": "registry.k8s.io/pause:3.9",
"command": ["/pause"],
"hostname": "pod-123",
"network": {
"namespace": "/var/run/netns/pod-123"
}
}

8. 容器生命周期

8.1 容器状态机

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
┌─────────────────────────────────────────────────────────────────────────┐
│ 容器状态机 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Created │
│ │ │
│ │ start │
│ ▼ │
│ ┌────────────┐ Running ◄────────────────────┐ │
│ │ Container │───────────│ │ │
│ │ Created │ │ restart │ │
│ └────────────┘ │ │ │
│ │ │ │
│ ┌──────▼──────┐ │ │
│ │ Stopped │──────────────┘ │
│ │ │ │
│ └─────────────┘ │
│ │ │
│ │ kill (signal) │
│ ▼ │
│ ┌─────────────┐ │
│ │ Exited │ │
│ └─────────────┘ │
│ │ │
│ │ delete │
│ ▼ │
│ ┌─────────────┐ │
│ │ Deleted │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

8.2 优雅终止

1
2
3
4
5
6
7
8
9
10
11
# 容器终止流程

# 1. kubelet 发送 SIGTERM
kill -SIGTERM <pid>

# 2. 容器内应用处理 SIGTERM(默认 30 秒)

# 3. 超时后发送 SIGKILL
kill -SIGKILL <pid>

# 4. containerd-shim 清理资源

9. 关键源码路径

组件 路径
CRI 定义 staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/
Kubelet CRI pkg/kubelet/cri/
containerd github.com/containerd/containerd
runc github.com/opencontainers/runc
CNI github.com/containernetworking/cni

面试题

基础题

1. 什么是 CRI?CRI 的作用是什么?

CRI(Container Runtime Interface)是 Kubernetes 定义的容器运行时接口标准。它允许 kubelet 使用统一的 API 与不同的容器运行时(如 containerd、cri-o)交互,而不需要关心具体实现。

2. Kubernetes 1.24 移除了 dockershim,这意味着什么?

dockershim 是 kubelet 内置的 Docker CRI 实现。移除后:

  • kubelet 不再内置支持 Docker
  • 需要使用 containerd 或 cri-o 作为容器运行时
  • Docker 镜像仍可使用(遵循 OCI 标准)

3. 什么是 OCI?OCI 运行时规范包含哪些内容?

OCI(Open Container Initiative)定义了容器标准和运行时规范:

  • runtime-spec:容器配置(namespaces、cgroups、资源限制)
  • image-spec:镜像格式和打包
  • distribution-spec:镜像分发

runc 是最常用的 OCI 运行时实现。

4. Pod Sandbox 是什么?Pause 容器的作用是什么?

Pod Sandbox 是 Pod 的基础设施环境:

  • 提供网络命名空间(所有容器共享)
  • 提供 PID 命名空间(可选)
  • Pause 容器是维持 Sandbox 生命周期的载体

Pause 容器(/pause)非常轻量,仅用于持有命名空间。

5. containerd 的主要组件有哪些?

  • API Layer:gRPC API 接口
  • Metadata Store:容器元数据存储(boltDB)
  • Content Store:镜像层存储
  • Snapshot Manager:快照管理(overlayfs)
  • Runtime Service:容器运行时服务
  • Task Service:任务管理
  • GC Service:垃圾回收

中级题

6. 描述一下容器创建的完整流程

  1. kubelet 调用 CRI RunPodSandbox 创建网络命名空间
  2. CNI 插件配置 Pod 网络
  3. containerd 创建 snapshot 准备 rootfs
  4. 创建 OCI 配置文件(config.json)
  5. 启动 containerd-shim 进程
  6. shim 调用 runc 创建容器
  7. runc 创建进程命名空间并启动进程
  8. 挂载 overlayfs 作为 rootfs
  9. 应用 cgroup 限制

7. 容器资源限制是如何实现的?

容器资源通过 Linux cgroup 实现:

  • CPU:通过 cpu.cfs_quota_us 和 cpu.cfs_period_us 限制
  • 内存:通过 memory.limit_in_bytes 限制
  • PID:通过 pids.max 限制
  • I/O:通过 blkio 限制

containerd 读取 Pod 的资源请求/限制,转换为 cgroup 配置。

8. 镜像是如何存储和管理的?

containerd 使用 content store 存储镜像:

  • Blobs:以 content-addressable 方式存储 layer 和 config
  • Snapshots:在 rootfs 上叠加 layers
  • Metadata:存储镜像和容器元数据的数据库

拉取镜像时,先获取 manifest,再并行拉取各 layer。

9. containerd-shim 的作用是什么?

containerd-shim 是容器和 containerd 之间的中介:

  • 保持容器运行(即使 containerd 重启)
  • 处理容器 IO(stdin/stdout)
  • 转发信号到容器进程
  • 收集容器退出状态

shim 允许 containerd 长时间运行而不阻塞容器。

10. CNI 插件在容器网络中的作用是什么?

CNI(Container Network Interface)负责:

  • 在容器创建时分配网络命名空间
  • 配置 veth pair 连接容器和主机
  • 分配 IP 地址
  • 配置路由和 iptables 规则
  • 容器删除时清理网络资源

高级题

11. 分析 containerd 的快照管理机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Snapshot 是容器 rootfs 的视图
// 支持多种快照类型:overlay, devmapper, zfs

type Snapshotter interface {
// Prepare - 创建可写快照
Prepare(ctx context, key, parent string) (Mounts, error)

// View - 创建只读快照
View(ctx, key, parent string) (Mounts, error)

// Mounts - 获取挂载点
Mounts(ctx, key string) (Mounts, error)

// Commit - 提交快照
Commit(ctx, name, key string) error
}

// overlayfs 快照示例
// lower: 镜像层 (readonly)
// upper: 容器层 (writable)
// merged: 容器的 rootfs

快照允许多个容器共享镜像层,节省存储空间。

12. 容器如何实现文件系统隔离?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
overlayfs 结构:
/
├── lower/ # 镜像层 (ro)
│ ├── bin/
│ ├── etc/
│ └── ...

├── upper/ # 容器层 (rw)
│ └── etc/
│ └── nginx.conf # 覆盖 lower 的文件

└── merged/ # 容器看到的 rootfs
├── bin/ # 来自 lower
└── etc/
├── passwd # 来自 lower
└── nginx.conf # 来自 upper

多个容器共享底层镜像,每个容器有独立的可写层。

13. 解释容器与宿主机之间的进程关系

1
2
3
4
5
6
7
8
9
# 宿主机看到的进程
systemd(1)-+-containerd-shim -- pause
|-containerd-shim -- nginx
|-containerd-shim -- redis

# 在容器内部看到的进程
1(pause)
/ |
nginx redis <- PID 命名空间隔离,内部都是 PID 1 的子进程

容器内 PID 1 是宿主机上的某个子进程,通过 PID namespace 实现隔离。

场景题

14. 如何排查容器启动失败的问题?

  1. 查看容器状态crictl ps -a
  2. 查看日志crictl logs <container-id>
  3. 检查镜像crictl images
  4. 检查 sandboxcrictl sandboxes
  5. 检查网络crictl inspectp <sandbox-id>
  6. 常见原因
    • 镜像拉取失败
    • 资源限制导致 OOM
    • 健康检查失败
    • 配置错误(端口冲突、路径不存在)

15. 如何优化容器镜像大小?

  1. 使用多阶段构建:分离构建和运行环境
  2. 选择轻量基础镜像:alpine、distroless
  3. 减少层数:合并 RUN 指令
  4. 清理缓存:删除 apt/yum 缓存
  5. 使用 .dockerignore:排除不必要文件
  6. 压缩文件:删除不需要的包

16. 如何实现容器的安全隔离?

  1. Linux 安全模块

    • Seccomp:限制系统调用
    • SELinux/AppArmor:强制访问控制
  2. 用户权限

    • 非 root 用户运行
    • ReadOnlyRootFilesystem
    • Drop Capabilities
  3. 网络隔离

    • NetworkPolicy
    • 禁用 NET_RAW
  4. 资源限制

    • 合理的 CPU/Memory 限制
    • PodSecurityPolicy/Standards