为什么 Kubernetes 要替换 Docker

Kubernetes决定移除Dockershim,原因是其引入了容器运行时接口(CRI)以解耦特定容器运行时的依赖。Docker并未支持CRI,导致社区需要维护额外的兼容层。CRI使得Kubernetes能够支持多种容器运行时,提高可扩展性和灵活性。移除Dockershim是Kubernetes成熟和追求更好扩展性的表现。

为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。

Kubernetes 是今天容器编排领域的事实标准,而 Docker 从诞生之日到今天都在容器中扮演着举足轻重的地位,也都是 Kubernetes 中的默认容器引擎。然而在 2020 年 12 月,Kubernetes 社区决定着手移除仓库中 Dockershim 相关代码[^1],这对于 Kubernetes 和 Docker 两个社区来说都意义重大。

kubelet-and-containers

图 1 - Dockershim

相信大多数的开发者都听说过 Kubernetes 和 Docker,也知道我们可以使用 Kubernetes 管理 Docker 容器,但是可能没有听说过 Dockershim,即 Docker 垫片。如上图所示,Kubernetes 中的节点代理 Kubelet 为了访问 Docker 提供的服务需要先经过社区维护的  Dockershim,Dockershim 会将请求转发给管理容器的 Docker 服务。

其实从上面的架构图中,我们就能猜测出 Kubernetes 社区从代码仓库移除 Dockershim 的原因:

  • Kubernetes 引入容器运行时接口(Container Runtime Interface、CRI)隔离不同容器运行时的实现机制,容器编排系统不应该依赖于某个具体的运行时实现;

  • Docker 没有支持也不打算支持 Kubernetes 的 CRI 接口,需要 Kubernetes 社区在仓库中维护 Dockershim;

可扩展性

Kubernetes 通过引入新的容器运行时接口将容器管理与具体的运行时解耦,不再依赖于某个具体的运行时实现。很多开源项目在早期为了降低用户的使用成本,都会提供开箱即用的体验,而随着用户群体的扩大,为了满足更多定制化的需求、提供更强的可扩展性,会引入更多的接口。Kubernetes 通过下面的一系列接口为不同模块提供了扩展性:

kubernetes-extensions

图 2 - Kubernetes 接口和可扩展性

Kubernetes 在较早期的版本中就引入了 CRD、CNI、CRI 和 CSI 等接口,只有用于扩展调度器的调度框架是 Kubernetes 中比较新的特性。我们在这里就不展开分析其他的接口和扩展了,简单介绍一下容器运行时接口。

Kubernetes 早在 1.3 就在代码仓库中同时支持了 rkt 和 Docker 两种运行时,但是这些代码为 Kubelet 组件的维护带来了很大的困难,不仅需要维护不同的运行时,接入新的运行时也很困难;容器运行时接口(Container Runtime Interface、CRI)是 Kubernetes 在 1.5 中引入的新接口,Kubelet 可以通过这个新接口使用各种各样的容器运行时。其实 CRI 的发布就意味着 Kubernetes 一定会将 Dockershim 的代码从仓库中移除。

CRI 是一系列用于管理容器运行时和镜像的 gRPC 接口,我们能在它的定义中找到 RuntimeServiceImageService 两个服务[^2],它们的名字很好地解释了各自的作用:

service RuntimeService {
    rpc Version(VersionRequest) returns (VersionResponse) {}

    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
    rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}

    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
    rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
    rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}
    rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}

    ...
}

service ImageService {
    rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
    rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
    rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
    rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
    rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
}

对 Kubernetes 稍有了解的人都能从上面的定义中找到一些熟悉的方法,它们都是容器运行时需要暴露给 Kubelet 的接口。Kubernetes 将 CRI 垫片实现成 gRPC 服务器与 Kubelet 中的客户端通信,所有的请求都会被转发给容器运行时处理。

cri-and-container-runtimes

图 3 - Kubernetes 和 CRI

