官术网_书友最值得收藏!

3.9 玩轉Pod調度

在Kubernetes平臺上,我們很少會直接創建一個Pod,在大多數情況下會通過RC、Deployment、DaemonSet、Job等控制器完成對一組Pod副本的創建、調度及全生命周期的自動控制任務。

在最早的Kubernetes版本里是沒有這么多Pod副本控制器的,只有一個Pod副本控制器RC(Replication Controller),這個控制器是這樣設計實現的:RC獨立于所控制的Pod,并通過Label標簽這個松耦合關聯關系控制目標Pod實例的創建和銷毀,隨著Kubernetes的發展,RC也出現了新的繼任者——Deployment,用于更加自動地完成Pod副本的部署、版本更新、回滾等功能。

嚴謹地說,RC的繼任者其實并不是Deployment,而是ReplicaSet,因為ReplicaSet進一步增強了RC標簽選擇器的靈活性。之前RC的標簽選擇器只能選擇一個標簽,而ReplicaSet擁有集合式的標簽選擇器,可以選擇多個Pod標簽,如下所示:

        selector:
            matchLabels:
              tier: frontend
            matchExpressions:
              - {key: tier, operator: In, values: [frontend]}

與RC不同,ReplicaSet被設計成能控制多個不同標簽的Pod副本。一種常見的應用場景是,應用MyApp目前發布了v1與v2兩個版本,用戶希望MyApp的Pod副本數保持為3個,可以同時包含v1和v2版本的Pod,就可以用ReplicaSet來實現這種控制,寫法如下:

        selector:
            matchLabels:
              version: v2
            matchExpressions:
              - {key: version, operator: In, values: [v1,v2]}

其實,Kubernetes的滾動升級就是巧妙運用ReplicaSet的這個特性來實現的,同時,Deployment也是通過ReplicaSet來實現Pod副本自動控制功能的。我們不應該直接使用底層的ReplicaSet來控制Pod副本,而應該使用管理ReplicaSet的Deployment對象來控制副本,這是來自官方的建議。

在大多數情況下,我們希望Deployment創建的Pod副本被成功調度到集群中的任何一個可用節點,而不關心具體會調度到哪個節點。但是,在真實的生產環境中的確也存在一種需求:希望某種Pod的副本全部在指定的一個或者一些節點上運行,比如希望將MySQL數據庫調度到一個具有SSD磁盤的目標節點上,此時Pod模板中的NodeSelector屬性就開始發揮作用了,上述MySQL定向調度案例的實現方式可分為以下兩步。

(1)把具有SSD磁盤的Node都打上自定義標簽“disk=ssd”。

(2)在Pod模板中設定NodeSelector的值為“disk: ssd”。

如此一來,Kubernetes在調度Pod副本的時候,就會先按照Node的標簽過濾出合適的目標節點,然后選擇一個最佳節點進行調度。

上述邏輯看起來既簡單又完美,但在真實的生產環境中可能面臨以下令人尷尬的問題。

(1)如果NodeSelector選擇的Label不存在或者不符合條件,比如這些目標節點此時宕機或者資源不足,該怎么辦?

(2)如果要選擇多種合適的目標節點,比如SSD磁盤的節點或者超高速硬盤的節點,該怎么辦?Kubernates引入了NodeAffinity(節點親和性設置)來解決該需求。

在真實的生產環境中還存在如下所述的特殊需求。

(1)不同Pod之間的親和性(Affinity)。比如MySQL數據庫與Redis中間件不能被調度到同一個目標節點上,或者兩種不同的Pod必須被調度到同一個Node上,以實現本地文件共享或本地網絡通信等特殊需求,這就是PodAffinity要解決的問題。

(2)有狀態集群的調度。對于ZooKeeper、Elasticsearch、MongoDB、Kafka等有狀態集群,雖然集群中的每個Worker節點看起來都是相同的,但每個Worker節點都必須有明確的、不變的唯一ID(主機名或IP地址),這些節點的啟動和停止次序通常有嚴格的順序。此外,由于集群需要持久化保存狀態數據,所以集群中的Worker節點對應的Pod不管在哪個Node上恢復,都需要掛載原來的Volume,因此這些Pod還需要捆綁具體的PV。針對這種復雜的需求,Kubernetes提供了StatefulSet這種特殊的副本控制器來解決問題,在Kubernetes 1.9版本發布后,StatefulSet才可用于正式生產環境中。

(3)在每個Node上調度并且僅僅創建一個Pod副本。這種調度通常用于系統監控相關的Pod,比如主機上的日志采集、主機性能采集等進程需要被部署到集群中的每個節點,并且只能部署一個副本,這就是DaemonSet這種特殊Pod副本控制器所解決的問題。

(4)對于批處理作業,需要創建多個Pod副本來協同工作,當這些Pod副本都完成自己的任務時,整個批處理作業就結束了。這種Pod運行且僅運行一次的特殊調度,用常規的RC或者Deployment都無法解決,所以Kubernates引入了新的Pod調度控制器Job來解決問題,并繼續延伸了定時作業的調度控制器CronJob。

與單獨的Pod實例不同,由RC、ReplicaSet、Deployment、DaemonSet等控制器創建的Pod副本實例都是歸屬于這些控制器的,這就產生了一個問題:控制器被刪除后,歸屬于控制器的Pod副本該何去何從?在Kubernates 1.9之前,在RC等對象被刪除后,它們所創建的Pod副本都不會被刪除;在Kubernates 1.9以后,這些Pod副本會被一并刪除。如果不希望這樣做,則可以通過kubectl命令的--cascade=false參數來取消這一默認特性:

        kubectl delete replicaset my-repset --cascade=false

接下來深入理解和實踐這些Pod調度控制器的各種功能和特性。

3.9.1 Deployment或RC:全自動調度

Deployment或RC的主要功能之一就是自動部署一個容器應用的多份副本,以及持續監控副本的數量,在集群內始終維持用戶指定的副本數量。

下面是一個Deployment配置的例子,使用這個配置文件可以創建一個ReplicaSet,這個ReplicaSet會創建3個Nginx應用的Pod:

        nginx-deployment.yaml
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: nginx-deployment
        spec:
          replicas: 3
          template:
            metadata:
              labels:
              app: nginx
            spec:
              containers:
              - name: nginx
              image: nginx:1.7.9
              ports:
              - containerPort: 80

