Kubernetes Jobs

2022-05-06 09:15 更新

Jobs

Job 會(huì)創(chuàng)建一個(gè)或者多個(gè) Pods,并將繼續(xù)重試 Pods 的執(zhí)行,直到指定數(shù)量的 Pods 成功終止。 隨著 Pods 成功結(jié)束,Job 跟蹤記錄成功完成的 Pods 個(gè)數(shù)。 當(dāng)數(shù)量達(dá)到指定的成功個(gè)數(shù)閾值時(shí),任務(wù)(即 Job)結(jié)束。 刪除 Job 的操作會(huì)清除所創(chuàng)建的全部 Pods。 掛起 Job 的操作會(huì)刪除 Job 的所有活躍 Pod,直到 Job 被再次恢復(fù)執(zhí)行。

一種簡(jiǎn)單的使用場(chǎng)景下,你會(huì)創(chuàng)建一個(gè) Job 對(duì)象以便以一種可靠的方式運(yùn)行某 Pod 直到完成。 當(dāng)?shù)谝粋€(gè) Pod 失敗或者被刪除(比如因?yàn)楣?jié)點(diǎn)硬件失效或者重啟)時(shí),Job 對(duì)象會(huì)啟動(dòng)一個(gè)新的 Pod。

你也可以使用 Job 以并行的方式運(yùn)行多個(gè) Pod。

運(yùn)行示例 Job 

下面是一個(gè) Job 配置示例。它負(fù)責(zé)計(jì)算 π 到小數(shù)點(diǎn)后 2000 位,并將結(jié)果打印出來。 此計(jì)算大約需要 10 秒鐘完成。

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

你可以使用下面的命令來運(yùn)行此示例:

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

輸出類似于:

job.batch/pi created

使用 ?kubectl ?來檢查 Job 的狀態(tài):

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 對(duì)應(yīng)的已完成的 Pods,可以執(zhí)行 ?kubectl get pods?。

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

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

輸出類似于:

pi-5rwd7

這里,選擇算符與 Job 的選擇算符相同。?--output=jsonpath? 選項(xiàng)給出了一個(gè)表達(dá)式, 用來從返回的列表中提取每個(gè) Pod 的 name 字段。

查看其中一個(gè) Pod 的標(biāo)準(zhǔn)輸出:

kubectl logs $pods

輸出類似于:

3.1415926535 #后面為Π的無窮數(shù)

編寫 Job 規(guī)約 

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

Job 配置還需要一個(gè)?.spec? 節(jié)。

Pod 模版

Job 的 ?.spec? 中只有 ?.spec.template? 是必需的字段。

字段 ?.spec.template? 的值是一個(gè) Pod 模版。 其定義規(guī)范與 Pod 完全相同,只是其中不再需要 ?apiVersion ?或 ?kind ?字段。

除了作為 Pod 所必需的字段之外,Job 中的 Pod 模版必需設(shè)置合適的標(biāo)簽 (參見Pod 選擇算符)和合適的重啟策略。

Job 中 Pod 的 ?RestartPolicy ?只能設(shè)置為 ?Never ?或 ?OnFailure ?之一。

Pod 選擇算符 

字段 ?.spec.selector? 是可選的。在絕大多數(shù)場(chǎng)合,你都不需要為其賦值。

Job 的并行執(zhí)行

