1. 程式人生 > 其它 >[K8s]Kubernetes-工作負載(下)

[K8s]Kubernetes-工作負載(下)

2.4 - DaemonSet

DaemonSet 確保全部(或者某些)節點上執行一個 Pod 的副本。當有節點加入叢集時,也會為他們新增一個 Pod。當有節點從叢集移除時,這些 Pod 也會被回收。刪除 DaemonSet 將會刪除它建立的所有 Pod。

DaemonSet 的一些典型用法:

  • 在每個節點上執行叢集守護程序
  • 在每個節點上執行日誌收集守護程序
  • 在每個節點上執行監控守護程序

一種簡單的用法是為每種型別的守護程序在所有的節點上都啟動一個 DaemonSet。一個稍微複雜的用法是為同一種守護程序部署多個 DaemonSet;每個具有不同的標誌,並且對不同硬體型別具有不同的記憶體、CPU 要求。

編寫 DaemonSet Spec

建立 DaemonSet

你可以在 YAML 檔案中描述 DaemonSet。例如,下面的 daemonset.yaml 檔案描述了一個執行 fluentd-elasticsearch Docker 映象的 DaemonSet:

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:
      # this toleration is to have the daemonset runnable on master nodes
      # remove it if your masters can't run pods
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        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

基於 YAML 檔案建立 DaemonSet:

kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

必需欄位

和所有其他 Kubernetes 配置一樣,DaemonSet 需要 apiVersion、kind 和 metadata 欄位。 有關配置檔案的基本資訊。

DaemonSet 物件的名稱必須是一個合法的 DNS 子域名。

DaemonSet 也需要一個 .spec 配置段。

Pod 模板

.spec 中唯一必需的欄位是 .spec.template。

.spec.template 是一個 Pod 模板。除了它是巢狀的,因而不具有 apiVersion 或 kind 欄位之外,它與 Pod 具有相同的 schema。

除了 Pod 必需欄位外,在 DaemonSet 中的 Pod 模板必須指定合理的標籤(檢視 Pod 選擇算符)。

在 DaemonSet 中的 Pod 模板必須具有一個值為 Always 的 RestartPolicy。當該值未指定時,預設是 Always。

Pod 選擇算符

.spec.selector 欄位表示 Pod 選擇算符,它與 Job 的 .spec.selector 的作用是相同的。

從 Kubernetes 1.8 開始,您必須指定與 .spec.template 的標籤匹配的 Pod 選擇算符。使用者不指定 Pod 選擇算符時,該欄位不再有預設值。選擇算符的預設值生成結果與 kubectl apply 不相容。此外,一旦建立了 DaemonSet,它的 .spec.selector 就不能修改。修改 Pod 選擇算符可能導致 Pod 意外懸浮,並且這對使用者來說是費解的。

spec.selector 是一個物件,如下兩個欄位組成:

  • matchLabels - 與 ReplicationController 的 .spec.selector 的作用相同。
  • matchExpressions - 允許構建更加複雜的選擇器,可以通過指定 key、value 列表以及將 key 和 value 列表關聯起來的 operator。

當上述兩個欄位都指定時,結果會按邏輯與(AND)操作處理。

如果指定了 .spec.selector,必須與 .spec.template.metadata.labels 相匹配。如果與後者不匹配,則 DeamonSet 會被 API 拒絕。

僅在某些節點上執行 Pod

如果指定了 .spec.template.spec.nodeSelector,DaemonSet 控制器將在能夠與 Node 選擇算符匹配的節點上建立 Pod。類似這種情況,可以指定 .spec.template.spec.affinity,之後 DaemonSet 控制器將在能夠與節點親和性匹配的節點上建立 Pod。如果根本就沒有指定,則 DaemonSet Controller 將在所有節點上建立 Pod。

Daemon Pods 是如何被排程的

通過預設排程器排程

FEATURE STATE: Kubernetes v1.23 [stable]

DaemonSet 確保所有符合條件的節點都執行該 Pod 的一個副本。通常,執行 Pod 的節點由 Kubernetes 排程器選擇。不過,DaemonSet Pods 由 DaemonSet 控制器建立和排程。這就帶來了以下問題:

  • Pod 行為的不一致性:正常 Pod 在被建立後等待排程時處於 Pending 狀態,DaemonSet Pods 建立後不會處於 Pending 狀態下。這使使用者感到困惑。
  • Pod 搶佔由預設排程器處理。啟用搶佔後,DaemonSet 控制器將在不考慮 Pod 優先順序和搶佔的情況下制定排程決策。

ScheduleDaemonSetPods 允許您使用預設排程器而不是 DaemonSet 控制器來排程 DaemonSets,方法是將 NodeAffinity 條件而不是 .spec.nodeName 條件新增到 DaemonSet Pods。預設排程器接下來將 Pod 繫結到目標主機。如果 DaemonSet Pod 的節點親和性配置已存在,則被替換(原始的節點親和性配置在選擇目標主機之前被考慮)。DaemonSet 控制器僅在建立或修改 DaemonSet Pod 時執行這些操作,並且不會更改 DaemonSet 的 spec.template。

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchFields:
      - key: metadata.name
        operator: In
        values:
        - target-host-name

此外,系統會自動新增 node.kubernetes.io/unschedulable:NoSchedule 容忍度到 DaemonSet Pods。在排程 DaemonSet Pod 時,預設排程器會忽略 unschedulable 節點。

汙點和容忍度

儘管 Daemon Pods 遵循汙點和容忍度規則,根據相關特性,控制器會自動將以下容忍度新增到 DaemonSet Pod:

容忍度鍵名 效果 版本 描述
node.kubernetes.io/not-ready NoExecute 1.13+ 當出現類似網路斷開的情況導致節點問題時,DaemonSet Pod 不會被逐出。
node.kubernetes.io/unreachable NoExecute 1.13+ 當出現類似於網路斷開的情況導致節點問題時,DaemonSet Pod 不會被逐出。
node.kubernetes.io/disk-pressure NoSchedule 1.8+ DaemonSet Pod 被預設排程器排程時能夠容忍磁碟壓力屬性。
node.kubernetes.io/memory-pressure NoSchedule 1.8+ DaemonSet Pod 被預設排程器排程時能夠容忍記憶體壓力屬性。
node.kubernetes.io/unschedulable NoSchedule 1.12+ DaemonSet Pod 能夠容忍預設排程器所設定的unschedulable屬性.
node.kubernetes.io/network-unavailable NoSchedule 1.12+ DaemonSet 在使用宿主網路時,能夠容忍預設排程器所設定的network-unavailable屬性。

與 Daemon Pods 通訊

與 DaemonSet 中的 Pod 進行通訊的幾種可能模式如下:

  • 推送(Push):配置 DaemonSet 中的 Pod,將更新發送到另一個服務,例如統計資料庫。 這些服務沒有客戶端。

  • NodeIP 和已知埠:DaemonSet 中的 Pod 可以使用 hostPort,從而可以通過節點 IP 訪問到 Pod。客戶端能通過某種方法獲取節點 IP 列表,並且基於此也可以獲取到相應的埠。

  • DNS:建立具有相同 Pod 選擇算符的無頭服務,通過使用 endpoints 資源或從 DNS 中檢索到多個 A 記錄來發現 DaemonSet。

  • Service:建立具有相同 Pod 選擇算符的服務,並使用該服務隨機訪問到某個節點上的守護程序(沒有辦法訪問到特定節點)。

