從0到1使用Kubernetes系列(五):Kubernetes Scheduling

Kubernetes做爲一個容器編排調度引擎,資源調度是它的最基本也是最重要的功能。當開發者部署一個應用時它運行在哪一個節點?這個節點滿不知足開發的運行要求?Kubernetes又是如何進行資源調度的呢?node

▌經過本文可瞭解到如下信息:nginx

  • 資源請求及限制對pod調度的影響
  • 查看調度事件events
  • 瞭解label選擇器對pod調度的影響
  • 瞭解節點親和性和Pod親和性對調度的影響
  • 不使用調度器,手動調度一個pod
  • 瞭解Daemonset的角色
  • 瞭解如何配置Kubernetes scheduler

在Kubernetes中有一個kube-scheduler組件,該組件運行在master節點上,它主要負責pod的調度。Kube-scheduler監聽kube-apiserver中是否有還未調度到node上的pod(即Spec.NodeName爲空的Pod),再經過特定的算法爲pod指定分派node運行。若是分配失敗,則將該pod放置調度隊列尾部以從新調度。調度主要分爲幾個部分:首先是預選過程,過濾不知足Pod要求的節點。而後是優選過程,對經過要求的節點進行優先級排序,最後選擇優先級最高的節點分配,其中涉及到的兩個關鍵點是過濾和優先級評定的算法。調度器使用一組規則過濾不符合要求的節點,其中包括設置了資源的request和指定了Nodename或者其餘親和性設置等等。優先級評定將過濾獲得的節點列表進行打分,調度器考慮一些總體的優化策略,好比將Deployment控制的多個副本集分配到不一樣節點上等。git

image.png

資源請求及限制對pod調度的影響

在部署應用時,開發者會考慮到使這個應用運行起來須要多少的內存和CPU資源的使用量,這樣才能判斷應將他運行在哪一個節點上。在部署文件resource屬性中添加requests字段用於說明運行該容器所需的最少資源,當調度器開始調度該Pod時,調度程序確保對於每種資源類型,計劃容器的資源請求總和必須小於節點的容量才能分配該節點運行Pod,resource屬性中添加limits字段用於限制容器運行時所得到的最大資源。若是該容器超出其內存限制,則可能被終止。 若是該容器能夠從新啓動,kubelet會將它從新啓動。若是調度器找不到合適的節點運行Pod時,就會產生調度失敗事件,調度器會將Pod放置調度隊列以循環調度,直到調度完成。github

在下面例子中,運行一個nginx Pod,資源請求了256Mi的內存和100m的CPU,調度器將判斷哪一個節點還剩餘這麼多的資源,尋找到了以後就會將這個Pod調度上去。同時也設置了512Mi的內存和300m的CPU的使用限制,若是該Pod運行以後超出了這一限制就將被重啓甚至被驅逐。web

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "256Mi"
        cpu: "100m"
      limits:
        memory: "512Mi"
        cpu: "300m"

參考文檔:redis

  • Assign CPU Resources to Containers and Pods
  • Assign Memory Resources to Containers and Pods

查看調度事件events

在部署應用後,可使用 kubectl describe 命令進行查看Pod的調度事件,下面是一個coredns被成功調度到node3運行的事件記錄。算法

$ kubectl describe po coredns-5679d9cd77-d6jp6 -n kube-system
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  29s   default-scheduler  Successfully assigned kube-system/coredns-5679d9cd77-d6jp6 to node3
  Normal  Pulled     28s   kubelet, node3     Container image "grc.io/kubernetes/coredns:1.2.2" already present on machine
  Normal  Created    28s   kubelet, node3     Created container
  Normal  Started    28s   kubelet, node3     Started container

下面是一個coredns被調度失敗的事件記錄,根據記錄顯示不可調度的緣由是沒有節點知足該Pod的內存請求。docker

$ kubectl describe po coredns-8447874846-5hpmz -n kube-system
...
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  22s (x3 over 24s)  default-scheduler  0/3 nodes are available: 3 Insufficient memory.

label選擇器對pod調度的影響

例如開發者須要部署一個ES集羣,因爲ES對磁盤有較高的要求,而集羣中只有一部分節點有SSD磁盤,那麼就須要將標記一下帶有SSD磁盤的節點即給這些節點打上Lable,讓ES的pod只能運行在帶這些標記的節點上。api

