虚拟化及云计算硬核技术内幕 (34) —— 墨菲定律与古希腊神话

2022-09-08 17:10:21 浏览数 (1)

Hi在上一期《虚拟化及云计算硬核技术内幕 (33) —— 你说的这个朋友是不是你自己》中,我们理解了怎么样使用docker实现应用的快速部署。

由于docker的启动无需重新启动一个操作系统,还可以复用宿主机操作系统内置的依赖库等运行时所需要的组件,把应用打包成为docker镜像后,部署和启动应用的过程可以大大缩短。

那么,如果需要为应用在不同的宿主机上,启动总共100个实例,我们应该怎么做呢?

思路A:手工在若干台宿主机上敲docker run命令:

docker run -itd --net bridge --ip 172.17.0.10

(记得别忘了指定不同的IP地址,并为其指定bridge方式的docker容器网络,不然应用是无法正常启动或提供服务的)

显然,这种脑回路会被认为是很傻很天真 (洋文曰:Too simple, sometimes naive),然后被大家批判一番。

思路B:利用Ansible一类的自动化工具,定制可以驱动docker的剧本(playbook),自动化在不同宿主机上执行docker命令。

这种思路有几个缺陷:

  1. 扩展性,如果运行docker的集群数量由10台扩展为20台,操作者是需要手工修改playbook的;
  2. 灵活性,如果需要修改这些docker容器的IP地址段,也需要手工修改playbook;
  3. 容错性,一旦playbook执行出错,是很难发现和定位问题的;

因此,这只是权宜之计。

思路C:开发一套程序,进行docker容器的自动编排,自动化批量运行和调度容器,为容器赋予网络和存储等功能。这被叫做容器编排平台。

Kubernetes就是最常见的容器编排平台。

Kubernetes的介绍,大家可以在其官方网站获取:

https://kubernetes.io/

熟悉kubernetes的同学可以很容易地回忆起来,kubernetes有deployment, deamonset, cronjob等各种工作负载模型,可以自动化地批量拉起容器,并将容器按策略调度到工作节点上。实际上,在kubernetes早期的版本中,真正的工作还是由docker完成的。

那么,kubernetes是如何调用docker的呢?是通过命令或脚本的方式吗?

我们知道,在Kubernetes的每个work node上,会部署一个kubelet。Kubernetes的master上的scheduler,会驱动kubelet,调用docker一类的容器运行时,拉取容器镜像,实施容器的启动/销毁等行为。

如图,kubelet位于每个节点上,让container runtime运行C1,C2,C3等容器,并通过kube-proxy向网络提供服务。

让我们注意到一个细节:

docker运行一个容器的步骤,其实分为三个部分:

1. 获取容器镜像;

2. 对容器镜像进行解析,并解包到unionfs;

3. 从unionfs中读取容器中应用的可执行文件并执行;

而容器运行时的OCI(Open Container Initiative)组织,制定的容器运行时标准,只规范了第3部分的行为。如果其他容器运行时组件没有实现1和2,实际上并不能实现镜像的管理,也就是无法提供完整的取代docker的功能!

根据著名的墨菲定律,如果我们不期望一些事情发生,那么这件事情从长远看是一定会发生的。由于一些特殊的利益分配原因,kubernetes宣布从1.22版本起不再支持docker,取而代之的是containerd,rkt或cri-o一类第三方容器运行时引擎。

也正如墨菲定律描述的那样,虽然containerd和cri-o完整地实现了docker的三个功能,但其他容器运行时引擎(如lxc,runc等),实际上只能实现功能3,并不能实现功能1/2,也就是无法获取和管理容器的镜像。这一类容器运行时引擎被称为"Low level container runtime"。与此对立的则是"High level container runtime"。后者可以实现镜像管理,以及gRPC方式的API接口。

这种gRPC方式的API接口,就是所谓的CRI(Container Runtime Interface)接口,Kubernetes通过CRI接口能够实现让High level container runtime获取和管理容器镜像。

CRI的介绍可以看这里: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md

CRI中,定义了两个gRPC服务:RuntimeService 和 ImageManagerService,分别用于容器运行时和镜像的管理。

https://github.com/kubernetes/cri-api/blob/master/pkg/apis/services.go

这个地方有其定义:

代码语言:javascript复制
// RuntimeService interface should be implemented by a container runtime.
// The methods should be thread-safe.
type RuntimeService interface {
  RuntimeVersioner
  ContainerManager
  PodSandboxManager
  ContainerStatsManager


  // UpdateRuntimeConfig updates runtime configuration if specified
  UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error
  // Status returns the status of the runtime.
  Status(verbose bool) (*runtimeapi.StatusResponse, error)
}
代码语言:javascript复制
// ImageManagerService interface should be implemented by a container image
// manager.
// The methods should be thread-safe.
type ImageManagerService interface {
  // ListImages lists the existing images.
  ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error)
  // ImageStatus returns the status of the image.
  ImageStatus(image *runtimeapi.ImageSpec, verbose bool) (*runtimeapi.ImageStatusResponse, error)
  // PullImage pulls an image with the authentication config.
  PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error)
  // RemoveImage removes the image.
  RemoveImage(image *runtimeapi.ImageSpec) error
  // ImageFsInfo returns information of the filesystem that is used to store images.
  ImageFsInfo() ([]*runtimeapi.FilesystemUsage, error)
}

以对标docker的containerd为例,它的工作原理如下图:

图中,Kubernetes通过Kubelet,经grpc(CRI)调用containerd的daemon,containerd本身可以实现镜像的拉取,解析和管理,而具体pod的运行则由另一个low level container runtime实现,如runc。containerd和runc之间的接口叫做shim。

有了low level container runtime和high level container runtime的配合,可以构成完整的容器运行时,从而支持Kubernetes为代表的容器编排引擎自动化发放容器业务,快速实现应用的部署和自动伸缩。

提到应用的自动伸缩,就不得不提到古希腊神话中的一个人物——

大家猜猜看是谁?

0 人点赞