1. 程式人生 > 其它 >k8s list watch機制、親和反親和及汙點容忍

k8s list watch機制、親和反親和及汙點容忍

目錄

一、排程約束

1.1、概念

Kubernetes 是通過 List-Watch 的機制進行每個元件的協作,保持資料同步的,每個元件之間的設計實現瞭解耦。

使用者是通過 kubectl 根據配置檔案,向 APIServer 傳送命令,在 Node 節點上面建立 Pod 和 Container。

APIServer 經過 API 呼叫,許可權控制,呼叫資源和儲存資源的過程,實際上還沒有真正開始部署應用。這裡 需要 Controller Manager、Scheduler 和 kubelet 的協助才能完成整個部署過程。

在 Kubernetes 中,所有部署的資訊都會寫到 etcd 中儲存。實際上 etcd 在儲存部署資訊的時候,會發送 Create 事件給 APIServer,而 APIServer 會通過監聽(Watch)etcd 發過來的事件。其他元件也會監聽(Watch)APIServer 發出來的事件。

1.2、Pod 啟動典型建立過程

(1)這裡有三個 List-Watch,分別是 Controller Manager(執行在 Master),Scheduler(執行在 Master),kubelet(執行在 Node)。他們在程序已啟動就會監聽(Watch)APIServer 發出來的事件。

(2)使用者通過 kubectl 或其他 API 客戶端提交請求給 APIServer 來建立一個 Pod 物件副本。

(3)APIServer 嘗試著將 Pod 物件的相關元資訊存入 etcd 中,待寫入操作執行完成,APIServer 即會返回確認資訊至客戶端。

(4)當 etcd 接受建立 Pod 資訊以後,會發送一個 Create 事件給 APIServer。

(5)由於 Controller Manager 一直在監聽(Watch,通過http的8080埠)APIServer 中的事件。此時 APIServer 接受到了 Create 事件,又會發送給 Controller Manager。

(6)Controller Manager 在接到 Create 事件以後,呼叫其中的 Replication Controller 來保證 Node 上面需要建立的副本數量。一旦副本數量少於 RC 中定義的數量,RC 會自動建立副本。總之它是保證副本數量的 Controller(PS:擴容縮容的擔當)。

(7)在 Controller Manager 建立 Pod 副本以後,APIServer 會在 etcd 中記錄這個 Pod 的詳細資訊。例如 Pod 的副本數,Container 的內容是什麼。

(8)同樣的 etcd 會將建立 Pod 的資訊通過事件傳送給 APIServer。

(9)由於 Scheduler 在監聽(Watch)APIServer,並且它在系統中起到了“承上啟下”的作用,“承上”是指它負責接收建立的 Pod 事件,為其安排 Node;“啟下”是指安置工作完成後,Node 上的 kubelet 程序會接管後繼工作,負責 Pod 生命週期中的“下半生”。 換句話說,Scheduler 的作用是將待排程的 Pod 按照排程演算法和策略繫結到叢集中 Node 上。

(10)Scheduler 排程完畢以後會更新 Pod 的資訊,此時的資訊更加豐富了。除了知道 Pod 的副本數量,副本內容。還知道部署到哪個 Node 上面了。並將上面的 Pod 資訊更新至 API Server,由 APIServer 更新至 etcd 中,儲存起來。

(11)etcd 將更新成功的事件傳送給 APIServer,APIServer 也開始反映此 Pod 物件的排程結果。

(12)kubelet 是在 Node 上面執行的程序,它也通過 List-Watch 的方式監聽(Watch,通過https的6443埠)APIServer 傳送的 Pod 更新的事件。kubelet 會嘗試在當前節點上呼叫 Docker 啟動容器,並將 Pod 以及容器的結果狀態回送至 APIServer。

(13)APIServer 將 Pod 狀態資訊存入 etcd 中。在 etcd 確認寫入操作成功完成後,APIServer將確認資訊傳送至相關的 kubelet,事件將通過它被接受。

注意:在建立 Pod 的工作就已經完成了後,為什麼 kubelet 還要一直監聽呢?原因很簡單,假設這個時候 kubectl 發命令,要擴充 Pod 副本數量,那麼上面的流程又會觸發一遍,kubelet 會根據最新的 Pod 的部署情況調整 Node 的資源。又或者 Pod 副本數量沒有發生變化,但是其中的映象檔案升級了,kubelet 也會自動獲取最新的映象檔案並且載入。

二、排程過程

