Pod詳解

羅家龍發表於2022-11-30

Pod詳解

Pod生命週期

我們一般將pod物件從建立至終的這段時間範圍稱為pod的生命週期,它主要包含下面的過程:

  • pod建立過程
  • 執行初始化容器(init container)過程
  • 執行主容器(main container)
  • 容器啟動後鉤子(post start)、容器終止前鉤子(pre stop)
  • 容器的存活性探測(liveness probe)、就緒性探測(readiness probe)
  • pod終止過程

在整個生命週期中,Pod會出現5種狀態(相位),分別如下:

  • 掛起(Pending):apiserver已經建立了pod資源物件,但它尚未被排程完成或者仍處於下載映象的過程中
  • 執行中(Running):pod已經被排程至某節點,並且所有容器都已經被kubelet建立完成
  • 成功(Succeeded):pod中的所有容器都已經成功終止並且不會被重啟
  • 失敗(Failed):所有容器都已經終止,但至少有一個容器終止失敗,即容器返回了非0值的退出狀態
  • 未知(Unknown):apiserver無法正常獲取到pod物件的狀態資訊,通常由網路通訊失敗所導致

一、建立和終止

pod的建立過程
1、使用者透過kubectl或其他api客戶端提交需要建立的pod資訊給apiServer
2、apiServer開始生成pod物件的資訊,並將資訊存入etcd,然後返回確認資訊至客戶端
3、apiServer開始反映etcd中的pod物件的變化,其它元件使用watch機制來跟蹤檢查apiServer上的變動
4、scheduler發現有新的pod物件要建立,開始為Pod分配主機並將結果資訊更新至apiServer
5、node節點上的kubelet發現有pod排程過來,嘗試呼叫docker啟動容器,並將結果回送至apiServer
6、apiServer將接收到的pod狀態資訊存入etcd中

pod的終止過程
1.使用者向apiServer傳送刪除pod物件的命令
2.apiServcer中的pod物件資訊會隨著時間的推移而更新,在寬限期內(預設30s),pod被視為dead
3.將pod標記為terminating狀態
4.kubelet在監控到pod物件轉為terminating狀態的同時啟動pod關閉過程
5.節點控制器監控到pod物件的關閉行為時將其從所有匹配到此節點的service資源的節點列表中移除
6.如果當前pod物件定義了preStop鉤子處理器,則在其標記為terminating後即會以同步的方式啟動執行
7.pod物件中的容器程式收到停止訊號
8.寬限期結束後,若pod中還存在仍在執行的程式,那麼pod物件會收到立即終止的訊號
9.kubelet請求apiServer將此pod資源的寬限期設定為0從而完成刪除操作,此時pod對於使用者已不可見

二、 初始化容器

初始化容器是在pod的主容器啟動之前要執行的容器,主要是做一些主容器的前置工作,它具有兩大特徵:
1、初始化容器必須執行完成直至結束,若某初始化容器執行失敗,那麼kubernetes需要重啟它直到成功完成
2、初始化容器必須按照定義的順序執行,當且僅當前一個成功之後,後面的一個才能執行
初始化容器有很多的應用場景,下面列出的是最常見的幾個:
提供主容器映象中不具備的工具程式或自定義程式碼
初始化容器要先於應用容器序列啟動並執行完成,因此可用於延後應用容器的啟動直至其依賴的條件得到滿足
接下來做一個案例,模擬下面這個需求:
假設要以主容器來執行nginx,但是要求在執行nginx之前先要能夠連線上mysql和redis所在伺服器
為了簡化測試,事先規定好mysql(192.168.100.11)和redis(192.168.100.12)伺服器的地址
建立pod-initcontainer.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-initcontainer
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
  initContainers:
  - name: test-mysql
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.100.11 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
  - name: test-redis
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.100.12 -c 1 ; do echo waiting for reids...; sleep 2; done;']
# 建立pod
[root@k8s-master01 ~]# kubectl create -f pod-initcontainer.yaml
pod/pod-initcontainer created
# 檢視pod狀態# 發現pod卡在啟動第一個初始化容器過程中,後面的容器不會執行
root@k8s-master01 ~]# kubectl describe pod  pod-initcontainer -n dev
........Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  49s   default-scheduler  Successfully assigned dev/pod-initcontainer to node1
  Normal  Pulled     48s   kubelet, node1     Container image "busybox:1.30" already present on machine
  Normal  Created    48s   kubelet, node1     Created container test-mysql
  Normal  Started    48s   kubelet, node1     Started container test-mysql
