前言

本篇是Kubernetes第六篇,大家一定要把環境搭建起來,看是解決不了問題的,必須實戰。

Kubernetes系列文章:
  1. Kubernetes介紹
  2. Kubernetes環境搭建
  3. Kubernetes-kubectl介紹
  4. Kubernetes-Pod介紹(-)
  5. Kubernetes-Pod介紹(二)-生命週期

Pod排程

在Kubernetes中我們很少直接建立一個Pod,大多數情況下會通過Replication Controller、Deployment、Daemonset、Job等控制器完成一組Pod的建立、排程以及生命週期的管理。這是因為單個Pod不能滿足我們提出的高可用、高併發的概念,除此之外在真實的生產環境下還有些行行色色的需求:

  1. 不同的Pod之間的親和性問題,例如主從MySQL資料庫不能夠分配到同一個節點上或者兩種Pod必須排程到同一個節點上,實現本地網路、檔案共享等等;
  2. 有狀態的叢集,例如Zookeeper、Kafka等有狀態的叢集,每個節點看起來都是差不多,但是每個節點都必須明確主節點,而且節點啟動有嚴格的順序要求,此外叢集中的資料也需要持久化儲存,每個工作節點掛點的時候,如何按照持計劃的資訊進行恢復等等問題;
  3. 每個Node上排程僅僅建立一個Pod,例如對Node節點的監控,主機節點日誌、效能採集節點只能部署一個節點;
  4. 批量排程的任務以及定時排程的任務,呼叫完成的時候要求Pod就銷燬;
Deployment或者Replication Controller

Deployment和Replication Controller主要功能就是自動部署一個容器應用的多份副本以及控制副本的數量,在叢集內部始終控制指定的副本的數量。

  1. 刪除現有的資源資訊;
#刪除pod
kubectl delete -f nginx-deployment.yaml
  1. 編輯nginx-deployment.yaml檔案;
#編輯nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
#建立名為nginx-deployment的Deployment資源物件
metadata:
  name: nginx-deployment
spec:
  selector:
    #通過標籤查詢pod
    matchLabels:
      app: nginx
  #副本個數
  replicas: 3
  template:
    #pod打標籤
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
  1. 建立Deployment物件;
kubectl apply -f nginx-deployment.yaml
  1. 檢視建立的資源物件資訊;
#獲取deployment物件資訊
kubectl get deployment
#獲取pod資訊
kubectl get pod
#獲取rs資訊
kubectl get rs

image.png

注意:在定義Deployment資源的時候,matchLables和template.labels時必須成對出現的,名字也必須相同;

親和性排程
NodeSelector

在Kubernetes中Pod的排程由kube-scheduler完成,最終實現Pod排程到最佳的節點上,這個過程都是自動完成的,我們是無法預計Pod最終被分配到那個節點上的,在實際的情況我們可能需要將Pod排程到指定的節點上,這個時候我們可以通過Node的Lable與Pod的NodeSelector的屬性匹配完成節點的定向排程。

  1. 刪除現有Pod,這裡我又在阿里雲買了一臺臨時的ECS來完成這個實驗,目前我們是1主兩從的狀態,這裡我遇到這樣一個問題大家可以參考解決
#刪除pod
kubectl delete -f nginx-deployment.yaml
#檢視node節點
kubectl get nodes
  1. 給節點打上標籤;
#檢視節點詳情
kubectl get nodes
#給節點打標籤
kubectl label nodes demo-work-1 zone=hangzhou
#檢視節點標籤
kubectl get node --show-labels

image.png
  1. 編輯nginx-deployment.yaml檔案;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      #node選擇器
      nodeSelector:
        zone: hangzhou
  1. 建立Deployment物件;
kubectl apply -f nginx-deployment.yaml
  1. 檢查Pod節點分佈,這裡我們會發現Pod節點都被排程到了demo-work-1節點上;
#檢視pod更多的節點資訊
kubectl get pods -o wide

注意:如果我們指定Pod的NodeSelector條件的時候,如果叢集上不存在包含相同標籤Node,Pod是無法排程成功的,也包含正在執行的Pod。