Lable是附着在K8S對象(如Pod、Service等)上的鍵值對。它能夠在建立對象的時候指定,也能夠在對象建立後隨時指定。Kubernetes最終將對labels最終索引和反向索引用來優化查詢和watch,在UI和命令行中會對它們排序。通俗的說,就是爲K8S對象打上各類標籤,方便選擇和調度。app

  1. 查看節點信息。

    $ kubectl get nodes
    NAME    STATUS   ROLES            AGE    VERSION
    node1   Ready    etcd,master      128m   v1.12.4
    node2   Ready    etcd,lb,master   126m   v1.12.4
    node3   Ready    etcd,lb,worker   126m   v1.12.4
  2. 選擇出有SSD磁盤的節點,並給這個節點打上標記(label)。

    $ kubectl label nodes <your-node-name> disktype=ssd
    node/<your-node-name> labeled
  3. 驗證節點上是否有成功打上對應label。

    $ kubectl get nodes --show-labels
    NAME    STATUS   ROLES            AGE    VERSION   LABELS
    node1   Ready    etcd,master      139m   v1.12.4   ...disktype=ssd,kubernetes.io/hostname=node1...
    node2   Ready    etcd,lb,master   137m   v1.12.4   ...kubernetes.io/hostname=node2...
    node3   Ready    etcd,lb,worker   137m   v1.12.4   ...kubernetes.io/hostname=node3...
  4. 建立一個ES的pod, 調度到有SSD磁盤標記的節點上。在pod的配置裏, 要指定nodeSelector屬性值爲disktype:ssd。這意味着pod啓動後會調度到打上了disktype=ssd標籤的node上。

    apiVersion: v1
        kind: Pod
        metadata:
          name: es
        spec:
          containers:
          - name: es
nodeSelector:
        disktype: ssd
