Kubernetes 核心组件运行机制

目录

 

k8s整体架构

分层架构

组件详解

API Server

Api Server的架构

test环境中的api server

List-Watch异步消息机制

模块之间的通信

Controller Manager

Scheduler

调度流程

kubelet

节点管理

Pod管理

容器健康检查

资源监控

kube-proxy

kube-proxy路由的具体方式


用了一个月的零零散散的时间对k8s的各个组件进行了系统的学习,整理了一下自己对于各个组件的大致运行机制的理解

k8s整体架构

k8s架构图如下所示

 

k8s集群由Master节点和Node节点构成

Master节点上的组件为

  • etcd,存储整个集群的数据和状态信息

  • apiserver,提供了资源操作的唯一入口,并提供认证、授权、鉴权等访问控制功能

  • controller manager, 中央控制管理器,负责维护集群的状态,比如故障检测、自动扩展、滚动更新等

  • scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上

Node节点上的组件为

  • kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理

  • kube-proxy负责为Service提供cluster内部的服务发现和负载均衡

  • Container runtime负责镜像管理以及Pod和容器的真正运行(k8s不仅支持docker容器,也支持其他实现Container Runtime Interface接口的容器,所以使得k8s具有集成可扩展性)

除了核心组件,k8s还提供了一些插件:

  • kube-dns负责为整个集群提供DNS服务

  • Heapster提供资源监控

  • Dashboard提供GUI

  • Federation提供跨可用区的集群

  • Fluentd-elasticsearch提供集群日志采集、存储与查询

 

分层架构

如果按照k8s的整体功能来进行分层划分,可以大致划分为下图所示的分层架构

 

  • 核心层:Kubernetes最核心的功能,对外提供API构建高层的应用,对内提供插件式应用执行环境(比如,容器运行,网络环境等)

  • 应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS解析等)

  • 管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等)

  • 接口层:kubectl命令行工具、客户端SDK以及集群联邦

  • 生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴

    • Kubernetes外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS应用、ChatOps等

    • Kubernetes内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等

 

组件详解

API Server

Kubernetes API Server的核心功能是提供Kubernetes各类 资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽, 是整个系统的数据总线和数据中心。除此之外,它还是集群管理的入口、资源配额控制的入口,并提供了完备的集群安全机制

最常用的kubectl命令其实内部就是访问api server提供的restful接口获取集群资源信息

Api Server的架构

 

如上图所示,Api Server主要分为三个层次

  • Api 层用于提供各种API接口

  • 访问控制层,即鉴权和身份验证,资源验证

  • 注册表层,Kubernetes把所有资源对象都保存在注册表 (Registry)中,针对注册表中的各种资源对象都定义了:资源对象的 类型、如何创建资源对象、如何转换资源的不同版本,以及如何将资源 编码和解码为JSON或ProtoBuf格式进行存储,for example: pod的元数据信息(这个元数据信息可以类比为数据库的表结构)

  • etcd数据库,用于持久化存储k8s资源对象的KV数据库

test环境中的api server

node2#  ps -ef|grep api
root     21175 21144  2 Mar12 ?        03:20:35 kube-apiserver --advertise-address=10.65.8.43 --allow-privileged=true --apiserver-count=3 --authorization-mode=Node,RBAC --bind-address=0.0.0.0 --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --endpoint-reconciler-type=lease --etcd-cafile=/etc/ssl/etcd/ssl/ca.pem --etcd-certfile=/etc/ssl/etcd/ssl/node-node2.pem --etcd-keyfile=/etc/ssl/etcd/ssl/node-node2-key.pem --etcd-servers=https://10.65.8.42:2379,https://10.65.8.43:2379,https://10.65.8.44:2379,https://10.65.8.45:2379,https://10.65.8.176:2379 --insecure-port=0 --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalDNS,InternalIP,Hostname,ExternalDNS,ExternalIP --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --runtime-config=admissionregistration.k8s.io/v1beta1 --secure-port=7443 --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-cluster-ip-range=100.127.224.0/20 --service-node-port-range=30000-32767 --storage-backend=etcd3 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key --v=4
由上可知,每个master节点都有一个kube-apiserver进程