更新 DaemonSet

如果節點的標籤被修改,DaemonSet 將立刻向新匹配上的節點新增 Pod,同時刪除不匹配的節點上的 Pod。

你可以修改 DaemonSet 建立的 Pod。不過並非 Pod 的所有欄位都可更新。下次當某節點(即使具有相同的名稱)被建立時,DaemonSet 控制器還會使用最初的模板。

您可以刪除一個 DaemonSet。如果使用 kubectl 並指定 --cascade=orphan 選項,則 Pod 將被保留在節點上。接下來如果建立使用相同選擇算符的新 DaemonSet,新的 DaemonSet 會收養已有的 Pod。如果有 Pod 需要被替換,DaemonSet 會根據其 updateStrategy 來替換。

你可以對 DaemonSet 執行滾動更新操作。

DaemonSet 的替代方案

init 指令碼

直接在節點上啟動守護程序(例如使用 init、upstartd 或 systemd)的做法當然是可行的。不過,基於 DaemonSet 來執行這些程序有如下一些好處:

  • 像所執行的其他應用一樣,DaemonSet 具備為守護程序提供監控和日誌管理的能力。

  • 為守護程序和應用所使用的配置語言和工具(如 Pod 模板、kubectl)是相同的。

  • 在資源受限的容器中執行守護程序能夠增加守護程序和應用容器的隔離性。然而,這一點也可以通過在容器中執行守護程序但卻不在 Pod 中執行之來實現。例如,直接基於 Docker 啟動。

裸 Pod

直接建立 Pod並指定其執行在特定的節點上也是可以的。然而,DaemonSet 能夠替換由於任何原因(例如節點失敗、例行節點維護、核心升級)而被刪除或終止的 Pod。由於這個原因,你應該使用 DaemonSet 而不是單獨建立 Pod。

靜態 Pod

通過在一個指定的、受 kubelet 監視的目錄下編寫檔案來建立 Pod 也是可行的。這類 Pod 被稱為靜態 Pod。不像 DaemonSet,靜態 Pod 不受 kubectl 和其它 Kubernetes API 客戶端管理。靜態 Pod 不依賴於 API 伺服器,這使得它們在啟動引導新叢集的情況下非常有用。此外,靜態 Pod 在將來可能會被廢棄。

Deployments

DaemonSet 與 Deployments 非常類似,它們都能建立 Pod,並且 Pod 中的程序都不希望被終止(例如,Web 伺服器、儲存伺服器)。

建議為無狀態的服務使用 Deployments,比如前端服務。對這些服務而言,對副本的數量進行擴縮容、平滑升級,比精確控制 Pod 執行在某個主機上要重要得多。當需要 Pod 副本總是執行在全部或特定主機上,並且當該 DaemonSet 提供了節點級別的功能(允許其他 Pod 在該特定節點上正確執行)時,應該使用 DaemonSet。

例如,網路外掛通常包含一個以 DaemonSet 執行的元件。這個 DaemonSet 元件確保它所在的節點的叢集網路正常工作。

2.5 - Jobs

Job 會建立一個或者多個 Pods,並將繼續重試 Pods 的執行,直到指定數量的 Pods 成功終止。隨著 Pods 成功結束,Job 跟蹤記錄成功完成的 Pods 個數。當數量達到指定的成功個數閾值時,任務(即 Job)結束。刪除 Job 的操作會清除所建立的全部 Pods。掛起 Job 的操作會刪除 Job 的所有活躍 Pod,直到 Job 被再次恢復執行。

一種簡單的使用場景下,你會建立一個 Job 物件以便以一種可靠的方式執行某 Pod 直到完成。當第一個 Pod 失敗或者被刪除(比如因為節點硬體失效或者重啟)時,Job 物件會啟動一個新的 Pod。

你也可以使用 Job 以並行的方式執行多個 Pod。

執行示例 Job

下面是一個 Job 配置示例。它負責計算 π 到小數點後 2000 位,並將結果打印出來。此計算大約需要 10 秒鐘完成。

kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

你可以使用下面的命令來執行此示例:

kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml

輸出類似於:

job.batch/pi created

使用 kubectl 來檢查 Job 的狀態:

kubectl describe jobs/pi

輸出類似於:

Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7

要檢視 Job 對應的已完成的 Pods,可以執行 kubectl get pods

要以機器可讀的方式列舉隸屬於某 Job 的全部 Pods,你可以使用類似下面這條命令:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods

輸出類似於:

pi-5rwd7

這裡,選擇算符與 Job 的選擇算符相同。--output=jsonpath 選項給出了一個表示式,用來從返回的列表中提取每個 Pod 的 name 欄位。

檢視其中一個 Pod 的標準輸出:

kubectl logs $pods

輸出類似於:

3.1415926535897.........................

編寫 Job 規約

與 Kubernetes 中其他資源的配置類似,Job 也需要 apiVersion、kind 和 metadata 欄位。Job 的名字必須是合法的 DNS 子域名。

Job 配置還需要一個.spec 節。

Pod 模版

Job 的 .spec 中只有 .spec.template 是必需的欄位。

欄位 .spec.template 的值是一個 Pod 模版。其定義規範與 Pod 完全相同,只是其中不再需要 apiVersion 或 kind 欄位。

除了作為 Pod 所必需的欄位之外,Job 中的 Pod 模版必需設定合適的標籤(參見Pod 選擇算符)和合適的重啟策略。

Job 中 Pod 的 RestartPolicy 只能設定為 Never 或 OnFailure 之一。

Pod 選擇算符

欄位 .spec.selector 是可選的。在絕大多數場合,你都不需要為其賦值。參閱設定自己的 Pod 選擇算符。

Job 的並行執行

適合以 Job 形式來執行的任務主要有三種:

  1. 非並行 Job:
    • 通常只啟動一個 Pod,除非該 Pod 失敗。
    • 當 Pod 成功終止時,立即視 Job 為完成狀態。
  2. 具有確定完成計數的並行 Job:
    • .spec.completions 欄位設定為非 0 的正數值。
    • Job 用來代表整個任務,當成功的 Pod 個數達到 .spec.completions 時,Job 被視為完成。
    • 當使用 .spec.completionMode="Indexed" 時,每個 Pod 都會獲得一個不同的索引值,介於 0 和 .spec.completions-1 之間。
  3. 帶工作佇列的並行 Job:
    • 不設定 spec.completions,預設值為 .spec.parallelism。
    • 多個 Pod 之間必須相互協調,或者藉助外部服務確定每個 Pod 要處理哪個工作條目。例如,任一 Pod 都可以從工作佇列中取走最多 N 個工作條目。
    • 每個 Pod 都可以獨立確定是否其它 Pod 都已完成,進而確定 Job 是否完成。
    • 當 Job 中 任何 Pod 成功終止,不再建立新 Pod。
    • 一旦至少 1 個 Pod 成功完成,並且所有 Pod 都已終止,即可宣告 Job 成功完成。
    • 一旦任何 Pod 成功退出,任何其它 Pod 都不應再對此任務執行任何操作或生成任何輸出。 所有 Pod 都應啟動退出過程。