適合以 Job 形式來運(yùn)行的任務(wù)主要有三種:

  1. 非并行 Job:
    • 通常只啟動(dòng)一個(gè) Pod,除非該 Pod 失敗。
    • 當(dāng) Pod 成功終止時(shí),立即視 Job 為完成狀態(tài)。
  2. 具有 確定完成計(jì)數(shù) 的并行 Job:
    • ?.spec.completions? 字段設(shè)置為非 0 的正數(shù)值。
    • Job 用來代表整個(gè)任務(wù),當(dāng)成功的 Pod 個(gè)數(shù)達(dá)到 ?.spec.completions? 時(shí),Job 被視為完成。
    • 當(dāng)使用 ?.spec.completionMode="Indexed"? 時(shí),每個(gè) Pod 都會(huì)獲得一個(gè)不同的 索引值,介于 0 和 ?.spec.completions-1? 之間。
  3. 帶 工作隊(duì)列 的并行 Job:
    • 不設(shè)置 ?spec.completions?,默認(rèn)值為 ?.spec.parallelism?。
    • 多個(gè) Pod 之間必須相互協(xié)調(diào),或者借助外部服務(wù)確定每個(gè) Pod 要處理哪個(gè)工作條目。 例如,任一 Pod 都可以從工作隊(duì)列中取走最多 N 個(gè)工作條目。
    • 每個(gè) Pod 都可以獨(dú)立確定是否其它 Pod 都已完成,進(jìn)而確定 Job 是否完成。
    • 當(dāng) Job 中 任何 Pod 成功終止,不再創(chuàng)建新 Pod。
    • 一旦至少 1 個(gè) Pod 成功完成,并且所有 Pod 都已終止,即可宣告 Job 成功完成。
    • 一旦任何 Pod 成功退出,任何其它 Pod 都不應(yīng)再對(duì)此任務(wù)執(zhí)行任何操作或生成任何輸出。 所有 Pod 都應(yīng)啟動(dòng)退出過程。

對(duì)于 非并行 的 Job,你可以不設(shè)置 ?spec.completions? 和 ?spec.parallelism?。 這兩個(gè)屬性都不設(shè)置時(shí),均取默認(rèn)值 1。

對(duì)于 確定完成計(jì)數(shù) 類型的 Job,你應(yīng)該設(shè)置 ?.spec.completions? 為所需要的完成個(gè)數(shù)。 你可以設(shè)置 ?.spec.parallelism?,也可以不設(shè)置。其默認(rèn)值為 1。

對(duì)于一個(gè) 工作隊(duì)列 Job,你不可以設(shè)置 ?.spec.completions?,但要將?.spec.parallelism? 設(shè)置為一個(gè)非負(fù)整數(shù)。

控制并行性 

并行性請(qǐng)求(?.spec.parallelism?)可以設(shè)置為任何非負(fù)整數(shù)。 如果未設(shè)置,則默認(rèn)為 1。 如果設(shè)置為 0,則 Job 相當(dāng)于啟動(dòng)之后便被暫停,直到此值被增加。

實(shí)際并行性(在任意時(shí)刻運(yùn)行狀態(tài)的 Pods 個(gè)數(shù))可能比并行性請(qǐng)求略大或略小, 原因如下:

  • 對(duì)于 確定完成計(jì)數(shù) Job,實(shí)際上并行執(zhí)行的 Pods 個(gè)數(shù)不會(huì)超出剩余的完成數(shù)。 如果 ?.spec.parallelism? 值較高,會(huì)被忽略。
  • 對(duì)于 工作隊(duì)列 Job,有任何 Job 成功結(jié)束之后,不會(huì)有新的 Pod 啟動(dòng)。 不過,剩下的 Pods 允許執(zhí)行完畢。
  • 如果 Job 控制器 沒有來得及作出響應(yīng),或者
  • 如果 Job 控制器因?yàn)槿魏卧颍ɡ?,缺?nbsp;?ResourceQuota ?或者沒有權(quán)限)無法創(chuàng)建 Pods。 Pods 個(gè)數(shù)可能比請(qǐng)求的數(shù)目小。
  • Job 控制器可能會(huì)因?yàn)橹巴?nbsp;Job 中 Pod 失效次數(shù)過多而壓制新 Pod 的創(chuàng)建。
  • 當(dāng) Pod 處于體面終止進(jìn)程中,需要一定時(shí)間才能停止。

完成模式 

FEATURE STATE: Kubernetes v1.22 [beta]