2.1、排程過程主要考慮問題

  • 公平:如何保證每個節點都能被分配資源
  • 資源高效利用:叢集所有資源最大化被使用
  • 效率:排程的效能要好,能夠儘快地對大批量的 pod 完成排程工作
  • 靈活:允許使用者根據自己的需求控制排程的邏輯

Sheduler 是作為單獨的程式執行的,啟動之後會一直監聽 APIServer,獲取 spec.nodeName 為空的 pod,對每個 pod 都會建立一個 binding,表明該 pod 應該放到哪個節點上。

2.2、排程的幾個部分

首先是過濾掉不滿足條件的節點,這個過程稱為預算策略(predicate);然後對通過的節點按照優先順序排序,這個是優選策略(priorities);最後從中選擇優先順序最高的節點。如果中間任何一步驟有錯誤,就直接返回錯誤。

2.2.1、預算策略(Predicate)常見的演算法

  • PodFitsResources:節點上剩餘的資源是否大於 pod 請求的資源。
  • PodFitsHost:如果 pod 指定了 NodeName,檢查節點名稱是否和 NodeName 匹配。
  • PodFitsHostPorts:節點上已經使用的 port 是否和 pod 申請的 port 衝突。
  • PodSelectorMatches:過濾掉和 pod 指定的 label 不匹配的節點。
  • NoDiskConflict:已經 mount 的 volume 和 pod 指定的 volume 不衝突,除非它們都是隻讀。

如果在 predicate 過程中沒有合適的節點,pod 會一直在 pending 狀態,不斷重試排程,直到有節點滿足條件。 經過這個步驟,如果有多個節點滿足條件,就繼續優選策略過程:按照優先順序大小對節點排序。

2.2.2、常見的優先順序選項

  • LeastRequestedPriority:通過計算CPU和Memory的使用率來決定權重,使用率越低權重越高。也就是說,這個優先順序指標傾向於資源使用比例更低的節點。
  • BalancedResourceAllocation:節點上 CPU 和 Memory 使用率越接近,權重越高。這個一般和上面的一起使用,不單獨使用。比如 node01 的 CPU 和 Memory 使用率 20:60,node02 的 CPU 和 Memory 使用率 50:50,雖然 node01 的總使用率比 node02 低,但 node02 的 CPU 和 Memory 使用率更接近,從而排程時會優選 node02。
  • ImageLocalityPriority:傾向於已經有要使用映象的節點,映象總大小值越大,權重越高。

通過演算法對所有的優先順序專案和權重進行計算,得出最終的結果。

2.3、指定排程節點示例

2.3.1、nodeName

pod.spec.nodeName 將 Pod 直接排程到指定的 Node 節點上,會跳過 Scheduler 的排程策略,該匹配規則是強制匹配

vim myapp.yaml
apiVersion: extensions/v1beta1  
kind: Deployment  
metadata:
  name: myapp
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: myapp
    spec:
      nodeName: node01
      containers:
      - name: myapp
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

kubectl apply -f myapp.yaml

#檢視pod狀態
kubectl get pods -o wide

#檢視詳細事件(發現未經過 scheduler 排程分配)
kubectl describe pod 

nodeSelector

使用pod.spec.nodeSelector引數,通過 kubernetes 的 label-selector 機制選擇節點,由排程器排程策略匹配 label,然後排程 Pod 到目標節點,該匹配規則屬於強制約束  

kubectl label --help                                                    #獲取標籤幫助

需要獲取 node 上的 NAME 名稱
kubectl get node

給對應的 node 設定標籤分別為 abc=a 和 abc=b
kubectl label nodes node01 abc=aaa
kubectl label nodes node02 abc=bbb

檢視標籤
kubectl get nodes --show-labels

修改成 nodeSelector 排程方式
vim myapp1.yaml
apiVersion: extensions/v1beta1  
kind: Deployment  
metadata:
  name: myapp1
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: myapp1
    spec:
      nodeSelector:
        abc: aaa
      containers:
      - name: myapp1
        image: soscscs/myapp:v1
        ports:
        - containerPort: 80

kubectl apply -f myapp1.yaml 

kubectl get pods -o wide

#檢視詳細事件(通過事件可以發現要先經過 scheduler 排程分配)
kubectl describe pod
#修改一個 label 的值,需要加上 --overwrite 引數
kubectl label nodes node02 abc=aaa --overwrite

#刪除一個 label,只需在命令列最後指定 label 的 key 名並與一個減號相連即可:
kubectl label nodes node02 abc-