對於非並行的 Job,你可以不設定 spec.completions 和 spec.parallelism。這兩個屬性都不設定時,均取預設值 1。

對於確定完成計數型別的 Job,你應該設定 .spec.completions 為所需要的完成個數。你可以設定 .spec.parallelism,也可以不設定。其預設值為 1。

對於一個工作佇列 Job,你不可以設定 .spec.completions,但要將.spec.parallelism 設定為一個非負整數。

關於如何利用不同型別的 Job 的更多資訊,請參見 Job 模式一節。

控制並行性

並行性請求(.spec.parallelism)可以設定為任何非負整數。如果未設定,則預設為 1。如果設定為 0,則 Job 相當於啟動之後便被暫停,直到此值被增加。

實際並行性(在任意時刻執行狀態的 Pods 個數)可能比並行性請求略大或略小,原因如下:

  • 對於確定完成計數 Job,實際上並行執行的 Pods 個數不會超出剩餘的完成數。如果 .spec.parallelism 值較高,會被忽略。
  • 對於工作佇列 Job,有任何 Job 成功結束之後,不會有新的 Pod 啟動。不過,剩下的 Pods 允許執行完畢。
  • 如果 Job 控制器沒有來得及作出響應。
  • 如果 Job 控制器因為任何原因(例如,缺少 ResourceQuota 或者沒有許可權)無法建立 Pods。Pods 個數可能比請求的數目小。
  • Job 控制器可能會因為之前同一 Job 中 Pod 失效次數過多而壓制新 Pod 的建立。
  • 當 Pod 處於體面終止程序中,需要一定時間才能停止。

完成模式

FEATURE STATE: Kubernetes v1.22 [beta]

帶有確定完成計數的 Job,即 .spec.completions 不為 null 的 Job,都可以在其 .spec.completionMode 中設定完成模式:

  • NonIndexed(預設值):當成功完成的 Pod 個數達到 .spec.completions 所設值時認為 Job 已經完成。換言之,每個 Job 完成事件都是獨立無關且同質的。要注意的是,當 .spec.completions 取值為 null 時,Job 被隱式處理為 NonIndexed。

  • Indexed:Job 的 Pod 會獲得對應的完成索引,取值為 0 到 .spec.completions-1。該索引可以通過三種方式獲取:

    • Pod 註解 batch.kubernetes.io/job-completion-index。
    • 作為 Pod 主機名的一部分,遵循模式 $(job-name)-$(index)。當你同時使用帶索引的 Job(Indexed Job)與 服務(Service),Job 中的 Pods 可以通過 DNS 使用確切的主機名互相定址。
    • 對於容器化的任務,在環境變數 JOB_COMPLETION_INDEX 中。

當每個索引都對應一個完成完成的 Pod 時,Job 被認為是已完成的。關於如何使用這種模式的更多資訊,可參閱用帶索引的 Job 執行基於靜態任務分配的並行處理。需要注意的是,對同一索引值可能被啟動的 Pod 不止一個,儘管這種情況很少發生。這時,只有一個會被記入完成計數中。

處理 Pod 和容器失效

Pod 中的容器可能因為多種不同原因失效,例如因為其中的程序退出時返回值非零,或者容器因為超出記憶體約束而被殺死等等。如果發生這類事件,並且 .spec.template.spec.restartPolicy = "OnFailure",Pod 則繼續留在當前節點,但容器會被重新執行。因此,你的程式需要能夠處理在本地被重啟的情況,或者要設定 .spec.template.spec.restartPolicy = "Never"。關於 restartPolicy 的更多資訊,可參閱 Pod 生命週期。

整個 Pod 也可能會失敗,且原因各不相同。例如,當 Pod 啟動時,節點失效(被升級、被重啟、被刪除等)或者其中的容器失敗而 .spec.template.spec.restartPolicy = "Never"。當 Pod 失敗時,Job 控制器會啟動一個新的 Pod。這意味著,你的應用需要處理在一個新 Pod 中被重啟的情況。尤其是應用需要處理之前執行所產生的臨時檔案、鎖、不完整的輸出等問題。

注意,即使你將 .spec.parallelism 設定為 1,且將 .spec.completions 設定為 1,並且 .spec.template.spec.restartPolicy 設定為 "Never",同一程式仍然有可能被啟動兩次。

如果你確實將 .spec.parallelism 和 .spec.completions 都設定為比 1 大的值,那就有可能同時出現多個 Pod 執行的情況。為此,你的 Pod 也必須能夠處理併發性問題。

Pod 回退失效策略

在有些情形下,你可能希望 Job 在經歷若干次重試之後直接進入失敗狀態,因為這很可能意味著遇到了配置錯誤。為了實現這點,可以將 .spec.backoffLimit 設定為視 Job 為失敗之前的重試次數。失效回退的限制值預設為 6。與 Job 相關的失效的 Pod 會被 Job 控制器重建,回退重試時間將會按指數增長(從 10 秒、20 秒到 40 秒)最多至 6 分鐘。當 Job 的 Pod 被刪除時,或者 Pod 成功時沒有其它 Pod 處於失敗狀態,失效回退的次數也會被重置(為 0)。

說明: 如果你的 Job 的 restartPolicy 被設定為 "OnFailure",就要注意執行該 Job 的 Pod 會在 Job 到達失效回退次數上限時自動被終止。這會使得除錯 Job 中可執行檔案的工作變得非常棘手。我們建議在除錯 Job 時將 restartPolicy 設定為 "Never",或者使用日誌系統來確保失效 Jobs 的輸出不會意外遺失。

Job 終止與清理

Job 完成時不會再建立新的 Pod,不過已有的 Pod 通常也不會被刪除。保留這些 Pod 使得你可以檢視已完成的 Pod 的日誌輸出,以便檢查錯誤、警告 或者其它診斷性輸出。Job 完成時 Job 物件也一樣被保留下來,這樣你就可以檢視它的狀態。在查看了 Job 狀態之後刪除老的 Job 的操作留給了使用者自己。你可以使用 kubectl 來刪除 Job(例如,kubectl delete jobs/pi 或者 kubectl delete -f ./job.yaml)。當使用 kubectl 來刪除 Job 時,該 Job 所建立的 Pods 也會被刪除。

預設情況下,Job 會持續執行,除非某個 Pod 失敗(restartPolicy=Never)或者某個容器出錯退出(restartPolicy=OnFailure)。這時,Job 基於前述的 spec.backoffLimit 來決定是否以及如何重試。一旦重試次數到達 .spec.backoffLimit 所設的上限,Job 會被標記為失敗,其中執行的 Pods 都會被終止。

