Kubernetes 架构核心组件工作原理解析

Kubernetes∶声明式系统


Kubernetes 的所有管理能力构建在对象抽象的基础上,核心对象包括∶

  • Node∶计算节点的抽象,用来描述计算节点的资源抽象、健康状态等。
  • Namespace∶资源隔离的基本单位,可以简单理解为文件系统中的目录结构。
  • Pod∶用来描述应用实例,包括镜像地址、资源需求等。 Kubernetes 中最核心的对象,也是打通应用和基础架构的秘密武器。
  • Service∶ 服务如何将应用发布成服务,本质上是负载均衡和域名服务的声明。

Kubernetes采用与borg类似架构


APIServer可以简单理解为Rest server,它接受外部请求的,无论是通过命令行还是浏览器,这些请求都会被转化为rest的调用,发到APIServer里面,APIServer会将请求存放到自己数据库里面就结束了,APIServer就做这些事情,接受请求,并且存储。

etcd本身是个数据库,其次etcd访问有种模式叫做watch模式,也就是当你去get一个对象的时候,你可以加一个watch的参数,那么客户端的这次get请求首先会完成,其次连接不会断掉,客户端会和etcd保持长连接,要请求的对象后续发生任何的变换,etcd会主动的将这个变化以事件的形式推送给客户端。

所以etcd 第一 是一个数据库  第二 是个消息中间件,任何对象的变更都会推送,所以Apiserver会去和etcd做连接,apiserver将数据推送到etcd,etc任何的对象变更,会主动通知apiserver。

apiserver除了去接受客户端和各个组件过来的请求,它也可以将对象的变更推送出去。所以这样的话整个系统就可以联动起来了,api server是简单的rest api,它提供了对象的传输,接受其他所有组件请求(上图可以看到,所有组件都是连接到api server的),也就是其他组件之间不互相通信,其他组件都和api server通信,将请求发送给apiserver,apiserver做认证,鉴权,校验之后将数据存储到etcd,任何对象的变化都会推送给其他的组件。

通过上面消息的机制来驱动整个系统的变化。

在管理节点上面还有两个重要组件,schedule,用户创建pod会被,会被apiserver接收到,apiserver就会告诉schedule有这么一个调度需求去做调度,schedule就会去做调度,先看你的pod需要多少资源,也就是你的需求是什么,然后去看集群里面资源利用状况,这个资源状况哪里来的呢?每个节点上面有个kubelet,第一它会将资源信息上报,报告每个节点的健康状况和资源使用情况,apiserver接收到这些请求之后,同样将数据存放到etcd里面,同时schedule能够接受到变化的请求,所以shcedule是知道集群整个资源利用情况的。

接收到调度请求之后,调度器就有能力帮它选择最适合的节点,完成调度,所谓的完成调度就是将pod和节点绑定,告诉APiserver,apiserver将pod的信息里面nodename属性更新到etcd里面保存下来。

最后kubelet就会去看和我这个节点相关的pod有些哪些,如果新的pod和我相关我就去拉起进程。

上面就是控制面组件的合作关系

各个组件细节


apiserver其实就是整个集群的api 网关,那么就包括认证和鉴权,确保客户端请求是合法的,并不是谁都可以给我发指令,我需要知道你是谁,其次还需要做鉴权,知道你是谁但是不代表你有整个集群的权限,所以要去校验有没有这个权限,最后你有这个权限,但是也不代表你的操作是合法的,所以要做准入控制。

conrtol manager管理了一堆的控制器,比如deployment控制器,replicate,lifecycle控制器,他们分别用来管理不同的对象。

control manager是让整个集群运作起来的核心,它是一个大脑,apiserver里面没有什么业务逻辑,它就接受请求,只要你有权限,请求又是合法的,就存一下,这就是apiserver的动作,这个请求存下来之后,具体怎么做是由control manager去做的。

工作节点上面的kubelet:第一 上报节点的健康状态和资源需求,第二 维护当前节点pod的生命周期,第三 cadvisor收集容器的资源使用情况