帶有 確定完成計(jì)數(shù) 的 Job,即 ?.spec.completions? 不為 null 的 Job, 都可以在其 ?.spec.completionMode? 中設(shè)置完成模式:

  • ?NonIndexed?(默認(rèn)值):當(dāng)成功完成的 Pod 個(gè)數(shù)達(dá)到 ?.spec.completions? 所 設(shè)值時(shí)認(rèn)為 Job 已經(jīng)完成。換言之,每個(gè) Job 完成事件都是獨(dú)立無關(guān)且同質(zhì)的。 要注意的是,當(dāng) ?.spec.completions? 取值為 null 時(shí),Job 被隱式處理為 ?NonIndexed?。
  • ?Indexed?:Job 的 Pod 會(huì)獲得對(duì)應(yīng)的完成索引,取值為 0 到 ?.spec.completions-1?。 該索引可以通過三種方式獲?。?/li>
    • Pod 注解 ?batch.kubernetes.io/job-completion-index?。
    • 作為 Pod 主機(jī)名的一部分,遵循模式 ?$(job-name)-$(index)?。 當(dāng)你同時(shí)使用帶索引的 Job(Indexed Job)與 服務(wù)(Service), Job 中的 Pods 可以通過 DNS 使用確切的主機(jī)名互相尋址。
    • 對(duì)于容器化的任務(wù),在環(huán)境變量 ?JOB_COMPLETION_INDEX? 中。
    當(dāng)每個(gè)索引都對(duì)應(yīng)一個(gè)完成完成的 Pod 時(shí),Job 被認(rèn)為是已完成的。需要注意的是,對(duì)同一索引值可能被啟動(dòng)的 Pod 不止一個(gè),盡管這種情況很少發(fā)生。 這時(shí),只有一個(gè)會(huì)被記入完成計(jì)數(shù)中。

處理 Pod 和容器失效

Pod 中的容器可能因?yàn)槎喾N不同原因失效,例如因?yàn)槠渲械倪M(jìn)程退出時(shí)返回值非零, 或者容器因?yàn)槌鰞?nèi)存約束而被殺死等等。 如果發(fā)生這類事件,并且 ?.spec.template.spec.restartPolicy = "OnFailure"?, Pod 則繼續(xù)留在當(dāng)前節(jié)點(diǎn),但容器會(huì)被重新運(yùn)行。 因此,你的程序需要能夠處理在本地被重啟的情況,或者要設(shè)置 ?.spec.template.spec.restartPolicy = "Never"?。

整個(gè) Pod 也可能會(huì)失敗,且原因各不相同。 例如,當(dāng) Pod 啟動(dòng)時(shí),節(jié)點(diǎn)失效(被升級(jí)、被重啟、被刪除等)或者其中的容器失敗而 ?.spec.template.spec.restartPolicy = "Never"?。 當(dāng) Pod 失敗時(shí),Job 控制器會(huì)啟動(dòng)一個(gè)新的 Pod。 這意味著,你的應(yīng)用需要處理在一個(gè)新 Pod 中被重啟的情況。 尤其是應(yīng)用需要處理之前運(yùn)行所產(chǎn)生的臨時(shí)文件、鎖、不完整的輸出等問題。

注意,即使你將 ?.spec.parallelism? 設(shè)置為 1,且將 ?.spec.completions? 設(shè)置為 1,并且 ?.spec.template.spec.restartPolicy? 設(shè)置為 "Never",同一程序仍然有可能被啟動(dòng)兩次。

如果你確實(shí)將 ?.spec.parallelism? 和 ?.spec.completions? 都設(shè)置為比 1 大的值, 那就有可能同時(shí)出現(xiàn)多個(gè) Pod 運(yùn)行的情況。 為此,你的 Pod 也必須能夠處理并發(fā)性問題。

Pod 回退失效策略

在有些情形下,你可能希望 Job 在經(jīng)歷若干次重試之后直接進(jìn)入失敗狀態(tài),因?yàn)檫@很 可能意味著遇到了配置錯(cuò)誤。 為了實(shí)現(xiàn)這點(diǎn),可以將 ?.spec.backoffLimit? 設(shè)置為視 Job 為失敗之前的重試次數(shù)。 失效回退的限制值默認(rèn)為 6。 與 Job 相關(guān)的失效的 Pod 會(huì)被 Job 控制器重建,回退重試時(shí)間將會(huì)按指數(shù)增長(zhǎng) (從 10 秒、20 秒到 40 秒)最多至 6 分鐘。 當(dāng) Job 的 Pod 被刪除時(shí),或者 Pod 成功時(shí)沒有其它 Pod 處于失敗狀態(tài),失效回退的次數(shù)也會(huì)被重置(為 0)。

