1. 程式人生 > >Pod 調度

Pod 調度

1.2 cti 內置 們的 添加 TP control 優先 warn

在大部分情況下,Pod只是容器的載體,通常我們會使用Deployment,RC,Job,ReplicaSet等對象來完成一組Pod的調度和控制。
當我們創建一個deployment或者RC後,kuernetes會自動根據我們的要求將一個或多個Pod副本自動調度到合適的節點上,這個過程kube-scheduler經過一系列算法自動完成,用戶無法幹預。在某些場景,我們也可以使用Kubernetes提供的其他調度策略來滿足我們的特殊需求。這些調度策略包括:

  • NodeSelector
  • NodeAffinity
  • PodAffinity
  • Pod驅逐
  • Taints和Tolerations

參考鏈接

NodeSelector 定向調度

NodeSelector非常簡單,就是將pod調度到我們指定的Node節點上,這裏分為兩個步驟:
(1) 對Node節點打上特定的label
(2) 創建pod時指定此label.

下面是一個簡單示例:
1、對node添加標簽,並驗證:

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

[root@node-1 ~]# kubectl get node
NAME       STATUS    ROLES     AGE       VERSION
10.0.0.2   Ready     <none>    9d        v1.10.4
10.0.0.3   Ready     <none>    9d        v1.10.4

[root@node-1 ~]# kubectl label nodes 10.0.0.2 disk=ssd
node "10.0.0.2" labeled

[root@node-1 ~]# kubectl get nodes --show-labels
NAME       STATUS    ROLES     AGE       VERSION   LABELS
10.0.0.2   Ready     <none>    9d        v1.10.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,kubernetes.io/hostname=10.0.0.2
10.0.0.3   Ready     <none>    9d        v1.10.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.0.0.3
  1. 創建一個pod,指定Label:
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent   # 節點上沒有nginx鏡像時才執行pull操作
  nodeSelector:
    disk: ssd

提示:如果指定的label在所有node上都無法匹配,則創建Pod失敗,會提示無法調度:
Warning FailedScheduling 7s (x6 over 22s) default-scheduler 0/2 nodes are available: 2 node(s) didn‘t match node selector.

調度成功後會顯示:

# kubectl describe pod nginx
...
Node-Selectors:  disk=ssd
Tolerations:     <none>
Events:
  Type    Reason                 Age   From               Message
  ----    ------                 ----  ----               -------
  Normal  Scheduled              4m    default-scheduler  Successfully assigned nginx to 10.0.0.2
  Normal  SuccessfulMountVolume  4m    kubelet, 10.0.0.2  MountVolume.SetUp succeeded for volume "default-token-hmvnc"
  Normal  Pulling                4m    kubelet, 10.0.0.2  pulling image "nginx"
  Normal  Pulled                 3m    kubelet, 10.0.0.2  Successfully pulled image "nginx"
  Normal  Created                3m    kubelet, 10.0.0.2  Created container
  Normal  Started                3m    kubelet, 10.0.0.2  Started container

默認情況下,kubernetes也自帶了一些標簽:

# kubectl get nodes --show-labels
NAME       STATUS    ROLES     AGE       VERSION   LABELS
10.0.0.2   Ready     <none>    9d        v1.10.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,kubernetes.io/hostname=10.0.0.2
10.0.0.3   Ready     <none>    9d        v1.10.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.0.0.3

NodeAffinity Node親和性調度

Node親和性調度是用於替換NodeSelector的全新調度策略,有兩種節點親和性表示:

  • requiredDuringSchedulingIgnoredDuringExecution:必須滿足指定的規則才可以調度Pod到Node上(功能與nodeSelector很像,語法不同),相當於硬限制。
  • preferredDuringSchedulingIgnoredDuringExecution:強調優先級,可以設置權重,但不是強制的,相當於軟限制。

這裏使用如下示例:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

此節親和性規則指出,只能將pod放置在具有標簽的節點上,標簽的關鍵字為kubernetes.io/e2e-az-name,其值必須為e2e-az1或e2e-az2。 另外,在滿足該標準的節點中,應該優選具有其標簽為another-node-label-key其值是another-node-label-value的節點。