# 動態檢視pod
[root@k8s-master01 ~]# kubectl get pods pod-initcontainer -n dev -w
NAME                             READY   STATUS     RESTARTS   AGE
pod-initcontainer                0/1     Init:0/2   0          15s
pod-initcontainer                0/1     Init:1/2   0          52s
pod-initcontainer                0/1     Init:1/2   0          53s
pod-initcontainer                0/1     PodInitializing   0          89s
pod-initcontainer                1/1     Running           0          90s
# 接下來新開一個shell,為當前伺服器新增兩個ip,觀察pod的變化
[root@k8s-master01 ~]# ifconfig ens33:1 192.168.5.14 netmask 255.255.255.0 up
[root@k8s-master01 ~]# ifconfig ens33:2 192.168.5.15 netmask 255.255.255.0 up

5.3.3 鉤子函式

鉤子函式能夠感知自身生命週期中的事件,並在相應的時刻到來時執行使用者指定的程式程式碼。
kubernetes在主容器的啟動之後和停止之前提供了兩個鉤子函式:

  • post start:容器建立之後執行,如果失敗了會重啟容器
  • pre stop :容器終止之前執行,執行完成之後容器將成功終止,在其完成之前會阻塞刪除容器的操作
    鉤子處理器支援使用下面三種方式定義動作:
    Exec命令:在容器內執行一次命令
……
lifecycle:

  postStart: 

    exec:

      command:

      - cat

      - /tmp/healthy

TCPSocket:在當前容器嘗試訪問指定的socket

……      
lifecycle:

  postStart:

    tcpSocket:

      port: 8080

……

HTTPGet:在當前容器中向某url發起http請求

……
lifecycle:

  postStart:

    httpGet:

      path: / #URI地址

      port: 80 #埠號

      host: 192.168.5.3 #主機地址

      scheme: HTTP #支援的協議,http或者https

……

接下來,以exec方式為例,演示下鉤子函式的使用,建立pod-hook-exec.yaml檔案,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart: 
        exec: # 在容器啟動的時候執行一個命令,修改掉nginx的預設首頁內容
          command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec: # 在容器停止之前停止nginx服務
          command: ["/usr/sbin/nginx","-s","quit"]
# 建立pod
[root@k8s-master01 ~]# kubectl create -f pod-hook-exec.yaml
pod/pod-hook-exec created
# 檢視pod
[root@k8s-master01 ~]# kubectl get pods  pod-hook-exec -n dev -o wide
NAME           READY   STATUS     RESTARTS   AGE    IP            NODE    
pod-hook-exec  1/1     Running    0          29s    10.244.2.48   node2   
# 訪問pod
[root@k8s-master01 ~]# curl 10.244.2.48
postStart...

5.3.4 容器探測

容器探測用於檢測容器中的應用例項是否正常工作,是保障業務可用性的一種傳統機制。如果經過探測,例項的狀態不符合預期,那麼kubernetes就會把該問題例項" 摘除 ",不承擔業務流量。kubernetes提供了兩種探針來實現容器探測,分別是:

  • liveness probes:存活性探針,用於檢測應用例項當前是否處於正常執行狀態,如果不是,k8s會重啟容器
  • readiness probes:就緒性探針,用於檢測應用例項當前是否可以接收請求,如果不能,k8s不會轉發流量
    livenessProbe 決定是否重啟容器,readinessProbe 決定是否將請求轉發給容器。
    上面兩種探針目前均支援三種探測方式:
    Exec命令:在容器內執行一次命令,如果命令執行的退出碼為0,則認為程式正常,否則不正常
……
livenessProbe:

  exec:

    command:

    - cat

    - /tmp/healthy

TCPSocket:將會嘗試訪問一個使用者容器的埠,如果能夠建立這條連線,則認為程式正常,否則不正常

……      
livenessProbe:

  tcpSocket:

    port: 8080

……

HTTPGet:呼叫容器內Web應用的URL,如果返回的狀態碼在200和399之間,則認為程式正常,否則不正常

……
livenessProbe:

  httpGet:

    path: / #URI地址

    port: 80 #埠號

    host: 127.0.0.1 #主機地址

    scheme: HTTP #支援的協議,http或者https

……