如果你的 Job 的 ?restartPolicy ?被設(shè)置為 "OnFailure",就要注意運(yùn)行該 Job 的 Pod 會(huì)在 Job 到達(dá)失效回退次數(shù)上限時(shí)自動(dòng)被終止。 這會(huì)使得調(diào)試 Job 中可執(zhí)行文件的工作變得非常棘手。 我們建議在調(diào)試 Job 時(shí)將 ?restartPolicy ?設(shè)置為 "Never", 或者使用日志系統(tǒng)來確保失效 Jobs 的輸出不會(huì)意外遺失。

Job 終止與清理

Job 完成時(shí)不會(huì)再創(chuàng)建新的 Pod,不過已有的 Pod 通常也不會(huì)被刪除。 保留這些 Pod 使得你可以查看已完成的 Pod 的日志輸出,以便檢查錯(cuò)誤、警告 或者其它診斷性輸出。 Job 完成時(shí) Job 對(duì)象也一樣被保留下來,這樣你就可以查看它的狀態(tài)。 在查看了 Job 狀態(tài)之后刪除老的 Job 的操作留給了用戶自己。 你可以使用 ?kubectl ?來刪除 Job(例如,?kubectl delete jobs/pi? 或者 ?kubectl delete -f ./job.yaml?)。 當(dāng)使用 ?kubectl ?來刪除 Job 時(shí),該 Job 所創(chuàng)建的 Pods 也會(huì)被刪除。

默認(rèn)情況下,Job 會(huì)持續(xù)運(yùn)行,除非某個(gè) Pod 失敗(?restartPolicy=Never?) 或者某個(gè)容器出錯(cuò)退出(?restartPolicy=OnFailure?)。 這時(shí),Job 基于前述的 ?spec.backoffLimit? 來決定是否以及如何重試。 一旦重試次數(shù)到達(dá) ?.spec.backoffLimit? 所設(shè)的上限,Job 會(huì)被標(biāo)記為失敗, 其中運(yùn)行的 Pods 都會(huì)被終止。

終止 Job 的另一種方式是設(shè)置一個(gè)活躍期限。 你可以為 Job 的 ?.spec.activeDeadlineSeconds? 設(shè)置一個(gè)秒數(shù)值。 該值適用于 Job 的整個(gè)生命期,無論 Job 創(chuàng)建了多少個(gè) Pod。 一旦 Job 運(yùn)行時(shí)間達(dá)到 ?activeDeadlineSeconds ?秒,其所有運(yùn)行中的 Pod 都會(huì)被終止,并且 Job 的狀態(tài)更新為 ?type: Failed? 及 ?reason: DeadlineExceeded?。

注意 Job 的 ?.spec.activeDeadlineSeconds? 優(yōu)先級(jí)高于其 ?.spec.backoffLimit? 設(shè)置。 因此,如果一個(gè) Job 正在重試一個(gè)或多個(gè)失效的 Pod,該 Job 一旦到達(dá) ?activeDeadlineSeconds ?所設(shè)的時(shí)限即不再部署額外的 Pod,即使其重試次數(shù)還未 達(dá)到 ?backoffLimit ?所設(shè)的限制。

例如:

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 規(guī)約和 Job 中的 Pod 模版規(guī)約 都有 ?activeDeadlineSeconds ?字段。 請(qǐng)確保你在合適的層次設(shè)置正確的字段。

還要注意的是,?restartPolicy ?對(duì)應(yīng)的是 Pod,而不是 Job 本身: 一旦 Job 狀態(tài)變?yōu)?nbsp;?type: Failed?,就不會(huì)再發(fā)生 Job 重啟的動(dòng)作。 換言之,由 ?.spec.activeDeadlineSeconds? 和 ?.spec.backoffLimit? 所觸發(fā)的 Job 終結(jié)機(jī)制 都會(huì)導(dǎo)致 Job 永久性的失敗,而這類狀態(tài)都需要手工干預(yù)才能解決。

自動(dòng)清理完成的 Job 

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

已完成 Job 的 TTL 機(jī)制 

FEATURE STATE: Kubernetes v1.23 [stable]

自動(dòng)清理已完成 Job (狀態(tài)為 ?Complete ?或 ?Failed?)的另一種方式是使用由 TTL 控制器所提供 的 TTL 機(jī)制。 通過設(shè)置 Job 的 ?.spec.ttlSecondsAfterFinished? 字段,可以讓該控制器清理掉 已結(jié)束的資源。