上面的示例使用了in 操作符,NodeAffinity語法支持的操作符包括in, NotIn, Exists, DoesNotExit, Gt, Lt。使用NotInDoesNotExist就可以實現排斥功能了。

  • 如果同時定義了nodeSelector和nodeAffinity,那麽必須兩個條件都得到滿足,Pod才能最終運行在指定的Node上。
  • 如果nodeAffinity指定了多個nodeSelectorTerms,那麽只需要其中一個能夠匹配成功即可。
  • 如果nodeSelectorTerms中有多個matchExpressions,則一個節點必須滿足所有matchExpressions才能運行該Pod。
  • 如果一個Pod所在的節點在Pod運行期間標簽發生了變化,不再符合該Pod的親和性需求,Pod不會進行重新調度,繼續在該節點上運行。

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

Pod親和性的調度策略將節點上的Pod也納入了考慮範圍,這種規則可以描述為:
如果在具有標簽X的Node節點上運行了一個或者多個符合條件Y的Pod,那麽Pod應該(拒絕/允許)運行在這個Node上。
這裏的X指的時集群中的節點,機架,區域等概念,通過內置的節點標簽topologyKey來實現。
條件Y表達的是pod對應的一個或者全部命名空間中的一個Label Selector
Pod的親和與互斥的條件設置也是requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution

如下示例,podAffinity 和 podAntiAffinity都被定義在affinity區域中:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0

這個示例表示了pod在調度時,需要滿足以下幾個要求:

  • 被調度的node節點與Pod標簽的key為‘security’,值為‘S1’的節點屬於同一個zone
  • 最好不要調度到運行label key 為 ‘security’,值為‘S1’ pod的節點上。

    pod affinity和 anti-affinity的邏輯操作運算有: In, NotIn, Exists, DoseNotExit.
    原則上,topologyKey可以使用任何合法的標簽Key賦值,但是出於性能和安全方面的考慮,對topologyKey有如下限制:

    • 在Pod親和性和requiredDuringSchedulingIgnoredDuringExecution的互斥性定義中,不允許使用空的topologyKey。
    • 如果Admisson controller 包含了LimitPodHardAntiAffinityTopology,那麽針對requiredDuringSchedulingIgnoredDuringExecution的Pod互斥性定義就被限制為kubernetes.io/hostname,要使用自定義的topologyKey,就要改寫或禁用該控制器。
    • 在preferredDuringSchedulingIgnoredDuringExecution 類型的pod互斥性定義中,空的topologyKey會被解釋為kubernetes.io/hostname, failure-domain.beta.kubernetes.io/zone 和 failure-domain.beta.kubernetes.io/region的組合。
    • 如果不是上述情況,就可以采用任意合法的topologyKey 了。

附加說明:

  • 除了設置LabelSelector和topologyKey,用戶還可以指定namespace列表來進行限制,同樣,使用Label Selector對namespace進行選擇。namespace的定義和Label Selector及topologyKey同級。 省略namespace的設置,表示使用定義了affinity/anti-affinity的Pod所在的namespace。 如果namespace設置為空值(“”),則表示所有namespace。
  • 在所有關聯requiredDuringSchedulingIgnoredDuringExecution的matchExpressions 全部滿足之後,系統才能將Pod調度到某個Node上。

Taints和Tolerations 汙點和容忍

參考鏈接

Taint前面的節點親和性作用相反,使用Taints規則,將拒絕Pod在Node上運行。
Taint需要和Tolerations配合使用,讓Pod避開那些不合適的Node。在Node上設置一個或多個Taint之後,除非Pod明確聲明能夠容忍這些“汙點”,否則無法在這些Node上運行。Toleration是Pod的屬性,讓Pod能夠運行在標註了Taint的Node節點上。

1、使用kubectl taint 命令為Node設置Taint信息:

kubectl  taint nodes 10.0.0.3  key=value:NoSchedule

為node節點的10.0.0.3加上一個Taint,Taint的鍵為Key,值為value, Taint的效果是NoSchedule。這裏表示的含義是任何Pod都不能調度到這個節點,除非設置了toleration.

刪除節點上的taint:

kubectl taint nodes 10.0.0.3 key:NoSchedule-

