容器运行时选型中,Containerd已取代Docker成为Kubernetes集群的默认运行时,这不是技术优劣的简单结论,而是云原生架构演进对组件职责边界重新划分的必然结果。Docker从未被淘汰,它只是从集群核心组件回归到开发者工具的本位。理解这一转变,需要从两个项目的设计哲学、架构差异和资源消耗特征入手。
Docker是一个完整的容器生命周期管理平台,包含镜像构建、容器运行、网络管理、存储卷处理和一组用户友好的CLI工具。Containerd定位完全不同——它只是一个容器运行时守护进程,专注于管理容器的生命周期:拉取镜像、创建容器、启动和停止容器。从调用链来看,两者在Kubernetes集群中的位置差异一目了然。
Docker在Kubernetes中的调用路径是:kubelet通过CRI接口调用dockershim适配层,dockershim再将请求转发给Docker daemon,Docker daemon最终调用containerd管理容器。这中间多了一层dockershim,而这一层正是Kubernetes社区在v1.20版本中标记为废弃、在v1.24版本中彻底移除的组件。据Kubernetes官方维护者Davanum Srinivas在KubeCon大会上的技术分享指出,dockershim的存在引入了不必要的复杂度和维护成本,移除它不是因为Docker不好,而是因为Containerd已经足够成熟,可以直接通过CRI接口与kubelet通信。
Containerd的调用路径则简洁得多:kubelet直接通过CRI插件与Containerd通信,Containerd管理runc或者其他底层OCI运行时来创建容器。少了一层转发,意味着更少的故障点和更低的延迟抖动。
Docker daemon是一个功能丰富的守护进程,预加载了大量模块。在实际生产环境测量中,Docker daemon的空闲内存占用通常在150MB到300MB之间,具体取决于启用的功能和插件数量。Containerd的空闲内存占用保持在80MB到120MB区间,几乎只有Docker的一半。这一差异在大规模集群中会被显著放大——一个运行500个节点的Kubernetes集群,仅运行时守护进程的空闲内存支出,Docker方案比Containerd方案多出约35GB到90GB的总内存消耗。
容器启动延迟方面,由于Containerd调用链更短,启动一个容器的耗时通常比Docker方案快200ms到500ms。对于启动时间不敏感的长生命周期服务,这个差异可以忽略。但对于弹性伸缩场景、Serverless容器平台,或者FaaS函数计算场景,几百毫秒的启动延迟差异直接决定了冷启动体验。下表对比了两者在几个关键维度的表现差异:
| 对比维度 | Docker | Containerd |
|---|---|---|
| 定位 | 完整容器平台(开发+运行) | 轻量级容器运行时 |
| 空闲内存占用 | 150-300MB | 80-120MB |
| K8s调用路径 | kubelet→dockershim→dockerd→containerd | kubelet→containerd(CRI) |
| 镜像构建能力 | 内置docker build | 不包含,需配合其他工具 |
| CLI工具 | docker CLI(功能丰富) | ctr/crictl/nerdctl |
| 容器启动延迟 | 基准线 | 减少200-500ms |
| 安全攻击面 | 较大(更多组件) | 较小(精简组件) |
Docker最大的价值不在生产集群的容器运行时层面,而在开发者工作流中。docker build是目前最成熟、最广泛使用的容器镜像构建方式,Dockerfile的语法简洁直观,生态工具链丰富。docker compose让多容器应用在本地开发环境的编排变得简单,docker scan提供镜像安全扫描,Docker Hub提供镜像托管和分发能力。
切换到Containerd的集群,开发者仍然需要使用Docker(或其他镜像构建工具)来构建镜像,把镜像推送到镜像仓库,然后由Containerd在集群中拉取和运行。这个工作流没有改变,变的只是集群节点上跑的是哪个守护进程。实际运维中,很多团队在CI/CD流水线中保留Docker用于构建镜像,在生产集群中用Containerd作为运行时,两者各司其职。
从Docker迁移到Containerd,第一件要处理的事情是CLI工具的切换。docker命令不再可用,替代方案有三种:crictl是Kubernetes社区推荐的工具,支持CRI标准操作,但功能相对基础;nerdctl是Containerd官方推出的类Docker CLI工具,命令格式与docker高度一致,对习惯Docker的运维人员更友好;ctr是Containerd自带的低级调试工具,不适合日常运维使用。实际使用中,nerdctl加上crictl的组合覆盖了绝大多数运维场景。
日志收集是另一个容易踩坑的地方。Docker默认将容器日志以JSON文件格式写入/var/lib/docker/containers目录,而Containerd使用CRI日志格式,日志路径和文件命名规则不同。如果团队依赖Fluentd、Filebeat等日志采集组件,需要确认采集配置是否兼容Containerd的日志路径。Containerd的容器日志默认存储在/var/log/pods目录下,路径结构为/var/log/pods/<namespace>_<pod_name>_<pod_uid>/<container_name>/,与Docker的存储位置完全不同。
镜像存储路径的差异也需要关注。Docker的镜像存储在/var/lib/docker目录,而Containerd的镜像和容器数据存储在/var/lib/containerd。磁盘空间管理和清理策略需要相应调整,特别是在节点磁盘空间紧张时,清理命令和对象都不同。
# Docker清理命令 docker system prune -a # Containerd清理命令 crictl rmi --prune # 或者使用nerdctl nerdctl system prune
对于已经在运行Kubernetes集群的团队,如果集群版本在v1.24以上,Docker作为运行时已经不再被官方支持,迁移到Containerd是必然选择。对于新建集群,直接使用Containerd作为运行时是最省事的方案,避免了后续迁移的成本。对于开发环境和个人工作站,Docker仍然是首选——docker compose、Docker Desktop和丰富的镜像生态让本地开发体验无可替代。
有一些场景下Docker不适合作为生产运行时,但Containerd也不是唯一选择。CRI-O是另一个专为Kubernetes设计的轻量级CRI运行时,由Red Hat主导开发,与Containerd形成竞争关系。选型时如果团队的基础设施偏Red Hat生态,CRI-O也值得评估。不过Containerd的社区活跃度更高,CNCF毕业项目的成熟度有保障,加上几乎所有主流云厂商的托管Kubernetes服务都默认使用Containerd,选择Containerd的生态兼容性风险最小。
评论
加载中...