02 - 关于CRI、OCI、 Docker、containerd的介绍

1 名词介绍


1.1 OCI

OCI(open Container Initiative):开放容器倡议。
    其主要目的是为了解决容器标准混乱的问题,因为没有统一的容器标准,工业界就无法按照统一的标准进行容器开发。因此,于2015年6月22日,Docker公司牵头和CoreOS、Google、RedHat等公司,共同成立了一个中立的基金会,并将自己的容器运行时 LibContainer 捐出,改名为 RunC ,基金会依据 RunC 制定了一套容器和镜像的标准和规范——OCI,即容器规范标准


可以简单理解为,OCI就是个标准。

OCI 标准包含两部分内容:

  • 容器运行时规范:该规范定义了如何根据相应的配置构建容器运行时。
  • 容器镜像规范:该规范定义了容器运行时使用的镜像的打包规范。

行业内现有的容器运行时可以分为两类:高级别运行时、低级别运行时,都遵循 OCI 标准:

  • 低级别运行时:runc、lxc、gvisor、kata…(单纯的管理容器,无法管理镜像)
  • 高级别运行时:docker、containerd、podman、cri-o、rkt…(不仅可以管理容器也可以管理镜像)

1.2 CRI

CRI(Container Runtime Interface):容器运行时接口。
    它是由Kubernetes定义的一组与容器进行交互的接口,它使 Kubernetes 更容易使用不同的容器运行时。它一个插件接口,也就意味着任何符合该标准实现的容器运行时都可以被 Kubernetes 所使用。


1.3 docker-shim

shim 翻译为 “垫片”
     dockershim是 Kubernetes 的一个组件,该组件使 Kubernetes 能够支持 Docker。是这样,由于 Docker 比 Kubernetes 更早,没有实现 CRI,所以这就是 dockershim 存在的原因(Kubernetes 是直接内置了 dockershim 在 kubelet 中),它支持将 Docker 被硬编码到 Kubernetes 中。
    随着容器化成为行业标准,Kubernetes 项目增加了对其他容器运行时的支持(比如通过 CRI 容器运行时接口来 直接 支持其他容器运行时,而不需要通过类似dockershim这种组件进行 间接 的对接支持) 。因此,dockershim 成为了 Kubernetes 项目中的一个异类,对 Docker 和 dockershim 的依赖也已经渗透到云原生计算基金会(CNCF)生态系统中的各种工具和项目中,导致代码脆弱。

    dockershim 组件在 Kubernetes v1.24 发行版本中已被移除;如果你想在 Kubernetes v1.24 及之后的版本中继续使用Docker Engine,一种来自第三方的替代品, cri-dockerd 是可供使用的。cri-dockerd 适配器允许你通过 容器运行时接口 CRI 来使用 Docker Engine。官网地址


2 容器的发展历程


2.1 docker版本的划分

  • 2017之前的版本:…, 1.7 ,1.8 ,1.9 ,1.10 ,1.11 ,1.12 , 1.13
  • 2017年的3月之后,Docker的版本命名开始发生变化,Docker版本从1.13.*直接跳入17.03,该版本的意思是17年3月。同时,还声明了Docker以后会以CE(Community Edition)和EE(Enterprise Edition)的形式发布。
  • Docker社区版(CE,Community Edition):社区版是开源免费的版本,适用于个人开发者和小型团队;
  • Docker企业版(EE,Enterprise Edition):是针对企业级用户提供的增强版,包含了更多的高级功能和支持,需要付费使用。

2.2 docker 架构的发展历程

     从 Docker 1.11 版本开始,Docker 容器运行就不是简单通过 Docker Daemon 来启动了,而是通过集成(或者说拆分成了) containerd、runc 等多个组件来完成的。
     虽然 Docker Daemon 守护进程模块在不停的重构,但是基本功能和定位没有太大的变化,一直都是 CS 架构:

  • Docker 1.11 版本之前,Docker Daemon 守护进程负责和 Docker Client 端交互,并负责容器的生命周期管理。
  • Docker 1.11 版本之后的架构中,Docker Daemon 守护进程仍然负责和 Docker Client 端交互,但容器的生命周期管理是由 containerd组件 负责的,containerd组件 向上为 Docker Daemon 提供 gRPC 接口(说明:docker daemon 守护进程,就相当于是一个代理了)。