終止 Job 的另一種方式是設定一個活躍期限。你可以為 Job 的 .spec.activeDeadlineSeconds 設定一個秒數值。該值適用於 Job 的整個生命期,無論 Job 建立了多少個 Pod。一旦 Job 執行時間達到 activeDeadlineSeconds 秒,其所有執行中的 Pod 都會被終止,並且 Job 的狀態更新為 type: Failed 及 reason: DeadlineExceeded。

注意 Job 的 .spec.activeDeadlineSeconds 優先順序高於其 .spec.backoffLimit 設定。因此,如果一個 Job 正在重試一個或多個失效的 Pod,該 Job 一旦到達 activeDeadlineSeconds 所設的時限即不再部署額外的 Pod,即使其重試次數還未達到 backoffLimit 所設的限制。

例如:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

注意 Job 規約和 Job 中的 Pod 模版規約都有 activeDeadlineSeconds 欄位。請確保你在合適的層次設定正確的欄位。

還要注意的是,restartPolicy 對應的是 Pod,而不是 Job 本身:一旦 Job 狀態變為 type: Failed,就不會再發生 Job 重啟的動作。換言之,由 .spec.activeDeadlineSeconds 和 .spec.backoffLimit 所觸發的 Job 終結機制都會導致 Job 永久性的失敗,而這類狀態都需要手工干預才能解決。

自動清理完成的 Job

完成的 Job 通常不需要留存在系統中。在系統中一直保留它們會給 API 伺服器帶來額外的壓力。如果 Job 由某種更高級別的控制器來管理,例如 CronJobs,則 Job 可以被 CronJob 基於特定的根據容量裁定的清理策略清理掉。

已完成 Job 的 TTL 機制

FEATURE STATE: Kubernetes v1.21 [beta]

自動清理已完成 Job(狀態為 Complete 或 Failed)的另一種方式是使用由 TTL 控制器所提供的 TTL 機制。通過設定 Job 的 .spec.ttlSecondsAfterFinished 欄位,可以讓該控制器清理掉已結束的資源。

TTL 控制器清理 Job 時,會級聯式地刪除 Job 物件。換言之,它會刪除所有依賴的物件,包括 Pod 及 Job 本身。注意,當 Job 被刪除時,系統會考慮其生命週期保障,例如其 Finalizers。

例如:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job pi-with-ttl 在結束 100 秒之後,可以成為被自動刪除的物件。

如果該欄位設定為 0,Job 在結束之後立即成為可被自動刪除的物件。如果該欄位沒有設定,Job 不會在結束之後被 TTL 控制器自動清除。

Job 模式

Job 物件可以用來支援多個 Pod 的可靠的併發執行。Job 物件不是設計用來支援相互通訊的並行程序的,後者一般在科學計算中應用較多。Job 的確能夠支援對一組相互獨立而又有所關聯的工作條目 的並行處理。這類工作條目可能是要傳送的電子郵件、要渲染的視訊幀、要編解碼的檔案、NoSQL 資料庫中要掃描的主鍵範圍等等。

在一個複雜系統中,可能存在多個不同的工作條目集合。這裡我們僅考慮使用者希望一起管理的工作條目集合之一 — 批處理作業

平行計算的模式有好多種,每種都有自己的強項和弱點。這裡要權衡的因素有:

  • 每個工作條目對應一個 Job 或者所有工作條目對應同一 Job 物件。後者更適合處理大量工作條目的場景;前者會給使用者帶來一些額外的負擔,而且需要系統管理大量的 Job 物件。
  • 建立與工作條目相等的 Pod 或者令每個 Pod 可以處理多個工作條目。前者通常不需要對現有程式碼和容器做較大改動;後者則更適合工作條目數量較大的場合,原因同上。
  • 有幾種技術都會用到工作佇列。這意味著需要執行一個佇列服務,並修改現有程式或容器使之能夠利用該工作佇列。與之比較,其他方案在修改現有容器化應用以適應需求方面可能更容易一些。

下面是對這些權衡的彙總,列 2 到 4 對應上面的權衡比較。模式的名稱對應了相關示例和更詳細描述的連結。

模式 單個 Job 物件 Pods 數少於工作條目數? 直接使用應用無需修改?
每工作條目一 Pod 的佇列 有時
Pod 數量可變的佇列
靜態任務分派的帶索引的 Job
Job 模版擴充套件

當你使用 .spec.completions 來設定完成數時,Job 控制器所建立的每個 Pod 使用完全相同的 spec。這意味著任務的所有 Pod 都有相同的命令列,都使用相同的映象和資料卷,甚至連環境變數都(幾乎)相同。這些模式是讓每個 Pod 執行不同工作的幾種不同形式。

下表顯示的是每種模式下 .spec.parallelism 和 .spec.completions 所需要的設定。其中,W 表示的是工作條目的個數。

模式 .spec.completions .spec.parallelism
每工作條目一 Pod 的佇列 W 任意值
Pod 個數可變的佇列 1 任意值
靜態任務分派的帶索引的 Job W
Job 模版擴充套件 1 應該為 1

高階用法

掛起 Job

FEATURE STATE: Kubernetes v1.21 [alpha]

說明:
該特性在 Kubernetes 1.21 版本中是 Alpha 階段,啟用該特性需要額外的步驟;請確保你正在閱讀與叢集版本一致的文件。

Job 被建立時,Job 控制器會馬上開始執行 Pod 建立操作以滿足 Job 的需求,並持續執行此操作直到 Job 完成為止。不過你可能想要暫時掛起 Job 執行,之後再恢復其執行。要掛起一個 Job,你可以將 Job 的 .spec.suspend 欄位更新為 true。之後,當你希望恢復其執行時,將其更新為 false。建立一個 .spec.suspend 被設定為 true 的 Job 本質上會將其建立為被掛起狀態。

當 Job 被從掛起狀態恢復執行時,其 .status.startTime 欄位會被重置為當前的時間。這意味著 .spec.activeDeadlineSeconds 計時器會在 Job 掛起時被停止,並在 Job 恢復執行時復位。

要記住的是,掛起 Job 會刪除其所有活躍的 Pod。當 Job 被掛起時,你的 Pod 會收到 SIGTERM 訊號而被終止。Pod 的體面終止期限會被考慮,不過 Pod 自身也必須在此期限之內處理完訊號。處理邏輯可能包括儲存進度以便將來恢復,或者取消已經做出的變更等等。Pod 以這種形式終止時,不會被記入 Job 的 completions 計數。

處於被掛起狀態的 Job 的定義示例可能是這樣子:

kubectl get job myjob -o yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  suspend: true
  parallelism: 1
  completions: 5
  template:
    spec:
      ...

Job 的 status 可以用來確定 Job 是否被掛起,或者曾經被掛起。

kubectl get jobs/myjob -o yaml

apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
  conditions:
  - lastProbeTime: "2021-02-05T13:14:33Z"
    lastTransitionTime: "2021-02-05T13:14:33Z"
    status: "True"
    type: Suspended
  startTime: "2021-02-05T13:13:48Z"

