job是什麼
對於ReplicaSet、ReplicationController等型別的控制器而言,它希望pod保持預期數目、持久執行下去,除非使用者明確刪除,否則這些物件一直存在,它們針對的是耐久性任務,如web服務等。對於非耐久性任務,比如壓縮檔案,任務完成後,pod需要結束執行,不需要pod繼續保持在系統中,這個時候就要用到Job。因此說Job是對ReplicaSet、ReplicationController等永續性控制器的補充。
Job與其它控制器的細微差別
Job定義方法與ReplicaSet等控制器相似,只有細微差別,如下:
Job中的restart policy必需是"Never"或者"OnFailure",這個很好理解,因為pod要執行到結束,而不是反覆重新啟動。
Job不需要選擇器,其中的pod也不需要標籤,系統在建立Job時會自動新增相關內容。當然使用者也可以出於資源組織的目的新增標籤,但這個與Job本身的實現沒有關係。
Job新增加兩個欄位:.spec.completions、.spec.parallelism。詳細用法在示例中說明
backoffLimit欄位:示例中說明
示例
非併發Job
非併發Job的含義是,Job啟動後,只執行一個pod,pod執行結束後整個Job也就立刻結束。
以下是簡單的Job配置檔案,只包含一個pod,輸出圓周率小數點後2000位,執行時間大概為10s:
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
以上示例無需設定選擇器、pod標籤。無需設定.spec.completions、.spec.parallelism,這兩個欄位的預設值都是1。backoffLimit=4,表示允許pod失敗的次數。將以上內容儲存成檔案並建立Job:
$ kubectl create -f https://k8s.io/examples/controllers/job.yaml
job "pi" created
確認Job狀態:
$ kubectl describe jobs/pi
Name: pi
Namespace: default
Selector: controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
Labels: controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
job-name=pi
Annotations: <none>
Parallelism: 1
Completions: 1
Start Time: Tue, 07 Jun 2016 10:56:16 +0200
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
job-name=pi
Containers:
pi:
Image: perl
Port:
Command:
perl
-Mbignum=bpi
-wle
print bpi(2000)
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: pi-dtn4q
從以上輸出可以看到系統自動新增的Selector、Pod labels。注意Events的輸出,全程只建立了一個pod。
列出Job的所有pod:
$ pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath={.items..metadata.name})
$ echo $pods
pi-aiw0a
檢視Pod的輸出
$ kubectl logs $pods
3.14159265358979323846264......
以上是最簡單的Job應用示例,輸出圓周率小數點後2000位。但是,考慮另外一種情況,假如我們需要計算圓周率小數點後3000、4000、5000位怎麼辦?一種方案是先將上例中的2000改成3000,建立並執行。然後再改成4000,再建立並執行,一直到5000。顯然這種方案並不高明:麻煩、資源利用率低。另外一種方法是同時建立4個Job,分別計算2000、3000、4000、5000。注意4個Job的name欄位不能衝突分別是pi-2000、pi-3000、pi-4000、pi-5000,
檔名分別為pi-2000.yaml、pi-3000.yaml、pi-4000.yaml、pi-5000.yaml,並儲存在目錄/tmp/pi下。利用kubectl對目錄的支援一次性建立4個Job:
$ kubectl create -f /tmp/pi
job "pi-2000" created
job "pi-3000" created
job "pi-4000" created
job "pi-5000" created
以上方法是偽併發,只適用於任務量少的情況。假如我們需要處理的任務是從pi-1到pi-10000,那麼以上方法就不適用了:
需要同時建立太多的Job與pod,不好管理。
資源配額限制。
粗併發Job
本例建立一個Job,但Job要建立多個pod。瞭解完示例後就明白為什麼叫“粗併發”。
本示例需要一個訊息佇列服務的配合,不詳細描述如何部署、填充訊息佇列服務。假設我們有一個RabbitMQ服務,叢集內訪問地址為:amqp://guest:guest@rabbitmq-service:5672。其有一個名為job1的佇列,佇列內有apple banana cherry date fig grape lemon melon共8個成員。
另外假設我們有一個名為gcr.io/
apiVersion: batch/v1
kind: Job
metadata:
name: job-wq-1
spec:
completions: 8
parallelism: 2
template:
metadata:
name: job-wq-1
spec:
containers:
- name: c
image: gcr.io/<project>/job-wq-1
env:
- name: BROKER_URL
value: amqp://guest:guest@rabbitmq-service:5672
- name: QUEUE
value: job1
restartPolicy: OnFailure
上例中,completions的值為8,等於job1佇列中元素的個數。因為每個成功的pod處理一個元素,所以需要成功8次,job1中的所有成員就會被處理完成。在粗併發模式下,completions的值必需指定,否則其預設值為1,整個Job只處理一個成員就結束了。
上例中,parallelism的值是2。雖然需要pod成功8次,但在同一時間,只允許有兩個pod併發。一個成功結束後,再啟動另一個。這個引數的主要目的是控制併發pod的個數,可根據實際情況調整。當然可以不指定,那麼預設的併發個數就是1。
env中的內容告訴image如何訪問佇列。
將以上內容儲存在job.yaml檔案中,執行Job:
kubectl create -f ./job.yaml
稍等片刻Job執行完成,檢視結果:
$ kubectl describe jobs/job-wq-1
Name: job-wq-1
Namespace: default
Selector: controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
Labels: controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
job-name=job-wq-1
Annotations: <none>
Parallelism: 2
Completions: 8
Start Time: Wed, 06 Sep 2017 16:42:02 +0800
Pods Statuses: 0 Running / 8 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
job-name=job-wq-1
Containers:
c:
Image: gcr.io/causal-jigsaw-637/job-wq-1
Port:
Environment:
BROKER_URL: amqp://guest:guest@rabbitmq-service:5672
QUEUE: job1
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
───────── ──────── ───── ──── ───────────── ────── ────── ───────
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-hcobb
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-weytj
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-qaam5
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-b67sr
26s 26s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-xe5hj
15s 15s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-w2zqe
14s 14s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-d6ppa
14s 14s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-p17e0
檢視Events,可以看到總共建立了8個pod。在本例中,每處理佇列中的一個成員都需要建立一個pod,開銷很大。如果佇列中的成員個數非常龐大,那麼這種處理方式就不適用。我們希望少建立pod、每個pod能處理多條記錄,請看下面示例
細併發Job
redis:6379> lrange job2 0 -1
1) "apple"
2) "banana"
3) "cherry"
4) "date"
5) "fig"
6) "grape"
7) "lemon"
8) "melon"
9) "orange"
接下來建立image,詳細過程不描述。只需確定這個image執行如下名為worker.py的python程式
#!/usr/bin/env python
import time
import rediswq
host="redis"
# Uncomment next two lines if you do not have Kube-DNS working.
# import os
# host = os.getenv("REDIS_SERVICE_HOST")
q = rediswq.RedisWQ(name="job2", host="redis")
print("Worker with sessionID: " + q.sessionID())
print("Initial queue state: empty=" + str(q.empty()))
while not q.empty():
item = q.lease(lease_secs=10, block=True, timeout=2)
if item is not None:
itemstr = item.decode("utf=8")
print("Working on " + itemstr)
time.sleep(10) # Put your actual work here instead of sleep.
q.complete(item)
else:
print("Waiting for work")
print("Queue empty, exiting")
首先連線到redis的job2佇列。然後是一個while迴圈,每次讀job2中的一條記錄並輸出,然後sleep 10s。迴圈退出的條件是job2佇列為空。這個image與示例2不同,示例2只處理一條記錄這就結束,而這個可以處理多條一直到佇列為空。
接下來定義Job:
apiVersion: batch/v1
kind: Job
metadata:
name: job-wq-2
spec:
parallelism: 2
template:
metadata:
name: job-wq-2
spec:
containers:
- name: c
image: gcr.io/myproject/job-wq-2
restartPolicy: OnFailure
上例中,無需像示例2一樣指定 completions的值,因為結束條件是job2為空,已經內嵌在image的邏輯中。parallelism=2表示可以併發兩個pod,不設定預設為1,在實際應用中可據實際情況自行調整。
執行Job:
kubectl create -f ./job.yaml
過一會檢視Job執行狀況:
$ kubectl describe jobs/job-wq-2
Name: job-wq-2
Namespace: default
Selector: controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
Labels: controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
job-name=job-wq-2
Annotations: <none>
Parallelism: 2
Completions: <unset>
Start Time: Mon, 11 Jan 2016 17:07:59 -0800
Pods Statuses: 1 Running / 0 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
job-name=job-wq-2
Containers:
c:
Image: gcr.io/exampleproject/job-wq-2
Port:
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
33s 33s 1 {job-controller } Normal SuccessfulCreate Created pod: job-wq-2-lglf8
雖然允許的最大併發數是2,但Events顯示只建立成功一個pod,這個是正常情況,最大並非必需,可能系統因為資源問題達不到最大。
檢視pod輸出:
$ kubectl logs pods/job-wq-2-7r7b2
Worker with sessionID: bbd72d0a-9e5c-4dd6-abf6-416cc267991f
Initial queue state: empty=False
Working on banana
Working on date
Working on lemon
細併發相比與粗併發,減少了建立pod的開銷,使每個pod能處理多條記錄,但是pod要自己決定退出條件,如果不退出,那麼Job永遠無法結束。
關於資源回收
Job建立的pod在結束執行後,無論是成功還是失敗,不會預設刪除,仍然保留在系統中,這樣使用者才可以檢視其日誌、狀態資訊、排除錯誤。使用者需要手動執行kubectl delete刪除所有執行結束的pod,為了方便組織資源,一次性刪除會部pod,可以被pod自定義標籤。Job在執行完成後也仍然保留在系統中,由使用者刪除。所以使用Job,使用者應注意資源回收,避免資源被耗盡。