1. 从Docker到containerd:为什么我们需要关注这个“引擎”
如果你在过去几年里接触过容器技术,那么“Docker”这个名字几乎就是容器的代名词。我们习惯了用docker run启动一个容器,用docker ps查看运行状态,用docker build构建镜像。Docker 为我们封装了一个完整的、用户友好的容器生命周期管理体验。然而,在 Docker 这个“全家桶”之下,真正负责容器核心运行时(runtime)工作的,是一个名为containerd的组件。随着 Kubernetes 在 1.20 版本宣布弃用 Docker,并在后续版本中转向直接集成 containerd 和 CRI-O 作为其容器运行时,这个曾经默默无闻的后台引擎,正式走到了舞台中央。
简单来说,containerd 是一个行业标准的容器运行时。它专注于容器的核心生命周期管理:镜像的拉取与管理、容器的创建、启动、停止、删除,以及底层存储和网络命名空间的分配。它不像 Docker 那样提供构建镜像、高级网络编排或用户友好的 CLI 等上层功能,而是将这些职责剥离出去,自己则成为一个更专注、更稳定、性能开销更小的底层引擎。这种“专注”带来的好处是巨大的:更清晰的架构边界、更易于被其他系统(如 Kubernetes)集成、更快的启动速度,以及更少的安全攻击面。
对于运维工程师、SRE 或者正在构建基于容器的平台开发者而言,深入理解 containerd 不再是“可选项”,而是“必选项”。无论是排查 Kubernetes 节点上诡异的容器启动失败,还是优化大规模集群的镜像分发效率,亦或是实现自定义的容器管理逻辑,你最终都需要和 containerd 打交道。这份攻略的目的,就是带你穿透 Docker 这层“舒适”的封装,直接掌握容器生态中最核心的引擎,让你在面对生产环境中的复杂容器问题时,能够游刃有余。
2. containerd 核心架构与组件拆解
要驾驭 containerd,首先得理解它的内部构造。containerd 采用客户端-服务器架构,通过 gRPC API 对外提供服务,这使得它天生就适合被集成到分布式系统中。
2.1 守护进程:containerd 与 runc
当我们安装并启动 containerd 后,系统中会运行一个名为containerd的守护进程。这个守护进程是大脑,它管理着一切。但是,它并不直接创建容器进程。当需要启动一个容器时,containerd 会调用一个符合 OCI(Open Container Initiative)标准的低级运行时,最常用的就是runc。
你可以把 containerd 看作项目经理,而 runc 就是那个干具体活的工程师。containerd 负责制定计划(准备 rootfs、配置网络、生成 OCI 运行时规范文件config.json),然后 fork/exec 一个 runc 进程,由 runc 根据规范文件去调用 Linux 内核的命名空间、cgroups 等能力,最终创建出隔离的容器进程。这种分层设计非常清晰:containerd 管理状态和生命周期,runc 执行创建动作。
2.2 核心服务:Namespace, Content, Snapshotter
containerd 内部通过插件化的服务来组织功能,其中几个最关键的服务是:
命名空间服务:containerd 自身支持多租户隔离,其顶层逻辑单元就是“命名空间”。不同的命名空间下的镜像、容器、网络等都是隔离的。Kubernetes 会为每个 Pod 创建一个独立的 containerd 命名空间(通常以
k8s.io作为默认命名空间),这确保了不同 Pod 的资源不会相互干扰。你可以通过ctr namespace ls命令查看。内容服务:负责所有不可变内容(Blobs)的存储与管理,最主要的就是镜像层。镜像从仓库拉取下来后,会被拆解成一个个的压缩数据块(Blob),存储在内容存储区。这个服务不关心这些数据是什么,只负责存储、校验和寻址。
快照服务:这是理解容器存储效率的关键。当我们基于一个镜像创建容器时,并不是把镜像的所有层都完整拷贝一份。快照服务会为镜像的每一层创建一个“只读快照”,并为容器创建一个基于这些只读快照的“可写层”(即容器层)。所有容器层共享底层的只读镜像数据,只有写入的数据才会保存在自己的可写层中。这种写时复制机制极大地节省了磁盘空间,并加速了容器的创建。containerd 支持多种快照器,如
overlayfs(最常用)、devmapper、zfs等,通过配置文件中的snapshotter字段指定。
2.3 插件系统与 CRI 插件
containerd 的强大之处在于其高度可扩展的插件系统。几乎所有的核心功能,如运行时、快照器、内容存储后端,甚至是一些监控指标暴露,都是以插件形式实现的。
对于 Kubernetes 用户而言,最重要的插件莫过于CRI(Container Runtime Interface)插件。这个插件实现了 Kubernetes CRI 定义的所有 gRPC 接口(如RuntimeService和ImageService)。当 kubelet 需要管理 Pod 和容器时,它不再通过 Docker,而是直接通过 CRI 插件与 containerd 通信。这个插件负责将 Kubernetes Pod 的概念(包含多个容器共享网络等)翻译成 containerd 能理解的单个容器操作,并协调它们之间的关系。因此,在 Kubernetes 节点上,你的 containerd 配置中必须启用并正确配置 CRI 插件。
3. 实战部署与基础配置指南
理论了解之后,我们进入实战环节。我们将从零开始,在一个干净的 Linux 节点上部署 containerd,并进行基础配置。
3.1 安装与版本选择
目前,获取 containerd 最推荐的方式是通过其官方 GitHub Release 页面下载静态编译的二进制包,或者使用各大 Linux 发行版的包管理器。
以 Ubuntu 22.04 为例,使用官方二进制包安装:
# 下载最新稳定版 containerd,请替换为实际版本号 export VERSION="1.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 # 下载并安装 runc wget https://github.com/opencontainers/runc/releases/download/v1.1.0/runc.amd64 sudo install -m 755 runc.amd64 /usr/local/sbin/runc # 下载并安装 CNI 插件(用于容器网络) export CNI_VERSION="1.3.0" sudo mkdir -p /opt/cni/bin curl -L "https://github.com/containernetworking/plugins/releases/download/v${CNI_VERSION}/cni-plugins-linux-amd64-v${CNI_VERSION}.tgz" | sudo tar -C /opt/cni/bin -xz版本选择建议:生产环境务必使用稳定版。可以关注 GitHub Release 页面的标签,通常1.6.x,1.7.x这样的主版本号加小版本号的系列是长期支持版本。避免使用带有-rc(候选版本)或-beta后缀的版本。
3.2 生成与解读默认配置文件
containerd 的配置文件默认路径是/etc/containerd/config.toml。如果该文件不存在,我们可以让 containerd 自己生成一个默认配置:
sudo mkdir -p /etc/containerd sudo containerd config default | sudo tee /etc/containerd/config.toml让我们解读几个关键配置段:
# 根目录,存放 containerd 的持久化数据,如容器状态、镜像内容、命名空间元数据等。 root = "/var/lib/containerd" # 状态目录,存放运行时产生的临时状态信息,如容器标准输入输出管道。 state = "/run/containerd" # 指定默认的 OCI 运行时,这里使用的是 runc。 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" # 指定默认的快照器,overlayfs 是性能与兼容性最平衡的选择。 [plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "overlayfs" # 禁用拉取镜像时的 TLS 验证(仅用于测试私有仓库,生产环境应配置证书) [plugins."io.containerd.grpc.v1.cri".registry.configs."myregistry.local:5000".tls] insecure_skip_verify = true注意:
insecure_skip_verify = true是一个安全隐患,仅在内部测试且网络环境绝对安全时使用。对于生产环境的私有仓库,正确的做法是配置 CA 证书。将仓库的 CA 证书放置于/etc/containerd/certs.d/<仓库地址>/目录下,containerd 会自动识别。
3.3 配置 systemd 并启动服务
为了让 containerd 作为守护进程运行,我们需要一个 systemd 服务文件。containerd 的 release 包中通常包含一个containerd.service文件,我们需要将其放到正确的位置。
# 将服务文件复制到 systemd 目录 sudo cp /usr/local/lib/systemd/system/containerd.service /etc/systemd/system/ # 重新加载 systemd 配置 sudo systemctl daemon-reload # 设置开机自启 sudo systemctl enable --now containerd # 检查服务状态 sudo systemctl status containerd如果状态显示为active (running),恭喜你,containerd 已经成功运行。你可以使用自带的命令行工具ctr进行初步验证:
# 查看 containerd 版本 sudo ctr version # 列出命名空间(初始可能只有‘default’) sudo ctr namespace ls4. 深入镜像管理:拉取、导出与垃圾回收
镜像是一切容器的基础。containerd 的镜像管理逻辑清晰但不同于 Docker,需要适应。
4.1 使用ctr管理镜像
ctr是 containerd 自带的命令行工具,功能相对基础,适合调试和简单操作。
拉取镜像:
# 拉取一个镜像到 ‘docker.io’ 仓库的 ‘library’ 命名空间下 sudo ctr image pull docker.io/library/nginx:alpine这里需要完整的镜像地址。docker.io是仓库地址,library是组织名(对于官方镜像),nginx是镜像名,alpine是标签。
列出镜像:
sudo ctr image ls你会看到镜像以仓库地址/命名空间/镜像名:标签的格式显示。
导出与导入镜像:这是ctr非常实用的功能,常用于离线环境部署。
# 导出镜像为 tar 包 sudo ctr image export nginx.tar docker.io/library/nginx:alpine # 在另一台机器导入 sudo ctr image import nginx.tar删除镜像:
sudo ctr image rm docker.io/library/nginx:alpine4.2 镜像存储与垃圾回收原理
containerd 的镜像存储分为两部分:元数据(在/var/lib/containerd/io.containerd.content.v1.content中)和内容数据块(Blob,在/var/lib/containerd/io.containerd.snapshotter.v1.<snapshotter>等目录中)。当你删除一个镜像时,只是删除了它的元数据标签(Tag),其底层的数据块可能还被其他镜像引用着,因此不会立即被物理删除。
这就引出了垃圾回收的概念。containerd 需要定期清理那些没有被任何镜像引用的“悬空”数据块。垃圾回收有两种触发方式:
手动触发:使用
ctr命令。# 清理未被任何内容引用的数据块 sudo ctr content gc # 更激进的清理,包括未被任何容器使用的快照 sudo ctr snapshots prune自动触发:在
config.toml中配置。[plugins."io.containerd.gc.v1.scheduler"] deletion_threshold = 0 mutation_threshold = 100 pause_threshold = 0.02 schedule_delay = "0s" startup_delay = "100s"这个调度器插件会在后台定期执行垃圾回收。
deletion_threshold为 0 表示一旦有内容可删除就立即执行。在生产环境中,你可能需要调整这些阈值,避免在高峰时段进行密集的磁盘删除操作影响性能。
实操心得:在磁盘空间紧张的节点上,镜像删除后空间未释放是常见问题。首先用
ctr image ls确认镜像已删,然后用df -h和du -sh /var/lib/containerd/*对比查看,如果发现content或snapshotter目录仍然很大,大概率是垃圾回收未执行。手动运行ctr content gc和ctr snapshots prune通常能立即回收空间。建议在业务低峰期配置自动 GC。
4.3 配置私有镜像仓库
对接私有仓库是生产环境必备技能。假设我们有一个自签证书的私有仓库myregistry.local:5000。
放置 CA 证书:如果仓库使用自签名证书,需要将其 CA 证书放到 containerd 的信任目录。
sudo mkdir -p /etc/containerd/certs.d/myregistry.local:5000 # 将你的 ca.crt 文件复制进去 sudo cp ca.crt /etc/containerd/certs.d/myregistry.local:5000/containerd 会读取这个目录下的
.crt文件作为可信根证书。配置仓库认证:如果需要用户名密码认证,需要配置
config.toml。[plugins."io.containerd.grpc.v1.cri".registry.configs."myregistry.local:5000".auth] username = "myuser" password = "mypassword"更安全的做法是使用
config.toml引用外部文件,或者使用 Docker 的config.json。containerd 的 CRI 插件可以兼容 Docker 的认证配置,它会尝试从$HOME/.docker/config.json读取认证信息。对于系统服务,可以将该文件放在 kubelet 或 containerd 运行用户的 home 目录下。
5. 容器生命周期管理实战
掌握了镜像,我们来操作容器。这里我们分别用ctr和crictl(Kubernetes CRI 工具)来演示。
5.1 使用ctr运行容器
ctr运行容器的命令比docker run更底层,需要明确指定更多参数。
# 1. 创建一个容器 # 这里我们显式指定了快照器(snapshotter)和运行时(runtime) sudo ctr container create \ --snapshotter overlayfs \ --runtime io.containerd.runc.v2 \ docker.io/library/nginx:alpine \ my-nginx # 2. 启动容器 sudo ctr task start my-nginx # 3. 查看任务(即运行中的容器) sudo ctr task ls # 4. 连接到容器的控制台(标准输入输出) sudo ctr task attach my-nginx # 按 Ctrl+P, Ctrl+Q 可以退出 attach 而不停止容器 # 5. 在容器内执行命令 sudo ctr task exec --exec-id my-exec-1 -t my-nginx sh # 这会打开一个交互式 shell # 6. 停止和删除容器 sudo ctr task kill my-nginx # 发送信号,默认 SIGTERM sudo ctr task rm my-nginx # 删除任务 sudo ctr container rm my-nginx # 删除容器定义需要注意,ctr创建的容器是“单容器”视角,没有 Docker 那种端口映射、自定义网络等高级功能。它更适合于测试 containerd 本身的功能是否正常。
5.2 使用crictl模拟 Kubernetes 行为
crictl是 CRI 兼容的命令行工具,它的操作模式更贴近 Kubernetes。在配置好 containerd 的 CRI 插件后,我们可以用它来操作。
首先,需要配置crictl连接到 containerd 的 CRI 服务端点。创建或编辑/etc/crictl.yaml:
runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 pull-image-on-create: false然后就可以进行类似docker的操作了:
# 拉取镜像 sudo crictl pull nginx:alpine # 创建 Pod(沙箱)。Pod 是 Kubernetes 的概念,包含一组共享网络的容器。 # 这里需要提供一个 Pod 配置 JSON 文件,内容较多,通常由 kubelet 生成。 # 我们略过此步,直接创建容器。 # 创建容器(需要指定一个不存在的 Pod ID 用于测试,实际操作中由 kubelet 管理) POD_ID=$(sudo crictl runp dummy-pod-config.json) # 假设有一个配置文件 CONTAINER_ID=$(sudo crictl create $POD_ID container-config.json pod-config.json) # 启动容器 sudo crictl start $CONTAINER_ID # 查看容器 sudo crictl ps # 查看容器日志 sudo crictl logs $CONTAINER_ID # 执行命令 sudo crictl exec $CONTAINER_ID ls / # 停止和删除 sudo crictl stop $CONTAINER_ID sudo crictl rm $CONTAINER_ID sudo crictl stopp $POD_ID sudo crictl rmp $POD_ID通过crictl,你可以模拟 kubelet 的行为,这对于调试 Kubernetes 节点上 Pod 无法启动的问题极其有用。例如,当kubectl describe pod显示CreateContainerError时,你可以用crictl在对应节点上直接尝试拉取镜像或创建容器,从而快速定位是镜像问题、配置问题还是运行时问题。
5.3 容器日志管理
containerd 默认将容器的标准输出和标准错误(stdout/stderr)以二进制格式存储在/var/log/pods/和/var/log/containers/(当通过 CRI 运行时)目录下。这些日志文件会被 Kubernetes 的日志代理(如 Fluentd、Filebeat)收集。
对于通过ctr直接运行的容器,其日志可以通过ctr task logs命令查看:
sudo ctr task logs my-nginx但更常见的需求是配置日志驱动和轮转。这需要在config.toml中配置 CRI 插件的日志选项:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] # 使用 systemd 作为 cgroup 驱动,与 kubelet 配置保持一致 SystemdCgroup = true [plugins."io.containerd.grpc.v1.cri".containerd] # 禁用特权容器(安全加固) disable_privileged_ports = true # 日志配置 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] # 日志目录,通常保持默认 # Root = "/var/lib/containerd/runc"对于日志轮转,通常不在 containerd 层面配置,而是依赖系统的logrotate服务来管理/var/log/containers/*.log文件。
6. 生产环境调优与故障排查
将 containerd 投入生产,意味着要面对性能、稳定性和各种疑难杂症。本章节分享一些关键的调优点和排查思路。
6.1 关键配置调优
系统参数调优:在
/etc/sysctl.d/99-containerd.conf中设置以下参数,然后执行sysctl --system。# 提高网络连接跟踪表大小,应对大量容器网络连接 net.netfilter.nf_conntrack_max = 1048576 # 允许转发,容器网络必需 net.ipv4.ip_forward = 1 # 优化网络性能 net.core.somaxconn = 32768 net.ipv4.tcp_tw_reuse = 1containerd 配置调优:
# 在 config.toml 的 [debug] 部分调整日志级别,生产环境建议 info 或 warn [debug] level = "warn" # 调整 gRPC 服务的监听地址和参数 [grpc] address = "/run/containerd/containerd.sock" # 增大 gRPC 消息大小限制,应对大镜像或复杂配置 max_recv_message_size = 16777216 max_send_message_size = 16777216 [plugins."io.containerd.grpc.v1.cri"] # 禁用不需要的插件,减少内存占用 disable_apparmor = true # 如果不用 AppArmor restrict_oom_score_adj = true # 限制容器调整 OOM 分数,增强稳定性 # 流式服务器配置,影响 exec 和 logs 性能 stream_server_address = "127.0.0.1" stream_server_port = "0" enable_selinux = false # 如果不用 SELinux镜像拉取并行度与重试:在
config.toml的 CRI 插件部分,可以配置拉取镜像的并发数和重试策略,这对从网络不稳定的仓库拉取大镜像很有帮助。
6.2 常见故障排查命令与思路
当容器或 Pod 出现问题时,可以遵循以下排查路径:
检查 containerd 服务状态:
sudo systemctl status containerd -l。查看是否有错误日志。检查 containerd 运行时日志:
sudo journalctl -u containerd -f。这是最直接的错误信息来源。使用
crictl检查运行时状态:# 检查运行时服务是否就绪 sudo crictl info # 查看所有 Pod sudo crictl pods # 查看所有容器 sudo crictl ps -a # 查看镜像 sudo crictl images如果
crictl info失败,说明 CRI 服务未就绪,检查 containerd 配置中 CRI 插件是否启用。容器启动失败:如果
crictl ps -a显示容器状态为Created或Exited,使用sudo crictl inspect <container_id>查看详细状态和错误信息。常见错误包括:- 镜像拉取失败:网络问题、认证问题、镜像不存在。用
crictl pull手动测试。 - 启动命令错误:检查容器配置中的
Command和Args。 - 权限不足:容器要求特权模式,或者宿主机路径挂载权限不对。
- 运行时创建失败:检查 runc 版本兼容性,或查看
dmesg中是否有内核相关报错(如 seccomp 规则冲突)。
- 镜像拉取失败:网络问题、认证问题、镜像不存在。用
磁盘空间不足:容器运行、镜像拉取、日志写入都需要磁盘空间。使用
df -h和du -sh /var/lib/containerd/* /var/log/containers/检查关键目录。定期执行垃圾回收。网络问题:Pod 内容器无法互访或无法访问外网。首先检查 CNI 插件是否安装正确 (
ls /opt/cni/bin/)。然后检查 Pod 对应的网络命名空间和网卡:sudo crictl inspectp <pod_id> | grep -A 10 -B 5 ipAddress获取 Pod IP,再用nsenter命令进入网络命名空间排查。
6.3 监控与度量指标
containerd 暴露了丰富的 Prometheus 格式的度量指标,这对于监控集群中容器运行时的健康状态至关重要。
在config.toml中启用 metrics 收集:
[metrics] address = "0.0.0.0:1338" # 设置一个监听地址和端口 grpc_histogram = false重启 containerd 后,就可以通过http://<node-ip>:1338/metrics访问指标。关键指标包括:
container_runtime_cpu_usage_seconds_total:容器 CPU 使用时间。container_runtime_memory_usage_bytes:容器内存使用量。container_runtime_operations_total:各种运行时操作(如创建、启动、停止)的总次数。container_runtime_operations_errors_total:运行时操作出错的次数。container_runtime_operations_duration_seconds:运行时操作的耗时分布。
将这些指标接入 Prometheus 和 Grafana,可以建立容器运行时的监控大盘,及时发现性能瓶颈或异常错误率的增长。
从 Docker 的“黑盒”到 containerd 的“白盒”,我们深入了解了容器运行时的核心引擎。这份攻略涵盖了从架构原理、部署配置、镜像与容器管理,到生产调优和故障排查的全链路。掌握 containerd,不仅能让你更好地运维 Kubernetes 集群,更能让你在容器技术出现深水区问题时,拥有从底层定位和解决问题的能力。记住,工具本身在不断迭代,但理解其核心设计思想和关键路径,才是应对万变的不二法门。在实际操作中,多使用ctr和crictl进行探索和验证,将配置文件的变化与产生的现象关联起来,你的 containerd 实战经验就会在一次次的问题解决中快速积累。