kube-proxy:就是你去定义service的时候,发布服务的时候要为这个服务配置负载均衡。

Kubernetes 主节点(Master Node)


 kubeadm安装的集群默认将控制面的组件安装到了kube-system下面

[root@master ~]# kubectl get po -n kube-system
NAME                                      READY   STATUS    RESTARTS   AGE
calico-kube-controllers-97769f7c7-7md6n   1/1     Running   8          65d
calico-node-445z4                         1/1     Running   5          65d
calico-node-5j6fg                         1/1     Running   6          65d
calico-node-bjqq6                         1/1     Running   5          65d
coredns-6d56c8448f-6zm8m                  1/1     Running   5          65d
coredns-6d56c8448f-pltwl                  1/1     Running   6          65d
etcd-master                               1/1     Running   5          65d
kube-apiserver-master                     1/1     Running   3          34d
kube-controller-manager-master            1/1     Running   9          35d
kube-proxy-7ffvg                          1/1     Running   5          65d
kube-proxy-j75lv                          1/1     Running   5          65d
kube-proxy-npt9k                          1/1     Running   6          65d
kube-scheduler-master                     1/1     Running   16         65d
metrics-server-b66888848-c2p6b            1/1     Running   11         34d

Kubernetes 工作节点(worker node)


  

ETCD


 etcd是分布式的存储,它一般要做两件事情,一件事情是选主,选出哪个是master,所有的写需求经由master,否则当所有member都接受写操作,那么就会发生冲突.

raft协议保证两个事情,第一个通过投票方式选主,选择一个节点为leader,所有写操作从leader去做,其次leader在接收到写操作的数据之后,会将写的指令下发,下发到其他的节点上面去,其他节点通过某种机制来保证和leader同步,这样才能保住数据的一致性。

第二个etcd支持监听机制的,它也是消息中间件,

直接访问Etcd数据


etcdctl是操作etcd的客户端工具,可以yum安装,也可以进入etcd pod当中自带了。

下面是去get一些值,因为是键值对存储,我要去get所有的key,key一斜杠/开头我就可以将其打印出来,通过这样的方式就可以看到当前集群在etcd当中存储的路径。

[root@master etcd]# ETCDCTL_API=3  etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt  get  --keys-only --prefix / | head -n 10
/registry/apiextensions.k8s.io/customresourcedefinitions/bgpconfigurations.crd.projectcalico.org

/registry/apiextensions.k8s.io/customresourcedefinitions/bgppeers.crd.projectcalico.org

/registry/apiextensions.k8s.io/customresourcedefinitions/blockaffinities.crd.projectcalico.org

/registry/apiextensions.k8s.io/customresourcedefinitions/caliconodestatuses.crd.projectcalico.org

/registry/apiextensions.k8s.io/customresourcedefinitions/clusterinformations.crd.projectcalico.org

如果要监听对象的变化可以使用watch操作来监听,任何对象发生了变化,一旦敲了这条命令,那么就保持在那,一旦有对象发生了变更它就会通知你,这就是它的消息机制。

[root@master ~]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
dns-test                 1/1     Running   3          36d
nginx-74d69c5bbb-6478r   1/1     Running   3          34d
nginx-74d69c5bbb-mhmsc   1/1     Running   3          34d
nginx-web-0              1/1     Running   4          36d
nginx-web-1              1/1     Running   3          36d
[root@master ~]# kubectl delete pod nginx-web-1 
pod "nginx-web-1" deleted


[root@master etcd]# ETCDCTL_API=3  etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt  watch --prefix /registry/statefulsets/default/nginx-web
PUT
/registry/statefulsets/default/nginx-web
k8s

apps/v1
        StatefulSet

	nginx-webdefault"*$08a540f1-83b9-4575-9441-7e2a1e079e0528..b?