運行kubectl create命令創建這個Deployment:

        # kubectl create -f nginx-deployment.yaml
        deployment "nginx-deployment" created

查看Deployment的狀態:

        # kubectl get deployments
        NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
        nginx-deployment   3          3          3              3            18s

該狀態說明Deployment已創建好所有3個副本,并且所有副本都是最新的可用的。

通過運行kubectl get rs和kubectl get pods可以查看已創建的ReplicaSet(RS)和Pod的信息。

        # kubectl get rs
        NAME                              DESIRED   CURRENT   READY     AGE
        nginx-deployment-4087004473   3          3          3          53s
        # kubectl get pods
        NAME                                      READY     STATUS    RESTARTS   AGE
        nginx-deployment-4087004473-9jqqs   1/1       Running   0          1m
        nginx-deployment-4087004473-cq0cf   1/1       Running   0          1m
        nginx-deployment-4087004473-vxn56   1/1       Running   0          1m

從調度策略上來說,這3個Nginx Pod由系統全自動完成調度。它們各自最終運行在哪個節點上,完全由Master的Scheduler經過一系列算法計算得出,用戶無法干預調度過程和結果。

除了使用系統自動調度算法完成一組Pod的部署,Kubernetes也提供了多種豐富的調度策略,用戶只需在Pod的定義中使用NodeSelector、NodeAffinity、PodAffinity、Pod驅逐等更加細粒度的調度策略設置,就能完成對Pod的精準調度。下面對這些策略進行說明。

3.9.2 NodeSelector:定向調度

Kubernetes Master上的Scheduler服務(kube-scheduler進程)負責實現Pod的調度,整個調度過程通過執行一系列復雜的算法,最終為每個Pod都計算出一個最佳的目標節點,這一過程是自動完成的,通常我們無法知道Pod最終會被調度到哪個節點上。在實際情況下,也可能需要將Pod調度到指定的一些Node上,可以通過Node的標簽(Label)和Pod的nodeSelector屬性相匹配,來達到上述目的。

(1)首先通過kubectl label命令給目標Node打上一些標簽:

        kubectl label nodes <node-name> <label-key>=<label-value>

這里,我們為k8s-node-1節點打上一個zone=north標簽,表明它是“北方”的一個節點:

        $ kubectl label nodes k8s-node-1 zone=north
        NAME            LABELS                                               STATUS
        k8s-node-1     kubernetes.io/hostname=k8s-node-1,zone=north  Ready

上述命令行操作也可以通過修改資源定義文件的方式,并執行kubectl replace -f xxx.yaml命令來完成。

(2)然后,在Pod的定義中加上nodeSelector的設置,以redis-master-controller.yaml為例:

        apiVersion: v1
        kind: ReplicationController
        metadata:
          name: redis-master
          labels:
            name: redis-master
        spec:
          replicas: 1
          selector:
            name: redis-master
          template:
            metadata:
              labels:
              name: redis-master
            spec:
              containers:
              - name: master
              image: kubeguide/redis-master
              ports:
              - containerPort: 6379
              nodeSelector:
              zone: north

運行kubectl create -f命令創建Pod,scheduler就會將該Pod調度到擁有zone=north標簽的Node上。

使用kubectl get pods -o wide命令可以驗證Pod所在的Node:

        # kubectl get pods -o wide
        NAME                    READY    STATUS    RESTARTS   AGE       NODE
        redis-master-f0rqj   1/1       Running   0           19s       k8s-node-1

如果我們給多個Node都定義了相同的標簽(例如zone=north),則scheduler會根據調度算法從這組Node中挑選一個可用的Node進行Pod調度。

通過基于Node標簽的調度方式,我們可以把集群中具有不同特點的Node都貼上不同的標簽,例如“role=frontend”“role=backend”“role=database”等標簽,在部署應用時就可以根據應用的需求設置NodeSelector來進行指定Node范圍的調度。

需要注意的是,如果我們指定了Pod的nodeSelector條件,且在集群中不存在包含相應標簽的Node,則即使在集群中還有其他可供使用的Node,這個Pod也無法被成功調度。

除了用戶可以自行給Node添加標簽,Kubernetes也會給Node預定義一些標簽,包括:

◎ kubernetes.io/hostname

◎ beta.kubernetes.io/os(從1.14版本開始更新為穩定版,到1.18版本刪除)

◎ beta.kubernetes.io/arch(從1.14版本開始更新為穩定版,到1.18版本刪除)

◎ kubernetes.io/os(從1.14版本開始啟用)

◎ kubernetes.io/arch(從1.14版本開始啟用)

用戶也可以使用這些系統標簽進行Pod的定向調度。

NodeSelector通過標簽的方式,簡單實現了限制Pod所在節點的方法。親和性調度機制則極大擴展了Pod的調度能力,主要的增強功能如下。

◎ 更具表達力(不僅僅是“符合全部”的簡單情況)。

◎ 可以使用軟限制、優先采用等限制方式,代替之前的硬限制,這樣調度器在無法滿足優先需求的情況下,會退而求其次,繼續運行該Pod。

◎ 可以依據節點上正在運行的其他Pod的標簽來進行限制,而非節點本身的標簽。這樣就可以定義一種規則來描述Pod之間的親和或互斥關系。

親和性調度功能包括節點親和性(NodeAffinity)和Pod親和性(PodAffinity)兩個維度的設置。節點親和性與NodeSelector類似,增強了上述前兩點優勢;Pod的親和與互斥限制則通過Pod標簽而不是節點標簽來實現,也就是上面第4點內容所陳述的方式,同時具有前兩點提到的優點。

NodeSelector將會繼續使用,隨著節點親和性越來越能夠表達nodeSelector的功能,最終NodeSelector會被廢棄。

3.9.3 NodeAffinity:Node親和性調度

NodeAffinity意為Node親和性的調度策略,是用于替換NodeSelector的全新調度策略。目前有兩種節點親和性表達。

◎ RequiredDuringSchedulingIgnoredDuringExecution:必須滿足指定的規則才可以調度Pod到Node上(功能與nodeSelector很像,但是使用的是不同的語法),相當于硬限制。

◎ PreferredDuringSchedulingIgnoredDuringExecution:強調優先滿足指定規則,調度器會嘗試調度Pod到Node上,但并不強求,相當于軟限制。多個優先級規則還可以設置權重(weight)值,以定義執行的先后順序。

