首先咱們來看一下 Job 的需求來源。咱們知道 K8s 裏面,最小的調度單元是 Pod,咱們能夠直接經過 Pod 來運行任務進程。這樣作將會產生如下幾種問題:node
咱們來看一下 Kubernetes 的 Job 爲咱們提供了什麼功能:api
咱們根據一個實例來看一下Job是如何來完成下面的應用的。微信
上圖是 Job 最簡單的一個 yaml 格式,這裏主要新引入了一個 kind 叫 Job,這個 Job 其實就是 job-controller 裏面的一種類型。 而後 metadata 裏面的 name 來指定這個 Job 的名稱,下面 spec.template 裏面其實就是 pod 的 spec。架構
這裏面的內容都是同樣的,惟一多了兩個點:app
因此在 Job 裏面,咱們主要重點關注的一個是 restartPolicy 重啓策略和 backoffLimit 重試次數限制。less
Job 建立完成以後,咱們就能夠經過 kubectl get jobs 這個命令,來查看當前 job 的運行狀態。獲得的值裏面,基本就有 Job 的名稱、當前完成了多少個 Pod,進行多長時間。dom
**AGE **的含義是指這個 Pod 從當前時間算起,減去它當時建立的時間。這個時長主要用來告訴你 Pod 的歷史、Pod 距今建立了多長時間。**DURATION **主要來看咱們 Job 裏面的實際業務到底運行了多長時間,當咱們的性能調優的時候,這個參數會很是的有用。**COMPLETIONS **主要來看咱們任務裏面這個 Pod 一共有幾個,而後它其中完成了多少個狀態,會在這個字段裏面作顯示。 微服務
<br />下面咱們來看一下 Pod,其實 Job 最後的執行單元仍是 Pod。咱們剛纔建立的 Job 會建立出來一個叫「pi」的一個 Pod,這個任務就是來計算這個圓周率,Pod 的名稱會以「${job-name}-${random-suffix}」,咱們能夠看一下下面 Pod 的 yaml 格式。性能
它比普通的 Pod 多了一個叫 ownerReferences,這個東西來聲明此 pod 是歸哪一個上一層 controller 來管理。能夠看到這裏的 ownerReferences 是歸 batch/v1,也就是上一個 Job 來管理的。這裏就聲明瞭它的 controller 是誰,而後能夠經過 pod 返查到它的控制器是誰,同時也能根據 Job 來查一下它下屬有哪些 Pod。架構設計
咱們有時候有些需求:但願 Job 運行的時候能夠最大化的並行,並行出 n 個 Pod 去快速地執行。同時,因爲咱們的節點數有限制,可能也不但願同時並行的 Pod 數過多,有那麼一個管道的概念,咱們能夠但願最大的並行度是多少,Job 控制器均可以幫咱們來作到。
這裏主要看兩個參數:一個是 completions,一個是 parallelism。
下面來看一下它的實際運行效果,上圖就是當這個 Job 總體運行完畢以後能夠看到的效果,首先看到 job 的名字,而後看到它一共建立出來了 8 個 pod,執行了 2 分 23 秒,這是建立的時間。
接着來看真正的 pods,pods 總共出來了 8 個 pod,每一個 pod 的狀態都是完成的,而後來看一下它的 AGE,就是時間。從下往上看,能夠看到分別有 73s、40s、110s 和 2m26s。每一組都有兩個 pod 時間是相同的,即:時間段是 40s 的時候是最後一個建立、 2m26s 是第一個建立的。也就是說,老是兩個 pod 同時建立出來,並行完畢、消失,而後再建立、再運行、再完畢。
好比說,剛剛咱們其實經過第二個參數來控制了當前 Job 並行執行的次數,這裏就能夠了解到這個緩衝器或者說管道隊列大小的做用。
下面來介紹另一個 Job,叫作 CronJob,其實也能夠叫定時運行 Job。CronJob 其實和 Job 大致是類似的,惟一的不一樣點就是它能夠設計一個時間。好比說能夠定時在幾點幾分執行,特別適合晚上作一些清理任務,還有能夠幾分鐘執行一次,幾小時執行一次等等,這就叫定時任務。
定時任務和 Job 相比會多幾個不一樣的字段:
schedule:schedule 這個字段主要是設置時間格式,它的時間格式和 Linux 的 crontime 是同樣的,因此直接根據 Linux 的 crontime 書寫格式來書寫就能夠了。舉個例子: */1 指每分鐘去執行一下 Job,這個 Job 須要作的事情就是打印出大約時間,而後打印出「Hello from the kubernetes cluster」 這一句話;
**startingDeadlineSeconds:**即:每次運行 Job 的時候,它最長能夠等多長時間,有時這個 Job 可能運行很長時間也不會啓動。因此這時,若是超過較長時間的話,CronJob 就會中止這個 Job;
concurrencyPolicy:就是說是否容許並行運行。所謂的並行運行就是,好比說我每分鐘執行一次,可是這個 Job 可能運行的時間特別長,假如兩分鐘才能運行成功,也就是第二個 Job 要到時間須要去運行的時候,上一個 Job 還沒完成。若是這個 policy 設置爲 true 的話,那麼無論你前面的 Job 是否運行完成,每分鐘都會去執行;若是是 false,它就會等上一個 Job 運行完成以後纔會運行下一個;
**JobsHistoryLimit:**這個就是每一次 CronJob 運行完以後,它都會遺留上一個 Job 的運行歷史、查看時間。固然這個額不能是無限的,因此須要設置一下歷史存留數,通常能夠設置默認 10 個或 100 個均可以,這主要取決於每一個人集羣不一樣,而後根據每一個人的集羣數來肯定這個時間。
下面看一下具體如何使用 Job。
首先看一下 job.yaml。這是一個很是簡單的計算 pi 的一個任務。使用 kubectl creat-f job.yaml,這樣 job 就能提交成功了。來看一下 kubectl.get.jobs,能夠看到這個 job 正在運行;get pods 能夠看到這個 pod 應該是運行完成了,那麼接下來 logs 一下這個 job 以及 pod。能夠看到下圖裏面打印出來了圓周率。
下面再來看第二個例子:
這個例子就是指剛纔的並行運行 Job 建立以後,能夠看到有第二個並行的 Job。
如今已經有兩個 Pod 正在 running,能夠看到它大概執行了快到 30s。
30s 以後它應該會起第二個。
第一批的 pod 已經執行完畢,第二批的 pod 正在 running,每批次分別是兩個Pod。也就是說後面每隔 40s 左右,就會有兩個 pod 在並行執行,它一共會執行 4 批,共 8 個 pod,等到全部的 pod 執行完畢,就是剛纔所說的並行執行的緩衝隊列功能。
過一段時間再看這個 pods,能夠發現第二批已經執行結束,接下來開始建立第三批······
下面來看第三個例子 —— CronJob。 CronJob 是每分鐘執行一次,每次一個 job。
以下圖 CronJob 已經建立了,能夠經過 get cronjob 來看到當前有一個 CronJob,這個時候再來看 jobs,因爲它是每分鐘執行一次,因此得稍微等一下。
同時能夠看到,上一個 job 還在運行,它的時間是 2m12s 左右,它的完成度是 7/八、6/8,剛剛看到 7/8 到 8/8,也就是說咱們上一個任務執行了最後一步,並且每次都是兩個兩個地去運行。每次兩個運行的 job 都會讓咱們在運行一些大型工做流或者工做任務的時候感到特別的方便。
上圖中能夠看到忽然出現了一個 job,「hello-xxxx」這個 job 就是剛纔所說的 CronJob。它距離剛纔 CronJob 提交已通過去 1 分鐘了,這樣就會自動建立出來一個 job,若是不去幹擾它的話,它之後大概每一分鐘都會建立出來這麼一個 job,除非等咱們何時指定它不能夠再運行的時候它纔會中止建立。
在這裏 CronJob 其實主要是用來運做一些清理任務或者說執行一些定時任務。好比說 Jenkins 構建等方面的一些任務,會特別有效。
咱們來看一下 job 的架構設計。Job Controller 其實仍是主要去建立相對應的 pod,而後 Job Controller 會去跟蹤 Job 的狀態,及時地根據咱們提交的一些配置重試或者繼續建立。同時咱們剛剛也提到,每一個 pod 會有它對應的 label,來跟蹤它所屬的 Job Controller,而且還去配置並行的建立, 並行或者串行地去建立 pod。
上圖是一個 Job 控制器的主要流程。全部的 job 都是一個 controller,它會 watch 這個 API Server,咱們每次提交一個 Job 的 yaml 都會通過 api-server 傳到 ETCD 裏面去,而後 Job Controller 會註冊幾個 Handler,每當有添加、更新、刪除等操做的時候,它會經過一個內存級的消息隊列,發到 controller 裏面。
經過 Job Controller 檢查當前是否有運行的 pod,若是沒有的話,經過 Scale up 把這個 pod 建立出來;若是有的話,或者若是大於這個數,對它進行 Scale down,若是這時 pod 發生了變化,須要及時 Update 它的狀態。
同時要去檢查它是不是並行的 job,或者是串行的 job,根據設置的配置並行度、串行度,及時地把 pod 的數量給建立出來。最後,它會把 job 的整個的狀態更新到 API Server 裏面去,這樣咱們就能看到呈現出來的最終效果了。
下面介紹第二個控制器:**DaemonSet。**一樣的問題:若是咱們沒有 DaemonSet 會怎麼樣?下面有幾個需求:
DaemonSet 也是 Kubernetes 提供的一個 default controller,它實際是作一個守護進程的控制器,它能幫咱們作到如下幾件事情:
下面舉個例子來看一下,DaemonSet.yaml 會稍微長一些。
首先是 kind:DaemonSet。若是以前學過 deployment,其實咱們再看這個 yaml 會比較簡單。例如它會有 matchLabel,經過 matchLabel 去管理對應所屬的 pod,這個 pod.label 也要和這個 DaemonSet.controller.label 想匹配,它才能去根據 label.selector 去找到對應的管理 Pod。下面 spec.container 裏面的東西都是一致的。<br /> <br />這裏用 fluentd 來作例子。DaemonSet 最經常使用的點在於如下幾點內容:
首先是存儲,GlusterFS 或者 Ceph 之類的東西,須要每臺節點上都運行一個相似於 Agent 的東西,DaemonSet 就能很好地知足這個訴求;
另外,對於日誌收集,好比說 logstash 或者 fluentd,這些都是一樣的需求,須要每臺節點都運行一個 Agent,這樣的話,咱們能夠很容易蒐集到它的狀態,把各個節點裏面的信息及時地彙報到上面;
還有一個就是,須要每一個節點去運行一些監控的事情,也須要每一個節點去運行一樣的事情,好比說 Promethues 這些東西,也須要 DaemonSet 的支持。
建立完 DaemonSet 以後,咱們可使用 kubectl get DaemonSet(DaemonSet 縮寫爲 ds)。能夠看到 DaemonSet 返回值和 deployment 特別像,即它當前一共有正在運行的幾個,而後咱們須要幾個,READY 了幾個。固然這裏面,READY 都是隻有 Pod,因此它最後建立出來全部的都是 pod。
這裏有幾個參數,分別是:須要的 pod 個數、當前已經建立的 pod 個數、就緒的個數,以及全部可用的、經過健康檢查的 pod;還有 NODE SELECTOR,由於 NODE SELECTOR 在 DaemonSet 裏面很是有用。有時候咱們可能但願只有部分節點去運行這個 pod 而不是全部的節點,因此有些節點上被打了標的話,DaemonSet 就只運行在這些節點上。好比,我只但願 master 節點運行某些 pod,或者只但願 Worker 節點運行某些 pod,就可使用這個 NODE SELECTOR。
其實 DaemonSet 和 deployment 特別像,它也有兩種更新策略:一個是 RollingUpdate,另外一個是 OnDelete。
RollingUpdate 其實比較好理解,就是會一個一個的更新。先更新第一個 pod,而後老的 pod 被移除,經過健康檢查以後再去見第二個 pod,這樣對於業務上來講會比較平滑地升級,不會中斷;
OnDelete 其實也是一個很好的更新策略,就是模板更新以後,pod 不會有任何變化,須要咱們手動控制。咱們去刪除某一個節點對應的 pod,它就會重建,不刪除的話它就不會重建,這樣的話對於一些咱們須要手動控制的特殊需求也會有特別好的做用。
下面舉一個例子。好比說咱們去改了些 DaemonSet 的鏡像,而後看到了它的狀態,它就會去一個一個地更新。
上圖這個就是剛纔 DaemonSet 的 yaml,會比剛纔會多一些, 咱們作一些資源的限制,這個都不影響。
下面咱們建立一下 DaemonSet ,而後再看一下它的狀態。下圖就是咱們剛纔看到的 DaemonSet 在 ready 裏打出來的狀態。
從下圖中能夠看到,一共有 4 個 pod 被建立出來。爲何是 4 個 pod呢?由於只有 4 個節點,因此每一個節點上都會運行一個對應的 pod。
這時,咱們來更新 DaemonSet, 執行完了kubectl apply -f 後,它的 DaemonSet 就已經更新了。接下來咱們去查看 DaemonSet 的更新狀態。
上圖中能夠看到:DaemonSet 默認這個是 RollingUpdate 的,咱們看到是 0-4,如今是 1-4,也就是說它在更新第一個,第一個更新完成會去更新第二個,第二個更新完,就更新第三個······這個就是 RollingUpdate。RollingUpdate 能夠作到全自動化的更新,不用有人值守,而是一個一個地去自動更新,更新的過程也比較平滑,這樣能夠有利於咱們在現場發佈或者作一些其餘操做。<br />
上圖結尾處能夠看到,整個的 DaemonSet 已經 RollingUpdate 完畢。
接下來看一下 DaemonSet 架構設計。DaemonSet 仍是一個 controller,它最後真正的業務單元也是 Pod,DaemonSet 其實和 Job controller 特別類似,它也是經過 controller 去 watch API Server 的狀態,而後及時地添加 pod。惟一不一樣的是,它會監控節點的狀態,節點新加入或者消失的時候會在節點上建立對應的 pod,而後同時根據你配置的一些 affinity 或者 label 去選擇對應的節點。
最後咱們來看一下 DaemonSet 的控制器,DaemonSet 其實和 Job controller 作的差很少:二者都須要根據 watch 這個 API Server 的狀態。如今 DaemonSet 和 Job controller 惟一的不一樣點在於,DaemonsetSet Controller須要去 watch node 的狀態,但其實這個 node 的狀態仍是經過 API Server 傳遞到 ETCD 上。
當有 node 狀態節點發生變化時,它會經過一個內存消息隊列發進來,而後DaemonSet controller 會去 watch 這個狀態,看一下各個節點上是都有對應的 Pod,若是沒有的話就去建立。固然它會去作一個對比,若是有的話,它會比較一下版本,而後加上剛纔提到的是否去作 RollingUpdate?若是沒有的話就會從新建立,Ondelete 刪除 pod 的時候也會去作 check 它作一遍檢查,是否去更新,或者去建立對應的 pod。
固然最後的時候,若是所有更新完了以後,它會把整個 DaemonSet 的狀態去更新到 API Server 上,完成最後所有的更新
「阿里巴巴雲原生微信公衆號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術公衆號。」