Kubernetes 不直接运行容器。它通过 kubelet 把容器编排的意图翻译成 CRI gRPC 调用,交给 containerd 或 CRI-O 执行。一个 Pod 从 API Server 的 YAML 到真正跑起来的进程,中间要经历 Sandbox 创建、Namespace 共享、Init Container 串行执行、主容器启动、Sidecar 注入——每一步都对应着容器运行时的具体操作。
本章站在 Kubernetes 的视角,看容器运行时如何被编排系统驱动。理解了 CRI 接口和 Pod 创建流程,你就能从 kubelet 日志和 crictl 输出中定位问题,也能理解为什么 Pod 里的容器共享 Network Namespace、pause 容器为什么存在、Init Container 和普通容器到底有什么区别。
Kubernetes 与容器运行时的关系经历了一次重大解耦。Kubernetes 1.5 之前,kubelet 硬编码调用 Docker API——Docker 是唯一支持的运行时。2016 年,Kubernetes 1.5 引入 CRI(Container Runtime Interface),将容器运行时操作抽象为一组 gRPC 接口,kubelet 不再直接调用任何运行时的 API。2018 年,dockershim 被标记为弃用;2020 年,Kubernetes 1.24 正式移除 dockershim——从此 Docker 不再是 Kubernetes 的”默认运行时”,containerd 取而代之。这场解耦的核心思想是:Kubernetes 不关心你用什么容器运行时,只关心你遵循 CRI 接口。理解这段历史,有助于理解为什么 Kubernetes 的容器运行时配置如此灵活——CRI 让 containerd、CRI-O、Kata Containers、gVisor 都可以成为 Kubernetes 的运行时。
前置知识
- Ch07 containerd 架构:containerd 实现了 CRI 插件,是 Kubernetes 的默认运行时
- Ch08 containerd-shim:Pod Sandbox 通过 shim 管理,pause 容器是第一个 shim 创建的进程
- Ch09 容器完整流程:Kubernetes 中的容器创建流程更复杂,理解单容器流程是前提
- Kubernetes 基础:Pod、Deployment、Service 等核心概念
如果你还不熟悉 Kubernetes 基础概念,推荐先阅读 Kubernetes 官方文档的 Pod 概念 章节。
一、Kubernetes 与容器运行时:kubelet 通过 CRI 与 containerd/runc 交互
1.1 Kubernetes 的容器运行时抽象
Kubernetes 从 1.5 版本引入 CRI(Container Runtime Interface),把容器运行时操作抽象为一组 gRPC 接口。kubelet 不再直接调用 Docker 或 containerd 的 API,而是通过 CRI 与运行时通信。这种解耦让 Kubernetes 可以支持多种容器运行时——containerd、CRI-O、Mirantis Container Runtime,甚至 Kata Containers 和 gVisor。
1.2 从 Docker 到 CRI 的演进
Kubernetes 早期直接调用 Docker Engine 的 API。但 Docker 的 API 设计面向单机使用,与 Kubernetes 的 Pod 语义存在根本性冲突——Docker 没有Pod 的概念,一个 Pod 中的多个容器需要共享 Network Namespace,而 Docker 的容器模型是每个容器独立的 Namespace。
| 阶段 | 时间 | 运行时方案 | 问题 |
|---|---|---|---|
| Docker 直接集成 | K8s 1.0 - 1.5 | kubelet → dockerd | Docker API 与 Pod 语义不匹配,每次 Docker 改 API 都要改 kubelet |
| CRI + dockershim | K8s 1.5 - 1.24 | kubelet → dockershim → dockerd | 多一层转换,性能损耗,维护负担 |
| CRI + containerd | K8s 1.24+ | kubelet → containerd | 原生 CRI 支持,性能最优,当前主流 |
| CRI + CRI-O | K8s 1.24+ | kubelet → CRI-O | 专为 K8s 设计的轻量运行时,Red Hat 主推 |
Kubernetes 1.24 正式移除了 dockershim(dockershim Removal)。如果你的集群还在用 Docker Engine 作为运行时,需要迁移到 containerd 或 CRI-O。Docker 构建的镜像仍然可以在任何 CRI 运行时上运行——因为镜像格式遵循 OCI 标准,与运行时无关。
1.3 kubelet 与容器运行时的交互方式
kubelet 通过 Unix Socket 与 CRI 运行时通信。默认的 socket 路径:
/var/run/containerd/containerd.sock
/var/run/crio/crio.sock
kubectl get nodes -o wide
crictl --runtime-endpoint unix:///var/run/containerd/containerd.sock infokubelet 在启动时通过 --container-runtime-endpoint 参数指定 CRI socket。每创建一个 Pod,kubelet 就通过这个 socket 发起一系列 CRI 调用。
二、CRI 接口详解:RuntimeService + ImageService,gRPC 协议
2.1 CRI 的两个服务
CRI 协议定义在 Kubernetes 仓库的 pkg/kubelet/cri/api 目录中,包含两个 gRPC 服务:
// RuntimeService - 容器和 Pod 的生命周期管理service RuntimeService { // Pod 管理 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 ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse); rpc ListContainers(ListContainersRequest) returns (ListContainersResponse); // 其他 rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse); rpc Exec(ExecRequest) returns (ExecResponse); rpc Attach(AttachRequest) returns (AttachResponse); rpc PortForward(PortForwardRequest) returns (PortForwardResponse);}// ImageService - 镜像管理service ImageService { rpc ListImages(ListImagesRequest) returns (ListImagesResponse); rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse); rpc PullImage(PullImageRequest) returns (PullImageResponse); rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse); rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse);}2.2 CRI 关键接口说明
| 接口 | 作用 | 对应的底层操作 |
|---|---|---|
RunPodSandbox | 创建 Pod Sandbox(pause 容器) | 创建 Network Namespace、挂载 /etc/resolv.conf、启动 pause 进程 |
CreateContainer | 在 Sandbox 中创建容器 | 准备 rootfs、生成 OCI Bundle、runc create |
StartContainer | 启动已创建的容器 | runc start |
StopContainer | 停止容器 | 发送 SIGTERM,超时后 SIGKILL |
RemoveContainer | 删除容器 | runc delete、清理 rootfs |
PullImage | 拉取镜像 | 从 Registry 下载镜像层、解压到 Snapshot |
ExecSync | 在容器中执行命令并等待返回 | runc exec |
2.3 CRI 与 containerd 的内部映射
containerd 内部通过 CRI 插件(pkg/cri)实现 CRI 接口。每个 CRI 调用在 containerd 内部的映射关系:
// containerd CRI 插件的核心映射(简化)// RunPodSandbox → 创建 pause 容器func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (*runtime.RunPodSandboxResponse, error) { // 1. 拉取 pause 镜像 image, err := c.ensureImageExists(ctx, sandboxImage, config)
// 2. 创建 containerd container(pause) container, err := c.containerService.Create(ctx, containerdContainer)
// 3. 创建 task(启动容器进程) task, err := container.NewTask(ctx, cio.NewCreator())
// 4. 设置网络(CNI) if config.NetworkConfig.PodCidr != "" { result, err := c.netPlugin.Setup(ctx, sandboxID, netnsPath) }
// 5. 启动 task err = task.Start(ctx)
return &runtime.RunPodSandboxResponse{PodSandboxId: sandboxID}, nil}这段代码展示了 containerd 如何把一个 RunPodSandbox CRI 调用翻译成内部操作:拉镜像 → 创建容器 → 创建 task → 配置网络 → 启动。关于 containerd 的完整架构,参见 Ch07 containerd 架构。
三、Pod 创建流程:Sandbox → Init Container → 主容器 → Sidecar
3.1 Pod 创建的完整时序
当一个 Pod 被调度到节点上,kubelet 通过以下步骤创建它:
3.2 Pod 创建的关键步骤
| 步骤 | 操作 | 失败处理 |
|---|---|---|
| 创建 Sandbox | RunPodSandbox,启动 pause 容器 | 重试,记录事件 |
| 配置网络 | CNI 插件设置 veth pair、路由、iptables | 清理 Sandbox,重试 |
| 运行 Init Container | 按顺序逐个执行,必须成功退出 | Pod 失败,根据 restartPolicy 决定是否重启 |
| 创建主容器 | 在 Sandbox 的 Namespace 中创建 | 重试 |
| 启动主容器 | 并行启动所有主容器(无依赖顺序) | 记录事件,可能触发重启 |
3.3 Pod 创建失败时的行为
spec: restartPolicy: Always # 总是重启(默认)Pod 创建过程中任何阶段失败,kubelet 都会记录事件并按照 restartPolicy 决定是否重试。Init Container 的失败尤其需要注意——它会导致整个 Pod 重启,所有 Init Container 从头执行。
四、Pod Sandbox:基础设施容器(pause),共享 Namespace
4.1 pause 容器的作用
每个 Pod 启动时,CRI 运行时首先创建一个 pause 容器。pause 容器的镜像极小(约 300KB),它的唯一功能是持有 Namespace——创建 Network Namespace、IPC Namespace,然后无限休眠。
// pause 容器的完整源码(kubernetes/build/pause/linux/pause.c)#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>
#define STRINGIFY(x) #x#define VERSION_STRING(x) STRINGIFY(x)
#ifndef VERSION#define VERSION HEAD#endif
static void sigdown(int signo) { psignal(signo, "Shutting down, got signal"); exit(0);}
static void sigreap(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0) ;}
int main(int argc, char **argv) { int i; for (i = 1; i < argc; ++i) { if (!strcmp(argv[i], "-v")) { printf("pause " VERSION_STRING(VERSION) "\n"); return 0; } }
if (getpid() != 1) /* Not in a container, just sleep forever */ while (1) sleep(10000);
if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 1; if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 1; if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0) return 1;
for (;;) pause(); fprintf(stderr, "Error: infinite loop terminated\n"); return 42;}pause 容器做三件事:
- 持有 Namespace:作为 Pod 中所有容器的 Namespace 锚点
- 回收僵尸进程:作为 PID 1,调用
waitpid回收子进程 - 信号处理:响应 SIGINT/SIGTERM 优雅退出
4.2 Namespace 共享机制
Pod 中所有容器共享 pause 容器的 Namespace。这是通过在创建容器时指定 PodSandboxId 实现的——CRI 运行时把 Sandbox 的 Namespace 路径传入 runc create 的配置中:
{ "linux": { "namespaces": [ {"type": "pid"}, {"type": "ipc", "path": "/proc/3456/ns/ipc"}, {"type": "uts", "path": "/proc/3456/ns/uts"}, {"type": "mount"}, {"type": "network", "path": "/proc/3456/ns/net"} ] }}这里 3456 是 pause 容器的 PID。通过 setns() 系统调用,新容器加入 pause 已有的 IPC、UTS、Network Namespace,但拥有独立的 PID 和 Mount Namespace。
| Namespace | Pod 内共享 | 说明 |
|---|---|---|
| Network | 共享 | 所有容器共享同一个 IP 和端口空间 |
| UTS | 共享 | 所有容器共享同一个 hostname |
| IPC | 共享 | 所有容器可以通过 System V IPC 通信 |
| PID | 独立 | 每个容器有独立的进程树(可配置共享) |
| Mount | 独立 | 每个容器有独立的文件系统视图 |
| User | 独立 | 每个容器有独立的用户映射 |
| Cgroup | 独立 | 每个容器有独立的 Cgroup(K8s 1.25+ 可配置共享) |
4.3 为什么需要 pause 容器
假设 Pod 中有容器 A 和容器 B。如果没有 pause 容器,容器 A 崩溃重启后,容器 B 的 Network Namespace 会怎样?
- 没有 pause:容器 A 持有 Network Namespace,A 崩溃后 Namespace 被销毁,容器 B 失去网络
- 有 pause:pause 持有 Network Namespace,A 崩溃重启后加入 pause 的 Namespace,B 不受影响
crictl ps --name POD
ls -la /proc/$(crictl inspect abc123 | jq '.info.pid')/ns/
crictl inspect def456 | jq '.info.pid'# 5678ls -la /proc/5678/ns/netpause 容器是 Pod 稳定性的基石。只要 pause 不退出,Pod 的 Network/IPC/UTS Namespace 就一直存在,业务容器的崩溃重启不会影响其他容器。
五、多容器模式:Sidecar/Ambassador/Adapter 模式
5.1 Sidecar 模式
Sidecar 是 Kubernetes 中最常见的多容器模式。一个辅助容器与主容器共存于同一个 Pod,共享 Network Namespace,通过 localhost 通信。
apiVersion: v1kind: Podmetadata: name: app-with-log-collectorspec: containers: - name: app image: nginx:1.25 volumeMounts: - name: logs mountPath: /var/log/nginx - name: log-collector image: fluent/fluentd:v1.16 volumeMounts: - name: logs mountPath: /var/log/nginx readOnly: true volumes: - name: logs emptyDir: {}Sidecar 的典型应用场景:
| 场景 | 主容器 | Sidecar | 通信方式 |
|---|---|---|---|
| 日志收集 | 业务应用 | Fluentd/Filebeat | 共享 Volume |
| 代理/服务网格 | 业务应用 | Envoy/Istio proxy | localhost 端口 |
| 监控采集 | 业务应用 | Prometheus exporter | HTTP localhost |
| 配置热更新 | 业务应用 | ConfigMap reloader | 信号 + 共享 Volume |
5.2 Ambassador 模式
Ambassador 容器作为 Pod 与外部服务的代理。主容器通过 localhost 访问 Ambassador,Ambassador 负责与外部服务的连接管理、认证、重试。
apiVersion: v1kind: Podmetadata: name: app-with-db-proxyspec: containers: - name: app image: myapp:latest env: - name: DB_HOST value: "127.0.0.1" # 连接 Ambassador - name: DB_PORT value: "3306" - name: db-proxy image: prom/mysqld-exporter:latest5.3 Adapter 模式
Adapter 容器对主容器的输出进行标准化转换。主容器输出自有格式的监控数据,Adapter 将其转换为 Prometheus 可以抓取的格式。
apiVersion: v1kind: Podmetadata: name: app-with-metrics-adapterspec: containers: - name: app image: myapp:latest - name: metrics-adapter image: metrics-adapter:latest5.4 三种模式对比
| 模式 | 辅助容器角色 | 与主容器关系 | 典型场景 |
|---|---|---|---|
| Sidecar | 增强/扩展主容器功能 | 主容器感知 Sidecar 存在 | 日志收集、服务网格、监控 |
| Ambassador | 代理外部服务连接 | 主容器不感知外部服务细节 | 数据库代理、外部 API 代理 |
| Adapter | 标准化主容器输出 | 主容器不感知输出格式要求 | 监控格式转换、日志格式统一 |
三种模式的本质都是利用 Pod 内共享 Network Namespace 的特性——容器之间通过 localhost 通信,零网络开销,无需服务发现。
六、Init Container:初始化顺序、与普通容器的区别
6.1 Init Container 的执行语义
Init Container 在 Pod 的主容器启动之前按顺序执行,每个 Init Container 必须成功退出(exit code 0)后,下一个才会启动。所有 Init Container 执行完毕,主容器才开始并行启动。
apiVersion: v1kind: Podmetadata: name: app-with-initspec: initContainers: - name: wait-for-db image: busybox:1.36 command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting; sleep 2; done'] - name: db-migrate image: myapp:latest command: ['sh', '-c', 'python manage.py migrate'] - name: generate-config image: busybox:1.36 command: ['sh', '-c', 'echo "config from $(hostname)" > /config/app.conf'] volumeMounts: - name: config mountPath: /config containers: - name: app image: myapp:latest command: ['python', 'manage.py', 'runserver'] volumeMounts: - name: config mountPath: /etc/app/config volumes: - name: config emptyDir: {}6.2 Init Container 与普通容器的区别
| 特性 | Init Container | 普通 Container |
|---|---|---|
| 执行方式 | 串行,按顺序逐个执行 | 并行,同时启动 |
| 退出要求 | 必须成功退出(exit 0) | 可以长期运行 |
| 重启行为 | 失败后整个 Pod 重启,所有 Init 重新执行 | 根据重启策略单独重启 |
| 资源请求 | 取所有 Init Container 的最大值 | 各容器独立计算 |
| 探针支持 | 不支持 readiness/liveness 探针 | 支持 |
| 生命周期 | Pod 启动阶段一次性执行 | Pod 运行期间持续运行 |
| Namespace | 共享 Sandbox Namespace | 共享 Sandbox Namespace |
6.3 Init Container 的资源计算
Init Container 的资源请求和限制计算方式与普通容器不同:
# init-1: cpu=500m, memory=256Mi# init-2: cpu=1000m, memory=128Mi# app: cpu=500m, memory=512Mi# sidecar: cpu=200m, memory=128Mi这种计算方式的原因是 Init Container 串行执行,同一时刻只有一个在运行,所以取最大值;而普通容器并行运行,所以求和。调度器使用 Pod 的有效请求来选择节点。
6.4 Sidecar Container(Kubernetes 1.28+)
Kubernetes 1.28 引入了原生 Sidecar Container,它是一种特殊的 Init Container——启动顺序与 Init Container 相同(串行),但不会因为退出而阻塞后续容器:
spec: initContainers: - name: log-collector image: fluent/fluentd:v1.16 restartPolicy: Always # 关键:标记为 Sidecar volumeMounts: - name: logs mountPath: /var/log containers: - name: app image: myapp:latest volumeMounts: - name: logs mountPath: /var/log volumes: - name: logs emptyDir: {}restartPolicy: Always 的 Init Container 就是 Sidecar Container。它会在所有普通容器启动前启动,并在整个 Pod 生命周期内持续运行。如果 Sidecar Container 崩溃,它会被自动重启。
七、容器生命周期:PostStart/PreStop 钩子、终止流程
7.1 容器生命周期钩子
Kubernetes 为容器定义了两个生命周期钩子:
spec: containers: - name: app image: nginx:1.25 lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo 'Container started' > /tmp/started"] preStop: exec: command: ["/bin/sh", "-c", "nginx -s quit; while pgrep nginx; do sleep 1; done"]| 钩子 | 触发时机 | 用途 | 注意事项 |
|---|---|---|---|
postStart | 容器创建后立即执行 | 初始化配置、注册服务 | 与容器 ENTRYPOINT 并行执行,不能保证在 ENTRYPOINT 之前完成 |
preStop | 容器终止前执行 | 优雅关闭、注销服务 | 同步阻塞,执行完毕后才发送 SIGTERM |
7.2 容器终止流程
容器终止的完整时序:
- API Server 收到删除 Pod 请求
- kubelet 触发
preStop钩子(同步阻塞) preStop执行完毕后,kubelet 调用StopContainer- CRI 运行时向容器进程发送
SIGTERM - 等待
terminationGracePeriodSeconds(默认 30 秒) - 超时后发送
SIGKILL强制终止 - 容器退出后,kubelet 调用
RemoveContainer清理
kubectl describe pod my-app# Events:7.3 容器状态机
容器在 Kubernetes 中有三种状态:Waiting(等待启动)、Running(运行中)、Terminated(已终止)。每种状态都有原因和消息字段,可以通过 kubectl describe pod 查看。
八、动手实践
8.1 用 crictl 观察 Pod 创建过程
kubectl apply -f - <<EOFapiVersion: v1kind: Podmetadata: name: demo-podspec: initContainers: - name: init-config image: busybox:1.36 command: ['sh', '-c', 'echo "initialized" > /shared/config'] volumeMounts: - name: shared mountPath: /shared containers: - name: app image: nginx:1.25 volumeMounts: - name: shared mountPath: /usr/share/nginx/html readOnly: true - name: sidecar image: busybox:1.36 command: ['sh', '-c', 'while true; do echo sidecar; sleep 10; done'] volumes: - name: shared emptyDir: {}EOF
crictl pods --name demo-pod
crictl ps -a --pod abc123
PAUSE_PID=$(crictl inspect def456 | jq '.info.pid')ls -la /proc/$PAUSE_PID/ns/
APP_PID=$(crictl inspect jkl012 | jq '.info.pid')readlink /proc/$APP_PID/ns/net
readlink /proc/$APP_PID/ns/mnt8.2 观察 Init Container 的执行顺序
kubectl apply -f - <<EOFapiVersion: v1kind: Podmetadata: name: init-order-demospec: initContainers: - name: step-1 image: busybox:1.36 command: ['sh', '-c', 'echo "Step 1: $(date)" >> /log/init.log; sleep 3'] volumeMounts: - name: log mountPath: /log - name: step-2 image: busybox:1.36 command: ['sh', '-c', 'echo "Step 2: $(date)" >> /log/init.log; sleep 3'] volumeMounts: - name: log mountPath: /log - name: step-3 image: busybox:1.36 command: ['sh', '-c', 'echo "Step 3: $(date)" >> /log/init.log; sleep 3'] volumeMounts: - name: log mountPath: /log containers: - name: app image: busybox:1.36 command: ['sh', '-c', 'cat /log/init.log; sleep 3600'] volumeMounts: - name: log mountPath: /log volumes: - name: log emptyDir: {}EOF
kubectl get pods init-order-demo -w# init-order-demo 0/1 1/1 Running 0
# 查看执行日志kubectl logs init-order-demo app# Step 1: Sat Apr 25 10:00:01 UTC 2026# Step 2: Sat Apr 25 10:00:04 UTC 2026# Step 3: Sat Apr 25 10:00:07 UTC 20268.3 观察容器终止流程
# 创建一个带 preStop 钩子的 Podkubectl apply -f - <<EOFapiVersion: v1kind: Podmetadata: name: graceful-shutdownspec: terminationGracePeriodSeconds: 60 containers: - name: app image: nginx:1.25 lifecycle: preStop: exec: command: ["/bin/sh", "-c", "echo 'Graceful shutdown started' && nginx -s quit && sleep 10 && echo 'Graceful shutdown completed'"]EOF
# 等待 Pod 运行kubectl wait --for=condition=Ready pod/graceful-shutdown
# 删除 Pod 并观察事件kubectl delete pod graceful-shutdown &kubectl get events --field-selector involvedObject.name=graceful-shutdown -w# 你会看到 preStop 钩子执行、SIGTERM 发送、容器退出的完整过程
# 对比:不带 preStop 的容器收到 SIGTERM 后可能直接退出# Nginx 默认处理 SIGTERM 的方式是 worker 进程在处理完当前请求后退出8.4 检查 CRI 运行时信息
# 查看 containerd 版本和 CRI 支持情况crictl info | jq '.config.cni'crictl info | jq '.config.containerd.runtimes'# {# "runc": {# "runtimeType": "io.containerd.runc.v2",# "options": {"BinaryName": "/usr/bin/runc"}# },# "runsc": {# "runtimeType": "io.containerd.runsc.v1",# "options": {"BinaryName": "/usr/bin/runsc"}# }# }
# 查看容器运行时详情kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.containerRuntimeVersion}'# containerd://1.7.8
# 查看容器日志(通过 crictl)CONTAINER_ID=$(crictl ps --name app -q | head -1)crictl logs $CONTAINER_ID
# 在容器中执行命令crictl exec $CONTAINER_ID cat /etc/hostname九、本章小结
上一章理解了Wasm 容器运行时。
本章从 Kubernetes 的视角审视了容器运行时的工作方式,核心要点:
| 主题 | 核心要点 | 关键词 |
|---|---|---|
| CRI 接口 | kubelet 通过 RuntimeService 和 ImageService 两个 gRPC 服务与容器运行时交互,RunPodSandbox、CreateContainer、StartContainer 是三个最关键的调用 | RuntimeService、ImageService、gRPC |
| Pod Sandbox | pause 容器持有 Pod 的 Network/IPC/UTS Namespace,是 Pod 稳定性的基石——只要 pause 不退出,业务容器的崩溃重启不影响其他容器 | pause 容器、Namespace 锚点、僵尸回收 |
| Pod 创建流程 | Sandbox → Init Container(串行) → 主容器(并行),每个阶段失败有不同的处理策略 | RunPodSandbox、串行/并行、restartPolicy |
| 多容器模式 | Sidecar、Ambassador、Adapter 三种模式都利用 Pod 内共享 Network Namespace 的特性,通过 localhost 通信 | Sidecar、Ambassador、Adapter、localhost |
| Init Container | 串行执行、必须成功退出、失败后整个 Pod 重启;Kubernetes 1.28+ 的原生 Sidecar Container 通过 restartPolicy: Always 标记 | 串行、exit 0、Sidecar Container |
| 容器生命周期 | postStart/preStop 钩子、SIGTERM/SIGKILL 终止流程、terminationGracePeriodSeconds 宽限期 | postStart、preStop、SIGTERM、宽限期 |
Pod 中共享 Network Namespace 意味着端口冲突是真实风险——两个容器不能监听同一个端口。在设计多容器 Pod 时,务必规划好端口分配。此外,共享 IPC Namespace 可能导致 System V IPC 资源泄漏,建议只在明确需要时启用 shareProcessNamespace。
这些知识将前几章的容器运行时原理与 Kubernetes 的编排实践连接起来。Ch09 容器完整流程追踪了 docker run 的完整调用链,本章则追踪了 Pod 创建的完整调用链——两者在底层都是 runc 创建 Namespace、配置 Cgroup、挂载 OverlayFS,区别在于 Kubernetes 通过 CRI 和 Sandbox 机制增加了 Pod 级别的编排语义。Ch07 containerd 架构介绍了 containerd 的内部设计,本章展示了 kubelet 如何通过 CRI 驱动 containerd。Ch10 容器安全中的 seccomp/AppArmor/Capabilities 同样适用于 Kubernetes 中的容器——Pod 的 securityContext 就是这些安全机制的 K8s 抽象。
参考
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