TTL 控制器清理 Job 時(shí),會(huì)級(jí)聯(lián)式地刪除 Job 對(duì)象。 換言之,它會(huì)刪除所有依賴的對(duì)象,包括 Pod 及 Job 本身。 注意,當(dāng) Job 被刪除時(shí),系統(tǒng)會(huì)考慮其生命周期保障,例如其 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? 在結(jié)束 100 秒之后,可以成為被自動(dòng)刪除的對(duì)象。

如果該字段設(shè)置為 ?0?,Job 在結(jié)束之后立即成為可被自動(dòng)刪除的對(duì)象。 如果該字段沒有設(shè)置,Job 不會(huì)在結(jié)束之后被 TTL 控制器自動(dòng)清除。

Job 模式 

Job 對(duì)象可以用來支持多個(gè) Pod 的可靠的并發(fā)執(zhí)行。 Job 對(duì)象不是設(shè)計(jì)用來支持相互通信的并行進(jìn)程的,后者一般在科學(xué)計(jì)算中應(yīng)用較多。 Job 的確能夠支持對(duì)一組相互獨(dú)立而又有所關(guān)聯(lián)的 工作條目 的并行處理。 這類工作條目可能是要發(fā)送的電子郵件、要渲染的視頻幀、要編解碼的文件、NoSQL 數(shù)據(jù)庫中要掃描的主鍵范圍等等。

在一個(gè)復(fù)雜系統(tǒng)中,可能存在多個(gè)不同的工作條目集合。這里我們僅考慮用戶希望一起管理的 工作條目集合之一 — 批處理作業(yè)。

并行計(jì)算的模式有好多種,每種都有自己的強(qiáng)項(xiàng)和弱點(diǎn)。這里要權(quán)衡的因素有:

  • 每個(gè)工作條目對(duì)應(yīng)一個(gè) Job 或者所有工作條目對(duì)應(yīng)同一 Job 對(duì)象。 后者更適合處理大量工作條目的場(chǎng)景; 前者會(huì)給用戶帶來一些額外的負(fù)擔(dān),而且需要系統(tǒng)管理大量的 Job 對(duì)象。
  • 創(chuàng)建與工作條目相等的 Pod 或者令每個(gè) Pod 可以處理多個(gè)工作條目。 前者通常不需要對(duì)現(xiàn)有代碼和容器做較大改動(dòng); 后者則更適合工作條目數(shù)量較大的場(chǎng)合,原因同上。
  • 有幾種技術(shù)都會(huì)用到工作隊(duì)列。這意味著需要運(yùn)行一個(gè)隊(duì)列服務(wù),并修改現(xiàn)有程序或容器 使之能夠利用該工作隊(duì)列。 與之比較,其他方案在修改現(xiàn)有容器化應(yīng)用以適應(yīng)需求方面可能更容易一些。

下面是對(duì)這些權(quán)衡的匯總,列 2 到 4 對(duì)應(yīng)上面的權(quán)衡比較。

模式 單個(gè) Job 對(duì)象 Pods 數(shù)少于工作條目數(shù)? 直接使用應(yīng)用無需修改?
每工作條目一 Pod 的隊(duì)列 ? 有時(shí)
Pod 數(shù)量可變的隊(duì)列 ? ?
靜態(tài)任務(wù)分派的帶索引的 Job ? ?
Job 模版擴(kuò)展 ?

當(dāng)你使用 ?.spec.completions? 來設(shè)置完成數(shù)時(shí),Job 控制器所創(chuàng)建的每個(gè) Pod 使用完全相同的 ?spec?。 這意味著任務(wù)的所有 Pod 都有相同的命令行,都使用相同的鏡像和數(shù)據(jù)卷,甚至連 環(huán)境變量都(幾乎)相同。 這些模式是讓每個(gè) Pod 執(zhí)行不同工作的幾種不同形式。

下表顯示的是每種模式下 ?.spec.parallelism? 和 ?.spec.completions? 所需要的設(shè)置。 其中,?W? 表示的是工作條目的個(gè)數(shù)。

模式 .spec.completions .spec.parallelism
每工作條目一 Pod 的隊(duì)列 W 任意值
Pod 個(gè)數(shù)可變的隊(duì)列 1 任意值
靜態(tài)任務(wù)分派的帶索引的 Job W
Job 模版擴(kuò)展 1 應(yīng)該為 1