Job 的 "Suspended" 型別的狀況在狀態值為 "True" 時意味著 Job 正被 掛起;lastTransitionTime 欄位可被用來確定 Job 被掛起的時長。如果此狀況欄位的取值為 "False",則 Job 之前被掛起且現在在執行。如果 "Suspended" 狀況在 status 欄位中不存在,則意味著 Job 從未 被停止執行。

當 Job 被掛起和恢復執行時,也會生成事件:

kubectl describe jobs/myjob

Name:           myjob
...
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  12m   job-controller  Created pod: myjob-hlrpl
  Normal  SuccessfulDelete  11m   job-controller  Deleted pod: myjob-hlrpl
  Normal  Suspended         11m   job-controller  Job suspended
  Normal  SuccessfulCreate  3s    job-controller  Created pod: myjob-jvb44
  Normal  Resumed           3s    job-controller  Job resumed

最後四個事件,特別是 "Suspended" 和 "Resumed" 事件,都是因為 .spec.suspend 欄位值被改來改去造成的。在這兩個事件之間,我們看到沒有 Pod 被建立,不過當 Job 被恢復執行時,Pod 建立操作立即被重啟執行。

指定你自己的 Pod 選擇算符

通常,當你建立一個 Job 物件時,你不會設定 .spec.selector。系統的預設值填充邏輯會在建立 Job 時新增此欄位。它會選擇一個不會與任何其他 Job 重疊的選擇算符設定。

不過,有些場合下,你可能需要過載這個自動設定的選擇算符。為了實現這點,你可以手動設定 Job 的 spec.selector 欄位。

做這個操作時請務必小心。如果你所設定的標籤選擇算符並不唯一針對 Job 對應的 Pod 集合,甚或該算符還能匹配其他無關的 Pod,這些無關的 Job 的 Pod 可能會被刪除。或者當前 Job 會將另外一些 Pod 當作是完成自身工作的 Pods,又或者兩個 Job 之一或者二者同時都拒絕建立 Pod,無法執行至完成狀態。如果所設定的算符不具有唯一性,其他控制器(如 RC 副本控制器)及其所管理的 Pod 集合可能會變得行為不可預測。Kubernetes 不會在你設定 .spec.selector 時嘗試阻止你犯這類錯誤。

下面是一個示例場景,在這種場景下你可能會使用剛剛講述的特性。

假定名為 old 的 Job 已經處於執行狀態。你希望已有的 Pod 繼續執行,但你希望 Job 接下來要建立的其他 Pod 使用一個不同的 Pod 模版,甚至希望 Job 的名字也發生變化。你無法更新現有的 Job,因為這些欄位都是不可更新的。因此,你會刪除 old Job,但 允許該 Job 的 Pod 集合繼續執行。這是通過 kubectl delete jobs/old --cascade=orphan 實現的。在刪除之前,我們先記下該 Job 所使用的選擇算符。

kubectl get job old -o yaml

輸出類似於:

kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

接下來你會建立名為 new 的新 Job,並顯式地為其設定相同的選擇算符。由於現有 Pod 都具有標籤 controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002,它們也會被名為 new 的 Job 所控制。

你需要在新 Job 中設定 manualSelector: true,因為你並未使用系統通常自動為你生成的選擇算符。

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

新的 Job 自身會有一個不同於 a8f3d00d-c6d2-11e5-9f87-42010af00002 的唯一 ID。設定 manualSelector: true 是在告訴系統你知道自己在幹什麼並要求系統允許這種不匹配的存在。

使用 Finalizer 追蹤 Job

FEATURE STATE: Kubernetes v1.22 [alpha]

說明:
要使用該行為,你必須為 API 伺服器和控制器管理器啟用 JobTrackingWithFinalizers 特性門控。預設是禁用的。
啟用後,控制面基於下述行為追蹤新的 Job。現有 Job 不受影響。作為使用者,你會看到的唯一區別是控制面對 Job 完成情況的跟蹤更加準確。

該功能未啟用時,Job 控制器(Controller)依靠計算叢集中存在的 Pod 來跟蹤作業狀態。也就是說,維持一個統計 succeeded 和 failed 的 Pod 的計數器。然而,Pod 可以因為一些原因被移除,包括:

  • 當一個節點宕機時,垃圾收集器會刪除孤立(Orphan)Pod。
  • 垃圾收集器在某個閾值後刪除已完成的 Pod(處於 Succeeded 或 Failed 階段)。
  • 人工干預刪除 Job 的 Pod。
  • 一個外部控制器(不包含於 Kubernetes)來刪除或取代 Pod。

如果你為你的叢集啟用了 JobTrackingWithFinalizers 特性,控制面會跟蹤屬於任何 Job 的 Pod。並注意是否有任何這樣的 Pod 被從 API 伺服器上刪除。為了實現這一點,Job 控制器建立的 Pod 帶有 Finalizer batch.kubernetes.io/job-tracking。控制器只有在 Pod 被記入 Job 狀態後才會移除 Finalizer,允許 Pod 可以被其他控制器或使用者刪除。

Job 控制器只對新的 Job 使用新的演算法。在啟用該特性之前建立的 Job 不受影響。你可以根據檢查 Job 是否含有 batch.kubernetes.io/job-tracking 註解,來確定 Job 控制器是否正在使用 Pod Finalizer 追蹤 Job。你不應該給 Job 手動新增或刪除該註解。

替代方案

裸 Pod

當 Pod 執行所在的節點重啟或者失敗,Pod 會被終止並且不會被重啟。Job 會重新建立新的 Pod 來替代已終止的 Pod。因為這個原因,我們建議你使用 Job 而不是獨立的裸 Pod,即使你的應用僅需要一個 Pod。

副本控制器

Job 與副本控制器是彼此互補的。副本控制器管理的是那些不希望被終止的 Pod(例如,Web 伺服器),Job 管理的是那些希望被終止的 Pod(例如,批處理作業)。

正如在 Pod 生命期中討論的,Job 僅適合於 restartPolicy 設定為 OnFailure 或 Never 的 Pod。注意:如果 restartPolicy 未設定,其預設值是 Always。

單個 Job 啟動控制器 Pod

另一種模式是用唯一的 Job 來建立 Pod,而該 Pod 負責啟動其他 Pod,因此扮演了一種後啟動 Pod 的控制器的角色。這種模式的靈活性更高,但是有時候可能會把事情搞得很複雜,很難入門,並且與 Kubernetes 的整合度很低。

這種模式的例項之一是用 Job 來啟動一個執行指令碼的 Pod,指令碼負責啟動 Spark 主控制器(參見 Spark 示例),執行 Spark 驅動,之後完成清理工作。

這種方法的優點之一是整個過程得到了 Job 物件的完成保障,同時維持了對建立哪些 Pod、如何向其分派工作的完全控制能力。

2.6 - 已完成資源的 TTL 控制器

FEATURE STATE: Kubernetes v1.21 [beta]

TTL 控制器提供了一種 TTL 機制來限制已完成執行的資源物件的生命週期。TTL 控制器目前只處理 Job,可能以後會擴充套件以處理將完成執行的其他資源,例如 Pod 和自定義資源。