Kubernetes 中的声明式接口非常常见,作为声明式接口的拥趸,CRI 没有使用声明式的接口是一件听起来『非常怪异』的事情[^3]。不过 Kubernetes 社区考虑过让容器运行时重用 Pod 资源,这样容器运行时可以实现不同的控制逻辑来管理容器,能够极大地简化 Kubelet 和容器运行时之间的接口,但是社区出于以下两点考虑,最终没有选择声明式的接口:

  1. 所有的运行时都需要重新实现相同的逻辑支持很多 Pod 级别的功能和机制;

  2. Pod 的定义在 CRI 设计时演进地非常快,初始化容器等功能都需要运行时的配合;

虽然社区最终为 CRI 选择了命令式的接口,但是 Kubelet 仍然会保证 Pod 的状态会不断地向期望状态迁移。

不兼容接口

与容器运行时相比,Docker 更像是一个复杂的开发者工具,它提供了从构建到运行的全套功能。开发者可以很快地上手 Docker 并在本地运行并管理一些 Docker 容器,然而在集群中运行的容器运行时往往不需要这么复杂的功能,Kubernetes 需要的只是 CRI 中定义的那些接口。

docker-and-cri

图 4 - Docker & CRI

Docker 的官方文档加起来可能有一本书的厚度,相信没有任何开发者可以熟练运用 Docker 提供的全部功能。而作为开发者工具,虽然 Docker 中包含 CRI 需要的所有功能,但是都需要实现一层包装以兼容 CRI。除此之外,社区提出的很多新功能都没有办法在 Dockershim 中实现,例如 cgroups v2 以及用户命名空间。

Kubernetes 作为比较松散的开源社区,每个成员尤其是各个 SIG 的成员都只会在开源社区上花费有限的时间,而维护 Kubelet 的 sig-node 又尤其繁忙,很多新的功能都因为维护者没有足够的精力而被搁置,所以既然 Docker 社区看起来没有打算支持 Kubernetes 的 CRI 接口,维护 Dockershim 又需要花费很多精力,那么我们就能理解为什么 Kubernetes 会移除 Dockershim 了。

总结

今天的 Kubernetes 已经是非常成熟的项目,它的关注点也逐渐从提供更完善的功能转变到提供更好的扩展性,这样才能满足不同场景和不同公司定制化的业务需求。Kubernetes 在过去因为 Docker 的热门而选择 Docker,而在今天又因为高昂的维护成本而放弃 Docker,我们能够从这个过程中体会到容器领域的发展和进步。

移除 Docker 的种子其实从 CRI 发布时就种下了,Dockershim 一直都是 Kubernetes 为了兼容 Docker 获得市场采取的临时决定,对于今天已经统治市场的 Kubernetes 来说,Docker 的支持显得非常鸡肋,移除代码也就顺理成章了。我们在这里重新回顾一下 Kubernetes 在仓库中移除 Docker 支持的两个原因:

  • Kubernetes 在早期版本中引入 CRI 摆脱依赖某个具体的容器运行时依赖,屏蔽底层的诸多实现细节,让 Kubernetes 能够更关注容器的编排;

  • Docker 本身不兼容 CRI 接口,而且官方并没有实现 CRI 的打算,同时也不支持容器的一些新需求,所以 Dockershim 的维护成为了社区的想要摆脱负担;

到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:

  • Kubernetes 中还有哪些模块提供良好的扩展性?

  • 除了文中提到的 CRI-O、Containerd,还有哪些支持 CRI 的容器运行时?

推荐阅读

