kubernetes的核心竞争力是对各种workload的抽象和编排,在与计算、存储和网络对接中,分别对应衍生了其对接的事实标准CRI、CNI和CSI。今天我们来重点分析kubelet运行时,另外二者在接下来的文章中再做详细的介绍。
kubelet有以下的启动参数用来指定运行时:
–container-runtime string
The container runtime to use. Possible values: ‘docker’, ‘remote’, ‘rkt(deprecated)’. (default “docker”)
–container-runtime-endpoint string
[Experimental] The endpoint of remote runtime service. Currently unix socket is supported on Linux, and tcp is supported on windows.
当指定-container-runtime
为remote时,对应不会在kubelet启动docker-server,而直接调用CRI接口访问通过--container-runtime-endpoint
指定的服务,一般这是一个containerd或者CRIO。
部署架构
先从kubelet默认部署的dockershim运行时来分析。下面这张图展现了CRI客户端与服务端、以及服务端与底层服务是如何协调工作的。
客户端
和服务端之间基于unix socket套接字/run/dockershim.sock
,通过gRPC来通信。服务端
在kubelet源码中,整个服务端的代码都位于dockershim包中。它实现了对container、image、checkpoint、logs以及sandbox的操作,其本质都是调用docker和CNI plugin来实现的。这里值得特殊提到的是:若只想kubelet启动CRI server,而不启动kubelet其他功能,可以在启动参数中指定
--experimental-dockershim
参数的值为true
。
客户端
crictl
我们知道,如今安装kubeadm的时候,都会安装对应依赖包,比如kubelet
和crictl
等。这里的crictl
就是一个独立的客户端工具,当kubelet启动之后,可以基于它来调试CRI server。为了便于理解cri的各功能,我将crictl的命令提示列出来,后面是对每个命令操作的描述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37[root@vm ~]# crictl --help
NAME:
crictl - client for CRI
USAGE:
crictl [global options] command [command options] [arguments...]
VERSION:
v1.12.0
COMMANDS:
attach Attach to a running container
create Create a new container
exec Run a command in a running container
version Display runtime version information
images List images
inspect Display the status of one or more containers
inspecti Return the status of one or more images
inspectp Display the status of one or more pods
logs Fetch the logs of a container
port-forward Forward local port to a pod
ps List containers
pull Pull an image from a registry
runp Run a new pod
rm Remove one or more containers
rmi Remove one or more images
rmp Remove one or more pods
pods List pods
start Start one or more created containers
info Display information of the container runtime
stop Stop one or more running containers
stopp Stop one or more running pods
update Update one or more running containers
config Get and set crictl options
stats List container(s) resource usage statistics
completion Output bash shell completion code
help, h Shows a list of commands or help for one commandkubelet
在阅读kubelet源码的时候,有经常看到kubeGenericRuntimeManager
这个类。它在kubelet初始化的时候被创建,是整个kubelet的runtime;一旦kubelet需要容器底层操作资源时,就会调动这个接口方法来处理。
这两个客户端都是通过gRPC来访问CRI server的,所以当使用crictl的时候,需要保障其版本与kubelet的版本一致性,否则可能protobuf定义的报文格式有差异,导致无法正常访问。
服务端
服务端是通过dockershim包中的dockerService
来实现的。它实现了CRI的所有接口(pod对容器运行时的需求),在这些接口的handler中包含了抽象的业务逻辑,包括网络、日志、流式读写、镜像管理等。
在这些抽象之下,最终依然是调用容器和CNI接口实现的功能。
- 使用dockerlib库,基于
/run/docker.sock
来访问docker engine; - 从
/etc/cni/*
下查询pod所使用的CNI网络类型,然后调用/opt/cni/*
下的CNI插件来为pod添加、卸载网络。
源码分析
kubelet中,对容器运行时的源码主要包含以下几块(结合下图来分析):
服务端(dockershim包中)
dockershim.NewDockerService
创建出dockerService
类,它实现了CRI的所有接口,对应代码在action指定的文件中实现。同时,该类的属性包含了CNI、streamServicer、containerManager、checkPointHandler等,分别为CRI接口提供各个方面的功能。启动服务端(kubelet中)
图中上半部分是kubelet启动的时候,指定了--experimental-dockershim=true
参数后,只启动CRI server的流程;而下半部分是完整的kubelet启动后的流程。protobuf接口定义(api)
也就是定义CRI的方法和消息结构的定义了,主要位于kubernetes项目的/pkg/kubelet/apis/cri/
目录下。客户端
kubelet的客户端代码其实就是调用gRPC的接口了,主要位于GenericRuntimeManager
中。在上图中可以看到,kubelet会通过klet.runtime调用操作pod各种资源的方法。
演变方向
从基于dockerd到基于containerd,最后希望直接基于OCI标准,K8S试图取消中间所有环节,直接到达RunC。当然,k8s必须提供直接调试底层runC的工具来取代docker,否则kubelet已挂,整个机器就瘫痪了。当前crictl
就是一个比较好的工具!
Containerd
CRI-Containerd
在containerd早期的版本中,对CRI的支持是通过外部适配服务来实现的;自v1.1之后,containerd的势力逐渐往外扩展,将CRI作为plugin来运行,该plugin意图替代dockershim,除了提供CRI的支持之外,还提供对CNI的调用。
下图是containerd的架构图。从北向看,containerd在保留其对原有API的情况下,还对外向k8s提供了CRI API。从南向看,containerd除了支持runc外,也支持runhcs和kata等。
containerd的配置文件/etc/containerd/config.toml
, 可以看到,其中包含了关于CNI的配置信息。
1 | [plugins] |
现在使用CRI-Containerd来安装kubelet不再需要那么繁琐了,需要安装的,只有以下三个包。在使用docker的时候,其实是docker的安装包帮我们一并安装的以下三个可执行文件。
Binary Name | Support | OS | Architecture |
---|---|---|---|
containerd | seccomp, apparmor, overlay, btrfs |
linux | amd64 |
containerd-shim | overlay, btrfs | linux | amd64 |
runc | seccomp, apparmor | linux | amd64 |
其最终运行之后,相互之间调用的通信是这样的:
nvidia-docker
Nvidia提供了k8s pod使用GPU的一整套解决方案。运行时方面,nvidia提供了特定的运行时,主要的功能是为了让container访问从node节点上分配的GPU资源。
如上图所示,libnvidia-container被整合进docker的runc中。通过在runc的prestart hook 中调用nvidia-container-runtime-hook来控制GPU。启动容器时,prestart hook会校验环境变量GPU-enabled来校验该容器是否需要启用GPU,一旦确认需要启用,就调用nvidia定制的运行时来启动容器,从而为容器分配limit指定个数的GPU。
- 环境变量
NVIDIA_VISIBLE_DEVICES
: controls which GPUs will be accessible inside the container. By default, all GPUs are accessible to the container.NVIDIA_DRIVER_CAPABILITIES
: controls which driver features (e.g. compute, graphics) are exposed to the container.NVIDIA_REQUIRE_*
: a logical expression to define the constraints (e.g. minimum CUDA, driver or compute capability) on the configurations supported by the container.
下面是简单使用示例:
1 | $ sudo docker run -it --runtime=nvidia --shm-size=1g -e NVIDIA_VISIBLE_DEVICES=0,1 --rm nvcr.io/nvidia/pytorch:18.05-py3 |
在k8s的调度方面,nvidia基于k8s的device plugin来实现了kubelet层GPU资源的上报,通过在pod的spec中指定对应的limit来声明对GPU个数的申请情况。在spec中必须指定limit的值(且必须为整数),reqire的值要么不设置,要么等于limit的值。
kata
kata因为其底层使用hypervisor的虚拟化技术,在安全和隔离性方面对很多场景都有较强的吸引力,其发展也很不错;下面是kata的适配图。由于篇幅所限,后续再抽个机会,专门写一篇关于kata的文章。