高級(jí)用法 

掛起 Job 

FEATURE STATE: Kubernetes v1.21 [alpha]

該特性在 Kubernetes 1.21 版本中是 Alpha 階段,啟用該特性需要額外的步驟; 請(qǐng)確保你正在閱讀與集群版本一致的文檔。

Job 被創(chuàng)建時(shí),Job 控制器會(huì)馬上開始執(zhí)行 Pod 創(chuàng)建操作以滿足 Job 的需求, 并持續(xù)執(zhí)行此操作直到 Job 完成為止。 不過你可能想要暫時(shí)掛起 Job 執(zhí)行,或啟動(dòng)處于掛起狀態(tài)的job, 并擁有一個(gè)自定義控制器以后再?zèng)Q定什么時(shí)候開始。

要掛起一個(gè) Job,你可以更新 ?.spec.suspend? 字段為 true, 之后,當(dāng)你希望恢復(fù)其執(zhí)行時(shí),將其更新為 false。 創(chuàng)建一個(gè) ?.spec.suspend? 被設(shè)置為 true 的 Job 本質(zhì)上會(huì)將其創(chuàng)建為被掛起狀態(tài)。

當(dāng) Job 被從掛起狀態(tài)恢復(fù)執(zhí)行時(shí),其 ?.status.startTime? 字段會(huì)被重置為 當(dāng)前的時(shí)間。這意味著 ?.spec.activeDeadlineSeconds? 計(jì)時(shí)器會(huì)在 Job 掛起時(shí) 被停止,并在 Job 恢復(fù)執(zhí)行時(shí)復(fù)位。

要記住的是,掛起 Job 會(huì)刪除其所有活躍的 Pod。當(dāng) Job 被掛起時(shí),你的 Pod 會(huì) 收到 SIGTERM 信號(hào)而被終止。 Pod 的體面終止期限會(huì)被考慮,不過 Pod 自身也必須在此期限之內(nèi)處理完信號(hào)。 處理邏輯可能包括保存進(jìn)度以便將來恢復(fù),或者取消已經(jīng)做出的變更等等。 Pod 以這種形式終止時(shí),不會(huì)被記入 Job 的 ?completions ?計(jì)數(shù)。

處于被掛起狀態(tài)的 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 是否被掛起,或者曾經(jīng)被掛起。

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" 類型的狀況在狀態(tài)值為 "True" 時(shí)意味著 Job 正被 掛起;?lastTransitionTime ?字段可被用來確定 Job 被掛起的時(shí)長(zhǎng)。 如果此狀況字段的取值為 "False",則 Job 之前被掛起且現(xiàn)在在運(yùn)行。 如果 "Suspended" 狀況在 ?status ?字段中不存在,則意味著 Job 從未 被停止執(zhí)行。

當(dāng) Job 被掛起和恢復(fù)執(zhí)行時(shí),也會(huì)生成事件:

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

最后四個(gè)事件,特別是 "Suspended" 和 "Resumed" 事件,都是因?yàn)?nbsp;?.spec.suspend? 字段值被改來改去造成的。在這兩個(gè)事件之間,我們看到?jīng)]有 Pod 被創(chuàng)建,不過當(dāng) Job 被恢復(fù)執(zhí)行時(shí),Pod 創(chuàng)建操作立即被重啟執(zhí)行。

可變調(diào)度指令

FEATURE STATE: Kubernetes v1.23 [beta]

為了使用此功能,你必須在 API 服務(wù)器上啟用 ?JobMutableNodeSchedulingDirectives? 特性門控。 默認(rèn)情況下啟用。

在大多數(shù)情況下,并行作業(yè)會(huì)希望 Pod 在一定約束條件下運(yùn)行, 比如所有的 Pod 都在同一個(gè)區(qū)域,或者所有的 Pod 都在 GPU 型號(hào) x 或 y 上,而不是兩者的混合。

suspend 字段是實(shí)現(xiàn)這些語義的第一步。 suspend 允許自定義隊(duì)列控制器,以決定工作何時(shí)開始;然而,一旦工作被取消暫停, 自定義隊(duì)列控制器對(duì) Job 中 Pods 的實(shí)際放置位置沒有影響。