<think>好的,我现在需要帮助用户解决在Kubernetes弃用Docker后如何继续运行Docker镜像的问题。首先,我得回顾一下用户提到的参考资料,看看里面有没有相关的信息。 根据引用[2],当使用containerd替换Docker后,不能再用docker命令管理容器,而是用kubectl。这说明Kubernetes虽然不再依赖Docker,但容器运行时换成了containerd,而containerd本身可以支持Docker镜像,因为Docker镜像本来就是符合OCI标准的。所以用户可能担心的是镜像兼容性和操作方式的变化。 接下来,我需要确认Kubernetes移除Docker后的替代方案。用户的问题核心是如何继续使用现有的Docker镜像,而Kubernetes现在支持的容器运行时如containerd和CRI-O都支持OCI镜像,Docker镜像也符合这个标准,所以理论上镜像可以直接使用,不需要修改。但用户可能需要知道具体的操作步骤,比如如何配置节点使用containerd,或者是否需要转换镜像格式。 引用[1]提到了设置docker源和安装kubeadm的步骤,这可能涉及到节点环境的配置。当切换到containerd时,可能需要安装containerd并配置kubelet使用它作为运行时。此外,用户可能需要了解如何从Docker镜像仓库拉取镜像,因为containerd可以使用ctr命令,但通常推荐使用crictl工具,或者继续使用docker build构建镜像,然后导入到containerd中。 另外,引用[3]和[4]讨论了Docker Swarm和Kubernetes的对比,但用户的问题是关于Kubernetes本身,所以可能需要忽略这部分,专注于运行时替换后的镜像使用。 总结解决方案的步骤可能包括:1. 确认集群使用containerd或CRI-O作为容器运行时;2. 构建或拉取Docker镜像;3. 使用镜像拉取策略,确保镜像在节点上可用;4. 使用工具如nerdctl或crictl管理镜像;5. 调整CI/CD流程,可能需要保存镜像为tar文件再导入。 同时,用户可能关心是否需要修改现有的Dockerfile或镜像,是否需要调整Kubernetes的配置,比如Pod的runtimeClass,或者如何处理之前依赖Docker命令的运维操作。根据引用[2],这些操作现在需要通过kubectl或crictl来完成。 最后,用户可能需要验证镜像是否正常运行,所以提供一些检查命令,比如使用kubectl describe pod查看事件,或者使用crictl检查容器状态。 需要确保回答结构清晰,分步骤说明解决方案,并解答可能的疑问,比如镜像兼容性、工具链变化等。同时,根据用户提供的引用,适当引用相关部分来支持回答,例如说明containerd与Docker的兼容性,或者使用kubectl替代docker命令的情况。</think>在Kubernetes弃用Docker作为容器运行时后,您仍可无缝运行Docker镜像。以下是具体解决方案和操作流程: --- ### 一、技术原理 Docker镜像基于**OCI标准**(Open Container Initiative)构建,而Kubernetes支持的容器运行时(如containerd、CRI-O)均兼容该标准。因此,所有Docker镜像无需修改即可直接使用[^5]。 --- ### 二、具体实现步骤 #### 1. 配置容器运行时 在所有节点上安装并配置**containerd**(推荐)或**CRI-O**: ```bash # 安装containerd sudo apt-get install containerd sudo systemctl enable containerd # 配置kubelet使用containerd cat <<EOF | sudo tee /etc/default/kubelet KUBELET_EXTRA_ARGS=--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock EOF ``` #### 2. 镜像管理 | 原Docker操作 | containerd替代方案 | |-----------------------|--------------------------------------| | `docker build` | 仍可用Docker构建镜像后导出 | | `docker push/pull` | 直接使用镜像仓库(无需改变推送/拉取逻辑)| | `docker images` | `sudo crictl images` 或 `nerdctl images` | #### 3. 镜像格式转换(特殊场景) 当需要直接操作containerd时,可通过以下命令导入/导出镜像: ```bash # 导出Docker镜像 docker save my-image:tag > my-image.tar # 导入containerd sudo ctr -n=k8s.io images import my-image.tar ``` --- ### 三、关键注意事项 1. **运维命令变更** 容器调试需改用`kubectl`或`crictl`: ```bash # 查看容器日志 kubectl logs <pod-name> # 进入容器 kubectl exec -it <pod-name> -- sh # 查看容器列表 sudo crictl ps -a ``` 2. **CI/CD流水线调整** 保留Docker作为构建工具,在部署阶段将镜像推送到仓库后,Kubernetes会自动通过containerd拉取镜像[^2]。 3. **运行时配置验证** 检查节点运行时状态: ```bash kubectl get nodes -o wide kubectl describe node <node-name> | grep Container\ Runtime ``` --- ### 四、架构对比 ```mermaid graph LR A[Docker架构] -->|依赖| B(Docker Daemon) C[containerd架构] -->|直接集成| D(Kubelet) E[Kubernetes集群] -->|CRI接口| C ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值