Docker 公司将Docker Daemon做拆分的原因:
     Docker 将容器操作都迁移到 containerd 中去,是因为当时Docker公司在做 Swarm,想要进军 PaaS 市场,因此做了这个架构的切分,让 Docker Daemon 专门去负责上层的封装编排,当然后面的结果我们知道 Swarm 在 Kubernetes 面前是惨败,然后 Docker 公司就把 containerd 项目捐献给了 CNCF 基金会,这个也是现在的 Docker 架构。


2.3 通过docker创建容器的流程

Docker 1.11 版本之后,容器的架构情况:
02 - 关于CRI、OCI、 Docker、containerd的介绍_第1张图片

Docker 1.11 版本之后,当我们要创建一个容器的时候:

    
    首先:我们通过 Docker Client 将需求提交给 Docker Daemon 之后,Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 来创建一个容器,containerd 收到请求后,也并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器。

————————————————
注释:
    我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就为了规避这个问题。

    containerd-shim:可以认为是托管我们容器父进程的一个工具,每一个容器起起来之后,都会有一个conatinerd-shim存在;containerd-shim主要是来控制你的容器。
————————————————

    然后,创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作,这些操作其实已经有了标准的规范,那就是 OCI(开放容器标准),runc 就是其中的一个实现,runc 就可以按照这个 OCI 标准来创建一个符合规范的容器。(说明:runc就是一个二进制命令)

————————————————
注释:
    所以,真正启动容器是通过 containerd-shim 去调用 runc 来启动容器的,runc 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程,负责收集容器进程的状态,上报给 containerd,并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理,确保不会出现僵尸进程。

————————————————

# 以下是 kubernetes v1.28、docker-ce v24.0.7的环境信息,供参考辅助理解本节内容:
[root@master-01 ~]# docker version
Client: Docker Engine - Community
 Version:           24.0.7
 API version:       1.43
 Go version:        go1.20.10
 Git commit:        afdd53b
 Built:             Thu Oct 26 09:11:35 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.5
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.6
  Git commit:       a61e2b4
  Built:            Fri Jul 21 20:38:05 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.26
  GitCommit:        3dd1e886e55dd695541fdcd67420c2888645a495
 runc:
  Version:          1.1.10
  GitCommit:        v1.1.10-0-g18a0cb0
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

[root@master-01 ~]#pstree -a
systemd─
  ├─containerd
  │   └─10*[{containerd}]
  ├─containerd-shim -namespace moby -id 31821dbe5898d02c6543fb1d8e26e1b57fbe888a6ee06af82e31620cbbc32b20 -address /run/containerd/containerd.sock
  │   ├─nginx
  │   │   ├─nginx
  │   │   └─nginx
  │   └─12*[{containerd-shim}]
  ├─dockerd -H fd:// --containerd=/run/containerd/containerd.sock
  │   ├─docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.2 -container-port 80
  │   │   └─6*[{docker-proxy}]
  │   ├─docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.17.0.2 -container-port 80
  │   │   └─5*[{docker-proxy}]
  │   └─12*[{dockerd}]


2.4 Docker vs containerd

K8S 为什么要放弃使用 Docker 容器运行时,而使用 containerd 呢?


    其实,我们仔细观察也不难发现,使用 Docker 的话调用链比较长,真正容器相关的操作其实使用 containerd 就完全足够了,Docker 太过于复杂笨重了。【使用containerd不仅性能提高了、更稳定(调用链变短了),而且资源占用也会变小(组件变少)


    当然, Docker 深受欢迎的很大一个原因就是提供了很多对用户操作比较友好的功能,但是,对于 Kubernetes 来说压根不需要这些功能,因为都是通过接口去操作容器的,所以,站在Kubernetes 的维度来考虑,自然希望将容器运行时切换成 containerd 。

02 - 关于CRI、OCI、 Docker、containerd的介绍_第2张图片

你可能感兴趣的:(k8s小知识,docker,容器,运维,kubernetes,云原生)