
1. 项目概述一次技术栈的“和平分手”如果你在2020年底之后才开始接触Kubernetes可能会觉得“K8s放弃Docker”是个老生常谈的话题。但对于很多从Docker Swarm时代一路走来的老运维或者习惯了docker run、docker-compose up这套工作流的开发者来说这无疑是一次认知上的地震。当时社区里充满了困惑和不解“我Docker用得好好的K8s凭什么说不用就不用了”“是不是以后就不能用Docker镜像了”“我的CI/CD流水线是不是要重写了”事实上这并非一次突如其来的“决裂”而是一场酝酿已久、水到渠成的技术架构演进。K8sKubernetes并没有“放弃”容器本身它放弃的只是Docker作为一个容器运行时的默认集成。理解这场变革不仅能帮你厘清K8s与Docker如今的关系更能让你深入理解云原生基础设施的底层设计哲学。简单来说K8s需要的是一个更专注、更标准化的“发动机”容器运行时而Docker是一个功能丰富的“整车厂”。当K8s这个“智能驾驶平台”成熟后它更希望直接对接标准化的“发动机”而不是整合一个自带方向盘、座椅和音响的“整车厂”。接下来我们就从容器生态的演变、接口标准化的必然以及实际运维的视角彻底拆解这次“分手”背后的深层逻辑。2. 核心需求解析K8s到底需要什么要理解为什么“分手”首先要明白K8s和Docker各自的核心诉求是什么。这绝非简单的“谁更好”而是角色定位的根本不同。2.1 Docker的定位开发者友好的全能工具箱Docker的成功在于它极大地降低了容器技术的使用门槛。它提供了一站式解决方案Docker Daemon一个常驻后台的守护进程负责管理容器生命周期、镜像构建与分发。Docker CLI我们熟悉的docker命令是与Daemon交互的客户端工具。Docker Registry镜像仓库服务。Docker Compose用于定义和运行多容器应用的工具。Docker Desktop为Mac/Windows用户提供完整的桌面端体验。Docker把容器运行时、镜像构建、网络、存储、集群编排早期Swarm等众多功能打包在一起形成了一个强大的“全家桶”。对于开发者个人或小团队这个全家桶非常方便开箱即用。2.2 K8s的定位生产级编排系统的标准化诉求K8s的目标是成为数据中心的操作系统管理成千上万个容器化应用。它的核心诉求是稳定与可靠作为基础设施必须极其稳定任何组件的崩溃都不应导致集群大面积故障。标准化与可插拔希望底层组件网络、存储、运行时能通过标准接口接入避免被单一厂商绑定促进生态繁荣。轻量与高效每个组件职责单一减少不必要的开销和攻击面。安全的生命周期管理对容器的创建、运行、监控需要有更精细、更安全的控制能力。问题就出在这里。Docker Daemon作为一个大而全的单体守护进程与K8s的诉求产生了根本性冲突。3. 技术架构冲突与CRI的诞生矛盾的核心在于架构。在早期K8s是通过一个叫dockershim的组件来调用Docker的。3.1 “垫片”架构的固有缺陷dockershim的本质是一个适配器它把K8s定义的容器操作指令翻译成Docker Daemon能理解的APIDocker Engine API。这个架构带来了几个致命问题单点故障与稳定性风险Docker Daemon是一个独立的、有状态的守护进程。如果它崩溃了所有通过它创建的容器都会失去管理尽管容器本身可能还在运行。这对于K8s控制平面来说是不可接受的。K8s希望运行时是轻量的、无状态的即使运行时重启也不应影响现有容器的状态。额外的抽象层与性能损耗调用链变成了kubelet-dockershim-Docker Daemon-containerd-runc。每多一层就意味着更多的序列化/反序列化、进程间通信开销和潜在的故障点。功能冗余与维护负担Docker Daemon提供了很多K8s根本不需要的功能比如内置的镜像构建、Docker Swarm集群管理等。K8s只需要它最核心的容器生命周期管理能力。维护dockershim这个“胶水代码”成了K8s社区一个沉重的负担尤其是当Docker Engine API发生变化时。安全边界模糊Docker Daemon通常以root权限运行拥有巨大的权限。dockershim的存在使得攻击面增大。3.2 CRI容器运行时的“普通话”标准为了解决这些问题Kubernetes社区提出了容器运行时接口Container Runtime Interface, CRI。你可以把CRI想象成容器运行时的“普通话”标准。目标定义一套K8s具体是kubelet与任何容器运行时之间通信的通用API协议基于gRPC。好处任何实现了CRI的容器运行时都可以无缝接入K8s。K8s无需关心底层运行时是Docker、containerd还是CRI-O它只需要用“普通话”CRI发号施令即可。CRI的诞生标志着K8s在基础设施标准化上迈出了关键一步。它希望底层运行时是一个专注、高效、稳定的“引擎”而不是一个“整车厂”。3.3 Docker与CRI的“兼容性”问题那么Docker本身支持CRI吗不支持。Docker Daemon暴露的是自己的Docker Engine API而不是CRI。这就是最根本的“语言不通”。为了让Docker能在K8s 1.23版本之前继续工作社区不得不一直维护着dockershim这个“翻译官”。但随着CRI的成熟和替代方案如containerd的稳定继续维护这个多余的、有问题的翻译层就显得越来越不划算。最终K8s社区做出了一个合乎逻辑的决定废弃dockershim直接拥抱实现了CRI的标准化运行时。这被很多人解读为“K8s放弃Docker”准确地说是“K8s放弃通过非标准的、间接的方式dockershim来调用Docker所包含的容器运行时功能”。4. 替代方案containerd的崛起与实操迁移那么Docker“离开”后谁接替了它的位置答案是containerd。事实上它一直都在。4.1 containerd从幕后到台前很多人不知道的是从Docker 1.11版本开始Docker Daemon的底层容器运行时功能就已经被拆分成独立的containerd项目。Docker Daemon本身变成了一个更上层的管理工具它通过API调用containerd来实际创建和管理容器。containerd是一个专注于容器核心功能的工业级运行时镜像传输、容器执行、存储管理、网络命名空间管理。它比Docker Daemon更轻量、更专注并且原生实现了CRI接口通过一个叫cri-containerd的插件。所以当K8s移除dockershim后它并不是找了一个新朋友而是选择了直接和老朋友containerd“牵手”。架构从kubelet-dockershim-Docker Daemon-containerd简化为了kubelet-CRI-containerd少了两层更简洁、更高效、更稳定。4.2 对用户的实际影响镜像、命令与工作流这是大家最关心的问题改变之后对我们有什么影响镜像兼容性完全不受影响。Docker镜像遵循OCI开放容器倡议标准格式。containerd和所有其他主流容器运行时都完全支持OCI标准。你之前所有docker pull下来的镜像都可以继续在K8s中使用无需任何转换。镜像仓库如Docker Hub、Harbor也完全通用。构建工具不受影响。你仍然可以使用docker build来构建镜像。Docker作为一个强大的镜像构建工具和开发者桌面体验工具其地位并未改变。构建好的镜像推送到仓库K8s集群中的containerd会拉取并运行它。你的CI/CD流水线中关于镜像构建的部分通常无需改动。节点运维命令需要改变习惯。这是主要的变化点。以前在K8s节点上排查问题我们习惯用docker ps、docker logs、docker exec等命令。现在需要改用containerd提供的命令行工具crictlCRI兼容的工具或ctrcontainerd原生工具。重要提示crictl的命令设计刻意模仿了dockerCLI的使用习惯以降低迁移成本。例如docker ps-crictl psdocker logs container-id-crictl logs container-iddocker exec -it container-id sh-crictl exec -it container-id shdocker images-crictl imagesctr命令更底层功能更强但语法与docker差异较大一般用于更高级的调试。Docker Desktop等工具不受影响。Docker Desktop for Mac/Windows 在“启用Kubernetes”时内部早已使用containerd作为K8s的运行时。对于开发者本地环境一切照旧。4.3 迁移实操指南与注意事项如果你的集群是在K8s 1.24版本之前搭建的且仍在使用dockershim那么升级到1.24时需要迁移。主流K8s安装工具如kubeadm、k3s、RKE2的新版本默认都已使用containerd。以使用kubeadm的集群为例迁移的核心步骤准备工作备份所有重要数据和应用配置。逐节点操作确保应用有高可用或可在其他节点重建。清空节点使用kubectl drain node-name --ignore-daemonsets安全驱逐节点上的Pod。卸载Docker Engine# 停止Docker服务 sudo systemctl stop docker # 卸载Docker引擎包 (以Ubuntu为例) sudo apt-get purge -y docker-ce docker-ce-cli # 清理残留文件谨慎操作确保不需要旧镜像/容器数据 sudo rm -rf /var/lib/docker安装containerd# 安装containerd.io包 (具体版本需匹配K8s要求) sudo apt-get update sudo apt-get install -y containerd.io # 生成默认配置文件 sudo mkdir -p /etc/containerd sudo containerd config default | sudo tee /etc/containerd/config.toml # 修改配置启用Systemd Cgroup驱动与kubelet保持一致 sudo sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml # 重启containerd sudo systemctl restart containerd sudo systemctl enable containerd配置kubelet使用containerd修改kubelet参数通常位于/var/lib/kubelet/kubeadm-flags.env或/etc/default/kubelet。确保--container-runtime参数为remote且--container-runtime-endpoint指向containerd的CRI socket默认是unix:///run/containerd/containerd.sock。# 例如在kubeadm环境中编辑配置文件后重启kubelet sudo systemctl restart kubelet验证与恢复使用crictl ps查看容器是否正常运行。使用kubectl get nodes查看节点状态是否恢复为Ready。取消节点保护kubectl uncordon node-name。实操心得在生产环境迁移时强烈建议先在测试环境完整走通流程。重点关注自定义容器运行时配置如私有镜像仓库的认证、日志驱动等如何从Docker迁移到containerd。containerd的配置文件是/etc/containerd/config.toml其结构与Docker的daemon.json不同需要重新学习。5. 深入对比containerd vs Docker Daemon理解两者的区别能更好地把握这次架构变更的精髓。特性维度Docker Daemoncontainerd定位完整的容器平台面向开发者专注的容器运行时面向基础设施架构单体守护进程集成度高模块化设计通过插件扩展APIDocker Engine API (REST)CRI (gRPC) 为主低级API为辅功能范围容器生命周期、镜像构建、网络、存储、集群Swarm等核心的容器生命周期、镜像管理、存储管理资源占用相对较高更轻量内存和CPU占用更少启动速度较慢更快稳定性组件耦合一损俱损风险稍高职责单一更稳定故障影响面小安全性庞大的root进程攻击面大更小的攻击面支持rootless模式简单来说Docker Daemon是一个“瑞士军刀”而containerd是一把锋利的“主厨刀”。对于K8s这个需要高效、稳定、标准化后厨的“大酒店”来说一把专注的好刀比一个多功能工具更合适。6. 常见问题与排查技巧实录迁移到containerd后运维习惯需要改变。以下是一些常见问题和处理技巧。6.1 命令转换速查与技巧场景需要进入容器排查问题。旧习惯docker exec -it container-id bash新方法crictl exec -it container-id bash技巧如果容器内没有bash可以尝试sh。获取容器ID最快捷的方式是结合kubectlkubectl get pods -n namespace pod-name -o jsonpath{.status.containerStatuses[0].containerID} | cut -d/ -f3。这个命令能直接输出容器ID供crictl使用。场景查看容器日志。旧习惯docker logs -f container-id新方法crictl logs -f container-id技巧crictl logs默认输出所有日志。对于K8s Pod日志通常也被收集到/var/log/pods/和/var/log/containers/目录下你可以直接使用tail -f查看这些文件这在crictl不可用时是备选方案。场景检查容器内进程。旧习惯docker top container-id新方法首先用crictl inspect container-id获取容器的PID然后使用nsenter命令进入容器的命名空间查看或者直接用ps -ef | grep pid查看进程树。更简单的方法是使用crictl exec container-id ps aux。6.2 镜像拉取失败问题排查这是迁移后最常见的问题之一尤其是使用私有镜像仓库时。症状Pod状态为ImagePullBackOff事件显示Failed to pull image。排查思路第一步确认镜像地址和标签。使用kubectl describe pod pod-name查看事件详情。第二步在节点上手动拉取测试。使用crictl pull命令模拟拉取这能绕过K8s直接测试containerd的配置。sudo crictl pull myprivateregistry.com/myapp:v1第三步检查containerd的私有仓库配置。Docker的认证配置在~/.docker/config.json而containerd需要在其配置文件/etc/containerd/config.toml中配置[plugins.io.containerd.grpc.v1.cri.registry.mirrors]和[plugins.io.containerd.grpc.v1.cri.registry.configs]部分。这是与Docker最大的配置差异点。第四步重启containerd。修改配置后务必sudo systemctl restart containerd。第五步检查K8s的Secret。如果使用imagePullSecrets确保Secret在正确的命名空间且内容正确。避坑技巧对于私有仓库的HTTPS证书问题如果使用自签名证书需要在containerd配置中指定ca文件或者直接配置insecure_skip_verify true仅限测试环境。配置格式示例[plugins.io.containerd.grpc.v1.cri.registry.configs.myregistry:5000.tls] insecure_skip_verify true6.3 容器日志与存储路径变更Docker的默认工作目录是/var/lib/docker而containerd的默认目录是/var/lib/containerd。这带来两个变化日志路径容器标准输出日志不再位于/var/lib/docker/containers/.../*.log。K8s环境下容器日志被kubelet通过CRI接口获取后默认写入节点文件的/var/log/pods/namespace_pod_uid/container-name/目录下按序号分文件存储。同时在/var/log/containers/目录下有指向这些日志文件的符号链接方便查找。镜像存储镜像和容器层数据现在存储在/var/lib/containerd/io.containerd.content.v1.content/等子目录下结构更为复杂。一般不需要直接操作这些文件。磁盘空间清理以前用docker system prune现在可以用sudo crictl rmi --prune删除未被任何容器引用的镜像。清理容器sudo crictl rm删除已停止的容器。注意containerd没有一键清理所有缓存数据的命令需要手动结合crictl和ctr命令或定期清理/var/lib/containerd目录风险高需谨慎。6.4 crictl与ctr命令的选择crictl这是K8s项目维护的、兼容CRI的调试工具。它的命令和输出格式针对K8s环境做了优化能更好地显示与Pod、容器沙箱pause容器相关的信息。日常节点运维和问题排查首选crictl。ctr这是containerd原生的命令行客户端功能更强大可以操作所有containerd管理的命名空间而crictl只操作k8s.io命名空间。它可以管理镜像、容器、命名空间、快照等。当你需要进行底层操作或者crictl无法满足需求时如导入导出镜像、管理非K8s容器才使用ctr。例如导入一个离线镜像包# 使用ctr导入 sudo ctr -nk8s.io images import /path/to/image.tar # 使用crictl查看是否导入成功 sudo crictl images7. 总结与展望生态的必然选择回顾整个过程K8s放弃对Docker的默认集成不是一个针对Docker的“惩罚”而是云原生生态走向成熟和标准化的必然结果。CRI标准的确立就像为容器运行时定义了USB接口让K8s这个“主机”可以连接任何符合标准的“外设”运行时无论是containerd、CRI-O还是其他任何实现。对于用户而言这次变化带来的短期阵痛主要是运维命令的改变远小于其带来的长期收益更稳定的集群、更高效的资源利用、更清晰的架构边界。Docker本身作为容器技术的布道者和卓越的开发者工具其历史地位不可撼动它只是在一个更专业化的生产环境编排领域将核心的运行时职责交给了更专注的组件。现在当我们再部署一个K8s集群时标准栈已经变成了Kubernetes containerd runc。这是一个更清晰、更健壮、更面向未来的架构。理解这次变迁意味着你不仅跟上了技术的步伐更深入理解了云原生基础设施演进的底层逻辑——标准化、解耦和专注。下次再有人问起“K8s和Docker是什么关系”你可以清晰地告诉他它们是曾经亲密的合作伙伴如今在标准化道路上各自扮演着更专业、更高效的角色。Docker负责制造优秀的“集装箱”镜像和提供友好的“码头”开发体验而K8s和containerd则负责在庞大的“物流中心”数据中心里高效、自动化地调度和管理这些集装箱的运输与存放。