此特性允許在 Job 開始之前更新調(diào)度指令,從而為定制隊(duì)列提供影響 Pod 放置的能力,同時(shí)將 Pod 與節(jié)點(diǎn)間的分配關(guān)系留給 kube-scheduler 決定。 這一特性僅適用于之前從未被暫停過的、已暫停的 Job。 控制器能夠影響 Pod 放置,同時(shí)參考實(shí)際 pod-to-node 分配給 kube-scheduler。這僅適用于從未暫停的 Jobs。

Job 的 Pod 模板中可以更新的字段是節(jié)點(diǎn)親和性、節(jié)點(diǎn)選擇器、容忍、標(biāo)簽和注解。

指定你自己的 Pod 選擇算符

通常,當(dāng)你創(chuàng)建一個(gè) Job 對(duì)象時(shí),你不會(huì)設(shè)置 ?.spec.selector?。 系統(tǒng)的默認(rèn)值填充邏輯會(huì)在創(chuàng)建 Job 時(shí)添加此字段。 它會(huì)選擇一個(gè)不會(huì)與任何其他 Job 重疊的選擇算符設(shè)置。

不過,有些場(chǎng)合下,你可能需要重載這個(gè)自動(dòng)設(shè)置的選擇算符。 為了實(shí)現(xiàn)這點(diǎn),你可以手動(dòng)設(shè)置 Job 的 ?spec.selector? 字段。

做這個(gè)操作時(shí)請(qǐng)務(wù)必小心。 如果你所設(shè)定的標(biāo)簽選擇算符并不唯一針對(duì) Job 對(duì)應(yīng)的 Pod 集合,甚或該算符還能匹配 其他無關(guān)的 Pod,這些無關(guān)的 Job 的 Pod 可能會(huì)被刪除。 或者當(dāng)前 Job 會(huì)將另外一些 Pod 當(dāng)作是完成自身工作的 Pods, 又或者兩個(gè) Job 之一或者二者同時(shí)都拒絕創(chuàng)建 Pod,無法運(yùn)行至完成狀態(tài)。 如果所設(shè)置的算符不具有唯一性,其他控制器(如 RC 副本控制器)及其所管理的 Pod 集合可能會(huì)變得行為不可預(yù)測(cè)。 Kubernetes 不會(huì)在你設(shè)置 ?.spec.selector? 時(shí)嘗試阻止你犯這類錯(cuò)誤。

下面是一個(gè)示例場(chǎng)景,在這種場(chǎng)景下你可能會(huì)使用剛剛講述的特性。

假定名為 ?old ?的 Job 已經(jīng)處于運(yùn)行狀態(tài)。 你希望已有的 Pod 繼續(xù)運(yùn)行,但你希望 Job 接下來要?jiǎng)?chuàng)建的其他 Pod 使用一個(gè)不同的 Pod 模版,甚至希望 Job 的名字也發(fā)生變化。 你無法更新現(xiàn)有的 Job,因?yàn)檫@些字段都是不可更新的。 因此,你會(huì)刪除 ?old ?Job,但 允許該 Job 的 Pod 集合繼續(xù)運(yùn)行。 這是通過 ?kubectl delete jobs/old --cascade=orphan? 實(shí)現(xiàn)的。 在刪除之前,我們先記下該 Job 所使用的選擇算符。

kubectl get job old -o yaml

輸出類似于:

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

接下來你會(huì)創(chuàng)建名為 ?new ?的新 Job,并顯式地為其設(shè)置相同的選擇算符。 由于現(xiàn)有 Pod 都具有標(biāo)簽 ?controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002?, 它們也會(huì)被名為 ?new ?的 Job 所控制。

你需要在新 Job 中設(shè)置 ?manualSelector: true?,因?yàn)槟悴⑽词褂孟到y(tǒng)通常自動(dòng)為你 生成的選擇算符。

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

新的 Job 自身會(huì)有一個(gè)不同于 ?a8f3d00d-c6d2-11e5-9f87-42010af00002? 的唯一 ID。 設(shè)置 ?manualSelector: true? 是在告訴系統(tǒng)你知道自己在干什么并要求系統(tǒng)允許這種不匹配 的存在。

使用 Finalizer 追蹤 Job 