2、在Pod中定義容忍(Tolerate)具有該Taint的Node,使得Pod可以被調度到這個節點:

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的軟限制版本。
系統允許在同一個Node上設置多個Taint,也可以在Pod上設置多個Toleration,匹配上的就會按照規則執行,而剩下的會對Pod產生效果:

  • 如果剩余的Taint中存在effect=NoSchedule,則調度器不會把該Pod調度到這一節點上。
  • 如果剩余的Taint中沒有NoSchedule效果,但是有PreferNoSchedule效果,則調度器會嘗試不把這個Pod指派給這個節點。
  • 如果剩余的Taint的效果有NoExecute,並且這個Pod已經在該節點上運行,則會被驅逐;如果沒有在該節點上運行,也不會再被調度到該節點上。
  • 如果pod已經在節點上正常運行,此時添加一個新的Taint,使得effect=NoSchedule,Pod還會繼續在此節點上運行。
  • 如果給Node加上effect=NoSchedule的Taint,那麽該節點Node上正在運行的所有無對應Toleration的Pod都會被立刻驅逐,而具有相應Toleration的Pod則永遠不會被逐出,可以加入一個可選的tolerationSeconds ,當taint添加後,還可以繼續運行多長時間,單位為秒:
tolerations:
- key: "node.alpha.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

如果在寬限時間(這裏示例是6000s)內Taint被移除,則不會觸發驅逐事件。

使用汙點和容忍策略一般可以運用於以下場景:

  • 獨占節點:如要拿出一部分節點給特定的應用使用,結合Admisson Controller實現。
  • 具有特殊硬件設備的節點
  • 定義Pod驅逐行為,以應對節點故障(Alpha)

DaemonSet 在每個Node上調度一個Pod

參考鏈接

DaemonSet用於管理集群中每個節點僅運行一份Pod的副本實例。這種用法一般適合如下應用:

  • 在每個Node上運行一個GlusterFS存儲或者Ceph存儲的Daemon進程。
  • 在每個Node上運行一個日誌采集程序,例如Fluentd或者Logstash。
  • 在每個Node上運行一個性能監控程序,采集Node的性能數據,如Prometheus Node Exporter, collectd, Datadog agent, New Relic agent, 或 Ganglia gmond。

如下示例,會在每個節點創建一個DaemonSet:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: k8s.gcr.io/fluentd-elasticsearch:1.20
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

由於上面的示例中,使用的kube-system的namespace示例,在查詢daemonset的時候需要指定namespace

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

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結束。

3、並行Job需要一個獨立的Queue,Work item都在一個Queue中存放,不能設置Job的.spec.completions參數,此時Job有以下特性:

  • 每個Pod能獨立判斷和決定是否還有任務項需要處理。
  • 如果某個Pod正常結束,則Job不會再啟動新的Pod.
  • 如果一個Pod成功結束,此時應該不存在其他Pod還在幹活的情況,他們應該都處於即將結束,退出的狀態。
  • 如果所有Pod都結束了,其至少有一個Pod成功結束,則整個Job算成功結束。

Cronjob 定時任務

Kubernetes的定時任務和Linux Cron定時任務語法類似,這裏有如下示例:

apiVersion: batch/v1beta1
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

可以發現每隔一分鐘執行了一次:

# kubectl get cronjob hello -o wide
NAME      SCHEDULE      SUSPEND   ACTIVE    LAST SCHEDULE   AGE       CONTAINERS   IMAGES    SELECTOR
hello     */1 * * * *   False     0         33s             7m        hello        busybox   <none>

# kubectl get jobs --watch
NAME               DESIRED   SUCCESSFUL   AGE
hello-1529412060   1         1            3m
hello-1529412120   1         1            2m
hello-1529412180   1         1            1m
hello-1529412240   1         0            6s

查看日誌信息:

# kubectl get pods --selector=job-name=hello-1529412600
NAME                     READY     STATUS      RESTARTS   AGE
hello-1529412600-slcps   0/1       Completed   0          20s

# kubectl  get pod hello-1529412600-slcps
NAME                     READY     STATUS      RESTARTS   AGE
hello-1529412600-slcps   0/1       Completed   0          51s

# kubectl  logs hello-1529412600-slcps
Tue Jun 19 12:50:17 UTC 2018
Hello from the Kubernetes cluster

刪除cronjob:

# kubectl  delete cronjob hello

自定義調度器

除了上面所述的相關調度策略外,kubernetes還支持自定義調度器,可以使用任何語言開發我們需要的調度規則,將自定義的調度器使用kubectl proxy來運行,指定的文件格式如下:

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

Pod 調度