List-Watch异步消息机制

在K8s中etcd存储了各种数据和状态信息,而只有API Server能够直接与ETCD进行交互,也就是其他的组件获取需要的信息只能通过API Server来获取,并且其他组件通过监听API server来执行相应的操作,这种机制在k8s集群中叫List-Watch机制,流程图如下所示

 

由于k8s不希望除了api server的其他组件能够直接访问或者监听etcd,所以k8s自己复写了一套watch接口,其他组件来监听api server就能watch api server,并且还可以选择性地监听需要的信息,比如scheduler,kubelete需要的通知会有些是不同的

List-watch主要作用为:

  • 1、组件启动时通过"List"接口,获取所需资源的全量的数据并且缓存到内存中

  • 2、组件向apiserver而不是etcd发起watch请求,在组件启动时就进行订阅,告诉apiserver需要知道什么数据发生变化。Watch是一个典型的发布-订阅模式。

3、组件向apiserver发起的watch请求是可以带条件的,例如,scheduler想要watch的是所有未被调度的Pod,也就是满足Pod.destNode=""的Pod来进行调度操作;而kubelet只关心自己节点上的Pod列表。apiserver向etcd发起的watch是没有条件的,只能知道某个数据发生了变化或创建、删除,但不能过滤具体的值。也就是说对象数据的条件过滤必须在apiserver端而不是etcd端完成。

在kubectl命令上带上watch参数,可以看到背后的一系列流程

node2# kubectl get po -n demo --watch -v 7
I0320 11:07:42.774438   17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.775212   17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.779140   17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.790768   17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.791435   17010 round_trippers.go:416] GET https://10.65.8.42:6443/api/v1/namespaces/demo/pods?limit=500
I0320 11:07:42.791462   17010 round_trippers.go:423] Request Headers:
I0320 11:07:42.791477   17010 round_trippers.go:426]     Accept: application/json
I0320 11:07:42.791489   17010 round_trippers.go:426]     User-Agent: kubectl/v1.13.5 (linux/amd64) kubernetes/2166946
I0320 11:07:42.803740   17010 round_trippers.go:441] Response Status: 200 OK in 12 milliseconds
I0320 11:07:42.804192   17010 round_trippers.go:416] GET https://10.65.8.42:6443/api/v1/namespaces/demo/pods?resourceVersion=328639400&watch=true
I0320 11:07:42.804208   17010 round_trippers.go:423] Request Headers:
I0320 11:07:42.804219   17010 round_trippers.go:426]     Accept: application/json
I0320 11:07:42.804227   17010 round_trippers.go:426]     User-Agent: kubectl/v1.13.5 (linux/amd64) kubernetes/2166946
I0320 11:07:42.805324   17010 round_trippers.go:441] Response Status: 200 OK in 1 milliseconds
打开另一个窗口,创建pod
node2# kubectl create -f pod.yaml

可以观察到刚才的watch接口立即监听到了pod的创建, 并且打印了pod创建的过程的信息

NAME       READY   STATUS    RESTARTS   AGE
demo-pod   0/1     Pending   0          0s
demo-pod   0/1   Pending   0     0s
demo-pod   0/1   ContainerCreating   0     0s

demo-pod   1/1   Running   0     12s
K8s 的informer模块封装了 list-watch的API, 其他具体调用方只需要指定资源,编写时间处理函数就行

如下图所示,informer首先通过通过List接口罗列所有的资源,然后再调用watch接口监听资源的变更时间,每次有变更事件时会将变更时间放到FIFO队列中,由另一个协程从队列中取出事件,并调用相应的注册函数处理

此外,还维护了一个缓存,因为每个组件处理变更时可能要查询资源信息,为了降低api server的负载,会从本地缓存中读取

 

以k8s master的pod.go为例:

先看NewFilteredPodInformer(kubernetes-master/staging/src/k8s.io/client-go/informers/core/v1/pod.go)方法创建一个informer

func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
   return cache.NewSharedIndexInformer(
      &cache.ListWatch{
         ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
            if tweakListOptions != nil {
               tweakListOptions(&options)
            }
            return client.CoreV1().Pods(namespace).List(context.TODO(), options)
         },
         WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
            if tweakListOptions != nil {
               tweakListOptions(&options)
            }
            return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)
         },
      },
      &corev1.Pod{},
      resyncPeriod,
      indexers,
   )
}
 