IgnoredDuringExecution的意思是:如果一個Pod所在的節點在Pod運行期間標簽發生了變更,不再符合該Pod的節點親和性需求,則系統將忽略Node上Label的變化,該Pod能繼續在該節點運行。

下面的例子設置了NodeAffinity調度的如下規則。

◎ requiredDuringSchedulingIgnoredDuringExecution要求只運行在amd64的節點上(beta.kubernetes.io/arch In amd64)。

◎ preferredDuringSchedulingIgnoredDuringExecution的要求是盡量運行在磁盤類型為ssd(disk-type In ssd)的節點上。

代碼如下:

        apiVersion: v1
        kind: Pod
        metadata:
          name: with-node-affinity
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: beta.kubernetes.io/arch
                  operator: In
                  values:
                  - amd64
              preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 1
                preference:
                  matchExpressions:
                  - key: disk-type
                    operator: In
                    values:
                    - ssd
          containers:
          - name: with-node-affinity
            image: gcr.io/google_containers/pause:2.0

從上面的配置中可以看到In操作符,NodeAffinity語法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。雖然沒有節點排斥功能,但是用NotIn和DoesNotExist就可以實現排斥的功能了。

NodeAffinity規則設置的注意事項如下。

◎ 如果同時定義了nodeSelector和nodeAffinity,那么必須兩個條件都得到滿足,Pod才能最終運行在指定的Node上。

◎ 如果nodeAffinity指定了多個nodeSelectorTerms,那么其中一個能夠匹配成功即可。

◎ 如果在nodeSelectorTerms中有多個matchExpressions,則一個節點必須滿足所有matchExpressions才能運行該Pod。

3.9.4 PodAffinity:Pod親和與互斥調度策略

Pod間的親和與互斥從Kubernetes 1.4版本開始引入。這一功能讓用戶從另一個角度來限制Pod所能運行的節點:根據在節點上正在運行的Pod的標簽而不是節點的標簽進行判斷和調度,要求對節點和Pod兩個條件進行匹配。這種規則可以描述為:如果在具有標簽X的Node上運行了一個或者多個符合條件Y的Pod,那么Pod應該(如果是互斥的情況,那么就變成拒絕)運行在這個Node上。

這里X指的是一個集群中的節點、機架、區域等概念,通過Kubernetes內置節點標簽中的key來進行聲明。這個key的名字為topologyKey,意為表達節點所屬的topology范圍。

◎ kubernetes.io/hostname

◎ failure-domain.beta.kubernetes.io/zone

◎ failure-domain.beta.kubernetes.io/region

與節點不同的是,Pod是屬于某個命名空間的,所以條件Y表達的是一個或者全部命名空間中的一個Label Selector。

和節點親和相同,Pod親和與互斥的條件設置也是requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution。Pod的親和性被定義于PodSpec的affinity字段下的podAffinity子字段中。Pod間的互斥性則被定義于同一層次的podAntiAffinity子字段中。

下面通過實例來說明Pod間的親和性和互斥性策略設置。

1.參照目標Pod

首先,創建一個名為pod-flag的Pod,帶有標簽security=S1和app=nginx,后面的例子將使用pod-flag作為Pod親和與互斥的目標Pod:

        apiVersion: v1
        kind: Pod
        metadata:
          name: pod-flag
          labels:
            security: "S1"
            app: "nginx"
        spec:
          containers:
          - name: nginx
            image: nginx

2.Pod的親和性調度

下面創建第2個Pod來說明Pod的親和性調度,這里定義的親和標簽是security=S1,對應上面的Pod“pod-flag”,topologyKey的值被設置為“kubernetes.io/hostname”:

        apiVersion: v1
        kind: Pod
        metadata:
          name: pod-affinity
        spec:
          affinity:
            podAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                matchExpressions:
                - key: security
                  operator: In
                  values:
                  - S1
                topologyKey: kubernetes.io/hostname
          containers:
          - name: with-pod-affinity
            image: gcr.io/google_containers/pause:2.0

創建Pod之后,使用kubectl get pods -o wide命令可以看到,這兩個Pod在同一個Node上運行。

有興趣的讀者還可以測試一下,在創建這個Pod之前,刪掉這個節點的kubernetes.io/hostname標簽,重復上面的創建步驟,將會發現Pod一直處于Pending狀態,這是因為找不到滿足條件的Node了。

3.Pod的互斥性調度

創建第3個Pod,我們希望它不與目標Pod運行在同一個Node上:

        apiVersion: v1
        kind: Pod
        metadata:
          name: anti-affinity
        spec:
          affinity:
            podAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                matchExpressions:
                - key: security
                  operator: In
                  values:
                  - S1
              topologyKey: failure-domain.beta.kubernetes.io/zone
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app
                    operator: In
                    values:
                    - nginx
                topologyKey: kubernetes.io/hostname
          containers:
          - name: anti-affinity
            image: gcr.io/google_containers/pause:2.0

這里要求這個新Pod與security=S1的Pod為同一個zone,但是不與app=nginx的Pod為同一個Node。創建Pod之后,同樣用kubectl get pods -o wide來查看,會看到新的Pod被調度到了同一Zone內的不同Node上。

與節點親和性類似,Pod親和性的操作符也包括In、NotIn、Exists、DoesNotExist、Gt、Lt。

原則上,topologyKey可以使用任何合法的標簽Key賦值,但是出于性能和安全方面的考慮,對topologyKey有如下限制。

◎ 在Pod親和性和RequiredDuringScheduling的Pod互斥性的定義中,不允許使用空的topologyKey。

◎ 如果Admission controller包含了LimitPodHardAntiAffinityTopology,那么針對Required DuringScheduling的Pod互斥性定義就被限制為kubernetes.io/hostname,要使用自定義的topologyKey,就要改寫或禁用該控制器。

◎ 在PreferredDuringScheduling類型的Pod互斥性定義中,空的topologyKey會被解釋為kubernetes.io/hostname 、 failure-domain.beta.kubernetes.io/zone及failure-domain.beta.kubernetes.io/region的組合。

◎ 如果不是上述情況,就可以采用任意合法的topologyKey了。

PodAffinity規則設置的注意事項如下。

◎ 除了設置Label Selector和topologyKey,用戶還可以指定Namespace列表來進行限制,同樣,使用Label Selector對Namespace進行選擇。Namespace的定義和Label Selector及topologyKey同級。省略Namespace的設置,表示使用定義了affinity/anti-affinity的Pod所在的Namespace。如果Namespace被設置為空值(""),則表示所有Namespace。