此功能目前是 Beta 版而自動啟用,並且可以通過 kube-apiserver 和 kube-controller-manager 上的特性門控 TTLAfterFinished 禁用。

TTL 控制器

TTL 控制器現在只支援 Job。叢集操作員可以通過指定 Job 的 .spec.ttlSecondsAfterFinished 欄位來自動清理已結束的作業(Complete 或 Failed)。

TTL 控制器假設資源能在執行完成後的 TTL 秒內被清理,也就是當 TTL 過期後。當 TTL 控制器清理資源時,它將做級聯刪除操作,即刪除資源物件的同時也刪除其依賴物件。注意,當資源被刪除時,由該資源的生命週期保證其終結器(Finalizers)等被執行。

可以隨時設定 TTL 秒。以下是設定 Job 的 .spec.ttlSecondsAfterFinished 欄位的一些示例:

  • 在資源清單(manifest)中指定此欄位,以便 Job 在完成後的某個時間被自動清除。
  • 將此欄位設定為現有的、已完成的資源,以採用此新功能。
  • 在建立資源時使用 mutating admission webhook 動態設定該欄位。叢集管理員可以使用它對完成的資源強制執行 TTL 策略。

使用 mutating admission webhook 在資源完成後動態設定該欄位,並根據資源狀態、標籤等選擇不同的 TTL 值。

警告

更新 TTL 秒

請注意,在建立資源或已經執行結束後,仍可以修改其 TTL 週期,例如 Job 的 .spec.ttlSecondsAfterFinished 欄位。但是一旦 Job 變為可被刪除狀態(當其 TTL 已過期時),即使您通過 API 增加其 TTL 時長得到了成功的響應,系統也不保證 Job 將被保留。

時間偏差

由於 TTL 控制器使用儲存在 Kubernetes 資源中的時間戳來確定 TTL 是否已過期,因此該功能對叢集中的時間偏差很敏感,這可能導致 TTL 控制器在錯誤的時間清理資源物件。

在 Kubernetes 中,需要在所有節點上執行 NTP 以避免時間偏差。時鐘並不總是如此正確,但差異應該很小。設定非零 TTL 時請注意避免這種風險。

2.7 - CronJob

FEATURE STATE: Kubernetes v1.21 [stable]

CronJob 建立基於時隔重複排程的 Jobs。

一個 CronJob 物件就像 crontab (cron table) 檔案中的一行。它用 Cron 格式進行編寫,並週期性地在給定的排程時間執行 Job。

注意:
所有 CronJob 的 schedule: 時間都是基於 kube-controller-manager. 的時區。
如果你的控制平面在 Pod 或是裸容器中運行了 kube-controller-manager,那麼為該容器所設定的時區將會決定 Cron Job 的控制器所使用的時區。

注意:
如 v1 CronJob API 所述,官方並不支援設定時區。
Kubernetes 專案官方並不支援設定如 CRON_TZ 或者 TZ 等變數。CRON_TZ 或者 TZ 是用於解析和計算下一個 Job 建立時間所使用的內部庫中一個實現細節。不建議在生產叢集中使用它。

為 CronJob 資源建立清單時,請確保所提供的名稱是一個合法的 DNS 子域名。名稱不能超過 52 個字元。這是因為 CronJob 控制器將自動在提供的 Job 名稱後附加 11 個字元,並且存在一個限制,即 Job 名稱的最大長度不能超過 63 個字元。

CronJob

CronJob 用於執行週期性的動作,例如備份、報告生成等。這些任務中的每一個都應該配置為週期性重複的(例如:每天/每週/每月一次);你可以定義任務開始執行的時間間隔。

示例

下面的 CronJob 示例清單會在每分鐘打印出當前時間和問候訊息:

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 from the Kubernetes cluster
          restartPolicy: OnFailure

Cron 時間表語法

# ┌───────────── 分鐘 (0 - 59)
# │ ┌───────────── 小時 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6) (週日到週一;在某些系統上,7 也是星期日)
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
輸入 描述 相當於
@yearly (or @annually) 每年 1 月 1 日的午夜執行一次 0 0 1 1 *
@monthly 每月第一天的午夜執行一次 0 0 1 * *
@weekly 每週的週日午夜執行一次 0 0 * * 0
@daily (or @midnight) 每天午夜執行一次 0 0 * * *
@hourly 每小時的開始一次 0 * * * *

例如,下面這行指出必須在每個星期五的午夜以及每個月 13 號的午夜開始任務:

0 0 13 * 5

要生成 CronJob 時間表表示式,你還可以使用 crontab.guru 之類的 Web 工具。

CronJob 限制

CronJob 根據其計劃編排,在每次該執行任務的時候大約會建立一個 Job。我們之所以說 "大約",是因為在某些情況下,可能會建立兩個 Job,或者不會建立任何 Job。我們試圖使這些情況儘量少發生,但不能完全杜絕。因此,Job 應該是冪等的。

如果 startingDeadlineSeconds 設定為很大的數值或未設定(預設),並且 concurrencyPolicy 設定為 Allow,則作業將始終至少執行一次。

注意:
如果 startingDeadlineSeconds 的設定值低於 10 秒鐘,CronJob 可能無法被排程。這是因為 CronJob 控制器每 10 秒鐘執行一次檢查。

對於每個 CronJob,CronJob 控制器(Controller)檢查從上一次排程的時間點到現在所錯過了排程次數。如果錯過的排程次數超過 100 次,那麼它就不會啟動這個任務,並記錄這個錯誤:

Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

需要注意的是,如果 startingDeadlineSeconds 欄位非空,則控制器會統計從 startingDeadlineSeconds 設定的值到現在而不是從上一個計劃時間到現在錯過了多少次 Job。例如,如果 startingDeadlineSeconds 是 200,則控制器會統計在過去 200 秒中錯過了多少次 Job。

如果未能在排程時間內建立 CronJob,則計為錯過。例如,如果 concurrencyPolicy 被設定為 Forbid,並且當前有一個排程仍在執行的情況下,試圖排程的 CronJob 將被計算為錯過。

例如,假設一個 CronJob 被設定為從 08:30:00 開始每隔一分鐘建立一個新的 Job,並且它的 startingDeadlineSeconds 欄位未被設定。如果 CronJob 控制器從 08:29:00 到 10:21:00 終止執行,則該 Job 將不會啟動,因為其錯過的排程次數超過了 100。

為了進一步闡述這個概念,假設將 CronJob 設定為從 08:30:00 開始每隔一分鐘建立一個新的 Job,並將其 startingDeadlineSeconds 欄位設定為 200 秒。如果 CronJob 控制器恰好在與上一個示例相同的時間段(08:29:00 到 10:21:00)終止執行,則 Job 仍將從 10:22:00 開始。造成這種情況的原因是控制器現在檢查在最近 200 秒(即 3 個錯過的排程)中發生了多少次錯過的 Job 排程,而不是從現在為止的最後一個排程時間開始。

CronJob 僅負責建立與其排程時間相匹配的 Job,而 Job 又負責管理其代表的 Pod。