#指定標籤查詢 node 節點
kubectl get node -l abc=aaa

三、親和性

3.1、官方文件

https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node/

3.2、節點親和性

pod.spec.nodeAffinity
preferredDuringSchedulingIgnoredDuringExecution:軟策略
requiredDuringSchedulingIgnoredDuringExecution:硬策略	  

3.3、Pod 親和性

pod.spec.affinity.podAffinity/podAntiAffinity
preferredDuringSchedulingIgnoredDuringExecution:軟策略
requiredDuringSchedulingIgnoredDuringExecution:硬策略	

3.4、鍵值運算關係

In:label 的值在某個列表中
NotIn:label 的值不在某個列表中
Gt:label 的值大於某個值
Lt:label 的值小於某個值
Exists:某個 label 存在
DoesNotExist:某個 label 不存在

3.5、節點親和性(硬策略)測試

vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname    #指定node的標籤
            operator: NotIn     #設定Pod安裝到kubernetes.io/hostname的標籤值不在values列表中的node上
            values:
            - node02
		
kubectl apply -f pod1.yaml
kubectl get pods -o wide

3.6、節點親和性(軟策略)測試

vim pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1   #如果有多個軟策略選項的話,權重越大,優先順序越高
        preference:
        matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node03

kubectl apply -f pod2.yaml
kubectl get pods -o wide

3.7、軟硬策略結合測試

如果把硬策略和軟策略合在一起使用,則要先滿足硬策略之後才會滿足軟策略

apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:        #先滿足硬策略,排除有kubernetes.io/hostname=node02標籤的節點
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - node02
      preferredDuringSchedulingIgnoredDuringExecution:         #再滿足軟策略,優先選擇有abc=aaa標籤的節點
      - weight: 1
        preference:
        matchExpressions:
        - key: abc
          operator: In
          values:
          - aaa

3.8、親和性與反親和性

排程策略			匹配標籤	操作符										拓撲域支援		排程目標
nodeAffinity		主機		 In, NotIn, Exists,DoesNotExist, Gt, Lt		   否				指定主機
podAffinity			Pod		  In, NotIn, Exists,DoesNotExist				是				Pod與指定Pod同一拓撲域
podAntiAffinity		Pod		  In, NotIn, Exists,DoesNotExist				是				Pod與指定Pod不在同一拓撲域

3.8.1、親和性測試

#建立一個標籤為 app=myapp01 的 Pod
vim pod4.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: with-node-affinity
    image: nginx

kubectl apply -f pod3.yaml 
kubectl get pods --show-labels -o wide
#使用 Pod 親和性排程
vim pod5.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp02
  labels:
    app: myapp02
spec:
  containers:
  - name: myapp02
    image: soscscs/myapp:v1
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - myapp01
        topologyKey: kubernetes.io/hostname

#僅當節點和至少一個已執行且有鍵為“app”且值為“myapp01”的標籤 的 Pod 處於同一拓撲域時,才可以將該 Pod 排程到節點上。 (更確切的說,如果節點 N 具有帶有鍵 kubernetes.io/hostname 和某個值 V 的標籤,則 Pod 有資格在節點 N 上執行, 以便叢集中至少有一個節點具有鍵 kubernetes.io/hostname 和值為 V 的節點正在執行具有鍵“app”和值 “myapp01”的標籤的 pod。)
#topologyKey 是節點標籤的鍵。如果兩個節點使用此鍵標記並且具有相同的標籤值,則排程器會將這兩個節點視為處於同一拓撲域中。 排程器試圖在每個拓撲域中放置數量均衡的 Pod。
#如果kubernetes.io/hostname對應的值不一樣就是不同的拓撲域。比如 Pod1 在kubernetes.io/hostname=node01的 Node 上,Pod2 在kubernetes.io/hostname=node02的 Node 上,Pod3 在kubernetes.io/hostname=node01的 Node 上,則 Pod2 和 Pod1、Pod3 不在同一個拓撲域,而Pod1 和 Pod3在同一個拓撲域。

3.8.2、使用 Pod 反親和性排程

vim pod6.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp03
  labels:
    app: myapp03
spec:
  containers:
  - name: myapp03
    image: soscscs/myapp:v1
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - myapp01
          topologyKey: kubernetes.io/hostname