◎ 在所有關聯requiredDuringSchedulingIgnoredDuringExecution的matchExpressions全都滿足之后,系統才能將Pod調度到某個Node上。

關于Pod親和性和互斥性調度的更多信息可以參考其設計文檔,網址為https://github.com/kubernetes/kubernetes/blob/master/docs/design/podaffinity.md

3.9.5 Taints和Tolerations(污點和容忍)

前面介紹的NodeAffinity節點親和性,是在Pod上定義的一種屬性,使得Pod能夠被調度到某些Node上運行(優先選擇或強制要求)。Taint則正好相反,它讓Node拒絕Pod的運行。

Taint需要和Toleration配合使用,讓Pod避開那些不合適的Node。在Node上設置一個或多個Taint之后,除非Pod明確聲明能夠容忍這些污點,否則無法在這些Node上運行。Toleration是Pod的屬性,讓Pod能夠(注意,只是能夠,而非必須)運行在標注了Taint的Node上。

可以用kubectl taint命令為Node設置Taint信息:

          $ kubectl taint nodes node1 key=value:NoSchedule

這個設置為node1加上了一個Taint。該Taint的鍵為key,值為value,Taint的效果是NoSchedule。這意味著除非Pod明確聲明可以容忍這個Taint,否則就不會被調度到node1上。

然后,需要在Pod上聲明Toleration。下面的兩個Toleration都被設置為可以容忍(Tolerate)具有該Taint的Node,使得Pod能夠被調度到node1上:

          tolerations:
          - key: "key"
            operator: "Equal"
            value: "value"
            effect: "NoSchedule"

          tolerations:
          - key: "key"
            operator: "Exists"
          effect: "NoSchedule"

Pod的Toleration聲明中的key和effect需要與Taint的設置保持一致,并且滿足以下條件之一。

◎ operator的值是Exists(無須指定value)。

◎ operator的值是Equal并且value相等。

如果不指定operator,則默認值為Equal。

另外,有如下兩個特例。

◎ 空的key配合Exists操作符能夠匹配所有的鍵和值。

◎ 空的effect匹配所有的effect。

在上面的例子中,effect的取值為NoSchedule,還可以取值為PreferNoSchedule,這個值的意思是優先,也可以算作NoSchedule的軟限制版本——一個Pod如果沒有聲明容忍這個Taint,則系統會盡量避免把這個Pod調度到這一節點上,但不是強制的。后面還會介紹另一個effect“NoExecute”。

系統允許在同一個Node上設置多個Taint,也可以在Pod上設置多個Toleration。Kubernetes調度器處理多個Taint和Toleration的邏輯順序為:首先列出節點中所有的Taint,然后忽略Pod的Toleration能夠匹配的部分,剩下的沒有忽略的Taint就是對Pod的效果了。下面是幾種特殊情況。

◎ 如果在剩余的Taint中存在effect=NoSchedule,則調度器不會把該Pod調度到這一節點上。

◎ 如果在剩余的Taint中沒有NoSchedule效果,但是有PreferNoSchedule效果,則調度器會嘗試不把這個Pod指派給這個節點。

◎ 如果在剩余的Taint中有NoExecute效果,并且這個Pod已經在該節點上運行,則會被驅逐;如果沒有在該節點上運行,則也不會再被調度到該節點上。

例如,我們這樣對一個節點進行Taint設置:

        $ kubectl taint nodes node1 key1=value1:NoSchedule
        $ kubectl taint nodes node1 key1=value1:NoExecute
        $ kubectl taint nodes node1 key2=value2:NoSchedule

然后在Pod上設置兩個Toleration:

        tolerations:
        - key: "key1"
          operator: "Equal"
          value: "value1"
          effect: "NoSchedule"
        - key: "key1"
          operator: "Equal"
          value: "value1"
          effect: "NoExecute"

這樣的結果是該Pod無法被調度到node1上,這是因為第3個Taint沒有匹配的Toleration。但是如果該Pod已經在node1上運行了,那么在運行時設置第3個Taint,它還能繼續在node1上運行,這是因為Pod可以容忍前兩個Taint。

一般來說,如果給Node加上effect=NoExecute的Taint,那么在該Node上正在運行的所有無對應Toleration的Pod都會被立刻驅逐,而具有相應Toleration的Pod永遠不會被驅逐。不過,系統允許給具有NoExecute效果的Toleration加入一個可選的tolerationSeconds字段,這個設置表明Pod可以在Taint添加到Node之后還能在這個Node上運行多久(單位為s):

        tolerations:
        - key: "key1"
          operator: "Equal"
          value: "value1"
          effect: "NoExecute"
          tolerationSeconds: 3600

上述定義的意思是,如果Pod正在運行,所在節點都被加入一個匹配的Taint,則這個Pod會持續在這個節點上存活3600s后被逐出。如果在這個寬限期內Taint被移除,則不會觸發驅逐事件。

Taint和Toleration是一種處理節點并且讓Pod進行規避或者驅逐Pod的彈性處理方式,下面列舉一些常見的用例。

1.獨占節點

如果想要拿出一部分節點專門給一些特定應用使用,則可以為節點添加這樣的Taint:

        $ kubectl taint nodes nodename dedicated=groupName:NoSchedule

然后給這些應用的Pod加入對應的Toleration。這樣,帶有合適Toleration的Pod就會被允許同使用其他節點一樣使用有Taint的節點。

通過自定義Admission Controller也可以實現這一目標。如果希望讓這些應用獨占一批節點,并且確保它們只能使用這些節點,則還可以給這些Taint節點加入類似的標簽dedicated=groupName,然后Admission Controller需要加入節點親和性設置,要求Pod只會被調度到具有這一標簽的節點上。

2.具有特殊硬件設備的節點

在集群里可能有一小部分節點安裝了特殊的硬件設備(如GPU芯片),用戶自然會希望把不需要占用這類硬件的Pod排除在外,以確保對這類硬件有需求的Pod能夠被順利調度到這些節點。

可以用下面的命令為節點設置Taint:

        $ kubectl taint nodes nodename special=true:NoSchedule
        $ kubectl taint nodes nodename special=true:PreferNoSchedule

然后在Pod中利用對應的Toleration來保障特定的Pod能夠使用特定的硬件。