NodeSelector通過標籤的方式,完成將節點的定向排程,這種親和性的排程機制極大提升的Pod排程能力,幫助Kubernetes更好的去完成我們需求,但是NodeSelector排程方式還是過於簡單,因此Kubernetes還提供NodeAffinity和PodAffinity兩種維度親和排程功能。

NodeAffinity

NodeAffinity翻譯過來是Node親和性排程,目的是為了替換NodeSelector,NodeAffinity目前有兩種親和性的表達方式:

  1. requiredDuringSchedulingIgnoredDuringExecution:表示 pod 必須部署到滿足條件的節點上,如果沒有滿足條件的節點,就不斷重試;
  2. preferredDuringSchedulingIgnoredDuringExecution:表示優先部署在滿足條件的節點上,如果沒有滿足條件的節點,就忽略這些條件,按照正常邏輯部署,多個優先順序級別的規則還可以設定權重;

IgnoredDuringExecution的意思是,Pod所在節點在執行期間發生變更,不在符合Pod節點的親和性規則,系統不會影響已經在節點上執行的Pod。

  1. 刪除現有Pod,給新節點打上zone=shanghai標籤,檢視Node列表;
#刪除pod
kubectl delete -f nginx-deployment.yaml
#打上海標籤
kubectl label nodes demo-work-2 zone=shanghai
#看看Node標籤
kubectl get node --show-labels

image.png
  1. 上個實戰已經給Node節點打好標籤,所以這裡我們直接編輯nginx-deployment.yaml檔案;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 10
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      affinity:
        nodeAffinity:
          #優先匹配hangzhou的節點 其次匹配shanghai 
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 80
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - hangzhou
            - weight: 20
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - shanghai
  1. 建立Deployment物件;
kubectl apply -f nginx-deployment.yaml
  1. 檢視Pod節點的分佈;
#檢視pod更多的節點資訊
kubectl get pods -o wide

image.png

在配置中我們看到可以使用操作符,操作符型別有以下幾種:

  1. In:表示所有資訊都要在value的列表裡面;

  2. NotIn: 標籤的值不在某個列表裡面;

  3. Exists: 存在某個標籤;

  4. DoesNotExist: 某個標籤不存在;

  5. Gt: 標籤的值大於某個值;

  6. Lt: 標籤的值小於某個值;

注意點:

  1. 如果同時設定nodeSelector和nodeAffinity則必須同時滿足兩個條件,Pod才會執行到最終的節點上:

  2. 如果同時指定多個nodeSelectorTerms,只要滿足其中一個就可以匹配成功;

  3. 如果nodeSelectorTerms有多個matchExpressions,則必須滿足所有matchExpressions才可以執行Pod;

PodAffinity And PodAntiAffinity

在生產環境中存在一類Pod,他們Pod之間相互依賴,要求儘可能被部署到相同的Node節點上,比如將應用的前端和後端部署在一起,減少訪問延遲,或者為了避免Pod之間相互競爭,要求某些Pod相互遠離,這就是Pod之間的親和性或者互斥性。Pod親和同樣有requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution兩種規則。

  1. 刪除Pod;
#刪除pod
kubectl delete -f nginx-deployment.yaml
  1. 編輯nginx-deployment.yaml檔案,修改Pod名稱為backend,並且將節點的個數調整為3個;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: backend
  replicas: 3
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      affinity:
        nodeAffinity:
          #優先匹配hangzhou的節點 其次匹配shanghai
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 80
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - hangzhou
            - weight: 20
              preference:
                matchExpressions:
                - key: zone
                  operator: In
                  values:
                    - shanghai 
  1. 新建檔案podAffinity-deployment.yaml, 選擇標籤為zone,並且app=backend的Pod;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: podaffinitydemo
spec:
  selector:
    matchLabels:
      app: frontend
  replicas: 3
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "128m"
        ports:
        - containerPort: 80
      affinity:
        #親和
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            #選擇標籤鍵為zone
            - topologyKey: zone
              labelSelector:
                #必須匹配到zone=hangzhou app=backend
                matchExpressions:
                - key: app
                  operator: In             
                  values:
                    - backend
  1. 建立Deployment資源;
