2020年,Kubernetes社区一声宣布:将逐步弃用对Docker的原生支持,2022年K8s 1.24版本正式移除dockershim适配层——这一决策在云原生圈引发轩然大波。
要知道,早期K8s曾将Docker作为唯一支持的容器运行时,二者一度被视为“云原生黄金搭档”。很多开发者至今仍有疑问:Docker明明普及了容器技术,为什么K8s要“抛弃”它?所谓的“妥协”,到底妥协了什么?
先澄清一个误区:K8s不是“抛弃Docker”,是“不再为它妥协”
很多人误以为“K8s弃用Docker”就是Docker没用了,这是典型的理解偏差。事实上:
- K8s放弃的,是「对Docker作为容器运行时的原生支持」,而非Docker本身;
- Docker构建的OCI标准镜像,在K8s中依然可以正常运行(所有CRI兼容运行时都支持OCI镜像);
- 本地开发、CI/CD构建场景,Docker依然是最便捷的工具(docker build、docker-compose仍是主流)。
K8s的核心诉求,是「找到更轻量、更标准、更易维护的容器运行时」,而Docker的设计理念,从根本上与这个诉求产生了冲突——K8s为了兼容Docker,付出了太多不必要的妥协,直到再也无法承受。
要理解这种冲突,先搞懂一个核心概念:容器运行时(Container Runtime) ——它的核心作用只有一个:创建和管理容器,对接底层操作系统内核,是K8s启动Pod的“底层引擎”。
而Docker,从来都不只是一个“容器运行时”。
妥协1:Docker的“全家桶”架构,给K8s增加了冗余负担
Docker的定位是「完整的容器平台」,而非单纯的运行时——它就像一个“外卖平台”,不仅有负责“做菜”的核心引擎(containerd),还有负责“接单”的CLI、“配菜”的镜像构建、“送餐”的网络/存储工具,是一个功能齐全的“全家桶”。
但K8s只需要一个“能做菜的后厨”(容器运行时),不需要整个外卖平台。可早期没有更好的选择,K8s只能基于Docker的核心引擎containerd,额外开发一个「适配层dockershim」,才能实现与Docker的对接。
关键问题:冗余的调用链路,拖慢性能、浪费资源
当K8s用Docker作为运行时时,容器启动的调用链路是这样的:
Kubelet → dockershim(适配层) → dockerd(Docker守护进程) → containerd → runc → 内核
而直接使用containerd作为运行时,调用链路是:
Kubelet → containerd(内置CRI插件) → runc → 内核
多出来的「dockershim+dockerd」两层,带来了两个致命问题:
- 性能损耗:额外的调用链路会增加10-15%的容器启动延迟,在大规模集群中,这种延迟会被无限放大——比如1000个节点的集群,Docker方式的Pod冷启动并发量只有32 pods/sec,而containerd能达到48 pods/sec,差距显著;
- 资源浪费:dockerd进程本身会占用100MB以上的内存,一个节点看似不多,但万级节点集群的总内存开销会达到TB级,这对追求资源高效利用的K8s来说,是完全不必要的浪费。
更关键的是:K8s只需要containerd的“容器生命周期管理”功能,而Docker的镜像构建、网络管理等功能,要么被K8s自身的能力替代(如CNI网络插件),要么在生产集群中用不到——相当于“为了吃一口菜,买了整个餐厅”,性价比极低。
妥协2:维护dockershim适配层,成为K8s团队的沉重负担
Docker没有原生实现K8s定义的「容器运行时接口(CRI)」——CRI是K8s为了兼容多种容器运行时而设计的标准接口,任何符合CRI的运行时(如containerd、CRI-O),都能无缝对接K8s,无需额外开发适配层。
为了兼容Docker,K8s团队不得不开发并维护dockershim适配层,而这层代码,成了K8s代码库中最复杂、最耗时的组件之一。
一组触目惊心的数据:维护成本高到离谱
| 组件 | 代码行数 | 维护团队 | 核心痛点 |
|---|---|---|---|
| dockershim | 40,000+行 | Kubernetes团队 | 需同步适配Docker版本迭代,频繁解决兼容性问题 |
| containerd | 20,000+行 | CNCF社区 | 原生支持CRI,维护成本低,与K8s生态同步演进 |
| CRI-O | 15,000+行 | Red Hat团队 | 专为K8s设计,轻量简洁,无多余功能冗余 |
更麻烦的是:Docker的版本迭代完全独立于K8s,每次Docker更新,都可能导致dockershim适配层出现兼容性问题——K8s团队需要投入大量精力,去适配Docker的变更,而这些精力,本可以用在K8s自身的核心功能(如调度、自愈、扩缩容)上。
用K8s联合创始人Joe Beda的话来说:“维护dockershim,就像在高速行驶的汽车上,一边开车一边换轮胎——毫无必要,且风险极高。”
妥协3:Docker的安全边界模糊,与K8s的安全诉求冲突
K8s作为企业级容器编排平台,「安全」是核心诉求之一——集群规模越大,安全风险越高,而Docker的“全家桶”架构,天生存在安全隐患。
dockerd守护进程拥有比containerd更大的攻击面——它不仅管理容器,还负责镜像构建、网络配置等多种功能,功能越复杂,潜在的漏洞就越多。
根据2018-2020年的漏洞统计:dockerd相关的CVE漏洞有32个,而同期containerd的CVE漏洞只有9个,差距超过3倍。这些漏洞一旦被利用,可能导致整个K8s节点被入侵,进而影响整个集群的安全。
此外,Docker的权限模型与K8s的安全策略也存在冲突:dockerd需要较高的系统权限才能运行,而containerd可以以更低的权限启动,更符合K8s“最小权限原则”的安全设计理念。
核心真相:不是Docker不好,是“道不同,不相为谋”
看到这里,你应该明白:K8s弃用Docker,不是Docker技术落后,而是二者的「定位和目标完全不同」,早已分道扬镳。
Docker的目标:做“开发者友好的完整容器平台”
Docker的核心价值,是「降低容器技术的使用门槛」——让开发者通过简单的docker run、docker build命令,就能快速构建、运行容器,专注于业务开发,而不用关心底层内核细节。
它的优势在「开发、测试场景」:本地调试、CI/CD构建、单机部署,Docker的“全家桶”功能能大幅提升效率,这也是它至今仍被广泛使用的原因。
K8s的目标:做“企业级、大规模的容器编排平台”
K8s的核心价值,是「高效管理大规模容器集群」——实现容器的自动调度、故障自愈、扩缩容、负载均衡,追求高可用、高性能、高安全性,降低集群运维成本。
它需要的容器运行时,必须满足「轻量、标准、稳定、易维护」的特点,而Docker的“全家桶”架构,恰恰与这些需求背道而驰。
类比理解:就像“打车”vs“开公交”
Docker就像「网约车」:灵活、便捷,适合个人出行(开发者本地开发),能满足个性化需求,但不适合大规模载客(企业级集群);
K8s就像「公交系统」:追求高效、低成本、规模化,需要的是标准化的“公交车”(containerd/CRI-O),而不是功能复杂、成本高昂的网约车——Docker的冗余功能,对K8s来说,就像网约车的“私人座椅、个性化导航”,完全多余。
开发者该如何应对?
不用恐慌,这场变革对大多数开发者来说,影响很小,核心是「分清场景,按需选择」:
1. 本地开发/CI/CD:继续用Docker,无需更换
Docker的镜像构建、Docker Compose等功能,在本地开发和CI/CD场景中依然是最优解——你用docker build构建的镜像,依然可以正常部署到K8s集群(因为镜像遵循OCI标准),无需任何修改。
2. K8s生产集群:优先使用containerd或CRI-O
目前K8s的默认运行时已经是containerd,它不仅轻量、高性能,还与Docker共享底层核心引擎,迁移成本极低。
关键命令替代(从Docker切换到containerd):
# 查看容器(替代docker ps)
crictl ps
# 查看容器日志(替代docker logs)
crictl logs <容器ID>
# 进入容器(替代docker exec)
crictl exec -it <容器ID> sh
# 查看镜像(替代docker images)
crictl images
3. 特殊场景:如需继续用Docker,可使用cri-dockerd适配器
如果你的集群依赖Docker的特有功能(如Docker Swarm、docker in docker),可以安装cri-dockerd适配器(Docker和Mirantis联合维护),实现K8s与Docker的对接——但不推荐生产环境使用,毕竟多一层适配,就多一层风险和开销。
总结:云原生的本质,是“标准化与专业化”的演进
K8s不再为Docker妥协,本质上是云原生生态成熟的必然结果——从“单一工具主导”到“标准化分工”,每个组件都专注于自己的核心领域:
- Docker:专注于「开发者体验」,负责容器构建、本地调试,成为云原生的“入口工具”;
- containerd/CRI-O:专注于「容器运行时」,负责容器的创建和管理,成为K8s的“底层引擎”;
- K8s:专注于「容器编排」,负责大规模集群的管理,成为云原生的“核心调度平台”。
这场变革,不是谁替代谁,而是生态的“各司其职”——Docker没有被淘汰,只是找到了自己的定位;K8s没有“抛弃”谁,只是不再为了兼容而牺牲自身的演进速度。
对开发者而言,理解这种演进趋势,分清场景选择合适的工具,才是最核心的能力——毕竟,云原生的世界里,“适配变化”比“固守工具”更重要。