和上面的獨占節點的示例類似,使用Admission Controller來完成這一任務會更方便。例如,Admission Controller使用Pod的一些特征來判斷這些Pod,如果可以使用這些硬件,就添加Toleration來完成這一工作。要保障需要使用特殊硬件的Pod只被調度到安裝這些硬件的節點上,則還需要一些額外的工作,比如將這些特殊資源使用opaque-int-resource的方式對自定義資源進行量化,然后在PodSpec中進行請求;也可以使用標簽的方式來標注這些安裝有特別硬件的節點,然后在Pod中定義節點親和性來實現這個目標。

3.定義Pod驅逐行為,以應對節點故障(為Alpha版本的功能)

前面提到的NoExecute這個Taint效果對節點上正在運行的Pod有以下影響。

◎ 沒有設置Toleration的Pod會被立刻驅逐。

◎ 配置了對應Toleration的Pod,如果沒有為tolerationSeconds賦值,則會一直留在這一節點中。

◎ 配置了對應Toleration的Pod且指定了tolerationSeconds值,則會在指定時間后驅逐。

◎ Kubernetes從1.6版本開始引入一個Alpha版本的功能,即把節點故障標記為Taint(目前只針對node unreachable及node not ready,相應的NodeCondition "Ready"的值分別為Unknown和False)。激活TaintBasedEvictions功能后(在--feature-gates參數中加入TaintBasedEvictions=true),NodeController會自動為Node設置Taint,而在狀態為Ready的Node上,之前設置過的普通驅逐邏輯將會被禁用。注意,在節點故障的情況下,為了保持現存的Pod驅逐的限速(rate-limiting)設置,系統將會以限速的模式逐步給Node設置Taint,這就能避免在一些特定情況下(比如Master暫時失聯)大量的Pod被驅逐。這一功能兼容于tolerationSeconds,允許Pod定義節點故障時持續多久才被逐出。

例如,一個包含很多本地狀態的應用可能需要在網絡發生故障時,還能持續在節點上運行,期望網絡能夠快速恢復,從而避免被從這個節點上驅逐。

Pod的Toleration可以這樣定義:

        tolerations:
        - key: "node.alpha.kubernetes.io/unreachable"
          operator: "Exists"
          effect: "NoExecute"
          tolerationSeconds: 6000

對于Node未就緒狀態,可以把Key設置為node.alpha.kubernetes.io/notReady。

如果沒有為Pod指定node.alpha.kubernetes.io/notReady的Toleration,那么Kubernetes會自動為Pod加入tolerationSeconds=300的node.alpha.kubernetes.io/notReady類型的Toleration。

同樣,如果Pod沒有定義node.alpha.kubernetes.io/unreachable的Toleration,那么系統會自動為其加入tolerationSeconds=300的node.alpha.kubernetes.io/unreachable類型的Toleration。

這些系統自動設置的toleration在Node發現問題時,能夠為Pod確保驅逐前再運行5min。這兩個默認的Toleration由Admission Controller“DefaultTolerationSeconds”自動加入。

3.9.6 Pod Priority Preemption:Pod優先級調度

對于運行各種負載(如Service、Job)的中等規模或者大規模的集群來說,出于各種原因,我們需要盡可能提高集群的資源利用率。而提高資源利用率的常規做法是采用優先級方案,即不同類型的負載對應不同的優先級,同時允許集群中的所有負載所需的資源總量超過集群可提供的資源,在這種情況下,當發生資源不足的情況時,系統可以選擇釋放一些不重要的負載(優先級最低的),保障最重要的負載能夠獲取足夠的資源穩定運行。

在Kubernetes 1.8版本之前,當集群的可用資源不足時,在用戶提交新的Pod創建請求后,該Pod會一直處于Pending狀態,即使這個Pod是一個很重要(很有身份)的Pod,也只能被動等待其他Pod被刪除并釋放資源,才能有機會被調度成功。Kubernetes 1.8版本引入了基于Pod優先級搶占(Pod Priority Preemption)的調度策略,此時Kubernetes會嘗試釋放目標節點上低優先級的Pod,以騰出空間(資源)安置高優先級的Pod,這種調度方式被稱為“搶占式調度”。在Kubernetes 1.11版本中,該特性升級為Beta版本,默認開啟,在后繼的Kubernetes 1.14版本中正式Release。如何聲明一個負載相對其他負載“更重要”?我們可以通過以下幾個維度來定義:

◎ Priority,優先級;

◎ QoS,服務質量等級;

◎ 系統定義的其他度量指標。

優先級搶占調度策略的核心行為分別是驅逐(Eviction)與搶占(Preemption),這兩種行為的使用場景不同,效果相同。Eviction是kubelet進程的行為,即當一個Node發生資源不足(under resource pressure)的情況時,該節點上的kubelet進程會執行驅逐動作,此時Kubelet會綜合考慮Pod的優先級、資源申請量與實際使用量等信息來計算哪些Pod需要被驅逐;當同樣優先級的Pod需要被驅逐時,實際使用的資源量超過申請量最大倍數的高耗能Pod會被首先驅逐。對于QoS等級為“Best Effort”的Pod來說,由于沒有定義資源申請(CPU/Memory Request),所以它們實際使用的資源可能非常大。Preemption則是Scheduler執行的行為,當一個新的Pod因為資源無法滿足而不能被調度時,Scheduler可能(有權決定)選擇驅逐部分低優先級的Pod實例來滿足此Pod的調度目標,這就是Preemption機制。

需要注意的是,Scheduler可能會驅逐Node A上的一個Pod以滿足Node B上的一個新Pod的調度任務。比如下面的這個例子:

一個低優先級的Pod A在Node A(屬于機架R)上運行,此時有一個高優先級的Pod B等待調度,目標節點是同屬機架R的Node B,他們中的一個或全部都定義了anti-affinity規則,不允許在同一個機架上運行,此時Scheduler只好“丟車保帥”,驅逐低優先級的Pod A以滿足高優先級的Pod B的調度。

Pod優先級調度示例如下。

首先,由集群管理員創建PriorityClasses,PriorityClass不屬于任何命名空間:

        apiVersion: scheduling.k8s.io/v1beta1
        kind: PriorityClass
        metadata:
          name: high-priority
        value: 1000000
        globalDefault: false
        description: "This priority class should be used for XYZ service pods only."

上述YAML文件定義了一個名為high-priority的優先級類別,優先級為100000,數字越大,優先級越高,超過一億的數字被系統保留,用于指派給系統組件。