#如果節點處於 Pod 所在的同一拓撲域且具有鍵“app”和值“myapp01”的標籤, 則該 pod 不應將其排程到該節點上。 (如果 topologyKey 為 kubernetes.io/hostname,則意味著當節點和具有鍵 “app”和值“myapp01”的 Pod 處於相同的區域,Pod 不能被排程到該節點上。)

四、親和總結

#節點親和

排程到滿足 Node 節點的標籤條件的Node節點 nodeAffinity
硬策略:必須滿足條件 requiredDuringSchedulingIgnoredDuringExecution
軟策略:儘量滿足條件,滿足不了也沒關係 preferredDuringSchedulingIgnoredDuringExecution

硬策略配置:
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: KEY_NAME
            operator: In/NotIn/Exists/DoesNotExist/Gt/Lt
            values:
            - KEY_VALUE

軟策略配置:
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: WEIGHT_VALUE
        preference:
          matchExpressions:
          - key: KEY_NAME
            operator: In/NotIn/Exists/DoesNotExist
            values:
            - KEY_VALUE

#pod親和
pod親和(podAffinity):排程到滿足pod的標籤條件所對應的node節點(用的硬策略)
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In/NotIn/Exists/DoesNotExist
            values:
            - KEY_VALUE
        topologyKey: kubernetes.io/hostname
#Pod親和必須攜帶拓撲域欄位

pod反親和(podAntiAffinity):不排程到滿足pod的標籤條件所對應的node節點(用的軟策略)
spec:
  containers:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: WEIGHT_VALUE
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In/NotIn/Exists/DoesNotExist
              values:
              - KEY_VALUE
          topologyKey: kubernetes.io/hostname

五、汙點(Taint)和容忍(Tolerations)

5.1、汙點(Taint)

節點親和性,是Pod的一種屬性(偏好或硬性要求),它使Pod被吸引到一類特定的節點。Taint 則相反,它使節點能夠排斥一類特定的 Pod。
Taint 和 Toleration 相互配合,可以用來避免 Pod 被分配到不合適的節點上。每個節點上都可以應用一個或多個 taint ,這表示對於那些不能容忍這些 taint 的 Pod,是不會被該節點接受的。如果將 toleration 應用於 Pod 上,則表示這些 Pod 可以(但不一定)被排程到具有匹配 taint 的節點上。
使用 kubectl taint 命令可以給某個 Node 節點設定汙點,Node 被設定上汙點之後就和 Pod 之間存在了一種相斥的關係,可以讓 Node 拒絕 Pod 的排程執行,甚至將 Node 已經存在的 Pod 驅逐出去。

5.1.1、汙點的組成格式

key=value:effect
每個汙點有一個key和value作為汙點的標籤,其中value可以為空,effect描述汙點的作用

5.1.2、taint effect支援的三個選項

NoSchedule:表示 k8s 將不會將 Pod 排程到具有該汙點的 Node 上
PreferNoSchedule:表示 k8s 將盡量避免將 Pod 排程到具有該汙點的 Node 上
NoExecute:表示 k8s 將不會將 Pod 排程到具有該汙點的 Node 上,同時會將 Node 上已經存在的 Pod 驅逐出去

5.1.3、master 汙點設定

master 就是因為有 NoSchedule 汙點,k8s 才不會將 Pod 排程到 master 節點上
kubectl describe node master01

5.1.4、node上設定汙點

#設定汙點
kubectl taint nodes node01 key1=value1:NoSchedule

#節點說明中,查詢 Taints 欄位
kubectl describe node node-name

#去除汙點
kubectl taint nodes node01 key1:NoSchedule-

5.1.5、例項

kubectl taint nodes node02 check=mycheck:NoExecute
#檢視 Pod 狀態,會發現 node02 上的 Pod 已經被全部驅逐(注:如果是 Deployment 或者 StatefulSet 資源型別,為了維持副本數量則會在別的 Node 上再建立新的 Pod)
kubectl get pods -o wide

5.2、容忍(Tolerations)

設定了汙點的 Node 將根據 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之間產生互斥的關係,Pod 將在一定程度上不會被排程到 Node 上。但我們可以在 Pod 上設定容忍(Tolerations),意思是設定了容忍的 Pod 將可以容忍汙點的存在,可以被排程到存在汙點的 Node 上。

5.2.1、容忍示例

#兩個節點都設定汙點
kubectl taint nodes node01 check=mycheck:NoExecute
kubectl taint nodes node02 check2=mycheck2:NoExecute

vim test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1

kubectl apply -f pod3.yaml
#設定容忍
vim demo2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: myapp04
  labels:
    app: myapp04