再看cache.NewSharedIndexInformer类所在的run方法func (s *sharedIndexInformer) Run(stopCh <-chan struct{})

func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{     //关键信息1
		KnownObjects:          s.indexer,
		EmitDeltaTypeReplaced: true,
	})

	cfg := &Config{
		Queue:            fifo,
		ListerWatcher:    s.listerWatcher,
		ObjectType:       s.objectType,
		FullResyncPeriod: s.resyncCheckPeriod,
		RetryOnError:     false,
		ShouldResync:     s.processor.shouldResync,

		Process:           s.HandleDeltas,                  //关键信息2
		WatchErrorHandler: s.watchErrorHandler,
	}

	func() {
		s.startedLock.Lock()
		defer s.startedLock.Unlock()

		s.controller = New(cfg)
		s.controller.(*controller).clock = s.clock
		s.started = true
	}()

	// Separate stop channel because Processor should be stopped strictly after controller
	processorStopCh := make(chan struct{})
	var wg wait.Group
	defer wg.Wait()              // Wait for Processor to stop
	defer close(processorStopCh) // Tell Processor to stop
	wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run)
	wg.StartWithChannel(processorStopCh, s.processor.run)                //关键信息3

	defer func() {
		s.startedLock.Lock()
		defer s.startedLock.Unlock()
		s.stopped = true // Don't want any new listeners
	}()
	s.controller.Run(stopCh)                                             //关键信息4
}

上面代码中标记了4个关键信息

  • 1、可以看到初始话了一个fifo队列,作为Config的参数,这个FIFO队列就是上面说到的用于存放监听到API Server回调消息的事件的队列

  • 2、Config还有一个关键参数Process,赋值是s.HandleDeltas,最后通过Config生成了一个controller对象

    HandleDeltas方法就是对于FIFO队列中每一个监听事件进行的处理,代码如下:

    可以看到调用processor.distribute方法分发到相应的协程进行处理

        

func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
	s.blockDeltas.Lock()
	defer s.blockDeltas.Unlock()

	// from oldest to newest
	for _, d := range obj.(Deltas) {
		switch d.Type {
		case Sync, Replaced, Added, Updated:
			s.cacheMutationDetector.AddObject(d.Object)
			if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
				if err := s.indexer.Update(d.Object); err != nil {
					return err
				}

				isSync := false
				switch {
				case d.Type == Sync:
					// Sync events are only propagated to listeners that requested resync
					isSync = true
				case d.Type == Replaced:
					if accessor, err := meta.Accessor(d.Object); err == nil {
						if oldAccessor, err := meta.Accessor(old); err == nil {
							// Replaced events that didn't change resourceVersion are treated as resync events
							// and only propagated to listeners that requested resync
							isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()
						}
					}
				}
				s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)
			} else {
				if err := s.indexer.Add(d.Object); err != nil {
					return err
				}
				s.processor.distribute(addNotification{newObj: d.Object}, false)
			}
		case Deleted:
			if err := s.indexer.Delete(d.Object); err != nil {
				return err
			}
			s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
		}
	}
	return nil
}
  • 3、s.processor.run,方法就是通过channel接收上面步骤2分发过来的事件,进行相应的处理

func (p *processorListener) run() {
	// this call blocks until the channel is closed.  When a panic happens during the notification
	// we will catch it, **the offending item will be skipped!**, and after a short delay (one second)
	// the next notification will be attempted.  This is usually better than the alternative of never
	// delivering again.
	stopCh := make(chan struct{})
	wait.Until(func() {
		for next := range p.nextCh {
			switch notification := next.(type) {
			case updateNotification:
				p.handler.OnUpdate(notification.oldObj, notification.newObj)
			case addNotification:
				p.handler.OnAdd(notification.newObj)
			case deleteNotification:
				p.handler.OnDelete(notification.oldObj)
			default:
				utilruntime.HandleError(fmt.Errorf("unrecognized notification: %T", next))
			}
		}
		// the only way to get here is if the p.nextCh is empty and closed
		close(stopCh)
	}, 1*time.Second, stopCh)
}
  • 4、最后一步是controller的run方法,有两个作用,1是生成Reflector对应,并调用Reflector的Run方法,而Reflector中的Run方法中是开启ListWatch,即前面提到的先通过List接口获取所有需要的资源,再对所有资源进行Watch(监听)

    另一个作用就是for循环从FIFO队列中取出监听事件,并调用步骤2中的HandleDeltas方法进行处理

          

