核心技术概念和API对象
API对象是Kubernetes集群中的管理操作单元。
Kubernetes集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的 API对象,支持对该功能的管理操作。
每个API对象都有四大类属性∶
- TypeMeta
- MetaData
- Spec
- Status
几乎所有的对象都包含了上面四大类属性。
TypeMeta
Java里面会有包管理,每个包里面会有不同的类,我们会将相同类型的类放在一个包里面,kubernets也是一样的,针对每一个被管理的对象,它有一个kind,kubermets会将相同功能,相同目的的这些对象放到一个group里面,kind定义了它是什么,group是将不同对象的一个组织方式。
version是用来支持api向前兼容的一个设定,版本是不断演进的,kubernets会在第一个版本把这个模型的版本设计为v1aplpha1,它是一个alpha版本,随着版本的演进,它会不断的去提升这个版本,如果是一个demo的版本,那么永远都在alpha的阶段,v1alpha1->v1alpha2->v1alpha3,如果觉得功能相对成熟了,你可以拿去试一试,基本快到生产就绪了,它会变成beta,它会变为v1beta1,v1beta1->v1beta2->v1beta3,直到认为这个功能已经生产就绪了,这个模型基本上不会改变了,这个时候会将他变为v1。
所以它会有不断的版本演进,但是版本演进要做向前兼容,那么kubernetes对每个对象都增加了version的控制,所以当一个对象从v1alpha1到v1alpha2的演进的时候,在这个过度版本它会对两个版本都支持,也就是可以从v1alpha1的版本去访问它,也可以从v1alpha2的版本访问它,但是在kubernetes里面在apiserver的层面,它实现了conversion的方法,用来负责不同版本之间的转化,所谓version就是用来控制版本的。
所以整个typemeta就定义了一个对象是哪一个组的,什么类型的,哪一个版本的,所以基本就定义了对象是什么。
Metadata
定义了对象的原数据,namespace是用来放置对象的,对象可以通过namespace做隔离。
任何的kubernetes的对象会分为两类,一类像节点这种对象,一个计算节点是属于整个集群的,这类对象是nonnamespace,它的namespace值永远都是空的。
还有一种是pod,service,都是归属于某个租户/用户的,这些对象属于namespace。
一个对象namespace+name就代表了在整个集群当中唯一值。
typemeta定义了我是啥,metadata定义了我是谁。
Finalizer
如果只接触社区的kubernetes,那么对Finalizer可能没有太多的概念,Finalizer本身是资源锁。
一个对象,当我们创建的时候,把这个对象的请求发送到apiserver,apiserver经过认证鉴权存入到etcd,如果删除这个对象,也是经过一样的链路,它最后从etcd中删除掉,这个删除本身是物理删除,相当于etcd当中这条数据就抹掉了,这个对象就消失了。
如果写了控制器监控这个对象,那么会监听这个对象的创建,这个对象创建了就去做一些事情,对象删除了就会去做另外一些事情,我们不能够相信系统永远都在工作,假设控制器出错了,或者控制器在升级,这个时候控制器没有在工作,这个时候去删除对象,假设这个对象消失了,那么这个事件已经消失了,在这个对象删除之后,再去启动我的控制器,控制器是看不到之前这个对象删除的事件,因为在控制器启动之前这个事件已经发生了,在启动新的控制器实例的时候,没有这个消息通知,所以就丢失了对象删除的这样一个事件。
所以fanalizer本质上是资源锁,一个对象加了finalizer,在删除对象的时候不会做物理删除,它会做逻辑删除,其实也就是在它属性的事件戳里面deletetimestamp置为在删除那一刻的时间戳。
这样的好处是当用户去删除对象,如果这个对象有finalizer,那么这个对象不会消失,然后这个控制器可以捕获这个事件,即使是控制器在维护,然后重新启动之后就能够看到这个事件了,当发现一个对象deletetimestamp不为空,说明用户想删除它,那么控制器里面就可以去做一些逻辑处理。
当我们去配置外部资源的时候,比如创建一个pod,这个pod创建完之后,要去配置外部的一些网络,或者配置外部的dns,外部的负载均衡,这个时候就需要为对象加finalizer,那么这需要我写一个控制器,控制器去配置外部dns,配置外部dns之前就要给这个对象加上finalizer,由我都配置控制器去加的,加好finalizer之后这个对象就被锁住了,它只要有finalizer就不会被物理删除,当我去删除这个对象的时候,控制器会看到update的事件,看到这个对象的deletetimestamp不为空,那么这个controller要去清除外部配置,当完成外部配置的清除之后,那么由controler将finalizer删除掉,清除掉之后,k8s发现这个对象finalizer为空了,那么自动由逻辑删除变为物理删除,这个对象就消失了。
所以finalizer是个资源锁。
resourceversion
它其实也是一把锁,在微服务场景下如果有多个组件去修改一个对象,怎么保证对象的访问安全。
也就是多个进程去修改一个对象的时候,怎么样保证它的安全。
第一种就是加锁,加锁之后再去修改,其他进程或者线程要去访问同样对象的时候要去等待锁,如果拿不到锁就要阻塞,这种方式带来了什么问题呢?首先它特别的安全,其次很多线程会阻塞,特别是高并发的时候,一个人拿到锁,其他人要访问同样的资源,那么它就需要去做等待,这样一个系统很多进程就会处于阻塞状态,所以整个系统的效率较低。
还有一种就是乐观锁,能不能通过一种方式记录一个版本信息,当两个线程去访问同一个对象的时候,这个时候给对象添加一个版本号,比如v1,那么拿到的都是这个对象的v1版本,这个时候两个人要同时去修改这个对象,这两个修改对象虽然同时发生,但是请求都会发给apiserver这一端,当apiserver这一端接收到请求的时候,它一定会一个先处理,一个后处理,它还是顺序执行的。这个时候当它处理第一个请求的时候,它会看到在v1的版本上去做修改的,你要去将一个属性更新掉,这个请求是合法的,那么apiserver会将这个请求处理掉,将这个属性更新,并且将版本变为v2,在处理第二个请求的时候,它一样会去看第二个请求带的版本号是什么,当前server端的版本变为v2了,说明之前发来的请求是非法的,是基于一个老版本的数据做修改的,所以会拒绝这个请求。
所以resourceversion就是这样一个版本信息,它就是一个乐观锁,它维护了对象的版本信息,每个组件要去修改同一个对象的时候要带着版本过来,由apiserver来判断是不是基于最新版本做修改的,如果不是就拒绝,如果是的话就接受,这样多个组件就可以以无锁的状态来修改同一个对象。
apiserver拒绝了一个请求,那么客户端有义务拿到最新的版本重试,这样的话系统就是以无锁的状态运行起来。
Etcd 存储机制 reversion
reversion主要由两部分组成,第一部分main rev,每次事务进行加一,第二部分 sub rev,同一个事务中的每次操作加一。
对于任何的数据都有reversion的概念,它相当于是一个版本的信息,reversion主要由两部分组成,一部分是main rev,一部分是sub rev。
下面去get一个key,-wjson是将其细节打印出来,这里面可以看到有create mode reversion,这里就有一个版本的概念,对于任何etcd里面存的对象,它都有版本的概念,有个当前的reversion 3。
reversion是当前集群类似于自增长的这么一个值,当我们去对整个集群做任何数据修改的时候,它的reversion就会增加,reversion分为了sub和main两个部分,如果你开了一个事务,那么这个事务里面所有写操作可以公用mian reversion,然后里面子命令对应sub reversion。
对于k8s来说,我们大部分时候使用main reversion,sub reversion没有用。
回过头看k8s对象的时候,有个resource version,可以将它理解为乐观锁,其实这个resource reversion就和etcd里面的对象mod reversion是一一对应的,当对etcd做数据变更的时候,这个etcd mod revesion也会跟着变化。k8s在读取对象的时候就会以mod reversion作为它的resource version。
ResourceVersion
resource version到底怎么使用,我们任何的对象都有resource version,可以看到为204,所以单个对象的resourceversion就是对象最后修改的时间,之前说过最后修改时间就是它的mode version,这个对象会被创建,会被修改,他的mode reversion就是当这个对象修改的时候这个etcd里面reversion的增长的值,注意不同的对象的reversion是公用同一个增长序列的,这个reversion是整个集群增长的,不同的对象它的mode reversion就是它最后修改一次的reversion,这个reversion就是当你查询对象它显示的resourceversion。
再来回顾乐观锁,当两个控制器要去修改一个对象的时候,那么这两个进程拿到的是当前这个对象的mode reversion,当一个进程要去修改它,这个请求先发过去了,修改对象resourceversion也就是mode reversion,和etcd里面是一样的,这个请求被接受并且被修改了,那么这个对象的mode reversion是发生变化了的,这个时候第二个请求再发过去,它基于老的modereversion,在这个请求被etcd处理的时候他就会发现你是基于老版本,我现在已经增长了,所以你这个请求是不合法的,它就给你返回409 conflict,这个时候客户端有责任去拉去新的版本,包括新的reversion,在那个基础之上重新做修改。
如果list对象的时候,不加resource version,这就意味着告诉apiserver说我不相信你的cache,你要把最新的数据还给我,就会导致请求穿透apiserver,直接到etcd,在写代码的时候一定要注意。
当使用label去做对象过滤查询的时候,这个过滤时在apiserver做的,etcd本身是没有过滤能力的,所以apiserver依然会将全量请求发送到etcd里面,发过来以后再apiserver这边做过滤。
上面是容易出问题的点。
在查询对象的时候可以通过标签过滤器去查询的。
spec和status
spec是用户输入的,也就是我期望你是什么状态。
每个对象都有控制器,status是由控制器去做配置的,配置完成以后写回到这个对象,status是由控制器去控制的。
DeploymeStatus
最后我们来看一下 DeploymeStatus。前面的课程我们学习到,每一个资源都有它的 spec.status。这里可以看一下,deployment status 中描述的三个其实是它的 conversion 状态,也就是 Processing、Complete 以及 Failed。
[root@k8s-master ~]# kubectl get deployment nginx-deployment -o yaml
status:
availableReplicas: 2
conditions:
- lastTransitionTime: "2021-03-11T01:14:43Z"
lastUpdateTime: "2021-03-11T01:14:43Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2021-03-11T01:14:39Z"
lastUpdateTime: "2021-03-11T01:54:15Z"
message: ReplicaSet "nginx-deployment-5574db6478" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 4
readyReplicas: 2
replicas: 2
updatedReplicas: 2
以 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 状态。
常用Kubernetes对象以及分组
可以看到将不同的对象放到不同的group里面,支撑核心业务的放在core/v1里面,需要做应用管理放在apps/v1。