下面以liveness probes為例,做幾個演示:
方式一:Exec
建立pod-liveness-exec.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"] # 執行一個檢視檔案的命令
建立pod,觀察效果
# 建立Pod
[root@k8s-master01 ~]# kubectl create -f pod-liveness-exec.yaml
pod/pod-liveness-exec created
# 檢視Pod詳情
[root@k8s-master01 ~]# kubectl describe pods pod-liveness-exec -n dev
......
  Normal   Created    20s (x2 over 50s)  kubelet, node1     Created container nginx
  Normal   Started    20s (x2 over 50s)  kubelet, node1     Started container nginx
  Normal   Killing    20s                kubelet, node1     Container nginx failed liveness probe, will be restarted
  Warning  Unhealthy  0s (x5 over 40s)   kubelet, node1     Liveness probe failed: cat: can't open '/tmp/hello11.txt': No such file or directory
  # 觀察上面的資訊就會發現nginx容器啟動之後就進行了健康檢查# 檢查失敗之後,容器被kill掉,然後嘗試進行重啟(這是重啟策略的作用,後面講解)# 稍等一會之後,再觀察pod資訊,就可以看到RESTARTS不再是0,而是一直增長
[root@k8s-master01 ~]# kubectl get pods pod-liveness-exec -n dev
NAME                READY   STATUS             RESTARTS   AGE
pod-liveness-exec   0/1     CrashLoopBackOff   2          3m19s
# 當然接下來,可以修改成一個存在的檔案,比如/tmp/hello.txt,再試,結果就正常了......

方式二:TCPSocket
建立pod-liveness-tcpsocket.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-tcpsocket
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      tcpSocket:
        port: 8080 # 嘗試訪問8080埠
建立pod,觀察效果
# 建立Pod
[root@k8s-master01 ~]# kubectl create -f pod-liveness-tcpsocket.yaml
pod/pod-liveness-tcpsocket created
# 檢視Pod詳情
[root@k8s-master01 ~]# kubectl describe pods pod-liveness-tcpsocket -n dev
......
  Normal   Scheduled  31s                            default-scheduler  Successfully assigned dev/pod-liveness-tcpsocket to node2
  Normal   Pulled     <invalid>                      kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    <invalid>                      kubelet, node2     Created container nginx
  Normal   Started    <invalid>                      kubelet, node2     Started container nginx
  Warning  Unhealthy  <invalid> (x2 over <invalid>)  kubelet, node2     Liveness probe failed: dial tcp 10.244.2.44:8080: connect: connection refused
  # 觀察上面的資訊,發現嘗試訪問8080埠,但是失敗了# 稍等一會之後,再觀察pod資訊,就可以看到RESTARTS不再是0,而是一直增長
[root@k8s-master01 ~]# kubectl get pods pod-liveness-tcpsocket  -n dev
NAME                     READY   STATUS             RESTARTS   AGE
pod-liveness-tcpsocket   0/1     CrashLoopBackOff   2          3m19s
# 當然接下來,可以修改成一個可以訪問的埠,比如80,再試,結果就正常了......

方式三:HTTPGet
建立pod-liveness-httpget.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:  # 其實就是訪問http://127.0.0.1:80/hello  
        scheme: HTTP #支援的協議,http或者https
        port: 80 #埠號
        path: /hello #URI地址
建立pod,觀察效果
# 建立Pod
[root@k8s-master01 ~]# kubectl create -f pod-liveness-httpget.yaml
pod/pod-liveness-httpget created
# 檢視Pod詳情
[root@k8s-master01 ~]# kubectl describe pod pod-liveness-httpget -n dev
.......
  Normal   Pulled     6s (x3 over 64s)  kubelet, node1     Container image "nginx:1.17.1" already present on machine
  Normal   Created    6s (x3 over 64s)  kubelet, node1     Created container nginx
  Normal   Started    6s (x3 over 63s)  kubelet, node1     Started container nginx
  Warning  Unhealthy  6s (x6 over 56s)  kubelet, node1     Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing    6s (x2 over 36s)  kubelet, node1     Container nginx failed liveness probe, will be restarted
  # 觀察上面資訊,嘗試訪問路徑,但是未找到,出現404錯誤# 稍等一會之後,再觀察pod資訊,就可以看到RESTARTS不再是0,而是一直增長