func (c *controller) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	go func() {
		<-stopCh
		c.config.Queue.Close()
	}()
	r := NewReflector(
		c.config.ListerWatcher,
		c.config.ObjectType,
		c.config.Queue,
		c.config.FullResyncPeriod,
	)
	r.ShouldResync = c.config.ShouldResync
	r.WatchListPageSize = c.config.WatchListPageSize
	r.clock = c.clock
	if c.config.WatchErrorHandler != nil {
		r.watchErrorHandler = c.config.WatchErrorHandler
	}

	c.reflectorMutex.Lock()
	c.reflector = r
	c.reflectorMutex.Unlock()

	var wg wait.Group

	wg.StartWithChannel(stopCh, r.Run)            //调用Reflector的Run方法开启ListWatch

	wait.Until(c.processLoop, time.Second, stopCh)  //循环从FIFO队列中获取监听回调时间
	wg.Wait()
}

模块之间的通信

Kubernetes api server负责各个模块的通信

1、每个node上的kubelet进程会定时访问api-server的restful接口上报自身状态

2、kubelet进程也会通过List-Watch机制监听pod信息

3、kube-controller-manager中的Node Controller模块通过API Server提供 的Watch接口实时监控Node的信息

4、scheduler通过List-Watch机制监听到新建pod的信息之后,会选择合适的node列表,将pod绑定到目标节点上

 

Controller Manager

在k8s集群中有多种Controller,分别对特定的资源进行监控和做相应的调整,常见的有Replication Controller(项目中最常用的Deployment可以看做Replication Controller的最新版), Node Controller(监听Node节点的状态),总共可分为以下几大类的Controller

 

而Controller Manager正是对资源的核心管理者,也就是是master中的每个Controller保证了各类资源保持预期状态,而Controller Manager保证了各个Controller保持在预期状态

每个Controller的作用分别如下:

Replication Controller

Replication Controller的作用是管理Pod副本,因此具有重新调度,弹性伸缩,滚动更新等功能

目前普遍使用Deployment和Replica Set代替Replication Controller,Replica Set相对于Replication Controller而言,优势是支持多标签selector, Replica Set由Deployment来进行管理,Deployment控制的是Replica Set的对象

Node Controller

Node Controller负责管理Node节点。

每个Node节点有一个kubelet进程,进程在启动时会向API Server注册自身的节点信息,并且之后会定时上报,Api Server会将其存储在ETCD中,存储的这些信息包括节点健康状况,节点资源,节点地址信息,操作系统版本,docker版本,kubelete版本等信息

节点健康状况包含就绪、未就绪、Unknown三种

使用kubectl get nodes命令可以查看所有nodes节点信息

而Node Controller也通过监听API Server实时获取Node的相关信息,来实现管理和监控集群中的node节点

Node Controller核心工作过程如下:

Node Controller会在本地实时保存nodeStatusMap,存储各个状态信息,每次读取最新的Node信息会进行更新

 

ResourceQuota Controller

Resource Quota Controller为资源配额管理,支持三个层次的资源配额管理

  • 容器级别,对container cpu,memory进行限制

  • Pod级别,对pod内所有container的可用资源进行限制

  • Namespace级别

对于容器和pod,k8s提供LimitRanger,而NameSpace级别,提供ResourceQuota,如下所示:

- apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: pods-medium
  spec:
    hard:
      cpu: "10"
      memory: 20Gi
      pods: "10"
    scopeSelector:
      matchExpressions:
      - operator : In
        scopeName: PriorityClass
        values: ["medium"]
​

创建ResourceQuata Contrller之后会保存到etcd中,当通过API Server创建资源时,API Server会先通过准入可控制器(可以理解为网关逻辑中的一个check项), 读取对应的资源统计信息,判断是否超出范围。