控制器版本

從 Kubernetes v1.21 版本開始,CronJob 控制器的第二個版本被用作預設實現。要禁用此預設 CronJob 控制器而使用原來的 CronJob 控制器,請在 kube-controller-manager 中設定特性門控 CronJobControllerV2,將此標誌設定為 false。例如:

--feature-gates="CronJobControllerV2=false"

2.8 - ReplicationController

說明: 現在推薦使用配置 ReplicaSet 的 Deployment 來建立副本管理機制。

ReplicationController 確保在任何時候都有特定數量的 Pod 副本處於執行狀態。換句話說,ReplicationController 確保一個 Pod 或一組同類的 Pod 總是可用的。

ReplicationController 如何工作

當 Pod 數量過多時,ReplicationController 會終止多餘的 Pod。當 Pod 數量太少時,ReplicationController 將會啟動新的 Pod。與手動建立的 Pod 不同,由 ReplicationController 建立的 Pod 在失敗、被刪除或被終止時會被自動替換。例如,在中斷性維護(如核心升級)之後,你的 Pod 會在節點上重新建立。因此,即使你的應用程式只需要一個 Pod,你也應該使用 ReplicationController 建立 Pod。ReplicationController 類似於程序管理器,但是 ReplicationController 不是監控單個節點上的單個程序,而是監控跨多個節點的多個 Pod。

在討論中,ReplicationController 通常縮寫為 "rc",並作為 kubectl 命令的快捷方式。

一個簡單的示例是建立一個 ReplicationController 物件來可靠地無限期地執行 Pod 的一個例項。更復雜的用例是執行一個多副本服務(如 web 伺服器)的若干相同副本。

執行一個示例 ReplicationController

這個示例 ReplicationController 配置執行 nginx Web 伺服器的三個副本。

kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

通過下載示例檔案並執行以下命令來執行示例任務:

kubectl apply -f https://k8s.io/examples/controllers/replication.yaml

輸出類似於:

replicationcontroller/nginx created

使用以下命令檢查 ReplicationController 的狀態:

kubectl describe replicationcontrollers/nginx

輸出類似於:

Name:        nginx
Namespace:   default
Selector:    app=nginx
Labels:      app=nginx
Annotations:    <none>
Replicas:    3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=nginx
  Containers:
   nginx:
    Image:              nginx
    Port:               80/TCP
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen       LastSeen     Count    From                        SubobjectPath    Type      Reason              Message
  ---------       --------     -----    ----                        -------------    ----      ------              -------
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-qrm3m
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-3ntk0
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-4ok8v

在這裡,建立了三個 Pod,但沒有一個 Pod 正在執行,這可能是因為正在拉取映象。稍後,相同的命令可能會顯示:

Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed

要以機器可讀的形式列出屬於 ReplicationController 的所有 Pod,可以使用如下命令:

pods=$(kubectl get pods --selector=app=nginx --output=jsonpath={.items..metadata.name})
echo $pods

輸出類似於:

nginx-3ntk0 nginx-4ok8v nginx-qrm3m

這裡,選擇算符與 ReplicationController 的選擇算符相同(參見 kubectl describe 輸出),並以不同的形式出現在 replication.yaml 中。--output=jsonpath 選項指定了一個表示式,僅從返回列表中的每個 Pod 中獲取名稱。

編寫一個 ReplicationController 規約

與所有其它 Kubernetes 配置一樣,ReplicationController 需要 apiVersion、 kind 和 metadata 欄位。有關使用配置檔案的常規資訊,參考物件管理。

ReplicationController 也需要一個 .spec 部分。

Pod 模板

.spec.template 是 .spec 的唯一必需欄位。

.spec.template 是一個 Pod 模板。它的模式與 Pod 完全相同,只是它是巢狀的,沒有 apiVersion 或 kind 屬性。

除了 Pod 所需的欄位外,ReplicationController 中的 Pod 模板必須指定適當的標籤和適當的重新啟動策略。對於標籤,請確保不與其他控制器重疊。參考 Pod 選擇算符。

只允許 .spec.template.spec.restartPolicy 等於 Always,如果沒有指定,這是預設值。

對於本地容器重啟,ReplicationController 委託給節點上的代理,例如 Kubelet 或 Docker。

ReplicationController 上的標籤

ReplicationController 本身可以有標籤(.metadata.labels)。通常,你可以將這些設定為 .spec.template.metadata.labels;如果沒有指定 .metadata.labels 那麼它預設為 .spec.template.metadata.labels。

但是,Kubernetes 允許它們是不同的,.metadata.labels 不會影響 ReplicationController 的行為。

Pod 選擇算符

.spec.selector 欄位是一個標籤選擇算符。ReplicationController 管理標籤與選擇算符匹配的所有 Pod。它不區分它建立或刪除的 Pod 和其他人或程序建立或刪除的 Pod。這允許在不影響正在執行的 Pod 的情況下替換 ReplicationController。

如果指定了 .spec.template.metadata.labels,它必須和 .spec.selector 相同,否則它將被 API 拒絕。如果沒有指定 .spec.selector,它將預設為 .spec.template.metadata.labels。

另外,通常不應直接使用另一個 ReplicationController 或另一個控制器(例如 Job)來建立其標籤與該選擇算符匹配的任何 Pod。如果這樣做,ReplicationController 會認為它建立了這些 Pod。Kubernetes 並沒有阻止你這樣做。

如果你的確建立了多個控制器並且其選擇算符之間存在重疊,那麼你將不得不自己管理刪除操作。

多個副本

你可以通過設定 .spec.replicas 來指定應該同時執行多少個 Pod。在任何時候,處於執行狀態的 Pod 個數都可能高於或者低於設定值。例如,副本個數剛剛被增加或減少時,或者一個 Pod 處於優雅終止過程中而其替代副本已經提前開始建立時。

如果你沒有指定 .spec.replicas,那麼它預設是 1。

使用 ReplicationController

刪除一個 ReplicationController 以及它的 Pod

要刪除一個 ReplicationController 以及它的 Pod,使用 kubectl delete。kubectl 將 ReplicationController 縮放為 0 並等待以便在刪除 ReplicationController 本身之前刪除每個 Pod。如果這個 kubectl 命令被中斷,可以重新啟動它。

當使用 REST API 或 Go 客戶端庫時,你需要明確地執行這些步驟(縮放副本為 0、等待 Pod 刪除,之後刪除 ReplicationController 資源)。

只刪除 ReplicationController

你可以刪除一個 ReplicationController 而不影響它的任何 Pod。

使用 kubectl,為 kubectl delete 指定 --cascade=false 選項。

當使用 REST API 或 Go 客戶端庫時,只需刪除 ReplicationController 物件。

一旦原始物件被刪除,你可以建立一個新的 ReplicationController 來替換它。只要新的和舊的 .spec.selector 相同,那麼新的控制器將領養舊的 Pod。但是,它不會做出任何努力使現有的 Pod 匹配新的、不同的 Pod 模板。如果希望以受控方式更新 Pod 以使用新的 spec,請執行滾動更新操作。

