Kubernetes 核心元件執行機制

2021-03-22 12:00:56

目錄

 

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/

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效能更高