同时,ResourceQuata Controller也会定期去统计各类资源信息

 

 

Namespace Controller

namespace controller主要是用来当要删除namespace资源时,进行删除操作,并且阻止在该namespace下的资源创建

EndPoint Controller

Endpoint的作用如下图所示,Endpoint可以看做是一个service下的路由表,保存了service下pod的访达地址

 

kubectl get endpoints -n namespace命令可以查看endpoint信息, endpoint对象被每个node节点上的kube-proxy使用,kube-proxy获取每个service的endpoint,实现Service的负载均衡

Endpoint Controller的功能通过监听Service,pod的创建或者更新,来及时更新和维护service对应的Endpoint对象的

Service Controller

service controller属于Kubernetes集群 与外部的云平台之间的一个接口控制器,暂不展开讨论

ServiceAccount Controller,Token Controller

k8s集群内部,SeriviceAccount Controller和Token Controller作用的是Service Account,Service Account是和User Account独立的两个账号

  • UserAccount, 针对管理员而言,比如我们使用kubeclt命令或者在网页上k8s dashboard上,这个账户的权限是全局性的

  • Service Account, 针对pod内的进程而言,有些情况下,pod内需要访问api server并进行一些操作,而这些操作默认的名为default的是没有的,那么可能需要新建Service Account代替default,在Deployment中通过serviceAccount字段来设置

而每个pod内部都会生成相应的token,crt等基本的鉴权文件用于和api server的交互和身份验证

node2# kubectl exec -it -n *******
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
..2021_02_25_12_37_32.328705902/ ..data/                          ca.crt                           namespace                        token
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
..2021_02_25_12_37_32.328705902/ ..data/                          ca.crt                           namespace                        token
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
..2021_02_25_12_37_32.328705902/ ..data/                          ca.crt                           namespace                        token
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
root@serviceaccount$ll
total 4
drwxrwxrwt 3 root root  140 Feb 25 20:37 ./
drwxr-xr-x 3 root root 4096 Feb 25 20:59 ../
drwxr-xr-x 2 root root  100 Feb 25 20:37 ..2021_02_25_12_37_32.328705902/
lrwxrwxrwx 1 root root   31 Feb 25 20:37 ..data -> ..2021_02_25_12_37_32.328705902/
lrwxrwxrwx 1 root root   13 Feb 25 20:37 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root   16 Feb 25 20:37 namespace -> ..data/namespace
lrwxrwxrwx 1 root root   12 Feb 25 20:37 token -> ..data/token
root@serviceaccount$cat ..data/token
eyJhbGciOiJSUzI1NiIsImtpZCI6InhKc3MtZlhmS0pTSnFPUWN3WEVURXVNM3lrTnBHcXpUdk5Sd3Y1NXI4WE0ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGMtdHJhbnNhY3Rpb24tdm4tdGVzdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLTRkamJ0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI0ODNiZjJkZC1iN2U3LTQ3NGUtODQ5Zi0zY2RiNTRmYzcwOGIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6YXBjLXRyYW5zYWN0aW9uLXZuLXRlc3Q6ZGVmYXVsdCJ9.bOyVr3SufsO4VovSl7CE5oLACilSaWAPkqWEkS-56G9vK3fD8clwM5olBQ_1chdI9BQ5mzZk5CjhyWkwxGbaq26P2S5gBK-UOzOSmUAoYlSO6wYeYKBD_EUirjL6HI5CsIQtoHX2F5fKQTCXSmlVK9rYqtJzosLlsYqwgNDFigweocovA0eFCPloDr1xOIdAkvorA8PF04PyPE0VrPI_6TK4e0CzAvX3enTUpwQgmgp-ouk4naCS3k0UdSAxmgB4-KAxawGJjYn4uF-WLaLWakPrS-0MdwPjjweJB0IupHLtp0nVKmL_QKbhkHXgrl8dn7PjLZh9DXrClXNuE8IeBwroot@serviceaccount$
root@serviceaccount$cat ..data/ca.crt
-----BEGIN CERTIFICATE-----
MI--------略
-----END CERTIFICATE-----
root@serviceaccount$