從 ReplicationController 中隔離 Pod

通過更改 Pod 的標籤,可以從 ReplicationController 的目標中刪除 Pod。此技術可用於從服務中刪除 Pod 以進行除錯、資料恢復等。以這種方式刪除的 Pod 將被自動替換(假設複製副本的數量也沒有更改)。

常見的使用模式

重新排程

如上所述,無論你想要繼續執行 1 個 Pod 還是 1000 個 Pod,一個 ReplicationController 都將確保存在指定數量的 Pod,即使在節點故障或 Pod 終止(例如,由於另一個控制代理的操作)的情況下也是如此。

擴縮容

通過設定 replicas 欄位,ReplicationController 可以允許擴容或縮容副本的數量。你可以手動或通過自動縮放控制代理來控制 ReplicationController 執行此操作。

滾動更新

ReplicationController 的設計目的是通過逐個替換 Pod 以方便滾動更新服務。

如 #1353 PR 中所述,建議的方法是使用 1 個副本建立一個新的 ReplicationController,逐個擴容新的(+1)和縮容舊的(-1)控制器,然後在舊的控制器達到 0 個副本後將其刪除。這一方法能夠實現可控的 Pod 集合更新,即使存在意外失效的狀況。

理想情況下,滾動更新控制器將考慮應用程式的就緒情況,並確保在任何給定時間都有足夠數量的 Pod 有效地提供服務。

這兩個 ReplicationController 將需要建立至少具有一個不同標籤的 Pod,比如 Pod 主要容器的映象標籤,因為通常是映象更新觸發滾動更新。

滾動更新是在客戶端工具 kubectl rolling-update 中實現的。訪問 kubectl rolling-update 任務以獲得更多的具體示例。

多個版本跟蹤

除了在滾動更新過程中執行應用程式的多個版本之外,通常還會使用多個版本跟蹤來長時間,甚至持續執行多個版本。這些跟蹤將根據標籤加以區分。

例如,一個服務可能把具有 tier in (frontend), environment in (prod) 的所有 Pod 作為目標。現在假設你有 10 個副本的 Pod 組成了這個層。但是你希望能夠 canary (金絲雀)釋出這個元件的新版本。你可以為大部分副本設定一個 ReplicationController,其中 replicas 設定為 9,標籤為 tier=frontend, environment=prod, track=stable 而為 canary 設定另一個 ReplicationController,其中 replicas 設定為 1,標籤為 tier=frontend, environment=prod, track=canary。現在這個服務覆蓋了 canary 和非 canary Pod。但你可以單獨處理 ReplicationController,以測試、監控結果等。

和服務一起使用 ReplicationController

多個 ReplicationController 可以位於一個服務的後面,例如,一部分流量流向舊版本,一部分流量流向新版本。

一個 ReplicationController 永遠不會自行終止,但它不會像服務那樣長時間存活。服務可以由多個 ReplicationController 控制的 Pod 組成,並且在服務的生命週期內(例如,為了執行 Pod 更新而執行服務),可以建立和銷燬許多 ReplicationController。服務本身和它們的客戶端都應該忽略負責維護服務 Pod 的 ReplicationController 的存在。

編寫多副本的應用

由 ReplicationController 建立的 Pod 是可替換的,語義上是相同的,儘管隨著時間的推移,它們的配置可能會變得異構。這顯然適合於多副本的無狀態伺服器,但是 ReplicationController 也可以用於維護主選、分片和工作池應用程式的可用性。這樣的應用程式應該使用動態的工作分配機制,例如 RabbitMQ 工作佇列,而不是靜態的或者一次性定製每個 Pod 的配置,這被認為是一種反模式。執行的任何 Pod 定製,例如資源的垂直自動調整大小(例如,CPU 或記憶體),都應該由另一個線上控制器程序執行,這與 ReplicationController 本身沒什麼不同。

ReplicationController 的職責

ReplicationController 僅確保所需的 Pod 數量與其標籤選擇算符匹配,並且是可操作的。目前,它的計數中只排除終止的 Pod。未來,可能會考慮系統提供的就緒狀態和其他資訊,我們可能會對替換策略新增更多控制,我們計劃發出事件,這些事件可以被外部客戶端用來實現任意複雜的替換或縮減策略。

ReplicationController 永遠被限制在這個狹隘的職責範圍內。它本身既不執行就緒態探測,也不執行活躍性探測。它不負責執行自動縮放,而是由外部自動縮放器控制,後者負責更改其 replicas 欄位值。我們不會向 ReplicationController 新增排程策略(例如, spreading)。它也不應該驗證所控制的 Pod 是否與當前指定的模板匹配,因為這會阻礙自動調整大小和其他自動化過程。類似地,完成期限、整理依賴關係、配置擴充套件和其他特性也屬於其他地方。我們甚至計劃考慮批量建立 Pod 的機制。

ReplicationController 旨在成為可組合的構建基元。我們希望在它和其他補充原語的基礎上構建更高級別的 API 或者工具,以便於將來的使用者使用。kubectl 目前支援的 "macro" 操作(執行、縮放、滾動更新)就是這方面的概念示例。例如,我們可以想象類似於 Asgard 的東西管理 ReplicationController、自動定標器、服務、排程策略、金絲雀釋出等。

API 物件

在 Kubernetes REST API 中 Replication controller 是頂級資源。更多關於 API 物件的詳細資訊可以在 ReplicationController API 物件找到。

ReplicationController 的替代方案

ReplicaSet

ReplicaSet 是下一代 ReplicationController,支援新的基於集合的標籤選擇算符。它主要被 Deployment 用來作為一種編排 Pod 建立、刪除及更新的機制。請注意,我們推薦使用 Deployment 而不是直接使用 ReplicaSet,除非你需要自定義更新編排或根本不需要更新。

Deployment(推薦)

Deployment 是一種更高級別的 API 物件,它以類似於 kubectl rolling-update 的方式更新其底層 ReplicaSet 及其 Pod。如果你想要這種滾動更新功能,那麼推薦使用 Deployment,因為與 kubectl rolling-update 不同,它們是宣告式的、服務端的,並且具有其它特性。

裸 Pod

與使用者直接建立 Pod 的情況不同,ReplicationController 能夠替換因某些原因被刪除或被終止的 Pod ,例如在節點故障或中斷節點維護的情況下,例如核心升級。因此,我們建議你使用 ReplicationController,即使你的應用程式只需要一個 Pod。可以將其看作類似於程序管理器,它只管理跨多個節點的多個 Pod ,而不是單個節點上的單個程序。ReplicationController 將本地容器重啟委託給節點上的某個代理(例如,Kubelet 或 Docker)。

Job

對於預期會自行終止的 Pod (即批處理任務),使用 Job 而不是 ReplicationController。

DaemonSet

對於提供機器級功能(例如機器監控或機器日誌記錄)的 Pod,使用 DaemonSet 而不是 ReplicationController。這些 Pod 的生命期與機器的生命期繫結:它們需要在其他 Pod 啟動之前在機器上執行,並且在機器準備重新啟動或者關閉時安全地終止。