kubectl apply -f nginx-deployment.yaml
kubectl apply -f podAffinity-deployment.yaml
  1. 檢視Pod節點的分佈;
#檢視pod更多的節點資訊
kubectl get pods -o wide

image.png

PodAntiAffinity就是反親和性,可以和PodAffinity一起出現在配置檔案中,與節點親和類似,在Pod親和中也可以使用操作符,相比於節點親和多了一個必須的值topologyKey,對於topologyKey使用有以下注意:

  1. 對於requiredDuringSchedulingIgnoredDuringExecution的Pod的反親和性和Pod的親和性必須出現topologyKey;

  2. 對於requiredDuringSchedulingIgnoredDuringExecution的Pod反親和性,引入LimitPodHardAntiAffinityTopology准入控制器來限制 topologyKey 只能是 kubernetes.io/hostname。如果要使用自定義拓撲域,則可以修改准入控制器,或者直接禁用它;

  3. 對於 preferredDuringSchedulingIgnoredDuringExecution 的 Pod 反親和性,空的 topologyKey 表示所有拓撲域。所有拓撲域還只能是 kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone 和 failure-domain.beta.kubernetes.io/region 的組合;

  4. 除上述情況外,topologyKey 可以是任何合法的標籤 key;

除了 labelSelector 和 topologyKey,也可以指定名稱空間,labelSelector 也可以匹配它。 如果忽略或者為空,則預設為 Pod 親和性/反親和性的定義所在的名稱空間。

Taints and Tolerations

親和性可以幫助Pod排程到指定的節點,但是在複雜的生產環境中,當你某個節點出現問題的時候,我們不希望再有Pod被排程到該節點,這裡的問題並不是節點掛掉了,而是磁碟爆滿、CPU、記憶體不足等等情況,這時候我們可以將節點標記Taint,Pod就不會排程到該節點上。還有一種特殊情況有時候我們仍然需要將Pod排程到被標記Taint的節點上,這個時候我們為Pod設定Toleration屬性來滿足Taint節點。

  1. 給節點標記Taint;
#給demo-work-1標記汙點 鍵名是 notRam,鍵值是 true,效果是NoSchedule
kubectl taint nodes demo-work-1 notRam=true:NoSchedule
#給demo-work-2標記汙點 鍵名是 haha,鍵值是 true,效果是NoSchedule
kubectl taint nodes demo-work-2 haha=true:NoSchedule
#移除汙點
kubectl taint nodes demo-work-1 notRam=true:NoSchedule-
  1. 編輯toleration-pod.yaml,設定Toleration屬性,保證節點被排程到對應汙點上;
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
  #設定Toleration
  tolerations:
  - key: "notRam"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"
  1. 建立Pod資源;
kubectl apply -f toleration-pod.yaml
  1. 檢視Pod節點的分佈;
#檢視pod更多的節點資訊
kubectl get pods -o wide

image.png

對於operator取值有兩種:

  1. 當operator 是 Exists不需要設定value的值;
  2. 當operator 是 Equal ,則它們的 value 必須相等;
排程策略

系統允許同一個Node設定多個Taint標籤,也允許Pod設定設定多個Toleration屬性。Kubernetes 處理多個Taint和Toleration的過程就像一個過濾器:首先列出所有Taint,忽略掉 Pod 中匹配的Taint。剩下的Taint有以下三種情況:

  1. 如果剩餘的Taint中存在effect=NoSchedule, 則排程器不會將 Pod 分配到該節點;

  2. 如果剩餘的Taint中不存在NoSchedule,但是存在PreferNoSchedule的汙點, 則排程器會嘗試不將 Pod 分配到該節點;

  3. 如果剩餘的Taint中存在NoExecute,如果有Pod執行在該節點上,則會被驅逐;如果沒有在該節點執行,則也不會排程到該節點;