[root@k8s-master01 ~]# kubectl get pod pod-liveness-httpget -n dev
NAME                   READY   STATUS    RESTARTS   AGE
pod-liveness-httpget   1/1     Running   5          3m17s
# 當然接下來,可以修改成一個可以訪問的路徑path,比如/,再試,結果就正常了......
至此,已經使用liveness Probe演示了三種探測方式,但是檢視livenessProbe的子屬性,會發現除了這三種方式,還有一些其他的配置,在這裡一併解釋下:
[root@k8s-master01 ~]# kubectl explain pod.spec.containers.livenessProbe
FIELDS:
   exec <Object>  
   tcpSocket    <Object>
   httpGet      <Object>
   initialDelaySeconds  <integer>  # 容器啟動後等待多少秒執行第一次探測
   timeoutSeconds       <integer>  # 探測超時時間。預設1秒,最小1秒
   periodSeconds        <integer>  # 執行探測的頻率。預設是10秒,最小1秒
   failureThreshold     <integer>  # 連續探測失敗多少次才被認定為失敗。預設是3。最小值是1
   successThreshold     <integer>  # 連續探測成功多少次才被認定為成功。預設是1
下面稍微配置兩個,演示下效果即可:
[root@k8s-master01 ~]# more pod-liveness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80 
        path: /
      initialDelaySeconds: 30 # 容器啟動後30s開始探測
      timeoutSeconds: 5 # 探測超時時間為5s

5.3.5 重啟策略

在上一節中,一旦容器探測出現了問題,kubernetes就會對容器所在的Pod進行重啟,其實這是由pod的重啟策略決定的,pod的重啟策略有 3 種,分別如下:

  • Always :容器失效時,自動重啟該容器,這也是預設值。
  • OnFailure : 容器終止執行且退出碼不為0時重啟
  • Never : 不論狀態為何,都不重啟該容器
    重啟策略適用於pod物件中的所有容器,首次需要重啟的容器,將在其需要時立即進行重啟,隨後再次需要重啟的操作將由kubelet延遲一段時間後進行,且反覆的重啟操作的延遲時長以此為10s、20s、40s、80s、160s和300s,300s是最大延遲時長。
    建立pod-restartpolicy.yaml:
apiVersion: v1
kind: Pod
metadata:
  name: pod-restartpolicy
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /hello
  restartPolicy: Never # 設定重啟策略為Never
執行Pod測試
# 建立Pod
[root@k8s-master01 ~]# kubectl create -f pod-restartpolicy.yaml
pod/pod-restartpolicy created
# 檢視Pod詳情,發現nginx容器失敗
[root@k8s-master01 ~]# kubectl  describe pods pod-restartpolicy  -n dev
......
  Warning  Unhealthy  15s (x3 over 35s)  kubelet, node1     Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing    15s                kubelet, node1     Container nginx failed liveness probe
  # 多等一會,再觀察pod的重啟次數,發現一直是0,並未重啟   
[root@k8s-master01 ~]# kubectl  get pods pod-restartpolicy -n dev
NAME                   READY   STATUS    RESTARTS   AGE
pod-restartpolicy      0/1     Running   0          5min42s

5.4 Pod排程

在預設情況下,一個Pod在哪個Node節點上執行,是由Scheduler元件採用相應的演算法計算出來的,這個過程是不受人工控制的。但是在實際使用中,這並不滿足的需求,因為很多情況下,我們想控制某些Pod到達某些節點上,那麼應該怎麼做呢?這就要求瞭解kubernetes對Pod的排程規則,kubernetes提供了四大類排程方式:

  • 自動排程:執行在哪個節點上完全由Scheduler經過一系列的演算法計算得出
  • 定向排程:NodeName、NodeSelector
  • 親和性排程:NodeAffinity、PodAffinity、PodAntiAffinity
  • 汙點(容忍)排程:Taints、Toleration

5.4.1 定向排程

