動手動腦學Kubernetes系列教程之Job介紹

在之前的文章中, 介紹了搭建好的#minikube#環境,如果你現在還沒有一個可用的minikube環境, 那麼可以去該篇文章中直接下載;

在之前的文章中, 先後介紹瞭如何從原始碼開始構建一個Node。js應用和Spring Boot 應用, 並且部署到Kubernetes 中(這個Kubernetes 環境主要是之前建好的#minikube#) , 現在我們開始進一步深入的學習Kubernetes, 用一個個可以實際執行的例子的形式, 深入理解#Kubernetes#的概念以及原理。

在#動手動腦學Kubernetes#系列教程中, 我們展示了Kubernetes的基本用法

在第一篇裡, 學習了#Pod#的基本知識;

在第二篇裡, 學習了標籤(#Label#)的語法, 使用Label來選擇和過濾Kubernetes 資源;

在第三篇裡, 介紹了#Deployment#的使用, 介紹了Deployment與Replica Set、Pod的關係, 並展示瞭如何進行應用的版本回滾;

在第四篇裡, 介紹了#Service#的使用,使用Replication Controller建立Pod, 並建立Service, 展示了從Service 呼叫應用的方法; 隨後又展示了擴充套件 Pod的數量為2, 比較了Service和之前的不同, 基本展示了Cluster IP 型別的Service的基本用法。

在第五篇裡, 介紹了#Namespace#的使用, 除了建立,列出系統的Namespace之外, 還說明Namespace 如何對資源進行隔離, 建立Development, Staging, Production等環境的方法。

在第六篇裡, 介紹了#Service Discovery#的使用, 講解了如何檢查Kube-dns, 如何檢查和使用Service的FQDN等知識, 對Kubernetes的DNS 系統有整體的理解。

在第七篇裡, 介紹了#Port Forwards#, #埠轉發#的用法, 在本地程式開發的時候, 使用埠轉發可以簡化本地測試的工作, 同時介紹了其他幾種本地埠轉發的用法。

在第八篇裡, 介紹了#Probe#(#探針#)的知識, 介紹了livenessProbe 和readinessProbe的用法,同時介紹了Pod 容器中的幾個狀態變化, 以及2個容器生命週期回撥介面。

在第九篇裡, 介紹了#環境變數#的用法, 使用環境變數可以把Pod 定義的資訊傳遞給執行其中的映象。

在第十篇裡, 我們介紹了#Volume#, 卷的用法, 主要展示了emptyDir卷的使用, emptyDir卷的生命週期是和Pod 生命週期同步的。

在第十一篇裡, 我們介紹了#Persistent Volume#, 也就是持久卷的用法,展示了當資料存入到PV 之後,資料超乎Pod 生命週期之外的情況。

在第十二篇裡, 介紹了Secret, 也就是機密資訊的用法, 機密是繫結在命名空間裡的, 在使用時候和Volume的用法一樣, 可以被Pod 訪問, 本篇展示了Opaque型別的Secret的用法。

在第十三篇裡, 介紹了日誌(logging)的使用, 介紹了Kubernetes中基本日誌記錄的檢視和常用的命令列引數,在理論部分展示了其他幾種logging的使用。

在本篇裡, 我們將學習Kubernetes Job的使用, 繼續學習吧!

動手動腦學Kubernetes系列教程之Job介紹

在應用程式開發中, 批處理作業(batch processing) 是必不可少的, 批處理作業一般是定時的, 執行一段時間後結束的作業,這些運營作業包括:

無需使用者互動即可最有效地處理大量資訊的自動化,複雜處理。 這些操作通常包括基於時間的事件(例如月末計算,通知或通訊)。

在非常大的資料集中重複處理複雜業務規則的定期應用(例如,保險利益確定或費率調整)。

整合從內部和外部系統接收的資訊,這些資訊通常需要以事務方式格式化,驗證和處理到記錄系統中。 批處理用於每天為企業處理數十億的交易。

這種作業和我們之前見到的一些Web應用,在生命週期方面並不相同, Web應用一般是始終處於等待使用者請求和處理使用者請求的過程中, 而批處理作業則多為定時任務, 任務完成之後自動銷燬, 然後等待下次排程。 常用的框架有Spring Batch, Quartz等。

而Kubernetes也提供了對批處理作業的支援, 專門提供了Job 資源物件;在Kubernetes中, 和Service/Deployment 一樣, 具體的執行是由Pod 來完成的, 而Job則負責對Pod的監管(

supervisor

), 由Pod來執行批處理, 執行一定時間才能完成的過程,例如計算或備份操作。

先來看一個具體的Job吧。

從映象開始

我們來建立一個自動計數的Pod, 這個Pod 會從9開始遞減, 減到1之後停止,

Github 地址: https://raw。githubusercontent。com/hintcnuie/kbe/main/specs/jobs/job。yaml

檔案內容:

apiVersion: batch/v1kind: Jobmetadata: name: countdownspec: template: metadata: name: countdown spec: containers: - name: counter image: centos:7 command: - “bin/bash” - “-c” - “for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done” restartPolicy: Never

從上面的檔案中可以看出, Job的定義裡面,

apiVersion

和我們之前的不一樣, 使用的是

batch/v1

, 類別是

Job

, 剩下的Container部分內容很簡單, 就是包含了一個

countdown

的容器, 在其中進行一個for 迴圈,然後退出。

執行Job

來建立這個Pod:

$ kubectl apply -f https://raw。githubusercontent。com/hintcnuie/kbe/main/specs/jobs/job。yamljob。batch/countdown created

可以檢查下Job 的狀態:

$ kubectl get jobsNAME        COMPLETIONS   DURATION   AGEcountdown   1/1           3s         111s

可以看出Job 執行了3秒鐘後結束, 現在的狀態是COMPLETE。

來看看對應的Pod的情況:

$ kubectl get pods NAME                                    READY   STATUS      RESTARTS   AGEcountdown-mfsb7                         0/1     Completed   0          4m5s$ kubectl logs countdown-mfsb7987654321

可以看到, Pod 的狀態也已經結束, 而且從上面的log 中看出, Pod 中的容器也打印出了迴圈中的數值。

來仔細看看這個Job 的情況:

$ kubectl describe jobs/countdownName:           countdownNamespace:      defaultSelector:       controller-uid=1bc5db99-232d-433e-a2cf-7d15cfdafd49Labels:         controller-uid=1bc5db99-232d-433e-a2cf-7d15cfdafd49                job-name=countdownAnnotations:    Parallelism:    1Completions:    1Start Time:     Fri, 19 Mar 2021 14:14:47 +0000Completed At:   Fri, 19 Mar 2021 14:14:50 +0000Duration:       3sPods Statuses:  0 Running / 1 Succeeded / 0 FailedPod Template:  Labels:  controller-uid=1bc5db99-232d-433e-a2cf-7d15cfdafd49           job-name=countdown  Containers:   counter:    Image:      centos:7    Port:           Host Port:      Command:      bin/bash      -c      for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done    Environment:      Mounts:         Volumes:        Events:  Type    Reason            Age    From            Message  ——    ————            ——   ——            ————-  Normal  SuccessfulCreate  7m47s  job-controller  Created pod: countdown-mfsb7  Normal  Completed         7m44s  job-controller  Job completed

從上面的狀態中我們可以更加詳細地看出, Job 已經執行完畢, 而且相應的Pod狀態也會變成結束。

這個Job 非常簡單, 執行的時間也比較短, 我們再來看一個稍微長一點的Job:

Github地址:https://raw。githubusercontent。com/hintcnuie/kbe/main/specs/jobs/pi-job。yaml

檔案內容:

apiVersion: batch/v1kind: Jobmetadata: name: pispec: template: spec: containers: - name: pi image: perl command: [“perl”, “-Mbignum=bpi”, “-wle”, “print bpi(2000)”] restartPolicy: Never backoffLimit: 4

來執行一下:

$ kubectl apply -f https://raw。githubusercontent。com/hintcnuie/kbe/main/specs/jobs/pi-job。yamljob。batch/pi created$ kubectl get jobs piNAME COMPLETIONS DURATION AGEpi 0/1 3m46s 3m46s

看一下這個Job 的情況:

$ kubectl describe job piName:           piNamespace:      defaultSelector:       controller-uid=70eb8e44-3579-4517-8a74-3db6983f77d6Labels:         controller-uid=70eb8e44-3579-4517-8a74-3db6983f77d6                job-name=piAnnotations:    Parallelism:    1Completions:    1Start Time:     Fri, 19 Mar 2021 14:55:50 +0000Pods Statuses:  1 Running / 0 Succeeded / 0 FailedPod Template:  Labels:  controller-uid=70eb8e44-3579-4517-8a74-3db6983f77d6           job-name=pi  Containers:   pi:    Image:      perl    Port:           Host Port:      Command:      perl      -Mbignum=bpi      -wle      print bpi(2000)    Environment:      Mounts:         Volumes:        Events:  Type    Reason            Age   From            Message  ——    ————            ——  ——            ————-  Normal  SuccessfulCreate  4m    job-controller  Created pod: pi-h54hw

可以看到, 這個Job 已經建立, 而且在寫稿之時, 其中Pod的容器還處於執行狀態, 檢視一下情況:

$ kubectl get pods pi-h54hwNAME       READY   STATUS      RESTARTS   AGEpi-h54hw   0/1     Completed   0          14m

看一下這個容器中的Log:

動手動腦學Kubernetes系列教程之Job介紹

log 裡面完整地打印出了π的前2000位的數值。

下面我們來學習一下Job相關的理論知識。

Job 模式

Job 物件可以用來支援多個 Pod 可靠的併發執行。 Job 物件不是設計用來支援相互通訊的並行程序的,後者一般在科學計算中應用較多。 Job 的確能夠支援對一組相互獨立而又有所關聯的

工作條目

的並行處理。 這類工作條目可能是要傳送的電子郵件、要渲染的影片幀、要編解碼的檔案、NoSQL 資料庫中要掃描的主鍵範圍等等。

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

批處理作業

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

每個工作條目對應一個 Job 或者所有工作條目對應同一 Job 物件。 後者更適合處理大量工作條目的場景; 前者會給使用者帶來一些額外的負擔,而且需要系統管理大量的 Job 物件。

建立與工作條目相等的 Pod 或者令每個 Pod 可以處理多個工作條目。 前者通常不需要對現有程式碼和容器做較大改動; 後者則更適合工作條目數量較大的場合,原因同上。

有幾種技術都會用到工作佇列。這意味著需要執行一個佇列服務,並修改現有程式或容器 使之能夠利用該工作佇列。 與之比較,其他方案在修改現有容器化應用以適應需求方面可能更容易一些。

Job 與Pod的關係

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

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

處理 Pod 和容器失效

Pod 中的容器可能因為多種不同原因失效,例如因為其中的程序退出時返回值非零, 或者容器因為超出記憶體約束而被殺死等等。 如果發生這類事件,並且

。spec。template。spec。restartPolicy = “OnFailure”

, Pod 則繼續留在當前節點,但容器會被重新執行。 因此,你的程式需要能夠處理在本地被重啟的情況,或者要設定 。spec。template。spec。restartPolicy = “Never”。

整個 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 的容器 會在 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/v1kind: Jobmetadata: name: pi-with-timeoutspec: 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 的知識我們暫時介紹到這裡, 今天講解的Job的例子, 是非並行性的一次性執行的作業, 在以後的篇幅裡面,我們會介紹定時Job(CronJob), 繼續學習吧!

動手動腦學Kubernetes系列教程之Job介紹

動手動腦學Kubernetes系列教程之Job介紹

動手動腦學Kubernetes系列教程之Job介紹

學而時習之,不亦說乎