驅逐策略

對於是設定NoExecute的Taint,會對正在執行的Pod有驅逐策略:

  1. 沒有設定Toleration的Pod會馬上被驅逐;

  2. 配置Toleration的Pod,如果沒有設定tolerationSeconds,則一種執行在節點上;

  3. 配置Toleration的Pod並且設定tolerationSeconds,則會在指定的時間後被驅逐,注意節點故障的情況下,系統會按照限速的模式逐步給Node新增Taint,避免特定情況下大量的Pod被驅逐;

自動新增的Toleration

Kubernetes默然情況下會給Pod新增以下兩種Toleration:

  1. key為node.kubernetes.io/not-ready,並設定tolerationSeconds=300;
  2. key為node.kubernetes.io/unreachable,並設定tolerationSeconds=300;

自動新增的容忍度意味著在其中一種問題被檢測到時 Pod 預設能夠繼續停留在當前節點執行 5 分鐘,而不是立即被驅逐,避免系統產生波動。

按照條件新增Taint

Kubernetes從1.6版本開始引入兩個Taint相關的新特性TaintNodesByCondition以及TaintBasedEvictions,用來改進Pod排程和驅逐問題,改造以後流程如下:

  1. 不斷檢查所有Node狀態,設定對應的Condition;

  2. 不斷的根據Node Condition設定對應的Taint;

  3. 不斷的更加Taint驅逐Node上的Pod;

其中,檢查Node的狀態並且設定Node的Taint就是TaintNodesByCondition的特性,在滿足以下情況的時候會自動給Node新增Taint:

  1. node.kubernetes.io/unreachable: 節點不可觸達,對應NodeCondition Ready為Unknown情況;

  2. node.kubernetes.io/not-ready: 節點未就緒,對應NodeCondition Ready為False情況;

  3. node.kubernetes.io/disk-pressure: 節點磁碟已滿;

  4. node.kubernetes.io/network-unavailable:節點網路不可用;

  5. node.kubernetes.io/unschedulable(1.10 或更高版本): 節點不可排程;

Kubernetes從1.13以上兩個特性會預設開啟,TaintNodesByCondition只會為節點設定NoSchedule的新增Taint;TaintBasedEvictions只會為節點新增NoExecute新增Taint,該特性被開啟之後,排程器會在有資源壓力的時候給對應的Node新增NoExecute的Taint,如果沒有設定對應的Toleration,則Pod會立即被驅逐,用此來保證Node不會崩潰。

優先排程

當叢集資源不足的時候,當我們需要建立Pod的時候,這個時候Pod會一致處於Pending狀態,即使該Pod是一個特別重要的Pod,我們也需要等待排程器釋放掉其他資源,才可以呼叫成功。針對這種情況,Kubernetes在1.8引入優先順序排程的Pod,當資源不足的時候有優先順序比較高的Pod需要排程的時候,會嘗試釋放一些優先順序比較低的資源,來滿足優先順序高的資源的排程,在1.14的版本中正式Release。

  1. 定義PriorityClass,命名為prioritydemo.yaml;
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "用於優先順序呼叫"
  1. 定義任意Pod,使用優先排程;
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx:latest
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority
  1. 建立資源;
#建立優先順序排程資源
kubectl apply -f prioritydemo.yaml
#建立pod
kubectl apply -f priority-pod.yaml
PriorityClass

PriorityClass 是一個無名稱空間物件,它定義了從優先順序類名稱到優先順序整數值的對映,值越大,優先順序越高。 PriorityClass 物件的名稱必須是有效的 DNS 子域名, 並且它不能以 system- 為字首。

關於使用PriorityClass注意:

  1. 如果你升級一個已經存在的但尚未使用優先順序排程的叢集,該叢集中已經存在的 Pod 的優先順序等效於0;

  2. 新增一個將 globalDefault 設定為 true 的 PriorityClass 不會改變現有 Pod 的優先順序。 此類 PriorityClass 的值僅用於新增 PriorityClass 後建立的 Pod;

  3. 如果你刪除了某個 PriorityClass 物件,則使用被刪除的 PriorityClass 名稱的現有 Pod 保持不變, 但是不能再建立使用已刪除的 PriorityClass 名稱的 Pod;