定向排程,指的是利用在pod上宣告nodeName或者nodeSelector,以此將Pod排程到期望的node節點上。注意,這裡的排程是強制的,這就意味著即使要排程的目標Node不存在,也會向上面進行排程,只不過pod執行失敗而已。
NodeName
NodeName用於強制約束將Pod排程到指定的Name的Node節點上。這種方式,其實是直接跳過Scheduler的排程邏輯,直接將Pod排程到指定名稱的節點。
接下來,實驗一下:建立一個pod-nodename.yaml檔案

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 指定排程到node1節點上
#建立Pod
[root@k8s-master01 ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created
#檢視Pod排程到NODE屬性,確實是排程到了node1節點上
[root@k8s-master01 ~]# kubectl get pods pod-nodename -n dev -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP            NODE      ......
pod-nodename   1/1     Running   0          56s   10.244.1.87   node1     ......   
# 接下來,刪除pod,修改nodeName的值為node3(並沒有node3節點)
[root@k8s-master01 ~]# kubectl delete -f pod-nodename.yaml
pod "pod-nodename" deleted
[root@k8s-master01 ~]# vim pod-nodename.yaml
[root@k8s-master01 ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created
#再次檢視,發現已經向Node3節點排程,但是由於不存在node3節點,所以pod無法正常執行
[root@k8s-master01 ~]# kubectl get pods pod-nodename -n dev -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP       NODE    ......
pod-nodename   0/1     Pending   0          6s    <none>   node3   ......           
  • NodeSelector
    NodeSelector用於將pod排程到新增了指定標籤的node節點上。它是透過kubernetes的label-selector機制實現的,也就是說,在pod建立之前,會由scheduler使用MatchNodeSelector排程策略進行label匹配,找出目標node,然後將pod排程到目標節點,該匹配規則是強制約束。
    接下來,實驗一下:
    1 首先分別為node節點新增標籤
[root@k8s-master01 ~]# kubectl label nodes node1 nodeenv=pro
node/node2 labeled
[root@k8s-master01 ~]# kubectl label nodes node2 nodeenv=test
node/node2 labeled

2 建立一個pod-nodeselector.yaml檔案,並使用它建立Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeSelector: 
    nodeenv: pro # 指定排程到具有nodeenv=pro標籤的節點上
#建立Pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created
#檢視Pod排程到NODE屬性,確實是排程到了node1節點上
[root@k8s-master01 ~]# kubectl get pods pod-nodeselector -n dev -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP          NODE    ......
pod-nodeselector   1/1     Running   0          47s   10.244.1.87   node1   ......
# 接下來,刪除pod,修改nodeSelector的值為nodeenv: xxxx(不存在打有此標籤的節點)
[root@k8s-master01 ~]# kubectl delete -f pod-nodeselector.yaml
pod "pod-nodeselector" deleted
[root@k8s-master01 ~]# vim pod-nodeselector.yaml
[root@k8s-master01 ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created
#再次檢視,發現pod無法正常執行,Node的值為none
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP       NODE    
pod-nodeselector   0/1     Pending   0          2m20s   <none>   <none>
# 檢視詳情,發現node selector匹配失敗的提示
[root@k8s-master01 ~]# kubectl describe pods pod-nodeselector -n dev
.......Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

5.4.2 親和性排程

上一節,介紹了兩種定向排程的方式,使用起來非常方便,但是也有一定的問題,那就是如果沒有滿足條件的Node,那麼Pod將不會被執行,即使在叢集中還有可用Node列表也不行,這就限制了它的使用場景。
基於上面的問題,kubernetes還提供了一種親和性排程(Affinity)。它在NodeSelector的基礎之上的進行了擴充套件,可以透過配置的形式,實現優先選擇滿足條件的Node進行排程,如果沒有,也可以排程到不滿足條件的節點上,使排程更加靈活。
Affinity主要分為三類:

  • nodeAffinity(node親和性): 以node為目標,解決pod可以排程到哪些node的問題

  • podAffinity(pod親和性) : 以pod為目標,解決pod可以和哪些已存在的pod部署在同一個拓撲域中的問題

  • podAntiAffinity(pod反親和性) : 以pod為目標,解決pod不能和哪些已存在pod部署在同一個拓撲域中的問題
    關於親和性(反親和性)使用場景的說明:
    親和性:如果兩個應用頻繁互動,那就有必要利用親和性讓兩個應用的儘可能的靠近,這樣可以減少因網路通訊而帶來的效能損耗。
    反親和性:當應用的採用多副本部署時,有必要採用反親和性讓各個應用例項打散分佈在各個node上,這樣可以提高服務的高可用性。

  • NodeAffinity
    首先來看一下NodeAffinity的可配置項:

pod.spec.affinity.nodeAffinity
  requiredDuringSchedulingIgnoredDuringExecution  Node節點必須滿足指定的所有規則才可以,相當於硬限制
    nodeSelectorTerms  節點選擇列表
      matchFields   按節點欄位列出的節點選擇器要求列表
      matchExpressions   按節點標籤列出的節點選擇器要求列表(推薦)
        key    鍵
        values 值
        operator 關係符 支援Exists, DoesNotExist, In, NotIn, Gt, Lt
  preferredDuringSchedulingIgnoredDuringExecution 優先排程到滿足指定的規則的Node,相當於軟限制 (傾向)
    preference   一個節點選擇器項,與相應的權重相關聯
      matchFields   按節點欄位列出的節點選擇器要求列表
      matchExpressions   按節點標籤列出的節點選擇器要求列表(推薦)
        key    鍵
        values 值
        operator 關係符 支援In, NotIn, Exists, DoesNotExist, Gt, Lt
    weight 傾向權重,在範圍1-100。
關係符的使用說明:

- matchExpressions:
  - key: nodeenv              # 匹配存在標籤的key為nodeenv的節點
    operator: Exists
  - key: nodeenv              # 匹配標籤的key為nodeenv,且value是"xxx"或"yyy"的節點
    operator: In
    values: ["xxx","yyy"]
  - key: nodeenv              # 匹配標籤的key為nodeenv,且value大於"xxx"的節點
    operator: Gt
    values: "xxx"

接下來首先演示一下requiredDuringSchedulingIgnoredDuringExecution ,
建立pod-nodeaffinity-required.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    nodeAffinity: #設定node親和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["xxx","yyy"]中的標籤
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
# 建立pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created
# 檢視pod狀態 (執行失敗)
[root@k8s-master01 ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP       NODE    ...... 
pod-nodeaffinity-required   0/1     Pending   0          16s   <none>   <none>  ......
# 檢視Pod的詳情# 發現排程失敗,提示node選擇失敗
[root@k8s-master01 ~]# kubectl describe pod pod-nodeaffinity-required -n dev
......
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.
#接下來,停止pod
[root@k8s-master01 ~]# kubectl delete -f pod-nodeaffinity-required.yaml
pod "pod-nodeaffinity-required" deleted
# 修改檔案,將values: ["xxx","yyy"]------> ["pro","yyy"]
[root@k8s-master01 ~]# vim pod-nodeaffinity-required.yaml
# 再次啟動
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created
# 此時檢視,發現排程成功,已經將pod排程到了node1上
[root@k8s-master01 ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE  ...... 
pod-nodeaffinity-required   1/1     Running   0          11s   10.244.1.89   node1 ......

接下來再演示一下requiredDuringSchedulingIgnoredDuringExecution ,
建立pod-nodeaffinity-preferred.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    nodeAffinity: #設定node親和性
      preferredDuringSchedulingIgnoredDuringExecution: # 軟限制
      - weight: 1
        preference:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的標籤(當前環境沒有)
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
# 建立pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-preferred.yaml
pod/pod-nodeaffinity-preferred created
# 檢視pod狀態 (執行成功)
[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-preferred -n dev
NAME                         READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-preferred   1/1     Running   0          40s
NodeAffinity規則設定的注意事項:
    1 如果同時定義了nodeSelector和nodeAffinity,那麼必須兩個條件都得到滿足,Pod才能執行在指定的Node上
    2 如果nodeAffinity指定了多個nodeSelectorTerms,那麼只需要其中一個能夠匹配成功即可
    3 如果一個nodeSelectorTerms中有多個matchExpressions ,則一個節點必須滿足所有的才能匹配成功
    4 如果一個pod所在的Node在Pod執行期間其標籤發生了改變,不再符合該Pod的節點親和性需求,則系統將忽略此變化
  • PodAffinity
    PodAffinity主要實現以執行的Pod為參照,實現讓新建立的Pod跟參照pod在一個區域的功能。
    首先來看一下PodAffinity的可配置項:
pod.spec.affinity.podAffinity
  requiredDuringSchedulingIgnoredDuringExecution  硬限制
    namespaces       指定參照pod的namespace
    topologyKey      指定排程作用域
    labelSelector    標籤選擇器
      matchExpressions  按節點標籤列出的節點選擇器要求列表(推薦)
        key    鍵
        values 值
        operator 關係符 支援In, NotIn, Exists, DoesNotExist.
      matchLabels    指多個matchExpressions對映的內容
  preferredDuringSchedulingIgnoredDuringExecution 軟限制
    podAffinityTerm  選項
      namespaces      
      topologyKey
      labelSelector
        matchExpressions  
          key    鍵
          values 值
          operator
        matchLabels 
    weight 傾向權重,在範圍1-100
topologyKey用於指定排程時作用域,例如:
    如果指定為kubernetes.io/hostname,那就是以Node節點為區分範圍
    如果指定為beta.kubernetes.io/os,則以Node節點的作業系統型別來區分

接下來,演示下requiredDuringSchedulingIgnoredDuringExecution,
1)首先建立一個參照Pod,pod-podaffinity-target.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-target
  namespace: dev
  labels:
    podenv: pro #設定標籤
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 將目標pod名確指定到node1上
# 啟動目標pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-target.yaml
pod/pod-podaffinity-target created
# 檢視pod狀況
[root@k8s-master01 ~]# kubectl get pods  pod-podaffinity-target -n dev
NAME                     READY   STATUS    RESTARTS   AGE
pod-podaffinity-target   1/1     Running   0          4s

2)建立pod-podaffinity-required.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    podAffinity: #設定pod親和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的標籤
          - key: podenv
            operator: In
            values: ["xxx","yyy"]
        topologyKey: kubernetes.io/hostname

上面配置表達的意思是:新Pod必須要與擁有標籤nodeenv=xxx或者nodeenv=yyy的pod在同一Node上,顯然現在沒有這樣pod,接下來,執行測試一下。

# 啟動pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created
# 檢視pod狀態,發現未執行
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev
NAME                       READY   STATUS    RESTARTS   AGE
pod-podaffinity-required   0/1     Pending   0          9s
# 檢視詳細資訊
[root@k8s-master01 ~]# kubectl describe pods pod-podaffinity-required  -n dev
......Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 2 node(s) didn't match pod affinity rules, 1 node(s) had taints that the pod didn't tolerate.
# 接下來修改  values: ["xxx","yyy"]----->values:["pro","yyy"]# 意思是:新Pod必須要與擁有標籤nodeenv=xxx或者nodeenv=yyy的pod在同一Node上
[root@k8s-master01 ~]# vim pod-podaffinity-required.yaml
# 然後重新建立pod,檢視效果
[root@k8s-master01 ~]# kubectl delete -f  pod-podaffinity-required.yaml
pod "pod-podaffinity-required" deleted
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created
# 發現此時Pod執行正常
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev
NAME                       READY   STATUS    RESTARTS   AGE   LABELS
pod-podaffinity-required   1/1     Running   0          6s    <none>
  • PodAntiAffinity
    PodAntiAffinity主要實現以執行的Pod為參照,讓新建立的Pod跟參照pod不在一個區域中的功能。
    它的配置方式和選項跟PodAffinty是一樣的,這裡不再做詳細解釋,直接做一個測試案例。
    1)繼續使用上個案例中目標pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide --show-labelsNAME                     READY   STATUS    RESTARTS   AGE     IP            NODE    LABELS
pod-podaffinity-required 1/1     Running   0          3m29s   10.244.1.38   node1   <none>     
pod-podaffinity-target   1/1     Running   0          9m25s   10.244.1.37   node1   podenv=pro

2)建立pod-podantiaffinity-required.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    podAntiAffinity: #設定pod親和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配podenv的值在["pro"]中的標籤
          - key: podenv
            operator: In
            values: ["pro"]
        topologyKey: kubernetes.io/hostname

上面配置表達的意思是:新Pod必須要與擁有標籤nodeenv=pro的pod不在同一Node上,執行測試一下。

# 建立pod
[root@k8s-master01 ~]# kubectl create -f pod-podantiaffinity-required.yaml
pod/pod-podantiaffinity-required created
# 檢視pod# 發現排程到了node2上
[root@k8s-master01 ~]# kubectl get pods pod-podantiaffinity-required -n dev -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE   .. 
pod-podantiaffinity-required   1/1     Running   0          30s   10.244.1.96   node2  ..

5.4.3 汙點和容忍

  • 汙點(Taints)
    前面的排程方式都是站在Pod的角度上,透過在Pod上新增屬性,來確定Pod是否要排程到指定的Node上,其實我們也可以站在Node的角度上,透過在Node上新增汙點屬性,來決定是否允許Pod排程過來。
    Node被設定上汙點之後就和Pod之間存在了一種相斥的關係,進而拒絕Pod排程進來,甚至可以將已經存在的Pod驅逐出去。
    汙點的格式為:key=value:effect, key和value是汙點的標籤,effect描述汙點的作用,支援如下三個選項:
  • PreferNoSchedule:kubernetes將盡量避免把Pod排程到具有該汙點的Node上,除非沒有其他節點可排程
  • NoSchedule:kubernetes將不會把Pod排程到具有該汙點的Node上,但不會影響當前Node上已存在的Pod
  • NoExecute:kubernetes將不會把Pod排程到具有該汙點的Node上,同時也會將Node上已存在的Pod驅離

使用kubectl設定和去除汙點的命令示例如下:

# 設定汙點
kubectl taint nodes node1 key=value:effect
# 去除汙點
kubectl taint nodes node1 key:effect-
# 去除所有汙點
kubectl taint nodes node1 key-

接下來,演示下汙點的效果:
1.準備節點node1(為了演示效果更加明顯,暫時停止node2節點)
2.為node1節點設定一個汙點: tag=chenyu:PreferNoSchedule;然後建立pod1( pod1 可以 )
3.修改為node1節點設定一個汙點: tag=chenyu:NoSchedule;然後建立pod2( pod1 正常 pod2 失敗 )
4.修改為node1節點設定一個汙點: tag=chenyu:NoExecute;然後建立pod3 ( 3個pod都失敗 )

# 為node1設定汙點(PreferNoSchedule)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=chenyu:PreferNoSchedule
# 建立pod1
[root@k8s-master01 ~]# kubectl run taint1 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP           NODE   
taint1-7665f7fd85-574h4   1/1     Running   0          2m24s   10.244.1.59   node1    
# 為node1設定汙點(取消PreferNoSchedule,設定NoSchedule)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag:PreferNoSchedule-
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=chenyu:NoSchedule
# 建立pod2
[root@k8s-master01 ~]# kubectl run taint2 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods taint2 -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP            NODE
taint1-7665f7fd85-574h4   1/1     Running   0          2m24s   10.244.1.59   node1 
taint2-544694789-6zmlf    0/1     Pending   0          21s     <none>        <none>   
# 為node1設定汙點(取消NoSchedule,設定NoExecute)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag:NoSchedule-
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=chenyu:NoExecute
# 建立pod3
[root@k8s-master01 ~]# kubectl run taint3 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED 
taint1-7665f7fd85-htkmp   0/1     Pending   0          35s   <none>   <none>   <none>    
taint2-544694789-bn7wb    0/1     Pending   0          35s   <none>   <none>   <none>     
taint3-6d78dbd749-tktkq   0/1     Pending   0          6s    <none>   <none>   <none>    
小提示:
    使用kubeadm搭建的叢集,預設就會給master節點新增一個汙點標記,所以pod就不會排程到master節點上.
 
  • 容忍(Toleration)
    上面介紹了汙點的作用,我們可以在node上新增汙點用於拒絕pod排程上來,但是如果就是想將一個pod排程到一個有汙點的node上去,這時候應該怎麼做呢?這就要使用到容忍。

汙點就是拒絕,容忍就是忽略,Node透過汙點拒絕pod排程上去,Pod透過容忍忽略拒絕
下面先透過一個案例看下效果:
1、上一小節,已經在node1節點上打上了NoExecute的汙點,此時pod是排程不上去的
2、本小節,可以透過給pod新增容忍,然後將其排程上去
建立pod-toleration.yaml,內容如下

apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 新增容忍
  - key: "tag"        # 要容忍的汙點的key
    operator: "Equal" # 運算子
    value: "chenyu"    # 容忍的汙點的value
    effect: "NoExecute"   # 新增容忍的規則,這裡必須和標記的汙點規則相同
# 新增容忍之前的pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED 
pod-toleration   0/1     Pending   0          3s    <none>   <none>   <none>           

# 新增容忍之後的pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED
pod-toleration   1/1     Running   0          3s    10.244.1.62   node1   <none>        
下面看一下容忍的詳細配置:
[root@k8s-master01 ~]# kubectl explain pod.spec.tolerations
......
FIELDS:
   key       # 對應著要容忍的汙點的鍵,空意味著匹配所有的鍵
   value     # 對應著要容忍的汙點的值
   operator  # key-value的運算子,支援Equal和Exists(預設)
   effect    # 對應汙點的effect,空意味著匹配所有影響
   tolerationSeconds   # 容忍時間, 當effect為NoExecute時生效,表示pod在Node上的停留時間

相關文章