深入解析Containerd:云原生标准容器运行时的架构、原理与实战

发布时间:2026/8/12 19:24:22
深入解析Containerd:云原生标准容器运行时的架构、原理与实战 1. 容器运行时演进与Containerd的定位如果你在过去几年里接触过Docker或者Kubernetes那么“容器运行时”这个词对你来说一定不陌生。但你是否曾好奇过当你执行docker run或kubelet拉起一个Pod时幕后到底是谁在真正地创建、运行和销毁那个隔离的进程环境很长一段时间里Docker Engine几乎成了容器的代名词它大包大揽从镜像构建、网络管理到容器生命周期管理无所不能。然而在云原生架构追求模块化、轻量化和标准化的浪潮下这种“单体巨人”的设计开始显得笨重。于是一个更专注、更纯粹的核心组件被剥离出来它就是containerd。简单来说containerd是一个行业标准的容器运行时。它的核心职责非常明确在宿主机上管理容器的完整生命周期——从镜像的拉取、解压、存储到容器的创建、启动、停止、删除以及底层与操作系统内核的交互如通过runc调用Linux命名空间和cgroups。你可以把它想象成汽车引擎中的“曲轴连杆机构”它不负责方向盘、刹车或空调那些是Docker或Kubernetes的上层功能但它却是将燃料镜像转化为动力运行中的容器最核心、最不可或缺的机械部件。containerd的诞生并非偶然它是容器技术栈垂直解耦的必然产物。Docker公司将其核心运行时能力捐赠给了云原生计算基金会CNCF使其成为一个中立、开放的项目。如今它不仅是Docker Engine的默认底层运行时自Docker 1.11起更是Kubernetes默认的容器运行时接口CRI实现之一。这意味着当你使用最新的K8s集群时有很大概率你的Pod正通过kubelet调用containerd在安静地工作。它的崛起标志着容器基础设施进入了专业化、模块化的新时代开发者可以更灵活地选择或组合不同的组件来构建自己的容器平台。2. Containerd的核心架构与组件解析要理解containerd为何高效且稳定必须深入其内部架构。它采用客户端-服务器模型通过gRPC API对外提供所有服务这种设计带来了清晰的边界和良好的可维护性。其核心架构主要围绕以下几个关键组件展开它们协同工作共同完成了容器管理的繁重任务。2.1 客户端与守护进程gRPC通信桥梁containerd的主体是一个常驻后台的守护进程containerd。所有对容器的操作指令无论是来自Docker CLI、ctrcontainerd自带的简易客户端还是Kubernetes的kubelet最终都会转化为gRPC请求发送给这个守护进程。gRPC基于HTTP/2和Protocol Buffers提供了高性能、跨语言的远程过程调用能力。这意味着你可以用Go、Python、Java等多种语言编写客户端来管理容器极大地扩展了生态。守护进程在启动时会创建一个唯一的、基于Unix Domain Socket的监听端点默认位于/run/containerd/containerd.sock。这种进程间通信方式比网络TCP更高效、更安全。所有客户端都必须通过这个socket与守护进程交互守护进程会验证客户端的权限确保只有授权用户或进程如root用户或属于docker组的用户才能执行敏感操作。注意在生产环境中对这个socket文件的权限管理至关重要。不当的权限设置可能导致容器逃逸或特权提升风险。务必遵循最小权限原则。2.2 存储与快照镜像与容器文件系统的基石镜像和容器的文件系统管理是containerd的核心能力之一主要由存储驱动和快照驱动协作完成。当您执行ctr image pull docker.io/library/nginx:latest时containerd会从镜像仓库拉取一个镜像的清单manifest和一系列分层的数据块blobs。这些数据块被存储在**内容可寻址存储Content-Addressable Storage, CAS**中。CAS的核心思想是每个数据块都通过其内容的加密哈希通常是SHA256来寻址。这意味着相同内容的层在系统中只会存储一份极大地节省了空间。拉取的镜像层存储在/var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/目录下文件名就是其哈希值。镜像拉取后需要为其准备一个可供容器运行的根文件系统。这就是快照驱动的工作。containerd支持多种快照驱动如overlayfs、btrfs、zfs等最常用的是overlayfs。快照驱动会基于镜像的各个只读层创建一个可写的顶层即“容器层”。这个过程非常轻量快速因为它利用了联合文件系统的特性无需复制整个镜像数据。例如当你基于同一个nginx镜像启动10个容器时磁盘上只有一份nginx的只读层数据10个容器各自拥有一个微小的可写层。这种设计是容器高密度部署的基石。你可以在/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/目录下看到这些快照的元数据。2.3 运行时与执行器容器进程的诞生地当文件系统准备就绪真正的“运行”环节才开始。运行时Runtime组件负责根据容器配置OCI Spec创建隔离的运行环境。containerd自身不直接与内核交互而是通过一个名为containerd-shim的轻量级进程来管理容器生命周期。这是一个经典的工作流程containerd守护进程收到“创建容器”的请求。containerd会调用一个符合OCIOpen Container Initiative标准的低级运行时来实际创建容器。最常用、也是默认的低级运行时是runc。runc是一个命令行工具它根据OCI运行时规范一个JSON配置文件调用Linux内核的命名空间namespace、控制组cgroup、能力capabilities等机制创建出一个隔离的容器进程。一旦runc创建了容器进程它就会退出。为了不让容器进程变成孤儿进程并持续管理它如转发信号、收集退出状态containerd会为每个容器启动一个containerd-shim进程。这个shim进程成为容器进程在容器内PID为1的父进程。containerd-shim负责保持容器内进程的stdio标准输入、输出、错误流与外界如containerd的连通并将容器的退出状态报告回containerd。同时因为shim的存在即使containerd守护进程重启容器也不会被终止这提供了更好的健壮性。这种“守护进程 - shim - 容器进程”的间接模型是实现容器热升级、live migration等高级功能的基础也使得containerd本身可以保持稳定而不会因为某个容器的崩溃而受影响。2.4 事件与监控洞察系统状态的窗口一个生产级的运行时必须提供可观性。containerd内置了一个强大的事件系统。所有重要的操作如镜像拉取、容器创建、启动、暂停、删除、任务执行等都会产生相应的事件。客户端可以通过订阅事件流实时获取集群内容器状态的变化。这对于构建自动化运维平台、监控告警系统至关重要。例如你可以编写一个简单的Go程序订阅containerd的事件一旦监听到容器异常退出exit事件就立即触发日志收集、重启或通知操作。事件系统通过gRPC流式接口提供确保了信息的实时性和低延迟。3. 从零开始Containerd的安装与基础配置了解了架构我们动手将其部署起来。这里以最常见的Linux发行版Ubuntu 22.04 LTS为例演示如何安装和配置containerd。3.1 系统准备与依赖安装首先确保系统已更新并安装一些基础工具和依赖。sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-releasecontainerd需要Linux内核支持一些关键特性。运行以下命令检查确保输出均为“enabled”。cat /proc/sys/net/bridge/bridge-nf-call-iptables # 应返回 1 cat /proc/sys/net/ipv4/ip_forward # 应返回 1 lsmod | grep overlay # 应能看到 overlay 模块 lsmod | grep br_netfilter # 最好能看到 br_netfilter 模块如果ip_forward不是1请执行sudo sysctl -w net.ipv4.ip_forward1并写入/etc/sysctl.conf。3.2 安装Containerd有多种方式安装containerd推荐使用官方发布的二进制包或通过操作系统的包管理器。方法一使用官方二进制包推荐版本最新访问 containerd的GitHub Release页面 下载对应系统架构的压缩包如containerd-version-linux-amd64.tar.gz。# 以1.7.0版本为例 VERSION1.7.0 wget https://github.com/containerd/containerd/releases/download/v${VERSION}/containerd-${VERSION}-linux-amd64.tar.gz # 解压到系统目录 sudo tar Cxzvf /usr/local containerd-${VERSION}-linux-amd64.tar.gz解压后主要的二进制文件containerd和ctr就在/usr/local/bin/下了。方法二通过APT仓库安装版本可能稍旧但管理方便# 添加Docker的官方GPG密钥和仓库其中包含了containerd sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt-get update sudo apt-get install -y containerd.io3.3 生成与调整默认配置文件安装完成后我们需要一个配置文件。containerd提供了一个生成默认配置的命令sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml这个默认配置在大多数情况下可以工作但有几个关键点我们通常需要调整。修改Cgroup驱动为systemd为了与系统更好地集成尤其是使用systemd作为init系统的发行版建议将cgroup驱动从cgroupfs改为systemd。sudo sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml打开配置文件你会在[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options]部分看到这个配置项。配置镜像仓库镜像可选但重要在国内环境拉取Docker Hub镜像可能很慢。我们可以配置镜像加速器。在配置文件中找到[plugins.io.containerd.grpc.v1.cri.registry.mirrors]部分添加如下配置以阿里云镜像加速器为例[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://your-mirror-id.mirror.aliyuncs.com]请将your-mirror-id替换为你从阿里云容器镜像服务获取的实际地址。3.4 启动Containerd服务并验证配置完成后使用systemd来管理containerd服务。# 创建systemd服务单元文件如果二进制安装方式 sudo tee /etc/systemd/system/containerd.service EOF [Unit] Descriptioncontainerd container runtime Documentationhttps://containerd.io Afternetwork.target local-fs.target [Service] ExecStartPre-/sbin/modprobe overlay ExecStart/usr/local/bin/containerd Typenotify Delegateyes KillModeprocess Restartalways RestartSec5 LimitNPROCinfinity LimitCOREinfinity LimitNOFILEinfinity TasksMaxinfinity OOMScoreAdjust-999 [Install] WantedBymulti-user.target EOF # 如果是通过apt安装服务文件通常已自动创建。 # 重新加载systemd配置启动并设置开机自启 sudo systemctl daemon-reload sudo systemctl enable --now containerd sudo systemctl status containerd如果状态显示为active (running)说明服务已成功启动。最后使用自带的ctr客户端进行快速验证sudo ctr version这个命令会输出客户端和服务器守护进程的版本信息确认两者可以正常通信。4. 实战操作通过ctr与nerdctl管理容器containerd提供了两个主要的命令行工具ctr和nerdctl。ctr是containerd原生自带的调试工具功能强大但命令相对底层对用户不太友好。nerdctl则是一个旨在兼容Docker CLI语法和用户体验的第三方工具对于从Docker迁移过来的用户来说几乎零学习成本。4.1 使用ctr进行基础容器操作ctr命令直接对应containerd的gRPC API其命名空间和资源管理概念非常清晰。镜像管理# 拉取一个镜像需要指定完整仓库地址 sudo ctr image pull docker.io/library/nginx:latest # 列出本地镜像 sudo ctr image ls # 给镜像打标签 sudo ctr image tag docker.io/library/nginx:latest myregistry.local:5000/nginx:v1 # 推送镜像到私有仓库需先登录 sudo ctr image push myregistry.local:5000/nginx:v1 --user username:password # 删除镜像 sudo ctr image rm docker.io/library/nginx:latest容器与任务管理在containerd中“容器”是一个静态的配置对象而“任务”才是这个配置的运行实例。# 1. 创建一个容器此时并未运行 sudo ctr container create docker.io/library/nginx:latest nginx-container # 2. 列出容器 sudo ctr container ls # 3. 启动容器创建一个任务 sudo ctr task start -d nginx-container # -d 表示后台运行 # 4. 列出正在运行的任务 sudo ctr task ls # 5. 进入容器执行命令类似于 docker exec sudo ctr task exec -t --exec-id myexec nginx-container sh # 6. 暂停和恢复任务 sudo ctr task pause nginx-container sudo ctr task resume nginx-container # 7. 停止任务 sudo ctr task kill -s SIGTERM nginx-container # 8. 删除任务和容器 sudo ctr task rm nginx-container sudo ctr container rm nginx-container实操心得ctr命令的命名空间-n参数是一个重要概念。默认使用default命名空间。Kubernetes的CRI插件会使用k8s.io命名空间。这意味着通过ctr -n k8s.io container ls可以看到K8s创建的所有容器而不会与default命名空间下的容器混淆。这种隔离在设计多租户系统时非常有用。4.2 使用nerdctl提升操作体验nerdctl的安装很简单从其GitHub Release页面下载对应二进制文件即可。它的命令与docker几乎完全一致。# 安装nerdctl示例 wget https://github.com/containerd/nerdctl/releases/download/v1.0.0/nerdctl-1.0.0-linux-amd64.tar.gz sudo tar Cxzvvf /usr/local/bin nerdctl-1.0.0-linux-amd64.tar.gz # 使用nerdctl命令和docker一样 sudo nerdctl pull nginx:latest sudo nerdctl run -d -p 80:80 --name my-nginx nginx:latest sudo nerdctl ps sudo nerdctl exec -it my-nginx bash sudo nerdctl logs my-nginx sudo nerdctl stop my-nginx sudo nerdctl rm my-nginxnerdctl还支持Docker Compose通过nerdctl compose子命令这对于在纯containerd环境中管理多容器应用栈是一个巨大的便利。它底层调用containerd但提供了开发者熟悉的接口是个人开发测试或中小型部署的绝佳选择。4.3 网络与存储配置初探默认情况下通过ctr创建的容器只有loopback网络没有外网连接。containerd本身不管理网络网络功能通常由CNIContainer Network Interface插件提供。配置CNI网络安装CNI插件二进制文件到/opt/cni/bin/。编写CNI配置文件到/etc/cni/net.d/。一个最简单的桥接网络配置10-bridge.conf可能如下{ cniVersion: 0.4.0, name: mynet, type: bridge, bridge: cni0, isGateway: true, ipMasq: true, ipam: { type: host-local, subnet: 10.22.0.0/16, routes: [ { dst: 0.0.0.0/0 } ] } }使用nerdctl创建容器时可以通过--network mynet指定使用该网络。ctr创建容器时则需要更复杂的配置通常结合nerdctl或Kubernetes使用更方便。配置持久化存储容器内数据是易失的。要实现持久化需要将宿主机目录挂载到容器内。在ctr中这通过在创建容器时指定--mount参数实现sudo ctr container create \ --mount typebind,src/host/data,dst/container/data,optionsrbind:rw \ docker.io/library/nginx:latest \ nginx-with-data在nerdctl中则和Docker一样使用-v参数-v /host/data:/container/data:rw。5. Containerd与Kubernetes集成CRI插件详解Kubernetes不直接与containerd交互而是通过一个标准的接口——容器运行时接口Container Runtime Interface, CRI。containerd内置了一个名为cri的插件它实现了CRI服务使得kubelet可以像与Docker的dockershim通信一样与containerd通信。5.1 CRI插件的工作原理当kubelet需要创建一个Pod时kubelet通过gRPC调用containerd的CRI插件服务。CRI插件首先拉取所需的镜像如果本地不存在。然后它调用containerd的核心服务来创建容器。这里有一个关键点CRI插件会为Kubernetes创建的所有资源镜像、容器都放在一个独立的命名空间——k8s.io中。这实现了与系统其他容器如通过ctr手动创建的的逻辑隔离。最后通过containerd-shim和runc启动容器进程。你可以通过查看containerd的配置文件来确认CRI插件已启用。在默认配置中disabled_plugins列表里不应该有cri。同时[plugins.io.containerd.grpc.v1.cri]部分包含了丰富的配置项如沙箱Pause镜像地址、CNI配置路径、注册表镜像等。5.2 配置Kubelet使用Containerd在Kubernetes节点上需要配置kubelet使用containerd作为运行时。这主要通过kubelet的启动参数--container-runtime-endpoint来实现。通常在通过kubeadm初始化或加入集群时可以传递以下配置# 在kubeadm init或join时使用配置文件 cat /etc/kubernetes/containerd-config.yaml EOF apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd containerRuntimeEndpoint: unix:///run/containerd/containerd.sock EOF # 然后使用这个配置文件初始化集群 sudo kubeadm init --config /etc/kubernetes/containerd-config.yaml对于已经存在的节点需要修改kubelet服务参数通常在/etc/systemd/system/kubelet.service.d/10-kubeadm.confEnvironmentKUBELET_EXTRA_ARGS--container-runtimeremote --container-runtime-endpointunix:///run/containerd/containerd.sock修改后重启kubelet服务sudo systemctl daemon-reload sudo systemctl restart kubelet。5.3 验证与问题排查配置完成后如何验证Kubernetes确实在使用containerd查看节点信息kubectl describe node node-name | grep Container\ Runtime输出应显示Container Runtime Version: containerd://version。在节点上查看容器# 查看所有由Kubernetes管理的容器 sudo ctr -n k8s.io container ls # 查看所有由Kubernetes管理的任务运行中的容器 sudo ctr -n k8s.io task ls你会看到很多名字类似k8s://namespace/pod-name/container-name的容器其中有一个特殊的pause容器它是每个Pod的“基础设施容器”用于持有Pod的网络命名空间。查看容器日志 如果某个Pod出现问题除了使用kubectl logs你也可以直接通过containerd查看其日志。首先找到容器的IDsudo ctr -n k8s.io container ls | grep pod-name然后查看该容器的任务进程日志。containerd的日志默认会重定向到系统的journald如果使用systemd或指定的日志文件。你可以通过journalctl -u containerd查看守护进程日志。单个容器的stdout/stderr日志Kubernetes CRI插件通常会将其重定向到/var/log/pods/和/var/log/containers/目录下的文件中供kubectl logs读取。6. 生产环境运维监控、调优与故障排查将containerd用于生产环境除了基本功能更需要关注其稳定性、性能和可观测性。6.1 关键指标监控containerd暴露了Prometheus格式的指标这对于监控其健康状态至关重要。默认情况下指标端点可能未启用。需要在配置文件/etc/containerd/config.toml中启用[metrics] address 0.0.0.0:1338 # 设置一个监听地址和端口重启containerd后就可以通过http://node-ip:1338/metrics访问指标。需要关注的核心指标包括containerd_grpc_requests_totalgRPC请求总数和延迟用于评估API负载和性能。containerd_container_actions_seconds_total容器操作创建、启动、停止的耗时。containerd_task_actions_seconds_total任务操作的耗时。go_goroutines,go_memstats_*Go运行时相关的指标反映containerd进程本身的资源使用情况。建议将这些指标集成到你的Prometheus Grafana监控栈中并设置告警规则例如当容器创建失败率或API延迟超过阈值时告警。6.2 性能调优与配置建议存储驱动选择overlayfs是默认且最通用的选择。对于某些特定文件系统如btrfs, zfs使用对应的快照驱动可能获得更好性能。在配置文件[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options]部分可以设置SystemdCgroup true这对于大量容器的场景管理更高效。资源限制虽然容器本身通过cgroups限制资源但也要防止containerd守护进程占用过多资源。可以通过systemd服务文件/etc/systemd/system/containerd.service为containerd进程本身设置资源限制例如LimitNOFILE1048576文件描述符数和LimitNPROCinfinity进程数根据实际负载调整。镜像垃圾回收长时间运行的集群会产生大量不再使用的镜像层。containerd CRI插件支持自动垃圾回收。在配置文件的[plugins.io.containerd.grpc.v1.cri]部分可以配置[plugins.io.containerd.grpc.v1.cri.image_gc] minimum_image_ttl_duration 720h # 镜像最短保留时间例如30天 image_gc_high_threshold 85 # 磁盘使用率高于85%时触发GC image_gc_low_threshold 80 # GC直到磁盘使用率低于80%定期手动清理也是一个好习惯sudo ctr images prune。日志配置containerd的日志级别可以在配置文件中通过level设置。生产环境建议设置为info避免过于冗长的debug日志。日志输出可以配置为文件或journald。确保日志轮转策略到位防止日志占满磁盘。6.3 常见故障排查实录在实际运维中你可能会遇到以下典型问题问题一容器启动失败报错failed to create shim task: OCI runtime create failed: ...排查思路这通常是低级运行时如runc或系统环境问题。首先检查runc二进制是否存在且可执行which runc。检查内核模块是否加载lsmod | grep overlay。查看更详细的错误信息。通常错误信息会包含具体原因如no space left on device可能是inode耗尽、invalid argument可能是内核版本不支持某个特性。尝试手动用runc运行一个最简单的容器看是否报错以隔离是否是containerd配置问题。问题二Pod一直处于ContainerCreating状态describe Pod显示Failed to create pod sandbox: rpc error: code Unknown desc failed to setup network for sandbox ...排查思路这是典型的网络问题CRI插件调用CNI配置失败。登录对应节点检查CNI插件二进制文件 (/opt/cni/bin/) 和配置文件 (/etc/cni/net.d/) 是否存在且权限正确。检查containerd日志中关于CNI的错误journalctl -u containerd --since 5 minutes ago | grep -i cni。检查节点网络是否正常iptables/nftables规则是否有冲突。尝试手动调用CNI插件为一个网络命名空间配置网络进行调试。问题三镜像拉取非常慢或失败排查思路确认镜像仓库地址和镜像名拼写无误。检查containerd配置中的镜像仓库镜像mirror是否生效。可以通过ctr image pull时加上--debug参数查看详细的拉取过程。检查节点网络连通性是否能正常访问目标仓库。对于私有仓库确保已正确配置认证。containerd的认证信息通常存储在~/.docker/config.json或通过ctr image pull --user user:pass指定。问题四ctr或nerdctl命令执行卡住或无响应排查思路首先检查containerd服务状态systemctl status containerd。检查containerd的socket文件是否存在且权限正确ls -la /run/containerd/containerd.sock。可能是containerd进程僵死。尝试重启containerd服务systemctl restart containerd。注意重启containerd守护进程不会影响已经运行的容器因为它们由独立的shim进程管理。查看系统资源内存、磁盘IO是否已耗尽导致containerd无法响应。问题五磁盘空间不足但不知道是哪个容器或镜像占用的排查思路使用df -h查看磁盘使用情况。使用sudo du -sh /var/lib/containerd/查看containerd数据目录总大小。深入分析各子目录/var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/存放镜像层内容是主要占用空间的地方。/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/存放快照元数据通常不大。使用ctr image ls查看镜像并使用ctr image rm删除不需要的镜像。使用ctr -n k8s.io container ls查看K8s容器结合kubectl get pods --all-namespaces判断哪些Pod已删除但容器未清理可能由于异常退出。可以手动ctr -n k8s.io container rm删除孤立的容器元数据需谨慎确保对应Pod确实已不存在。面对任何复杂问题一个有效的万能起步法是查看日志。journalctl -u containerd -f可以实时跟踪containerd的日志journalctl -u containerd --since today可以查看今天的日志结合grep过滤关键字是定位问题最快的方式。

相关新闻