注意點,使用優先順序搶佔式的排程策略會導致某些Pod永遠無法被排程。優先順序排程不僅增加系統複雜性,還會帶來很多不穩定的因素,建議在資源緊張的時候優先採用擴容手段。

DeamonSet

DeamonSet確保節點上執行一個 Pod 的副本。 當有節點加入叢集時, 也會為他們新增一個 Pod 。 當有節點從叢集移除時,Pod也會被回收。刪除 DaemonSet 將會刪除它建立的所有 Pod。DeamonSet排程策略與RC類似,除了內建演算法保證在Node上進行排程,也可以在Pod定義NodeSelector和NodeAffinity來滿足指定條件的節點進行排程。

  1. 刪除Pod;
kubectl delete -f priority-pod.yaml
  1. 新建fluentd-deamonset.yaml檔案,掛載物理機的/var/log和/var/lib/docker/containers目錄;
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      containers:
      - name: fluentd-elasticsearch
        image: fluentd:latest
        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
  1. 建立DeamonSet資源;
kubectl apply -f fluentd-deamonset.yaml
  1. 檢視Pod節點的分佈;
#檢視pod更多的節點資訊
kubectl get pods -o wide

image.png
DaemonSet場景
  1. 每個節點上執行叢集守護程序;

  2. 每個節點上執行日誌收集守護程序;

  3. 每個節點上執行監控守護程序;

Taints and Tolerations

image.png
批量排程

經常會遇到這樣的場景,有一批大量的資料需要計算,這個時候就需要我們批任務去處理,Kubernetes可以通過Job來定義啟動批處理任務,批處理任務通常並行多個計算節點進行處理一個工作項,處理完成以後,整個批處理任務結束,按照實現方式不同可分為以下幾種情況:

  1. Job Template Expansion模式: 一個Job物件對應一個待處理的工作項,有幾個工作項就對應幾個Job,通常適合工作項數量少,每個工作項處理資料量比較大的場景;
  2. Queue with Pod Per Work Item模式: 採用一個任務佇列存放工作項, 一個Job物件作為消費者去消費這些工作項,這種模式下每個Pod都對應一個工作項,一個工作項處理完成,Pod就結束了;

image.png
  1. Queue with Variable Pod Count模式: 同樣採用一個任務佇列存放任務項, 一個Job物件作為消費者去消費這些任務項,每個 Pod 不斷的去佇列拉取任務項,完成後繼續去佇列裡去任務項,直到佇列裡沒有任務,Pod 才退出。這種情況下,只要有一個 Pod 成功退出,就意味著整個 Job 結束;

image.png
  1. 新建busybox-job.yaml檔案;
apiVersion: batch/v1
kind: Job
metadata:
  name: jobdemo
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox
        command: ["sh", "-c", "echo job demo && sleep 5"]
      restartPolicy: Never
  1. 建立Job資源;
kubectl apply  -f busybox-job.yaml
  1. 檢視Job資源;
kubectl get jobs -l jobgroup=jobexample
  1. 檢查輸出內容;
kubectl logs -f -l jobgroup=jobexample

image.png
定時排程

在我們的日常需求中還有一種週期性任務,Kubernetes可以通過CronJobs 來建立週期性的任務,例如定期執行資料庫備份,此外還可以使用CronJobs用來在指定時間來執行的獨立任務,例如在叢集狀態空閒的時候執行某個Job。

  1. 建立檔案hello-cronjob.yaml檔案,實現每分鐘列印Hello Word;
apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello Word 
          restartPolicy: OnFailure
  1. 建立CronJob資源;
kubectl apply -f hello-cronjob.yaml
  1. 檢視CronJob資源;
kubectl get cronJob hello
  1. 檢查輸出內容,我們會發現每隔1分鐘就排程一個Pod;

image.png

結束

歡迎大家點點關注,點點贊!