Kubernetes-三大开放接口-初见
容器运行时接口CRI
历史
OCI出现是为了它的核心目标围绕容器的格式和运行时制定一个开放的工业化标准,并推动这个标准,保持容器的灵活性和开放性,容器能运行在任何的硬件和系统上。容器不应该绑定到特定的客户机或编排堆栈,不应该与任何特定的供应商紧密关联,并且可以跨多种操作系统
官网上对 OCI 的介绍如下:
Established in June 2015 by Docker and other leaders in the container industry, the OCI currently contains two specifications: the Runtime Specification (runtime-spec) and the Image Specification (image-spec). The Runtime Specification outlines how to run a “filesystem bundle” that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime.
OCI由docker以及其他容器行业领导者创建于2015年,目前主要有两个标准:容器运行时标准(runtime-spec)和容器镜像标准(image-spec)。
这两个标准通过OCI runtime filesytem bundle的标准格式连接在一起,OCI镜像可以通过工具转换成bundle,然后 OCI 容器引擎能够识别这个bundle来运行容器
文档主要做了两个事情:
- 创建镜像的规则
- 运行镜像的规则
了解了OCI,以及docker在兼容OCI标准架构的调整后, 迎来我们的重点 CRI,CRI是kubernetes推出的一个标准,推出标准可见其在容器编排领域的地位。
简介
容器运行时接口(Container Runtime Interface),简称 CRI。CRI 中定义了 容器 和 镜像 的服务的接口,因为容器运行时与镜像的生命周期是彼此隔离的,因此需要定义两个服务。该接口使用 Protocol Buffer,基于 gRPC,在 Kubernetes v1.10 + 版本中是在 pkg/kubelet/apis/cri/runtime/v1alpha2
的 api.proto
中定义的。
容器运行时(Container Runtime):顾名思义就是容器从拉取镜像到启动运行再到中止的整个生命周期。 其中最知名的就是Docker了。
每个容器运行时都有特点,因此不少用户希望Kubernetes能够支持更多的容器运行时。为了更具扩展性,kubernetes引入了容器运行时插件API,即 Container Runtime Interface,简称CRI。
- Protocol Buffers API(一种更高效的类似json的数据格式)包含两个gRPC服务:
- ImageService和RuntimeService。
- ImageService提供了从仓库拉取镜像、查看和移除镜像的功能。
- RuntimeService负责Pod和容器的生命周期管理,以及与容器的交互 (exec/attach/port-forward)。
- ImageService和RuntimeService。
架构
Container Runtime 实现了 CRI gRPC Server,包括 RuntimeService
和 ImageService
。该 gRPC Server 需要监听本地的 Unix socket,而 kubelet 则作为 gRPC Client 运行。
启用 CRI
除非集成了 rktnetes,否则 CRI 都是被默认启用了,从 Kubernetes 1.7 版本开始,旧的预集成的 docker CRI 已经被移除。
要想启用 CRI 只需要在 kubelet 的启动参数重传入此参数:--container-runtime-endpoint
远程运行时服务的端点。当前 Linux 上支持 unix socket,windows 上支持 tcp。
Kubelet拉起一个容器的过程:
-
Kubelet通过CRI接口(gRPC)调用docker-shim,请求创建一个容器这一步中,kubelet可以视作一个简单的CRI Client,而docker-shim就是接收请求的Server,注意的是docker-shim是内嵌在Kubelet中的。
-
docker-shim收到请求后,转化成Docker Daemon能听懂的请求,发到Docker Daemon上请求创建一个容器
-
Docker Daemon请求containerd创建一个容器
-
containerd收到请求后创建一个containerd-shim进程,通过containerd-shim操作容器,容器进程需要一个父进程来做诸如收集状态, 维持stdin等fd打开等工作
-
containerd-shim在调用runC来启动容器
-
runC 启动完容器后本身会直接退出,containerd-shim则会成为容器进程的父进程,负责收集容器进程的状态,上报给containerd。
通过上面kubelet创建容器的流程, 我们可以看到kubelet通过CRI的标准来与外部容器运行时进行交互。
kubernetes 早期版本1.5之前内置了docker和rkt,也就是支持两种运行时, 这个时候如果用户想自定义运行时就比较痛苦了,需要修改kubelet源码。
同时不同的容器运行时各有所长,随着k8s在容器编排领域里面老大的地位,许多用户希望kubernetes支持更多的容器运行时,满足不同用户,不同环境的使用。
于是从kubernetes1.5开始增加了CRI接口, 有了CRI接口无需修改kubelet源码就可以支持更多的容器运行时,
与此同时内置的docker和rtk逐渐从kubernetes源码中移除,到kubernetes1.11版本Kubelet内置的rkt代码删除,CNI的实现迁移到dockers-shim之内,除了docker之外,其他的容器运行时都通过CRI接入。
外部的容器运行时一般称为CRI shim,它除了实现CRI接口外,也要负责为容器配置网络,即CNI,有了CNI可以支持社区内的众多网络插件。
CRI 接口
CRI主要定义两个接口, ImageService和RuntimeService,如下图:
ImageService:负责镜像的生命管理周期
- 查询镜像列表
- 拉取镜像到本地
- 查询镜像状态
- 删除本地镜像
- 查询镜像占用空间
RuntimeService:负责管理Pod和容器的生命周期
- PodSandbox 的管理接口
PodSandbox是对kubernete Pod的抽象,用来给容器提供一个隔离的环境(比如挂载到相同的cgroup下面)并提供网络等共享的命名空间。PodSandbox通常对应到一个Pause容器或者一台虚拟机。 - Container 的管理接口
在指定的 PodSandbox 中创建、启动、停止和删除容器。 - Streaming API接口
包括Exec、Attach和PortForward 等三个和容器进行数据交互的接口,这三个接口返回的是运行时Streaming Server的URL,而不是直接跟容器交互。 - 状态接口
包括查询API版本和查询运行时状态。
容器生态可以下面的三层抽象:
Orchestration API -> Container API -> Kernel API
- Orchestration API: kubernetes API标准就是这层的标准,无可非议
- Container API: 标准就是CRI
- Kernel API: 标准就是OCI
当前支持的 CRI 后端
我们最初在使用 Kubernetes 时通常会默认使用 Docker 作为容器运行时,其实从 Kubernetes 1.5 开始已经支持 CRI,通过 CRI 接口可以指定使用其它容器运行时作为 Pod 的后端,目前支持 CRI 的后端有:
- cri-o:cri-o 是 Kubernetes 的 CRI 标准的实现,并且允许 Kubernetes 间接使用 OCI 兼容的容器运行时,可以把 cri-o 看成 Kubernetes 使用 OCI 兼容的容器运行时的中间层。
- cri-containerd:基于 Containerd 的 Kubernetes CRI 实现
- rkt:由 CoreOS 主推的用来跟 docker 抗衡的容器运行时
- frakti:基于 hypervisor 的 CRI
- docker:Kuberentes 最初就开始支持的容器运行时,目前还没完全从 kubelet 中解耦,Docker 公司同时推广了 OCI 标准
CRI 是由 SIG-Node 来维护的。
容器网络接口CNI
简介
容器网络接口(Container Network Interface),简称 CNI,是 CNCF 旗下的一个项目,由一组用于配置 Linux 容器的网络接口的规范和库组成,同时还包含了一些插件。CNI 仅关心容器创建时的网络分配,和当容器被删除时释放网络资源。有关详情请查看 GitHub。
Kubernetes 源码的 vendor/github.com/containernetworking/cni/libcni
目录中已经包含了 CNI 的代码,也就是说 Kubernetes 中已经内置了 CNI。
为什么出现?
不管是 Docker 还是 Kubernetes,在网络方面目前都没有一个完美的、终极的、普适性的解决方案,不同的用户和企业因为各种原因会使用不同的网络方案。
目前存在网络方案 flannel、calico、openvswitch、weave、ipvlan等,而且以后一定会有其他的网络方案,这些方案接口和使用方法都不相同,而不同的容器平台都需要网络功能,它们之间的适配如果没有统一的标准,会有很大的工作量和重复劳动。
CNI 就是这样一个标准,它旨在为容器平台提供网络的标准化。不同的容器平台(比如目前的 kubernetes、mesos 和 rkt)能够通过相同的接口调用不同的网络组件。
CNI(Conteinre Network Interface) 是 Google 和 CoreOS 主导制定的容器网络标准,它本身并不是实现或者代码,可以理解成一个协议。这个标准是在 rkt 网络提议 的基础上发展起来的,综合考虑了灵活性、扩展性、ip 分配、多网卡等因素。
这个协议连接了两个组件:容器管理系统和网络插件。它们之间通过 JSON 格式的文件进行通信,实现容器的网络功能。具体的事情都是插件来实现的,包括:创建容器网络空间(network namespace)、把网络接口(interface)放到对应的网络空间、给网络接口分配 IP 等等。
关于网络,Docker 也提出了 CNM 标准,它要解决的问题和 CNI 是重合的,也就是说目前两者是竞争关系。
目前 CNM 只能使用在 Docker 中,而 CNI 可以使用在任何容器运行时。CNM 主要用来实现 docker 自身的网络问题,也就是 docker network
子命令提供的功能。
接口定义
CNI 的接口中包括以下几个方法:
type CNI interface {
AddNetworkList (net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
DelNetworkList (net *NetworkConfigList, rt *RuntimeConf) error
AddNetwork (net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
DelNetwork (net *NetworkConfig, rt *RuntimeConf) error
}
该接口只有四个方法,添加网络、删除网络、添加网络列表、删除网络列表。
官方网络插件
所有的标准和协议都要有具体的实现,才能够被大家使用。CNI 也不例外,目前官方在 github 上维护了同名的 CNI 代码库,里面已经有很多可以直接拿来使用的 CNI 插件。
官方提供的插件目前分成三类:main、meta 和 ipam。main 是主要的实现了某种特定网络功能的插件;meta 本身并不会提供具体的网络功能,它会调用其他插件,或者单纯是为了测试;ipam 是分配 IP 地址的插件。
ipam 并不提供某种网络功能,只是为了灵活性把它单独抽象出来,这样不同的网络插件可以根据需求选择 ipam,或者实现自己的 ipam。
这些插件的功能说明如下:
- main
- loopback:这个插件很简单,负责生成
lo
网卡,并配置上127.0.0.1/8
地址 - bridge:和 docker 默认的网络模型很像,把所有的容器连接到虚拟交换机上
- macvlan:使用 macvlan 技术,从某个物理网卡虚拟出多个虚拟网卡,它们有独立的 ip 和 mac 地址
- ipvlan:和 macvlan 类似,区别是虚拟网卡有着相同的 mac 地址
- ptp:通过 veth pair 在容器和主机之间建立通道
- loopback:这个插件很简单,负责生成
- meta
- flannel:结合 bridge 插件使用,根据 flannel 分配的网段信息,调用 bridge 插件,保证多主机情况下容器
- ipam
- host-local:基于本地文件的 ip 分配和管理,把分配的 IP 地址保存在文件中
- dhcp:从已经运行的 DHCP 服务器中获取 ip 地址
接口参数
网络插件是独立的可执行文件,被上层的容器管理平台调用。网络插件只有两件事情要做:把容器加入到网络以及把容器从网络中删除。调用插件的数据通过两种方式传递:环境变量和标准输入。一般插件需要三种类型的数据:容器相关的信息,比如 ns 的文件、容器 id 等;网络配置的信息,包括网段、网关、DNS 以及插件额外的信息等;还有就是 CNI 本身的信息,比如 CNI 插件的位置、添加网络还是删除网络。
我们来看一下为容器添加网络是怎么工作的,删除网络和它过程一样。
把容器加入到网络
调用插件的时候,这些参数会通过环境变量进行传递:
CNI_COMMAND
:要执行的操作,可以是ADD
(把容器加入到某个网络)、DEL
(把容器从某个网络中删除)CNI_CONTAINERID
:容器的 ID,比如 ipam 会把容器 ID 和分配的 IP 地址保存下来。可选的参数,但是推荐传递过去。需要保证在管理平台上是唯一的,如果容器被删除后可以循环使用CNI_NETNS
:容器的 network namespace 文件,访问这个文件可以在容器的网络 namespace 中操作CNI_IFNAME
:要配置的 interface 名字,比如eth0
CNI_ARGS
:额外的参数,是由分号;
分割的键值对,比如 “FOO=BAR;hello=world”CNI_PATH
:CNI 二进制查找的路径列表,多个路径用分隔符:
分隔
网络信息主要通过标准输入,作为 JSON 字符串传递给插件,必须的参数包括:
cniVersion
:CNI 标准的版本号。因为 CNI 在演化过程中,不同的版本有不同的要求name
:网络的名字,在集群中应该保持唯一type
:网络插件的类型,也就是 CNI 可执行文件的名称args
:额外的信息,类型为字典ipMasq
:是否在主机上为该网络配置 IP masqueradeipam
:IP 分配相关的信息,类型为字典dns
:DNS 相关的信息,类型为字典
插件接到这些数据,从输入和环境变量解析到需要的信息,根据这些信息执行程序逻辑,然后把结果返回给调用者,返回的结果中一般包括这些参数:
- IPs assigned to the interface:网络接口被分配的 ip,可以是 IPv4、IPv6 或者都有
- DNS 信息:包含 nameservers、domain、search domains 和其他选项的字典
CNI 协议的内容还在不断更新,请到官方文档获取当前的信息。
CNI 的特性
CNI 作为一个协议/标准,它有很强的扩展性和灵活性。如果用户对某个插件有额外的需求,可以通过输入中的 args
和环境变量 CNI_ARGS
传输,然后在插件中实现自定义的功能,这大大增加了它的扩展性;CNI 插件把 main 和 ipam 分开,用户可以自由组合它们,而且一个 CNI 插件也可以直接调用另外一个 CNI 插件,使用起来非常灵活。
如果要实现一个继承性的 CNI 插件也不复杂,可以编写自己的 CNI 插件,根据传入的配置调用 main 中已经有的插件,就能让用户自由选择容器的网络。
在 kubernetes 中的使用
CNI 目前已经在 kubernetes 中开始使用,也是目前官方推荐的网络方案,具体的配置方法可以参考kubernetes 官方文档。
kubernetes 使用了 CNI 网络插件之后,工作过程是这样的:
- kubernetes 先创建 pause 容器生成对应的 network namespace
- 调用网络 driver(因为配置的是 CNI,所以会调用 CNI 相关代码)
- CNI driver 根据配置调用具体的 cni 插件
- cni 插件给 pause 容器配置正确的网络
- pod 中其他的容器都是用 pause 的网络
容器存储接口CSI
背景
Kubernetes原生支持一些存储类型的 PV,如 iSCSI、NFS、CephFS 等等,这些 in-tree 类型的存储代码放在 Kubernetes 代码仓库中。这里带来的问题是 Kubernetes 代码与三方存储厂商的代码强耦合:
- 更改 in-tree 类型的存储代码,用户必须更新 Kubernetes组件,成本较高
- in-tree 存储代码中的 bug 会引发 Kubernetes组件不稳定
- Kubernetes社区需要负责维护及测试 in-tree 类型的存储功能
- in-tree 存储插件享有与 Kubernetes核心组件同等的特权,存在安全隐患
- 三方存储开发者必须遵循 KKubernetes8s 社区的规则开发 in-tree 类型存储代码
CSI 容器存储接口标准的出现解决了上述问题,将三方存储代码与 Kubernetes代码解耦,使得三方存储厂商研发人员只需实现 CSI 接口(无需关注容器平台是 Kubernetes还是 Swarm 等)。
简介
容器存储接口(Container Storage Interface),简称 CSI,CSI 试图建立一个行业标准接口的规范,借助 CSI 容器编排系统(CO)可以将任意存储系统暴露给自己的容器工作负载。
csi
卷类型是一种 out-tree(即跟其它存储插件在同一个代码路径下,随 Kubernetes 的代码同时编译的) 的 CSI 卷插件,用于 Pod 与在同一节点上运行的外部 CSI 卷驱动程序交互。部署 CSI 兼容卷驱动后,用户可以使用 csi
作为卷类型来挂载驱动提供的存储。
CSI 持久化卷支持是在 Kubernetes v1.9 中引入的,作为一个 alpha 特性,必须由集群管理员明确启用。换句话说,集群管理员需要在 apiserver、controller-manager 和 kubelet 组件的 “--feature-gates =
” 标志中加上 “CSIPersistentVolume = true
”。
CSI 持久化卷具有以下字段可供用户指定:
driver
:一个字符串值,指定要使用的卷驱动程序的名称。必须少于 63 个字符,并以一个字符开头。驱动程序名称可以包含 “。”、“ - ”、“_” 或数字。volumeHandle
:一个字符串值,唯一标识从 CSI 卷插件的CreateVolume
调用返回的卷名。随后在卷驱动程序的所有后续调用中使用卷句柄来引用该卷。readOnly
:一个可选的布尔值,指示卷是否被发布为只读。默认是 false。
参考
https://www.jianshu.com/p/c7748893ab00