```
  1. 驗證pod啓動後是否調度到指定節點上。

    $ kubectl get pods -o wide
    NAMESPACE  NAME                   READY   STATUS    RESTARTS   AGE    IP              NODE    NOMINATED NODE
    default    es-5679d9cd77-sbmcx    1/1     Running   0          134m   10.244.2.3      node1   <none>

參考文檔:

  • Assign Pods to Nodes

節點親和性和Pod親和性對調度的影響

上小節講述的nodeSelector提供了一種很是簡單的方法,能夠將pod限制爲具備特定標籤的節點。而更爲強大的表達約束類型則能夠由Affinity和Anti-affinity來配置。即親和性與反親和性的設置。親和性和反親和性包括兩種類型:節點(反)親和性與Pod(反)親和性。

Node affinity與NodeSelector很類似,它容許你根據節點上的標籤限制你的pod能夠在哪些節點上進行調度。目前有兩種類型的節點關聯,稱爲required During Scheduling Ignored During Execution和 preferred During Scheduling Ignored During Execution。能夠將它們分別視爲「硬規則」和「軟規則」,前者指定了要將 pod調度到節點上必須知足的規則,然後者指定調度程序將嘗試強制但不保證的首選項。名稱中的「Ignored During Execution」部分意味着,相似於nodeSelector工做方式,若是節點上的標籤在運行時更改,再也不知足pod上的關聯性規則,pod仍將繼續在該節點上運行。Pod affinity強調的是同一個節點中Pod之間的親和力。能夠根據已在節點上運行的pod上的標籤來約束pod能夠調度哪些節點上。好比但願運行該Pod到某個已經運行了Pod標籤爲app=webserver的節點上,就可使用Pod affinity來表達這一需求。

目前有兩種類型Pod親和力和反親和力,稱爲required During Scheduling Ignored During Execution以及 preferred During Scheduling Ignored During Execution,其中表示「硬規則」與「軟規則」的要求。相似於Node affinity,IgnoredDuringExecution部分表示若是在Pod運行期間改變了Pod標籤致使親和性不知足以上規則,則pod仍將繼續在該節點上運行。不管是Selector仍是Affinity,都是基於Pod或者Node的標籤來表達約束類型。從而讓調度器按照約束規則來調度Pod運行在合理的節點上。

節點親和性以下所示,其中親和性定義爲:該pod只能放置在一個含有鍵爲kubernetes.io/hostname而且值爲node1或者node2標籤的節點上。此外,在知足該標準的節點中,具備其鍵爲app且值爲webserver的標籤的節點應該是優選的。

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - node1
            - node2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: app
            operator: In
            values:
            - webserver
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

Pod反親和性以下所示,其中反親和性定義爲:在此拓撲域(至關於以topologyKey的值進行的節點分組)中,命名空間爲default下有標籤鍵爲app,標籤值爲redis的Pod時不在此Node上運行。

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - redis
        namespaces:
        - default
        topologyKey: kubernetes.io/hostname
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

不使用調度器, 手動調度一個pod

Scheduling過程的本質其實就是給Pod賦予nodeName屬性合適的值。那麼在開發者進行Pod部署時就直接指定這個值是否可行呢?答案是確定的。以下配置,將nginx直接分配到node1上運行。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: node1

還有一種指定節點的部署方式——static pod,就像它名稱同樣,他是一個「靜態」的Pod,它不經過apiserver,直接由kubelet進行託管。在kubelet的啓動參數中--pod-manifest-path=DIR,這裏的DIR就是放置static pod的編排文件的目錄。把static pod的編排文件放到此目錄下,kubelet就能夠監聽到變化,並根據編排文件建立pod。還有一個啓動參數--manifest-url=URL,kubelet會從這個URL下載編排文件,並建立pod。static pod有一個特性是咱們使用docker或kubectl刪除static pod後, static pod還能被kubelet進程拉起。經過這種方式保證了應用的可用性。有點至關於systemd的功能, 但比systemd好的一點是, static pod的鏡像信息會在apiserver中註冊。 這樣的話, 咱們就能夠統一對部署信息進行可視化管理。 此外static pod是容器, 無需拷貝二進制文件到主機上, 應用封裝在鏡像裏也保證了環境的一致性, 不管是應用的編排文件仍是應用的鏡像都方便進行版本管理和分發。

在使用kubeadm部署kubernetes集羣時,static pod獲得了大量的應用,好比 etcd、kube-scheduler、kube-controller-manager、kube-apiserver 等都是使用 static pod的方式運行的。

使用static pod部署出來的pod名稱與其餘pod有很大的不一樣點,名稱中沒有「亂碼」,只是簡單的將pod的name屬性值與它運行在的node的name屬性值相鏈接而成。以下所示,coredns是經過Deployment部署出來的名稱中就有部分「亂碼」,而etcd,kube-apiserver這種Pod就是static pod。

$ kubectl get po --all-namespaces
NAMESPACE       NAME                          READY   STATUS    RESTARTS   AGE
kube-system   coredns-5679d9cd77-d6jp6        1/1     Running   0          6m59s
kube-system   etcd-node1                      1/1     Running   0          6m58s
kube-system   etcd-node2                      1/1     Running   0          6m58s
kube-system   etcd-node3                      1/1     Running   0          6m54s
kube-system   kube-proxy-nxj5d                1/1     Running   0          6m52s
kube-system   kube-proxy-tz264                1/1     Running   0          6m56s
kube-system   kube-proxy-zxgxc                1/1     Running   0          6m57s

瞭解Daemonset角色

DaemonSet是一種控制器,它確保在一些或所有Node上都運行一個指定的Pod。這些Pod就至關於守護進程同樣不指望被終止。當有Node加入集羣時,也會爲他們新增一個Pod。當有Node從集羣移除時,對應的Pod也會被回收。當刪除DaemonSet時將會刪除它建立的全部Pod。通常狀況下,Pod運行在哪一個節點上是由Kubernates調度器選擇的。可是在Kubernates 1.11版本以前由DaemonSet Controller建立的Pod在建立時已經肯定了在哪一個節點上運行(pod在建立的時候.spec.nodeName字段就指定了, 所以會被scheduler忽略),因此即便調度器沒有啓動DaemonSet Controller建立的Pod仍然也能夠被分配node。直到Kubernates 1.11版本,DaemonSet的pod由scheduler調度才做爲alpha特性引入。在上小節中kube-proxy就是以DaemonSet的方式進行運行的。

配置Kubernetes scheduler

若是須要配置一些高級的調度策略以知足咱們的須要,能夠修改默認調度程序的配置文件。kube-scheduler在啓動的時候能夠經過--policy-config-file參數來指定調度策略文件,開發者能夠根據本身的須要來組裝Predicates和Priority函數。選擇不一樣的過濾函數和優先級函數。調整控制優先級函數的權重和過濾函數的順序都會影響調度結果。

官方的Policy文件以下:

kind: Policy
apiVersion: v1
predicates:
- {name: PodFitsHostPorts}
- {name: PodFitsResources}
- {name: NoDiskConflict}
- {name: NoVolumeZoneConflict}
- {name: MatchNodeSelector}
- {name: HostName}
priorities:
- {name: LeastRequestedPriority, weight: 1}
- {name: BalancedResourceAllocation, weight: 1}
- {name: ServiceSpreadingPriority, weight: 1}
- {name: EqualPriority, weight: 1}

其中predicates區域是調度的預選階段所須要的過濾算法。priorities區域是優選階段的評分算法。

總結

再來回顧一下調度的主要構成部分:首先是預選過程,過濾掉不知足Pod要求的節點,而後是優選過程,對經過要求的節點進行優先級排序,最後選擇優先級最高的節點進行分配。當調度器不工做時或有臨時需求能夠手動指定nodeName屬性的值,讓其不經過調度器進行調度直接運行在指定的節點上。

關於豬齒魚

Choerodon開源多雲應用敏捷全鏈路技術平臺,是基於開源技術Kubernetes,Istio,knative,Gitlab,Spring Cloud來實現本地和雲端環境的集成,實現企業多雲/混合雲應用環境的一致性。平臺經過提供精益敏捷、持續交付、容器環境、微服務、DevOps等能力來幫助組織團隊來完成軟件的生命週期管理,從而更快、更頻繁地交付更穩定的軟件。

你們也能夠經過如下社區途徑瞭解豬齒魚的最新動態、產品特性,以及參與社區貢獻:

本篇文章出自Choerodon豬齒魚社區鍾梓凌&黃顯東。
相關文章
相關標籤/搜索