Scheduler

Scheduler在整个系统调度中起承上启下的作用,与APi Server, Controller Manager三个组件是master节点的最重要的三个组件,

总体机制是监听待调度pod的创建,并选择合适的Node更新etcd信息,最后被对应Node节点的kubelet进程监听到,进行后一步的操作,具体流程如下

 

调度流程

scheduler调度分为两个子流程,一个是预选,一个是确定最优

预选流程

预选是筛选出符合要求的候选节点,因为并不是所有的节点都会满足条件,比如有的节点资源不足以创建新的容器,有的pod在配置文件中指定了一些条件(比如在ip为某个范围内的机器,或者指定了标签)

默认预选策略如下:

  • NoDiskConflict

    待部署的pod和Node节点上的pod是否存在Volume挂载冲突

  • PodFitsResources

    待部署的pod和节点上已有的所有pod的需求资源是否已经超过节点拥有的资源

  • PodSelectorMatches

    判断待部署的pod,是否通过配置参数spec.nodeSelector制定标签,并且节点上是否包含该标签

    可以通过命令:kubectl label nodes <node-name> <label-key>=<label-value>来设置node的标签,在pod中再用spec.nodeSelector来指定标签

  • PodFitsHost

    判断待部署Pod的spec.nodeName域所指定的节点名称和备选节点的名称是否一致

  • PodFitsPorts

    判断待部署Pod所用的端口列表中的端口是否在备选节点中已被占用

每个节点只有通过上面5个默认预选策略后才能进入下一步的最优策略

最优策略

最优策略有多种

https://kubernetes.io/docs/reference/scheduling/policies/

  • LeastRequestedPriority

    从备选Node中选出资源消耗最小的节点

    计算已运行的pod和备选pod的CPU总占用量和内存总占用量(占用量为requested), 再通过以下公式 来计算,

    cpu((capacity – sum(requested)) * 10 / capacity) + memory((capacity – sum(requested)) * 10 / capacity) / 2,

    节点的优先级就由节点空闲资源与节点总容量的比值来决定

  • SelectorSpreadPriority

    为了更好的容灾,属于同一个service, replication controller的多个pod副本,计量调度到不同的节点上去

  • BalancedResourceAllocation

    由节点的资源使用率来决定,CPU和内存的使用率越接近,权重越高

 

kubelet

kubelet运行在每个node节点上,用于处理master节点下发的任务,以及管理自身的pod和pod中的容器

节点管理

kubelet进程启动之后会向API server注册节点信息,并定时向API Server同步节点信息(默认是10s),API server会将信息存入etcd

Pod管理

Kubelete通过以下方式来获取要运行的pod清单

静态文件配置

在node节点上查看kubelet进程

  node2# ps -ef|grep kubelet
  root      1864     1  5 Jan07 ?        2-20:25:22 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=gcr.io/google-containers/pause:3.1
 

可以看到kubelet进程有几个启动时的参数,其中, --config就是配置了kubelet的一些参数,apc项目中,该文件是/var/lib/kubelet/config.yaml, 可以看到两个参数

  staticPodPath: /etc/kubernetes/manifests     --静态pod文件的目录
  fileCheckFrequency: 20s                      --kubelet进程检查pod文件目录的间隔
查看master节点的静态pod文件目录如下,说明master节点kubelet进程启动时会默认启动这三个组件对应的pod
  node2# ls
  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml
在目录中新建一个pod.yaml
  apiVersion: v1
  kind: Pod
  metadata:
    name: demo-pod
    namespace: demo
    labels:
      app: demo
  spec:
    containers:
    - name: demo
      image: gcr.io/google-samples/kubernetes-bootcamp:v1
      ports:
      - containerPort: 8080

再查看pod,说明确实是主动检查了目录

  node2# kubectl get po -n demo
  NAME             READY   STATUS              RESTARTS   AGE
  demo-pod-node2   0/1     ContainerCreating   0          7s
  NAME             READY   STATUS              RESTARTS   AGE
  demo-pod-node2   0/1     ContainerCreating   0          7s

HTTP URL

跟静态文件配置类似,不过是通过url来获取,通过配置--manifest-url=<manifest-url>参数来获取待部署的pod列表

