三、应用编排与管理:核心原理
1、资源元信息
Kubernetes的资源对象组成:主要包括了Spec、Status两部分。其中Spec部分用来描述期望的状态,Status部分用来描述观测到的状态
Kubernetes的元数据部分。该部分主要包括了用来识别资源的标签:Labels;用来描述资源的注解:Annotations;用来描述多个资源之间相互关系的OwnerReference
1)、labels
Labels是一种具有标识型的Key:Value元数据。标签主要用来筛选资源和组合资源,可以使用类似于SQL查询select,来根据Label查询相关的资源
2)、Selector
最常见的Selector就是相等型Selector
假设系统中有四个Pod,每个Pod都有标识系统层级和环境的标签,通过Tie:front这个标签,可以匹配左边栏的Pod,相等型Selector还可以包括多个相等条件,多个相等条件之间是逻辑与的关系
通过Tie=front,Env=dev的Selector,可以筛选出所有Tie=front,而且Env=dev的Pod,也就是上图中左上角的Pod。另外一种Selector是集合型Selector,在例子中,Selector筛选所有环境是test或者gray的Pod
除了in的集合操作外,还有notin集合操作,比如tie notin(front,back),将会筛选所有tie不是front且不是back的Pod。另外,也可以根据是否存在某lable的筛选,如:Selector release,筛选所有带release标签的Pod。集合型和相等型的Selector,也可以用,
来连接,同样的标识逻辑与的关系
3)、Annotations
Annotations一般是系统或者工具用来存储资源的非标示性信息,可以用来扩展资源的spec/status的描述
4)、Ownereference
Ownereference一般就是指集合类的资源,比如说Pod集合,就有replicaset、statefulset
集合类资源的控制器会创建对应的归属资源。比如:replicaset控制器在操作中会创建Pod,被创建Pod的Ownereference就指向了创建Pod的replicaset,Ownereference使得用户可以方便地查找一个创建资源的对象,另外,还可以用来实现级联删除的效果
2、kubectl查看和修改Kubernetes元数据
pod1.yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx1
namespace: default
labels:
env: dev
tie: front
spec:
containers:
- name : nginx
image: nginx:1.8
ports:
- containerPort: 80
pod2.yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx2
namespace: default
labels:
env: dev
tie: front
spec:
containers:
- name : nginx
image: nginx:1.8
ports:
- containerPort: 80
创建两个pod
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods
No resources found in default namespace.
hanxiantaodeMBP:yamls hanxiantao$ kubectl apply -f pod1.yaml
pod/nginx1 created
hanxiantaodeMBP:yamls hanxiantao$ kubectl apply -f pod2.yaml
pod/nginx2 created
查看所有pod的标签
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx1 1/1 Running 0 62s env=dev,tie=front
nginx2 1/1 Running 0 58s env=dev,tie=front
查看nginx1这个pod的详细信息
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods nginx1 -o yaml | less
修改pod的label,改为env=test
hanxiantaodeMBP:yamls hanxiantao$ kubectl label pods nginx1 env=test
error: 'env' already has a value (dev), and --overwrite is false
hanxiantaodeMBP:yamls hanxiantao$ kubectl label pods nginx1 env=test --overwrite
pod/nginx1 labeled
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx1 1/1 Running 0 5m39s env=test,tie=front
nginx2 1/1 Running 0 5m35s env=dev,tie=front
去掉pod的tie这个标签
hanxiantaodeMBP:yamls hanxiantao$ kubectl label pods nginx1 tie-
pod/nginx1 labeled
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx1 1/1 Running 0 6m58s env=test
nginx2 1/1 Running 0 6m54s env=dev,tie=front
通过label来筛选pod
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels -l env=test
NAME READY STATUS RESTARTS AGE LABELS
nginx1 1/1 Running 0 7m33s env=test
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels -l env=test,env=dev
No resources found in default namespace.
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels -l env=dev,tie=front
NAME READY STATUS RESTARTS AGE LABELS
nginx2 1/1 Running 0 8m4s env=dev,tie=front
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods --show-labels -l 'env in (test,dev)'
NAME READY STATUS RESTARTS AGE LABELS
nginx1 1/1 Running 0 8m36s env=test
nginx2 1/1 Running 0 8m32s env=dev,tie=front
添加annotate
hanxiantaodeMBP:yamls hanxiantao$ kubectl annotate pods nginx1 my-annotate='my comment, ok'
pod/nginx1 annotated
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods -o yaml | less
apiVersion: v1
kind: Pod
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"env":"dev","tie":"front"},"name":"nginx1","namespace":"default"},"spec":{"containers":[{"image":"nginx:1.8","name":"nginx","ports":[{"containerPort":80}]}]}}
my-annotate: my comment, ok
creationTimestamp: "2020-12-24T00:18:18Z"
labels:
env: test
rs.yaml:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-replicasets
namespace: default
labels:
env: prod
spec:
replicas: 2
selector:
matchLabels:
env: prod
template:
metadata:
labels:
env: prod
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
通过创建replicaset对象来创建pod,pod中会包含ownerReference信息
hanxiantaodeMBP:yamls hanxiantao$ kubectl apply -f rs.yaml
replicaset.apps/nginx-replicasets created
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-replicasets-ld4n6 1/1 Running 0 2m3s
nginx-replicasets-xvr6k 1/1 Running 0 2m3s
nginx1 1/1 Running 0 27m
nginx2 1/1 Running 0 27m
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pods nginx-replicasets-ld4n6 -o yaml | less
name: nginx-replicasets-ld4n6
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: nginx-replicasets
uid: 5beab4c4-3aae-4c6c-a3f2-3822a2e3ba3a
resourceVersion: "567337"
selfLink: /api/v1/namespaces/default/pods/nginx-replicasets-ld4n6
uid: 9dbcafce-72ec-4088-a0ab-3eccaba95c2d
spec:
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
resources: {
}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-6c86p
readOnly: true
3、控制器模式
1)、控制循环
控制型模式最核心的就是控制循环的概念。在控制循环中包括了控制器,被控制的系统,以及能够观测系统的传感器,三个逻辑组件
外界通过修改资源spec来控制资源,控制器比较资源spec和status,从而计算一个diff,diff最后会用来决定执行对系统进行什么样的控制操作,控制操作会使得系统产生新的输出,并被传感器以资源status形式上报,控制器的各个组件将都会是独立自主地运行,不断使系统向spec表示终态趋近
2)、Sensor
控制循环中逻辑的传感器主要由Reflector、Informer、Indexer三个组件构成
Reflector通过List和Watch K8s server来获取资源的数据。List用来在Controller重启以及Watch中断的情况下,进行系统资源的全量更新;而Watch则在多次List之间进行增量的资源更新;Reflector在获取新的资源数据后,会在Delta队列中塞入一个包括资源对象信息本身以及资源对象事件类型的Delta记录,Delta队列中可以保证同一个对象在队列中仅有一条记录,从而避免Reflector重新List和Watch的时候产生重复的记录
Informer组件不断地从Delta队列中弹出delta记录,然后把资源对象交给indexer,让indexer把资源记录在一个缓存中,缓存在默认设置下是用资源的命名空间来做索引的,并且可以被Controller Manager或多个Controller所共享。之后,再把这个事件交给事件的回调函数
控制循环中的控制器组件主要由事件处理函数以及worker组成,事件处理函数之间会相互关注资源的新增、更新、删除的事件,并根据控制器的逻辑去决定是否需要处理。对需要处理的事件,会把事件关联资源的命名空间以及名字塞入一个工作队列中,并且由后续的worker池中的一个Worker来处理,工作队列会对存储的对象进行去重,从而避免多个Woker处理同一个资源的情况
Worker在处理资源对象时,一般需要用资源的名字来重新获得最新的资源数据,用来创建或者更新资源对象,或者调用其他的外部服务,Worker如果处理失败的时候,一般情况下会把资源的名字重新加入到工作队列中,从而方便之后进行重试
3)、控制循环例子——扩容
ReplicaSet是一个用来描述无状态应用的扩缩容行为的资源,ReplicaSet controler通过监听ReplicaSet资源来维持应用希望的状态数量,ReplicaSet中通过selector来匹配所关联的Pod,在这里考虑ReplicaSet rsA的,replicas从2被改到3的场景
首先,Reflector会watch到ReplicaSet和Pod两种资源的变化。发现ReplicaSet发生变化后,在delta队列中塞入了对象是rsA,而且类型是更新的记录
Informer一方面把新的ReplicaSet更新到缓存中,并与Namespace nsA作为索引。另外一方面,调用Update的回调函数,ReplicaSet控制器发现ReplicaSet发生变化后会把字符串的nsA/rsA字符串塞入到工作队列中,工作队列后的一个Worker从工作队列中取到了nsA/rsA这个字符串的key,并且从缓存中取到了最新的ReplicaSet数据
Worker通过比较ReplicaSet中spec和status里的数值,发现需要对这个ReplicaSet进行扩容,因此ReplicaSet的Worker创建了一个Pod,这个pod中的Ownereference取向了ReplicaSet rsA
然后Reflector Watch到的Pod新增事件,在delta队列中额外加入了Add类型的deta记录,一方面把新的Pod记录通过Indexer存储到了缓存中,另一方面调用了ReplicaSet控制器的Add回调函数,Add回调函数通过检查pod ownerReferences找到了对应的ReplicaSet,并把包括ReplicaSet命名空间和字符串塞入到了工作队列中
ReplicaSet的Woker在得到新的工作项之后,从缓存中取到了新的ReplicaSet记录,并得到了其所有创建的Pod,因为ReplicaSet的状态不是最新的,也就是所有创建Pod的数量不是最新的。因此在此时ReplicaSet更新status使得spec和status达成一致
4、控制器模式总结
Kubernetes所采用的控制器模式,是由声明式API驱动的。确切来说,是基于对Kubernetes资源对象的修改来驱动的
Kubernetes资源之后,是关注该资源的控制器。这些控制器将异步的控制系统向设置的终态驱近
这些控制器是自主运行的,使得系统的自动化和无人值守成为可能
因为Kubernete的控制器和资源都是可以自定义的,因此可以方便的扩展控制器模式。特别是对于有状态应用,我们往往通过自定义资源和控制器的方式,来自动化运维操作。这个也就是operator的场景
四、应用编排与管理:Deployment
1、需求来源
如果直接管理集群中所有的Pod,应用A、B、C的Pod,其实是散乱地分布在集群中
现在有以下的问题:
- 首先,如何保证集群内可用Pod的数量?也就是说我们应用A四个Pod如果出现了一些宿主机故障,或者一些网络问题,如何能保证它可用的数量?
- 如何为所有Pod更新镜像版本?我们是否要某一个Pod去重建新版本的Pod?
- 然后在更新过程中,如何保证服务的可用性?
- 以及更新过程中,如果发现了问题,如何快速回滚到上一个版本?
通过Deployment将应用A、B、C分别规划到不同的Deployment中,每个Deployment其实是管理的一组相同的应用Pod,这组Pod我们认为它是相同的一个副本,那么Deployment能帮我们做什么事情呢?
1)首先,Deployment定义了一种Pod期望数量,比如说应用A,我们期望Pod数量是四个,那么这样的话,controller就会持续维持Pod数量为期望的数量。当我们与Pod出现了网络问题或者宿主机问题的话,controller能帮我们恢复,也就是新扩出来对应的Pod,来保证可用的Pod数量与期望数量一致
2)配置Pod发布方式,也就是说controller会按照用户给定的策略来更新Pod,而且更新过程中,也可以设定不可用Pod数量在多少范围内
3)如果更新过程中发生问题的话,即所谓一键回滚,也就是说你通过一条命令或者一行修改能够将Deployment下面所有Pod更新为某一个旧版本
2、用例解读
1)、Deployment语法
apiVersion:apps/v1
,也就是说Deployment当前所属的组是apps,版本是v1
Deployment作为一个K8s资源,它有自己的metadata
元信息,这里定义的Deployment.name是nginx-deployment
Deployment.spec
中首先要有一个核心的字段,即replicas,这里定义期望的Pod数量为三个;selector其实是Pod选择器,那么所有扩容出来的Pod,它的Labels必须匹配selector层上的image.labels,也就是app:nginx
2)、查看Deployment状态
可以通过kubectl get deployment,看到Deployment总体的一个状态
- DESIRED:期望的Pod数量是3个
- CURRENT:当前实际Pod数量是3个
- UP-TO-DATE:其实是到达最新的期望版本的Pod数量
- AVAILABLE:这个其实是运行过程中可用的Pod数量。这里AVAILABLE并不简单是可用的,也就是Ready状态的,它其实包含了一些可用超过一定时间长度的Pod
- AGE:deployment创建的时长,如上图Deployment就是已经创建了80分钟
3)、查看Pod
Pod名字格式最前面一段:nginx-deployment,其实是Pod所属 Deployment.name;中间一段:template-hash,这里三个Pod是一样的,因为这三个Pod其实都是同一个template中创建出来的;最后一段,是一个random的字符串
通过get.pod可以看到,Pod的ownerReferences即Pod所属的controller资源,并不是Deployment,而是一个ReplicaSet。这个ReplicaSet的name,其实是nginx-deployment加上pod.template-hash。所有的Pod都是ReplicaSet创建出来的,而ReplicaSet它对应的某一个具体的Deployment.template版本
4)、更新镜像
首先kubectl后面有一个set image固定写法,这里指的是设定镜像;其次是一个deployment.v1.apps,这里也是一个固定写法,写的是我们要操作的资源类型,deployment是资源名、v1是资源版本、apps是资源组,这里也可以简写为deployment或者deployment.apps,比如说写为deployment的时候,默认将使用apps组v1版本
第三部分是要更新的deployment的name,也就是我们的nginx-deployment;再往后的nginx其实指的是template,也就是Pod中的container.name;这里可以注意到:一个Pod中,其实可能存在多个container,而我们指定想要更新的镜像的container.name,就是nginx
最后,指定我们这个容器期望更新的镜像版本,这里指的是nginx: 1.9.1。如上图所示:当执行完这条命令之后,可以看到deployment中的template.spec已经更新为nginx: 1.9.1
5)、快速回滚
通过kubectl执行的话,其实是kubectl rollout undo
这个命令,可以回滚到Deployment上一版本;通过rollout undo
加上to-revision
来指定可以回滚到某一个具体的版本
6)、DeploymeStatus
deploymentStatus中描述的三个其实是它的conversion状态,也就是Processing、Complete以及Failed
以Processing为例:Processing指的是Deployment正在处于扩容和发布中。比如说Processing状态的deployment,它所有的replicas及Pod副本全部达到最新版本,而且是available,这样的话,就可以进入complete状态。而complete状态如果发生了一些扩缩容的话,也会进入processing这个处理工作状态
如果在处理过程中遇到一些问题:比如说拉镜像失败了,或者说readiness probe检查失败了,就会进入failed状态;如果在运行过程中即complete状态,中间运行时发生了一些pod readiness probe检查失败,这个时候deployment也会进入failed状态。进入failed状态之后,除非所有点replicas均变成available,而且是updated最新版本,deployment才会重新进入complete状态
3、操作演示
hanxiantaodeMBP:yamls hanxiantao$ kubectl create -f deployment-case.yaml
deployment.apps/nginx-deployment created
hanxiantaodeMBP:yamls hanxiantao$ kubectl get deployment nginx-deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 15s
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-5d59d67564-528s8 1/1 Running 0 30s
nginx-deployment-5d59d67564-6znl8 1/1 Running 0 30s
nginx-deployment-5d59d67564-q47cp 1/1 Running 0 30s
hanxiantaodeMBP:yamls hanxiantao$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx-deployment-5d59d67564 3 3 3 99s
升级nginx为nginx:1.9.1
hanxiantaodeMBP:yamls hanxiantao$ kubectl set image deployment nginx-deployment nginx=nginx:1.9.1
deployment.apps/nginx-deployment image updated
hanxiantaodeMBP:yamls hanxiantao$ kubectl edit deployment nginx-deployment
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.9.1
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
resources: {
}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {
}
terminationGracePeriodSeconds: 30
hanxiantaodeMBP:yamls hanxiantao$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-69c44dfb78-6v2gv 1/1 Running 0 2m2s
nginx-deployment-69c44dfb78-gzbnf 1/1 Running 0 83s
nginx-deployment-69c44dfb78-j7wwl 1/1 Running 0 85s
hanxiantaodeMBP:yamls hanxiantao$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx-deployment-5d59d67564 0 0 0 8m33s
nginx-deployment-69c44dfb78 3 3 3 5m47s
回滚到上一版本
hanxiantaodeMBP:yamls hanxiantao$ kubectl rollout undo deployment nginx-deployment
deployment.apps/nginx-deployment rolled back
hanxiantaodeMBP:yamls hanxiantao$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx-deployment-5d59d67564 3 3 3 12m
nginx-deployment-69c44dfb78 0 0 0 9m15s
4、架构设计
1)、管理模式
Deployment只负责管理不同版本的ReplicaSet,由ReplicaSet来管理具体的Pod副本数,每个ReplicaSet对应Deployment template的一个版本
如上图所示:Deployment创建ReplicaSet,而ReplicaSet创建Pod。他们的OwnerRef其实都对应了其控制器的资源
2)、Deployment控制器
所有的控制器都是通过Informer中的Event 做一些Handler和Watch。这个地方Deployment控制器,其实是关注Deployment和ReplicaSet中的event,收到事件后会加入到队列中。而Deployment controller从队列中取出来之后,它的逻辑会判断Check Paused,这个Paused其实是Deployment是否需要新的发布,如果Paused设置为true的话,就表示这个Deployment只会做一个数量上的维持,不会做新的发布
如果Check paused为Yes也就是true的话,那么只会做Sync replicas。也就是说把replicas sync同步到对应的ReplicaSet 中,最后再Update Deployment status,那么controller这一次的ReplicaSet就结束了
如果paused为false的话,它就会做Rollout,也就是通过Create或者是Rolling的方式来做更新,更新的方式其实也是通过Create/Update/Delete这种ReplicaSet来做实现的
3)、ReplicaSet控制器
当Deployment分配ReplicaSet之后,ReplicaSet控制器本身也是从Informer中watch一些事件,这些事件包含了ReplicaSet和Pod的事件。从队列中取出之后,ReplicaSet controller的逻辑很简单,就只管理副本数。也就是说如果controller发现replicas比Pod数量大的话,就会扩容,而如果发现实际数量超过期望数量的话,就会删除Pod
上面Deployment控制器的图中可以看到,Deployment控制器其实做了更复杂的事情,包含了版本管理,而它把每一个版本下的数量维持工作交给ReplicaSet来做
4)、扩容模拟
有一个Deployment,它的副本数是2,对应的ReplicaSet有Pod1和Pod2。这时如果我们修改Deployment replicas, controller就会把replicas同步到当前版本的ReplicaSet中,这个ReplicaSet发现当前有2个Pod,不满足当前期望3个,就会创建一个新的Pod3
5)、发布模拟
Deployment当前初始的template,比如说template1这个版本。template1这个ReplicaSet对应的版本下有三个Pod:Pod1,Pod2,Pod3
这时修改template中一个容器的image, Deployment controller就会新建一个对应template2的ReplicaSet。创建出来之后ReplicaSet会逐渐修改两个ReplicaSet的数量,比如它会逐渐增加ReplicaSet2中replicas的期望数量,而逐渐减少ReplicaSet1中的Pod数量
那么最终达到的效果是:新版本的Pod为Pod4、Pod5和Pod6,旧版本的Pod已经被删除了,这里就完成了一次发布
6)、回滚模拟
回滚模拟,根据上面的发布模拟可以知道Pod4、Pod5、Pod6已经发布完成。这时发现当前的业务版本是有问题的,如果做回滚的话,不管是通过rollout命令还是通过回滚修改template,它其实都是把template回滚为旧版本的template1
这个时候Deployment会重新修改ReplicaSet1中Pod的期望数量,把期望数量修改为3个,且会逐渐减少新版本也就是ReplicaSet2中的replica数量,最终的效果就是把Pod从旧版本重新创建出来
发布模拟的图中可以看到,其实初始版本中Pod1、Pod2、Pod3是旧版本,而回滚之后其实是Pod7、Pod8、Pod9。就是说它的回滚并不是把之前的Pod重新找出来,而是说重新创建出符合旧版本template的Pod
7)、spec字段解析
8)、升级策略字段解析
课程地址:https://edu.aliyun.com/roadmap/cloudnative?spm=5176.11399608.aliyun-edu-index-014.4.dc2c4679O3eIId#suit