容器编排1:理解控制器模型

在前面的文章中,我们已经对Pod对象进行了深入地讨论。接下来,我们一起来看Kubernetes项目最核心的功能,编排。

一、从一个例子说起

在前面讨论Pod对象时,我们做出了一个这样的总结:“容器,是进程;Pod,是操作系统”。由于容器描述应用时过于简单,因此kubernetes将容器进一步抽象、封装成了Pod。在kubernetes的世界中,通常不会直接操作Pod对象,而是由控制器完成。比如,下面这个名叫nginx-deployment 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80

在这个例子中,Deployment控制器会确保携带了 app=nginx 标签的 Pod 的个数,永远等于 spec.replicas 指定的个数,即 2 个。这意味着,如果在这个集群中,携带 app=nginx 标签的 Pod 的个数大于 2 的时候,就会有旧的 Pod 被删除;反之,就会有新的 Pod 被创建。

二、控制器

在Kubernetes架构中,有一个名叫kube-controller-manager的组件。这个组件,就是一系列控制器的集合。我们可以查看一下 Kubernetes 项目的 pkg/controller 目录:

1
2
3
4
5
6
7
$ cd kubernetes/pkg/controller/
$ ls -d */
deployment/ job/ podautoscaler/
cloud/ disruption/ namespace/
replicaset/ serviceaccount/ volume/
cronjob/ garbagecollector/ nodelifecycle/ replication/ statefulset/ daemon/
...

这个目录下面的每一个控制器,都会以独有的方式负责某种编排功能。而我们的 Deployment,正是这些控制器中的一种。

2.1、控制循环

我们需要知道的是,上面这些控制器之所以被统一放在 pkg/controller 目录下,是因为它们都遵循 Kubernetes 项目中的一个通用编排模式,即:控制循环(control loop)。比如,现在有一个待编排对象 X,它有一个对应的控制器。那么,我就可以用一段 Go 语言风格的伪代码,来描述这个控制循环:

1
2
3
4
5
6
7
8
9
for {
实际状态 := 获取集群中对象X的实际状态(Actual State)
期望状态 := 获取集群中对象x的期望状态(Desired state)
if 实际状态 == 期望状态 {
什么都不做
} else {
执行编排动作,将实际状态调整为期望状态
}
}

在具体的实现中,实际状态往往来自于 Kubernetes 集群本身。比如,kubelet 通过心跳汇报的容器状态和节点状态,或者监控系统中保存的应用监控数据,再或者控制器主动收集的它自己感兴趣的信息,这些都是常见的实际状态的来源。而期望状态,一般来自于用户提交的 YAML 文件。比如,Deployment 对象中 Replicas 字段的值。很明显,这些信息往往都保存在 Etcd 中。

2.2、调谐循环

我们可以以上面的 Deployment 对象为例,简单描述一下控制器模型的实现:

  • Deployment控制器从Etcd中获取到所有携带了“app:ningx”标签的Pod,然后统计它们的数量,这就是实际状态;
  • YAML文件中Deployment对象的Replicas字段的值就是期望状态;
  • Deployment控制器将两个状态做比较,然后根据比较结果,确定是创建Pod,还是删除已有的Pod。

可以看到,一个 Kubernetes 对象的主要编排逻辑,实际上是在第三步的“对比”阶段完成的。这个操作,通常被叫作调谐(Reconcile)。这个调谐的过程,则被称作“Reconcile Loop”(调谐循环)或者“Sync Loop”(同步循环)。而调谐的最终结果,往往都是对被控制对象的某种写操作。比如,增加 Pod,删除已有的 Pod,或者更新 Pod 的某个字段。

2.3、对象模板

控制器,主要负责定义被控制对象的期望状态。比如,Deployment 里的 replicas=2 这个字段。而被控制对象,则来自于一个“模板”。比如,Deployment 里的 template 字段。可以看到,Deployment 这个 template 字段里的内容,跟一个标准的 Pod 对象的 API 定义,丝毫不差。而所有被这个 Deployment 管理的 Pod 实例,其实都是根据这个 template 字段的内容创建出来的。

像 Deployment 定义的 template 字段,在 Kubernetes 项目中有一个专有的名字,叫作 PodTemplate(Pod 模板)。Kubernetes中的大多数控制器,都会使用 PodTemplate 来统一定义它所要管理的 Pod。此外,我们还会看到其他类型的对象模板,比如 Volume 的模板。

在这里,我们可以对 Deployment 以及其他类似的控制器,做一个简单总结:

image-20201002165418796

如上图所示,类似于 Deployment 这样的控制器,实际上都是由上半部分的控制器定义(包括期望状态),加上下半部分的被控制对象的模板组成的。

三、小结

在这篇文章中,我们以Deployment为例,讨论了Kubernetes如何通过“控制器”,实现对各种不同对象或资源的编排:

  • Kubernetes支持多种容器编排功能,比如ReplicaSet、Deployment、StatefulSet、DaemonSet、CronJob等
  • 每一种编排功能都是由一个或多个控制器实现的,每个控制器通过控制循环完成各自的编排逻辑。
  • 在控制循环的作用下,最后的执行结果都和Deployment类似,即要么创建、更新一些Pod,要么删除一些已经存在的Pod(或者其他的API对象、资源)。
  • 当我们描述一个对象时,主要会涉及到两个核心内容:控制器定义(对象所使用的控制器)和被控制对象(即对象本身)。

容器编排1:理解控制器模型
https://kuberxy.github.io/2020/10/02/容器编排1:理解控制器模型/
作者
Mr.x
发布于
2020年10月2日
许可协议