0kubectl.kubernetes.io/last-applied-configuration.{"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"nginx-web","namespace":"default"},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"nginx_test"}},"serviceName":"nginx-test","template":{"metadata":{"labels":{"app":"nginx_test"}},"spec":{"containers":[{"image":"nginx:1.11","name":"nginx-test","ports":[{"containerPort":80,"name":"nginx-web"}]}]}}}}
z.

APIServer


apiserver本身是rest server,所以它的扩展是比较简单的,etcd是一个有状态的,对于有状态的加减一个member还是需要做些配置,对于无状态的apiserver来说就只需要做水平扩展就行了,如果不够了添加一个apiserver实例就行了,前面负载均衡把这个实例加进来就行了。

准入其实又分为两个阶段 mutating:你要建一个对象,这个对象发到apiserver了,但是从集群层面上来说要给这个对象做些变形,给你增加一些属性,或者改变某些值,这个时候就可以通过mytating操作在apiserver接收到请求到存储到etcd过程中给你原始的需求里面添加一些其他的属性,这样方便做平台层面的一些动作。

valiting:前面做了那些操作,你可能mutat了一个对象,变形了,但是对象是否合法呢?后面还有一些校验逻辑的,加一些validting的逻辑,比如一个对象的名称不能超过10,那怎么办,加一个validting的规则,让它去校验一下,如果超过10,就不让它通过,这里就需要实现准入了逻辑,准入不通过,那么就不会存储到etcd当中。

认证,鉴权,准入是三道门,任何一道门不通过请求就被废弃掉了,它不会存储到etcd里面。

apiserverf本身还是一个缓存,apiserver是唯一一个和etcd通信的组件,etcd本身规是分布式的键值对,可想而知性能不好特别的好,如果有海量的需求那么是一定没有办法帮你处理的。

apiserver里面维护了一份数据的缓存,如果是读操作,etcd这边是有cache的,如果在cache当中就直接返回给你了,这个请求不会发送到etcd里面去。

APIserver展开


 

apiserver是一个rest server,他要去注册每个对象的handle,所以有个api handle的过程,接下来会去做认证,做完认证之后要去做限流,限流就是自我保护,每个apiserver节点都有承受上线的,如果不做限流那么很可能并发就将其打死了,之后来到audit做审计,任何的对象操作会记录log,为了日后的安全审查,在之后authz就是做鉴权的,k8s自己支持RBAC的,另外你也可以编写自己的webhook去做鉴权。

Aggregator:因为本身apiserver作为一个rest api,它其实是有能力和nginx反向代理软件一样来做一些路由配置,比如你是标准的k8s对象,那么你就走默认的k8s apiserver,那么muta valid就继续往下走了,所有路走完了就到etcd了,但是如果我有额外的扩展对象,这些额外的对象我不想在k8s本身实例里面去实现,我希望有自己的逻辑,那么就需要自己去写aggregated apiserver,aggregate会去看你这个对象在另外一个apiserver里面那么就会将请求转到这个aggregated apiserver,在之后的逻辑和上面是一样的,只不过是独立部署的apiserver。

 Controller Manager


 整个集群的大脑是谁呢?是谁让集群变为声明式的系统,并且可以保证用户的期望和真实状态保持一致呢?就像你设置了空调温度25°,但是总要有空调外机去干活,去干活就要确保室温满足那个要求,在k8s里面由control manager去实现的,control manager是整个集群的大脑。

它里面有很多的控制器,很多的控制器都有不同的职责,所有的控制器都遵循同样的规范,先去读用户的这个抽象对象,这个对象里面有用户的期望状态,它读取到期望状态就需要去干活了,它就需要去做真实的配置,确保系统的真实状态和用户的期望状态保持一致。

控制器的工作流程


针对任何对象,k8s其实他有一些辅助工具叫做codegenerator,会帮你生成对象一些访问框架和代码框架,这个代码框架主要是control interface,分为两个部分,一部分为informer,另外一部分为lister,任何的对象k8s允许你去做监听,所谓的informer就是相当于去监听某个对象,你可以通过informer的框架去监听某个对象,这个对象发生任何的状态变化,比如增删改查这些写的动作,它都会生成新的event。控制器利用这个framework之后就需要去监听这些事件,任何的控制器都是生产者和消费者模型,监听到这些事件之后将Key拿出来(informer上面的部分其实是生产者,生产者去监听这些对象的事件,然后注册你的handle,它做的就是将对象时间的key存放到一个队列里面去,消费者就是worker thread,它要做的就是从队列里面取出数据,取出来去做配置)

 lister部分是用来干嘛的呢?相当于所有kuebernetes在list watch之后,它会在客户端那边存一个缓存,也就说通过List可以直接在客户端这边拿到这个对象的缓存状态,而不需要去访问apiserver了。list更多的是提供了接口,让你从clicent-cache里面获取对象。

Informer的内部工作机制


上面是APIserver,shareinformer通过list watch去关注一个对象,所谓list watch就是第一,他会将所有对象list返回回来,第二作为一个长连接,关注它后续的变更,所有这些框架都是通用的。

但是这个框架所关注的不同的对象是怎么转换的呢?就是informer里面有reflector,就是按照你给定的对象的类型,(本身apiserver是个rest server,它传回给你的是json或者protobuffer序列化的对象,那你要做反序列化,要将其转化为指定的对象,那么九通过反射的机制,去转化为不同类型的对象 )这里就有一个delta fifo quenue,这个对象最后会存到thread safe store,就是List部分,这部分对象无论是被list到还是watch到,它的事件变化都会都会通过informer发送到handler,由handler处理这些对象事件。

控制器协同工作原理


 当命令敲下去做了什么,第一将deployment创建出来了,就这样结束了,这个请求就被发到apiserver了,加上-v 9。

客户端做的第一件事情是它去读取config file,这个就是默认kubectl读取的配置

 这个配置里面有什么呢?

这里面有我的apiserver的地址,已经客户身份的一些认证信息,所以他会将请求发送到api server的地址

 所以通过create -f或者apply -f,就将这个deployment发送到apiserver去了,apiserver要做认证,鉴权,准入,做完之后就将请求存放到etcd里面去了,存放好之后这个时候,控制器就开始工作了,(control manager里面是有很多control的),deployment control关注的是deployment这个对象,他会去解析deployment对象,然后利用这个模板去创建一个应用,deployment它就会去创建副本集的对象叫replicaset,replicaset就是副本集,也就是deployment告诉apiserver我创建了一个副本集,这个副本集里面有个对象,然后模板是怎么样的,这个副本集被apiserver接受了之后,一样结果认证,鉴权,准入控制存入到etcd里面。

这个时候replicaset controller会watch apiserver,监听replicaset对象,他发现replicaset对象被创建了,它就需要去解析了,你需要几个副本以及pod的模板是怎么样的,那么replicatset controller就会去创建pod,这个pod一样会提交到apiserver这边,在Pod刚刚创建出来它有一个属性叫做nodename,刚创建出来nodename属性是空的,也就是pod没有经过调度的,它没有和任何节点产生绑定关系,这个时候调度器就去工作了,它去集群节点里面,找一个适合这个pod的节点,并且绑定pod,那么nodename这个值就被调度器填上了。

之后pod就和节点产生了绑定关系,这个节点就会去加载这个pod,通过容器运行时,将容器进程拉起来,(kubernetes将这些都标准化了,抽象为cri cni csi),通过cni插件将网络配置起来通过csi将存储帮它挂载上去,这样就完成了pod应用的加载。

schedule


 

 调度器predict阶段:你一个集群多少个节点,pod有一个pod的资源需求,先将不满足需求的过滤掉,比如资源不足,端口冲突。

过滤出来的节点就要去评分,里面有很多的插件,按照不同的维度,权重去评分,然后选择评分最高的节点,作为pod的被选节点。

最后通过bind的操作将节点和pod产生绑定关系。

Kubelet


 

Kube-proxy


 

推荐的Add-Ons


上面是kubernetes的核心组件,kubernetes还有一些Add-Ons组件,最核心的就是kube-dns,就是整个集群的域名服务,没有核心组件集群是启动不起来的,add-on是指有了核心组件,add-ons可以以用户创建pod的方式去启动起来。

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/125420934