静态文件配置和url配置的pod都属于静态pod(static pod),static pod有kubelet进程直接管理,并且kubelet进程会向API Server创建一个对应的mirror pod, 这样就可以通过api server来进行查看

API Server

一般情况下创建的pod都是通过API Server方式创建的(使用kubectl命令或者由scheduler调度)

kubelet进程通过API server client使用Watch-List的方式监听“/registry/nodes/$”当前节点的名称

和“/registry/pods”目录,将获取的信息同步到本地缓存中。

使用如下命令查看etcd信息,可以看到etcd存储的pod列表

node2# etcdctl --cert=/etc/ssl/etcd/ssl/ca.pem --key=/etc/ssl/etcd/ssl/ca-key.pem  get / --prefix | grep -a registry/pods

可以再查看具体pod信息

etcdctl --cert=/etc/ssl/etcd/ssl/ca.pem --key=/etc/ssl/etcd/ssl/ca-key.pem  get /registry/pods/${namespace}/${pod}

每个node节点上kubelet,都会监听所有针对pod的操作,一旦有新的pod绑定到node上,就会进行创建,而有本地pod发生更新或者删除操作,kubelet会做出对应的操作

https://juejin.cn/post/6856431987302465550

 

容器健康检查

Pod通过两类探针来检查容器的健康状态,由kubelet进程向容器发送探测

  • LivenessProbe探针,用于判断容器是否健康并反馈给kubelet

  • ReadinessProbe探针,用于判断容器是否启动完成,且准备接收请求。 如果ReadinessProbe探针检测到容器启动失败,则Pod的状态将被修改,当就绪时就能开始接收流量

apc项目中探针如下

readinessProbe:
  tcpSocket:
  port: $POD_PORT
  initialDelaySeconds: 5
  periodSeconds: 10
livenessProbe:
  tcpSocket:
  port: $POD_PORT
  initialDelaySeconds: 15
  periodSeconds: 20

探针处理方式有三种类型:

1、ExecAction:在容器内部执行一个命令,如果该命令的退出 状态码为0,则表明容器健康。

2、TCPSocketAction:通过容器的IP地址和端口号执行TCP检 查,如果端口能被访问,则表明容器健康。

3、HTTPGetAction:通过容器的IP地址和端口号及路径调用 HTTP Get方法,如果响应的状态码大于等于200且小于等于400,则认为 容器状态健康。

资源监控

k8s集群中有三类指标需要进行关注和保证稳定运行

  • kubernetes基础组件。也就是组成kubernetes的应用进程,如api-server、controller-manager、scheduler、kubelet等

  • node节点。也就是组成kubernetes的机器

  • Pod/容器。也就是业务进程的运行环境

kube-proxy

kube-proxy的作用是Service背后的代理兼负载均衡器,核心功能是将某个Service的访问请求转发到后端的pod实例上去

它负责建立和删除包括更新IPVS规则、通知API SERVER自己的更新,或者从API SERVER哪里获取其他kube-proxy的IPVS规则变化来更新自己的

kube-proxy的总体流程如下:

pod或者外部client访问Service对应的Cluster IP, 再由kube-proxy根据路由规则转发到不同的pod上

 

kube-proxy路由的具体方式

https://kubernetes.io/docs/concepts/services-networking/service/

https://arthurchiao.art/blog/cracking-k8s-node-proxy/#4-implementation-1-proxy-via-userspace-socket

kube-proxy转发

最老的k8s版本中kube-proxy转发如下图所示,kube-proxy监听service和pod信息,每个Service创建时,kube-proxy进程更新iptables(防火墙),将service的cluster IP代理到kube-proxy,当在某个node上请求service上,会转发到kube-proxy,再由kube-proxy转发到service背后的pod

 

iptables代理模式

上面的kube-proxy转发需要先从内核态切换到用户态,性能不好,目前的方法是kube-proxy只用来更新iptables规则,而iptables规则直接由cluster ip转发到pod

 

IPVS模式

IPVS模式跟iptables类似,但是IPVS由于其设计和定位,它的存储和查找路由规则更高,专门用于高性能负载均衡,比iptables性能更高

 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值