我們可以在任意Pod中引用上述Pod優先級類別:

        apiVersion: v1
        kind: Pod
        metadata:
          name: nginx
          labels:
            env: test
        spec:
          containers:
          - name: nginx
            image: nginx
            imagePullPolicy: IfNotPresent
          priorityClassName: high-priority

如果發生了需要搶占的調度,高優先級Pod就可能搶占節點N,并將其低優先級Pod驅逐出節點N,高優先級Pod的status信息中的nominatedNodeName字段會記錄目標節點N的名稱。需要注意,高優先級Pod仍然無法保證最終被調度到節點N上,在節點N上低優先級Pod被驅逐的過程中,如果有新的節點滿足高優先級Pod的需求,就會把它調度到新的Node上。而如果在等待低優先級的Pod退出的過程中,又出現了優先級更高的Pod,調度器將會調度這個更高優先級的Pod到節點N上,并重新調度之前等待的高優先級Pod。

優先級搶占的調度方式可能會導致調度陷入“死循環”狀態。當Kubernetes集群配置了多個調度器(Scheduler)時,這一行為可能就會發生,比如下面這個例子:

Scheduler A為了調度一個(批)Pod,特地驅逐了一些Pod,因此在集群中有了空余的空間可以用來調度,此時Scheduler B恰好搶在Scheduler A之前調度了一個新的Pod,消耗了相應的資源,因此,當Scheduler A清理完資源后正式發起Pod的調度時,卻發現資源不足,被目標節點的kubelet進程拒絕了調度請求!這種情況的確無解,因此最好的做法是讓多個Scheduler相互協作來共同實現一個目標。

最后要指出一點:使用優先級搶占的調度策略可能會導致某些Pod永遠無法被成功調度。因此優先級調度不但增加了系統的復雜性,還可能帶來額外不穩定的因素。因此,一旦發生資源緊張的局面,首先要考慮的是集群擴容,如果無法擴容,則再考慮有監管的優先級調度特性,比如結合基于Namespace的資源配額限制來約束任意優先級搶占行為。

3.9.7 DaemonSet:在每個Node上都調度一個Pod

DaemonSet是Kubernetes 1.2版本新增的一種資源對象,用于管理在集群中每個Node上僅運行一份Pod的副本實例,如圖3.3所示。

圖3.3 DaemonSet示例

這種用法適合有這種需求的應用。

◎ 在每個Node上都運行一個GlusterFS存儲或者Ceph存儲的Daemon進程。

◎ 在每個Node上都運行一個日志采集程序,例如Fluentd或者Logstach。

◎ 在每個Node上都運行一個性能監控程序,采集該Node的運行性能數據,例如Prometheus Node Exporter、collectd、New Relic agent或者Ganglia gmond等。

DaemonSet的Pod調度策略與RC類似,除了使用系統內置的算法在每個Node上進行調度,也可以在Pod的定義中使用NodeSelector或NodeAffinity來指定滿足條件的Node范圍進行調度。

下面的例子定義為在每個Node上都啟動一個fluentd容器,配置文件fluentd-ds.yaml的內容如下,其中掛載了物理機的兩個目錄“/var/log”和“/var/lib/docker/containers”:

        apiVersion: apps/v1
        kind: DaemonSet
        metadata:
          name: fluentd-cloud-logging
          namespace: kube-system
          labels:
            k8s-app: fluentd-cloud-logging
        spec:
          template:
            metadata:
              namespace: kube-system
              labels:
              k8s-app: fluentd-cloud-logging
            spec:
              containers:
              - name: fluentd-cloud-logging
              image: gcr.io/google_containers/fluentd-elasticsearch:1.17
              resources:
                limits:
                  cpu: 100m
                  memory: 200Mi
              env:
              - name: FLUENTD_ARGS
                value: -q
              volumeMounts:
              - name: varlog
                mountPath: /var/log
                readOnly: false
              - name: containers
                mountPath: /var/lib/docker/containers
                readOnly: false
              volumes:
              - name: containers
              hostPath:
                path: /var/lib/docker/containers
              - name: varlog
              hostPath:
                path: /var/log

使用kubectl create命令創建該DaemonSet:

        # kubectl create -f fluentd-ds.yaml
        daemonset "fluentd-cloud-logging" created

查看創建好的DaemonSet和Pod,可以看到在每個Node上都創建了一個Pod:

        # kubectl get daemonset --namespace=kube-system
        NAME                       DESIRED  CURRENT   NODE-SELECTOR   AGE
        fluentd-cloud-logging   2         2          <none>           3s
        # kubectl get pods --namespace=kube-system
        NAME                                READY      STATUS    RESTARTS   AGE
        fluentd-cloud-logging-7tw9z      1/1       Running   0           1h
        fluentd-cloud-logging-aqdn1      1/1       Running   0           1h

在Kubernetes 1.6以后的版本中,DaemonSet也能執行滾動升級了,即在更新一個DaemonSet模板的時候,舊的Pod副本會被自動刪除,同時新的Pod副本會被自動創建,此時DaemonSet的更新策略(updateStrategy)為RollingUpdate,如下所示:

        apiVersion: apps/v1
        kind: DaemonSet
        metadata:
          name: goldpinger
        spec:
          updateStrategy:
            type: RollingUpdate

updateStrategy的另外一個值是OnDelete,即只有手工刪除了DaemonSet創建的Pod副本,新的Pod副本才會被創建出來。如果不設置updateStrategy的值,則在Kubernetes 1.6之后的版本中會被默認設置為RollingUpdate。

3.9.8 Job:批處理調度

Kubernetes從1.2版本開始支持批處理類型的應用,我們可以通過Kubernetes Job資源對象來定義并啟動一個批處理任務。批處理任務通常并行(或者串行)啟動多個計算進程去處理一批工作項(work item),處理完成后,整個批處理任務結束。按照批處理任務實現方式的不同,批處理任務可以分為如圖3.4所示的幾種模式。

圖3.4 批處理任務的幾種模式

◎ Job Template Expansion模式:一個Job對象對應一個待處理的Work item,有幾個Work item就產生幾個獨立的Job,通常適合Work item數量少、每個Work item要處理的數據量比較大的場景,比如有一個100GB的文件作為一個Work item,總共有10個文件需要處理。

◎ Queue with Pod Per Work Item模式:采用一個任務隊列存放Work item,一個Job對象作為消費者去完成這些Work item,在這種模式下,Job會啟動N個Pod,每個Pod都對應一個Work item。