FEATURE STATE: Kubernetes v1.23 [beta]

要使用該行為,你必須為 API 服務(wù)器 和控制器管理器 啟用 ?JobTrackingWithFinalizers ?特性門控。 默認(rèn)是啟用的。
啟用后,控制面基于下述行為追蹤新的 Job。在啟用該特性之前創(chuàng)建的 Job 不受影響。 作為用戶,你會(huì)看到的唯一區(qū)別是控制面對(duì) Job 完成情況的跟蹤更加準(zhǔn)確。

該功能未啟用時(shí),Job 控制器(Controller) 依靠計(jì)算集群中存在的 Pod 來跟蹤作業(yè)狀態(tài)。 也就是說,維持一個(gè)統(tǒng)計(jì) ?succeeded ?和 ?failed ?的 Pod 的計(jì)數(shù)器。 然而,Pod 可以因?yàn)橐恍┰虮灰瞥?,包括?

  • 當(dāng)一個(gè)節(jié)點(diǎn)宕機(jī)時(shí),垃圾收集器會(huì)刪除孤立(Orphan)Pod。
  • 垃圾收集器在某個(gè)閾值后刪除已完成的 Pod(處于 ?Succeeded ?或 ?Failed ?階段)。
  • 人工干預(yù)刪除 Job 的 Pod。
  • 一個(gè)外部控制器(不包含于 Kubernetes)來刪除或取代 Pod。

如果你為你的集群?jiǎn)⒂昧?nbsp;??JobTrackingWithFinalizers ??特性,控制面會(huì)跟蹤屬于任何 Job 的 Pod。 并注意是否有任何這樣的 Pod 被從 API 服務(wù)器上刪除。 為了實(shí)現(xiàn)這一點(diǎn),Job 控制器創(chuàng)建的 Pod 帶有 ?Finalizer ?batch.kubernetes.io/job-tracking??。 控制器只有在 Pod 被記入 Job 狀態(tài)后才會(huì)移除 Finalizer,允許 Pod 可以被其他控制器或用戶刪除。

Job 控制器只對(duì)新的 Job 使用新的算法。在啟用該特性之前創(chuàng)建的 Job 不受影響。 你可以根據(jù)檢查 Job 是否含有 ?batch.kubernetes.io/job-tracking? 注解,來確定 Job 控制器是否正在使用 Pod Finalizer 追蹤 Job。 你不應(yīng)該給 Job 手動(dòng)添加或刪除該注解。

替代方案 

裸 Pod 

當(dāng) Pod 運(yùn)行所在的節(jié)點(diǎn)重啟或者失敗,Pod 會(huì)被終止并且不會(huì)被重啟。 Job 會(huì)重新創(chuàng)建新的 Pod 來替代已終止的 Pod。 因?yàn)檫@個(gè)原因,我們建議你使用 Job 而不是獨(dú)立的裸 Pod, 即使你的應(yīng)用僅需要一個(gè) Pod。

副本控制器 

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

正如在 Pod 生命期 中討論的, ?Job ?僅適合于 ?restartPolicy ?設(shè)置為 ?OnFailure ?或 ?Never ?的 Pod。 注意:如果 ?restartPolicy ?未設(shè)置,其默認(rèn)值是 ?Always?。

單個(gè) Job 啟動(dòng)控制器 Pod

另一種模式是用唯一的 Job 來創(chuàng)建 Pod,而該 Pod 負(fù)責(zé)啟動(dòng)其他 Pod,因此扮演了一種 后啟動(dòng) Pod 的控制器的角色。 這種模式的靈活性更高,但是有時(shí)候可能會(huì)把事情搞得很復(fù)雜,很難入門, 并且與 Kubernetes 的集成度很低。

這種模式的實(shí)例之一是用 Job 來啟動(dòng)一個(gè)運(yùn)行腳本的 Pod,腳本負(fù)責(zé)啟動(dòng) Spark 主控制器, 運(yùn)行 Spark 驅(qū)動(dòng),之后完成清理工作。

這種方法的優(yōu)點(diǎn)之一是整個(gè)過程得到了 Job 對(duì)象的完成保障, 同時(shí)維持了對(duì)創(chuàng)建哪些 Pod、如何向其分派工作的完全控制能力。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)