spec:
  containers:
  - name: myapp01
    image: nginx
  tolerations:
  - key: "check"
    operator: "Equal"
    value: "mycheck"
    effect: "NoExecute"
    tolerationSeconds: 15

#其中的 key、vaule、effect 都要與 Node 上設定的 taint 保持一致
#operator 的值為 Exists 將會忽略 value 值,即存在即可
#tolerationSeconds 用於描述當 Pod 需要被驅逐時可以在 Pod 上繼續保留執行的時間

5.2.3、其它注意事項

當不指定 key 值時,表示容忍所有的汙點 key
tolerations:
- operator: "Exists"

當不指定 effect 值時,表示容忍所有的汙點作用
tolerations:
- key: "key"
  operator: "Exists"

有多個 Master 存在時,防止資源浪費,可以如下設定
kubectl taint nodes Master-Name node-role.kubernetes.io/master=:PreferNoSchedule

如果某個 Node 更新升級系統元件,為了防止業務長時間中斷,可以先在該 Node 設定 NoExecute 汙點,把該 Node 上的 Pod 都驅逐出去
kubectl taint nodes node01 check=mycheck:NoExecute

此時如果別的 Node 資源不夠用,可臨時給 Master 設定 PreferNoSchedule 汙點,讓 Pod 可在 Master 上臨時建立
kubectl taint nodes master node-role.kubernetes.io/master=:PreferNoSchedule

待所有 Node 的更新操作都完成後,再去除汙點
kubectl taint nodes node01 check=mycheck:NoExecute-

六、Pod啟動階段(相位 phase)

6.1、pod啟動過程

1.排程到某臺 node 上。kubernetes 根據一定的優先順序演算法選擇一臺 node 節點將其作為 Pod 執行的 node
2.拉取映象
3.掛載儲存配置等
4.執行起來。如果有健康檢查,會根據檢查的結果來設定其狀態

6.2、phase的狀態

●Pending:表示APIServer建立了Pod資源物件並已經存入了etcd中,但是它並未被排程完成(比如還沒有排程到某臺node上),或者仍然處於從倉庫下載映象的過程中。

●Running:Pod已經被排程到某節點之上,並且Pod中所有容器都已經被kubelet建立。至少有一個容器正在執行,或者正處於啟動或者重啟狀態(也就是說Running狀態下的Pod不一定能被正常訪問)。

●Succeeded:有些pod不是長久執行的,比如job、cronjob,一段時間後Pod中的所有容器都被成功終止,並且不會再重啟。需要反饋任務執行的結果。

●Failed:Pod中的所有容器都已終止了,並且至少有一個容器是因為失敗終止。也就是說,容器以非0狀態退出或者被系統終止,比如 command 寫的有問題。

●Unknown:因為某些原因無法取得 Pod 的狀態,通常是因為與 Pod 所在主機通訊失敗

6.3、故障排除步驟

//檢視Pod事件
kubectl describe TYPE NAME_PREFIX  

//檢視Pod日誌(Failed狀態下)
kubectl logs <POD_NAME> [-c Container_NAME]

//進入Pod(狀態為running,但是服務沒有提供)
kubectl exec –it <POD_NAME> bash

//檢視叢集資訊
kubectl get nodes

//發現叢集狀態正常
kubectl cluster-info

//檢視kubelet日誌發現
journalctl -xefu kubelet

6.4、對節點執行維護操作

#將 Node 標記為不可排程的狀態,這樣就不會讓新建立的 Pod 在此 Node 上執行
kubectl cordon <NODE_NAME> 		 #該node將會變為SchedulingDisabled狀態
#kubectl drain 可以讓 Node 節點開始釋放所有 pod,並且不接收新的 pod程序。drain 本意排水,意思是將出問題的 Node 下的 Pod 轉移到其它 Node 下執行
kubectl drain <NODE_NAME> --ignore-daemonsets --delete-local-data --force

--ignore-daemonsets:無視 DaemonSet 管理下的 Pod。
--delete-local-data:如果有 mount local volume 的 pod,會強制殺掉該 pod。
--force:強制釋放不是控制器管理的 Pod,例如 kube-proxy。

#執行 drain 命令,會自動做了兩件事情:
(1)設定此 node 為不可排程狀態(cordon)
(2)evict(驅逐)了 Pod

#kubectl uncordon 將 Node 標記為可排程的狀態
kubectl uncordon <NODE_NAME>