◎ Queue with Variable Pod Count模式:也是采用一個任務隊列存放Work item,一個Job對象作為消費者去完成這些Work item,但與上面的模式不同,Job啟動的Pod數量是可變的。

還有一種被稱為Single Job with Static Work Assignment的模式,也是一個Job產生多個Pod,但它采用程序靜態方式分配任務項,而不是采用隊列模式進行動態分配。

如表3.4所示是這幾種模式的一個對比。

表3.4 批處理任務的模式對比

考慮到批處理的并行問題,Kubernetes將Job分以下三種類型。

1.Non-parallel Jobs

通常一個Job只啟動一個Pod,除非Pod異常,才會重啟該Pod,一旦此Pod正常結束,Job將結束。

2.Parallel Jobs with a fixed completion count

并行Job會啟動多個Pod,此時需要設定Job的.spec.completions參數為一個正數,當正常結束的Pod數量達至此參數設定的值后,Job結束。此外,Job的.spec.parallelism參數用來控制并行度,即同時啟動幾個Job來處理Work Item。

3.Parallel Jobs with a work queue

任務隊列方式的并行Job需要一個獨立的Queue,Work item都在一個Queue中存放,不能設置Job的.spec.completions參數,此時Job有以下特性。

◎ 每個Pod都能獨立判斷和決定是否還有任務項需要處理。

◎ 如果某個Pod正常結束,則Job不會再啟動新的Pod。

◎ 如果一個Pod成功結束,則此時應該不存在其他Pod還在工作的情況,它們應該都處于即將結束、退出的狀態。

◎ 如果所有Pod都結束了,且至少有一個Pod成功結束,則整個Job成功結束。

下面分別講解常見的三種批處理模型在Kubernetes中的應用例子。

首先是Job Template Expansion模式,由于在這種模式下每個Work item對應一個Job實例,所以這種模式首先定義一個Job模板,模板里的主要參數是Work item的標識,因為每個Job都處理不同的Work item。如下所示的Job模板(文件名為job.yaml.txt)中的$ITEM可以作為任務項的標識:

        apiVersion: batch/v1
        kind: Job
        metadata:
          name: process-item-$ITEM
          labels:
            jobgroup: jobexample
        spec:
          template:
            metadata:
              name: jobexample
              labels:
                jobgroup: jobexample
            spec:
              containers:
              - name: c
                image: busybox
                command: ["sh", "-c", "echo Processing item $ITEM&& sleep 5"]
              restartPolicy: Never

通過下面的操作,生成了3個對應的Job定義文件并創建Job:

        # for i in apple banana cherry
        > do
        >   cat job.yaml.txt | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
        > done
        # ls jobs
        job-apple.yaml  job-banana.yaml  job-cherry.yaml
        # kubectl create -f jobs
        job "process-item-apple" created
        job "process-item-banana" created
        job "process-item-cherry" created

首先,觀察Job的運行情況:

        # kubectl get jobs -l jobgroup=jobexample
        NAME                  DESIRED   SUCCESSFUL   AGE
        process-item-apple    1         1            4m
        process-item-banana   1         1            4m
        process-item-cherry   1         1            4m

其次,我們看看Queue with Pod Per Work Item模式,在這種模式下需要一個任務隊列存放Work item,比如RabbitMQ,客戶端程序先把要處理的任務變成Work item放入任務隊列,然后編寫Worker程序、打包鏡像并定義成為Job中的Work Pod。Worker程序的實現邏輯是從任務隊列中拉取一個Work item并處理,在處理完成后即結束進程。并行度為2的Demo示意圖如圖3.5所示。

圖3.5 Queue with Pod Per Work Item示例

最后,我們看看Queue with Variable Pod Count模式,如圖3.6所示。由于這種模式下,Worker程序需要知道隊列中是否還有等待處理的Work item,如果有就取出來處理,否則就認為所有工作完成并結束進程,所以任務隊列通常要采用Redis或者數據庫來實現。

圖3.6 Queue with Variable Pod Count示例

3.9.9 Cronjob:定時任務

Kubernetes從1.5版本開始增加了一種新類型的Job,即類似Linux Cron的定時任務Cron Job,下面看看如何定義和使用這種類型的Job。

首先,確保Kubernetes的版本為1.8及以上。

其次,需要掌握Cron Job的定時表達式,它基本上照搬了Linux Cron的表達式,區別是第1位是分鐘而不是秒,格式如下:

        Minutes Hours DayofMonth Month DayofWeek Year

其中每個域都可出現的字符如下。

◎ Minutes:可出現“,”“-”“*”“/”這4個字符,有效范圍為0~59的整數。

◎ Hours:可出現“,”“-”“*”“/”這4個字符,有效范圍為0~23的整數。

◎ DayofMonth:可出現“,”“-”“*”“/”“?”“L”“W”“C”這8個字符,有效范圍為0~31的整數。

◎ Month:可出現“,”“-”“*”“/”這4個字符,有效范圍為1~12的整數或JAN~DEC。

◎ DayofWeek:可出現“,”“-”“*”“/”“?”“L”“C”“#”這8個字符,有效范圍為1~7的整數或SUN~SAT。1表示星期天,2表示星期一,以此類推。

表達式中的特殊字符“*”與“/”的含義如下。

◎ *:表示匹配該域的任意值,假如在Minutes域使用“*”,則表示每分鐘都會觸發事件。

◎ /:表示從起始時間開始觸發,然后每隔固定時間觸發一次,例如在Minutes域設置為5/20,則意味著第1次觸發在第5min時,接下來每20min觸發一次,將在第25min、第45min等時刻分別觸發。

比如,我們要每隔1min執行一次任務,則Cron表達式如下:

        */1 * * * *

掌握這些基本知識后,就可以編寫一個Cron Job的配置文件了:

        cron.yaml
        apiVersion: batch/v1
        kind: CronJob
        metadata:
          name: hello
        spec:
          schedule: "*/1 * * * *"
          jobTemplate:
            spec:
              template:
                spec:
                  containers:
                  - name: hello
                    image: busybox
                    args:
                    - /bin/sh
                    - -c
                    - date; echo Hello from the Kubernetes cluster
                  restartPolicy: OnFailure

該例子定義了一個名為hello的Cron Job,任務每隔1min執行一次,運行的鏡像是busybox,執行的命令是Shell腳本,腳本執行時會在控制臺輸出當前時間和字符串“ Hello from the Kubernetes cluster”。

接下來執行kubectl create命令完成創建:

        # kubectl create -f cron.yaml
        cronjob "hello" created

然后每隔1min執行kubectl get cronjob hello查看任務狀態,發現的確每分鐘調度了一次:

        # kubectl get cronjob hello
        NAME       SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
        hello     */1 * * * *   False     0           Thu, 29 Jun 2017 11:32:00-0700
        ......
        # kubectl get cronjob hello
        NAME       SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
        hello     */1 * * * *   False     0           Thu, 29 Jun 2017 11:33:00-0700
        ......
        # kubectl get cronjob hello
        NAME       SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
        hello     */1 * * * *   False     0           Thu, 29 Jun 2017 11:34:00-0700

還可以通過查找Cron Job對應的容器,驗證每隔1min產生一個容器的事實,如下所示:

        # docker ps -a | grep busybox
        83f7b86728ea
    busybox@sha256:be3c11fdba7cfe299214e46edc642e09514dbb9bbefcd0d3836c05a1e0cd0642
    "/bin/sh -c 'date; ec"   About a minute ago  Exited (0) About a minute ago
    k8s_hello_hello-1498795860-qqwb4_default_207586cf-5d4a-11e7-86c1-000c2997487d_0
          36aa3b991980
      busybox@sha256:be3c11fdba7cfe299214e46edc642e09514dbb9bbefcd0d3836c05a1e0cd0642
      "/bin/sh -c 'date; ec"   2 minutes ago       Exited (0) 2 minutes ago
      k8s_hello_hello-1498795800-g92vx_default_fca21ec0-5d49-11e7-86c1-000c2997487d_0
          3d762ae35172
      busybox@sha256:be3c11fdba7cfe299214e46edc642e09514dbb9bbefcd0d3836c05a1e0cd0642
      "/bin/sh -c 'date; ec"   3 minutes ago       Exited (0) 3 minutes ago
      k8s_hello_hello-1498795740-3qxmd_default_d8c75d07-5d49-11e7-86c1-000c2997487d_0
          8ee5eefa8cd3
      busybox@sha256:be3c11fdba7cfe299214e46edc642e09514dbb9bbefcd0d3836c05a1e0cd0642
      "/bin/sh -c 'date; ec"   4 minutes ago       Exited (0) 4 minutes ago
      k8s_hello_hello-1498795680-mgb7h_default_b4f7aec5-5d49-11e7-86c1-000c2997487d_0

查看任意一個容器的日志,結果如下:

          # docker logs 83f7b86728ea
          Thu Jun 29 18:33:07 UTC 2017
          Hello from the Kubernetes cluster

運行下面的命令,可以更直觀地了解Cron Job定期觸發任務執行的歷史和現狀:

          # kubectl get jobs --watch
          NAME                 DESIRED   SUCCESSFUL   AGE
          hello-1498761060   1          1             31m
          hello-1498761120   1          1             30m
          hello-1498761180   1          1             29m
          hello-1498761240   1          1             28m
          hello-1498761300   1          1             27m
          hello-1498761360   1          1             26m
          hello-1498761420   1          1             25m

其中SUCCESSFUL列為1的每一行都是一個調度成功的Job,以第1行的“hello-1498761060”的Job為例,它對應的Pod可以通過下面的方式得到:

          # kubectl get pods --show-all | grep hello-1498761060
          hello-1498761060-shpwx   0/1       Completed   0          39m

查看該Pod的日志:

          # kubectl logs hello-1498761060-shpwx
          Thu Jun 29 18:31:07 UTC 2017
          Hello from the Kubernetes cluster

最后,當不需要某個Cron Job時,可以通過下面的命令刪除它:

        # kubectl delete cronjob hello
        cronjob "hello" deleted

在Kubernetes 1.9版本后,kubectrl命令增加了別名cj來表示cronjob,同時kubectl set image/env命令也可以作用在CronJob對象上了。

3.9.10 自定義調度器

如果Kubernetes調度器的眾多特性還無法滿足我們的獨特調度需求,則還可以用自己開發的調度器進行調度。從1.6版本開始,Kubernetes的多調度器特性也進入了快速發展階段。

一般情況下,每個新Pod都會由默認的調度器進行調度。但是如果在Pod中提供了自定義的調度器名稱,那么默認的調度器會忽略該Pod,轉由指定的調度器完成Pod的調度。

在下面的例子中為Pod指定了一個名為my-scheduler的自定義調度器:

        apiVersion: v1
        kind: Pod
        metadata:
          name: nginx
          labels:
            app: nginx
        spec:
          schedulerName: my-scheduler
          containers:
          - name: nginx
            image: nginx

如果自定義的調度器還未在系統中部署,則默認的調度器會忽略這個Pod,這個Pod將會永遠處于Pending狀態。

下面看看如何創建一個自定義的調度器。

可以用任何語言來實現簡單或復雜的自定義調度器。下面的簡單例子使用Bash腳本進行實現,調度策略為隨機選擇一個Node(注意,這個調度器需要通過kubectl proxy來運行):

        #!/bin/bash
        SERVER='localhost:8001'
        while true;
        do
            for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] |
    select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null)
    | .metadata.name' | tr -d '"');
            do
              NODES=($(kubectl --server $SERVER get nodes -o json | jq
    '.items[].metadata.name' | tr -d '"'))
              NUMNODES=${#NODES[@]}
              CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
              curl --header "Content-Type:application/json" --request POST --data
    '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target":
    {"apiVersion": "v1", "kind": "Node", "name":"'$CHOSEN'"}}'
    http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
              echo "Assigned $PODNAME to $CHOSEN"
            done
            sleep 1
        done

一旦這個自定義調度器成功啟動,前面的Pod就會被正確調度到某個Node上。

主站蜘蛛池模板: 上思县| 罗定市| 晋江市| 泰兴市| 苍梧县| 且末县| 新竹县| 西乡县| 定结县| 定日县| 马山县| 吉林省| 福海县| 襄樊市| 南宫市| 凉山| 丁青县| 海口市| 吴江市| 容城县| 盐边县| 简阳市| 永济市| 乌鲁木齐市| 柳江县| 若尔盖县| 白沙| 南开区| 长治市| 建昌县| 防城港市| 汨罗市| 女性| 大埔县| 莱州市| 宜宾县| 洮南市| 大荔县| 阳西县| 舞钢市| 资中县|