Kubernetes 工作負載管理

在 Kubernetes 中,Pod 是最小的管理單元,是一組緊密關聯的容器組合。

但是,單獨的 Pod 並不能保障總是可用,比如我們創建一個 nginx 的 Pod,因爲某些原因,該 Pod 被意外刪除,我們希望其能夠自動新建一個同屬性的 Pod。很遺憾,單純的 Pod 並不能滿足需求。

爲此,Kubernetes 實現了一系列控制器來管理 Pod,使 Pod 的期望狀態和實際狀態保持一致。目前常用的控制器有:

這裏只介紹 Deployment、DaemonSet、Job/CronJob。StatefulSet 留到後面Kubernetes有狀態應用管理章節再來介紹,因爲它涉及到很多其他的知識點,比如 Service、PV/PVC,等這些知識點介紹完成過後再來說 StatefulSet 要好一點。

Deployment

在說 Deployment 之前,先來了解一下 ReplicaSet(RS)。

在 Kubernetes 初期,是使用 RC(Replication Controller)來控制 Pod,保證其能夠按照用戶的期望運行,但是後面因爲各種原因淘汰了 RC,轉而使用 RS 來替代它。從功能上看 RC 和 RS 沒多大的變化,唯一的區別 RS 支持集合的 Selector,可以方便定義更復雜的條件。

我們可以定義一個簡單的 ReplicaSet 來感受一下:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-set
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

創建結果如下:

$ kubectl get po
NAME              READY   STATUS              RESTARTS   AGE
nginx-set-hmtq4   0/1     ContainerCreating   0          2s
nginx-set-j2jpr   0/1     ContainerCreating   0          2s
$ kubectl get rs
NAME        DESIRED   CURRENT   READY   AGE
nginx-set   2         2         0       5s

可以看到我們期望replicas: 2創建 2 個 Pod,所以通過kubectl get pod的時候可以看到有 2 兩個 Pod 正在創建,這時候如果我們刪除一個 Pod,RS 會立馬給我們重新拉一個 Pod,以滿足我們的期望。

不過,在實際中很少去直接使用 RS,而是使用 Deployment。Deployment 是比 RS 更高層的資源對象,它會去控制管理 RS,如下:

從上圖可以看到 Deployment、ReplicaSet、Pod 它們以層層控制關係,Deployment 可以擁有多個 ReplicaSet,一個 ReplicaSet 可以擁有多個 Pod。一個 Deployment 擁有多個 ReplicaSet 主要是爲了支持回滾操作,每當操作 Deployment 的時候,就會生成一個新的 ReplicaSet,然後逐步更新新的 Pod,而老的 ReplicaSet 會逐步減少 Pod 直到新的 ReplicaSet 全部接管。這時候並不會刪除老的 ReplicaSet,系統會將其保存下來,以備回滾使用。

ReplicaSet 還負責通過 "控制器模式",保證系統的 Pod 數永遠等於期望數,這也是 Deployment 只允許 restartPolicy=Always 的原因:只有在容器能保證自己始終處於 running 狀態,通過 ReplicaSet 調整 Pod 的數量纔有意義。

而在此基礎上,Deployment 同樣通過 "控制器模式",來操作 ReplicaSet 的個數和屬性,進而實現水平擴展 / 收縮和滾動更新這兩個動作。其中水平擴展和收縮非常容易實現,Deployment Controller 只需要修改它的 ReplicaSet 的 Pod 副本數就可以了。

創建一個 Deployment 的清單如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

啓動過後可以看到如下信息:

$ kubectl get deployments.apps 
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           19s
$ kubectl get rs
NAME                         DESIRED   CURRENT   READY   AGE
nginx-deployment-8f458dc5b   3         3         3       21s
$ kubectl get po
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-8f458dc5b-8nn5c   1/1     Running   0          24s
nginx-deployment-8f458dc5b-hxc57   1/1     Running   0          24s
nginx-deployment-8f458dc5b-znrff   1/1     Running   0          24s

從上面信息可知,如果創建一個 Deployment 對象,會自動創建一個 RS 對象,然後通過 RS 對象創建對應的 Pod 數。

水平擴展 / 收縮

上面我們創建一個 3 副本的 Pod,如果現在需要對其進行擴展 / 收縮,則可以通過以下三種方式:

具體採用哪種方式根據情況而定。

1、通過 kubectl scale 命令進行擴縮

擴展和收縮的命令是一樣,擴展就是增加副本數,收縮就是減少副本數。
(1)擴展 我們現在有 3 個副本,如果想要 4 個副本,則使用以下命令:

$ kubectl scale deployment nginx-deployment --replicas 4
deployment.apps/nginx-deployment scaled

可以看到 Pod 數變成了 4 個。

$ kubectl get po
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-8f458dc5b-8nn5c   1/1     Running   0          8m3s
nginx-deployment-8f458dc5b-cv6mw   1/1     Running   0          29s
nginx-deployment-8f458dc5b-hxc57   1/1     Running   0          8m3s
nginx-deployment-8f458dc5b-znrff   1/1     Running   0          8m3s

(2)收縮 現在集羣裏有 4 個副本,如果只想要 2 個副本,則使用如下命令

$ kubectl scale deployment nginx-deployment --replicas 2
deployment.apps/nginx-deployment scaled

現在集羣裏就只有兩個 Pod 了。

$ kubectl get po
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-8f458dc5b-8nn5c   1/1     Running   0          9m36s
nginx-deployment-8f458dc5b-hxc57   1/1     Running   0          9m36s

2、通過 kubectl edit 直接編輯 Deployment

我們也可以直接通過 kubectl edit 直接編輯運行中的 Deployment,修改其副本數,如下:

$ kubectl edit deployments.apps nginx-deployment -oyaml

編輯界面如下:

修改過後使用:wq保存退出,可以看到副本數又變成 4 個了。

$ kubectl get po
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-8f458dc5b-8nn5c   1/1     Running   0          14m
nginx-deployment-8f458dc5b-hxc57   1/1     Running   0          14m
nginx-deployment-8f458dc5b-mq69h   1/1     Running   0          92s
nginx-deployment-8f458dc5b-xktq2   1/1     Running   0          92s

3、通過修改本地 YAML 文件,使用 kubectl apply 更新

我們還可以通過直接修改本地 YAML 的方式擴縮,比如直接在 YAML 文件中將副本數改成 2:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

然後直接使用kubectl apply -f nginx.yaml部署即可。

滾動更新 / 回滾

業務應用基本都是通過 Deployment 的方式部署在 Kubernetes 中的,應用的更新和回滾是常態的工作,特別是在互聯網企業,快速迭代抓住用戶的一個重要途徑。

但是,並不是每一次的迭代都是 100% 正常的,如果異常,如何快速恢復也是要考慮的事情。

爲適應這種場景,Deployment 提供滾動更新和快速回滾的能力。

滾動更新

Deployment 默認的更新方式就是滾動更新,可以通過strategy.type來指定更新方式。

如果要更改更新方式,配置如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
...
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1

說明:
(1)、maxSurge:定義除了 DESIRED 數量之外,在一次滾動更新過程中,Deployment 還可以創建多少 Pod;
(2)、maxUnavailable:定義在一次滾動更新過程中,Deployment 最多可以刪除多少 Pod;另外,這兩個配置還可以通過設置百分值來表示。

一般情況下,我們就保持默認的更新方式即可,這也是在生產中用的比較多的。

現在,來看看滾動更新的效果。首先創建一個 nginx 應用,如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.8

然後使用kubectl apply -f deploy.yaml,然後使用kubectl get po -w觀察升級效果。

另外開啓一個 shell 窗口,使用以下命令更新應用:

$ kubectl patch deployment nginx-deployment --patch '{"spec": {"template": {"spec": {"containers": [{"name": "nginx","image":"nginx:1.9"}]}}}}'

然後可以從另一個窗口查看升級的過程,如下:

$ kubectl get po -w
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-6c74f576b9-h565l   1/1     Running   0          22s
nginx-deployment-6c74f576b9-k65q6   1/1     Running   0          22s
nginx-deployment-6c74f576b9-qr2xc   1/1     Running   0          22s
nginx-deployment-778d9f5866-n69qd   0/1     Pending   0          0s
nginx-deployment-778d9f5866-n69qd   0/1     Pending   0          0s
nginx-deployment-778d9f5866-n69qd   0/1     ContainerCreating   0          0s
nginx-deployment-778d9f5866-n69qd   0/1     ContainerCreating   0          0s
nginx-deployment-778d9f5866-n69qd   1/1     Running             0          41s
nginx-deployment-6c74f576b9-qr2xc   1/1     Terminating         0          3m23s
nginx-deployment-778d9f5866-42vhv   0/1     Pending             0          0s
nginx-deployment-778d9f5866-42vhv   0/1     Pending             0          0s
nginx-deployment-778d9f5866-42vhv   0/1     ContainerCreating   0          0s
nginx-deployment-778d9f5866-42vhv   0/1     ContainerCreating   0          1s
nginx-deployment-6c74f576b9-qr2xc   1/1     Terminating         0          3m24s
nginx-deployment-6c74f576b9-qr2xc   0/1     Terminating         0          3m24s
nginx-deployment-778d9f5866-42vhv   1/1     Running             0          1s
nginx-deployment-6c74f576b9-k65q6   1/1     Terminating         0          3m24s
nginx-deployment-778d9f5866-tndn8   0/1     Pending             0          0s
nginx-deployment-778d9f5866-tndn8   0/1     Pending             0          0s
nginx-deployment-778d9f5866-tndn8   0/1     ContainerCreating   0          0s
nginx-deployment-6c74f576b9-k65q6   1/1     Terminating         0          3m24s
nginx-deployment-6c74f576b9-qr2xc   0/1     Terminating         0          3m24s
nginx-deployment-6c74f576b9-qr2xc   0/1     Terminating         0          3m24s
nginx-deployment-778d9f5866-tndn8   0/1     ContainerCreating   0          0s
nginx-deployment-6c74f576b9-k65q6   0/1     Terminating         0          3m25s
nginx-deployment-6c74f576b9-k65q6   0/1     Terminating         0          3m25s
nginx-deployment-6c74f576b9-k65q6   0/1     Terminating         0          3m25s
nginx-deployment-778d9f5866-tndn8   1/1     Running             0          1s
nginx-deployment-6c74f576b9-h565l   1/1     Terminating         0          3m25s
nginx-deployment-6c74f576b9-h565l   1/1     Terminating         0          3m25s
nginx-deployment-6c74f576b9-h565l   0/1     Terminating         0          3m26s
nginx-deployment-6c74f576b9-h565l   0/1     Terminating         0          3m26s
nginx-deployment-6c74f576b9-h565l   0/1     Terminating         0          3m26s

老的版本是nginx-deployment-6c74f576b9-*,新的版本是nginx-deployment-778d9f5866-*,會先創建一個新版本 Pod,再刪除老版本 Pod,依次下去直到所有老的版本都被替換掉。

背後的實際邏輯是通過 Deployment 創建一個新的 ReplicaSet,然後通過新的 RS 來創建新的 Pod,可以通過kubectl get rs來查看:

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6c74f576b9   0         0         0       9m49s
nginx-deployment-778d9f5866   3         3         3       7m7s

這種滾動更新的好處是:如果在更新過程中,新版本 Pod 有問題,那麼滾動更新就會停止,這時候運維和開發就可以介入查看其原因,由於應用本身還有兩個舊版本的 Pod 在線,所以並不會對服務造成太大的影響;當然,這時候應在 Pod 中加上 health check 檢查應用的健康狀態,而不是簡單的依賴容器的 running 狀態。爲了進一步保證服務的延續性,Deployment Controller 還會確保在任何時間窗口內,只有指定比例的 Pod 處於離線狀態,同時它也會確保在任何時間窗口內,只有指定比例的 Pod 被創建,這個比例默認是 DESIRED 的 25%。

當然可以通過修改 Deployment 對象的一個字段 RollingUpdateStrategy 來自定義,比如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
...
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1

說明:
(1)、maxSurge:定義除了 DESIRED 數量之外,在一次滾動更新過程中,Deployment 還可以創建多少 Pod;
(2)、maxUnavailable:定義在一次滾動更新過程中,Deployment 最多可以刪除多少 Pod;另外,這兩個配置還可以通過設置百分值來表示。

如此,我們可以得到如下關係圖:

Deployment 實際控制的是 ReplicaSet 的數目以及每個 ReplicaSet 的屬性。而一個應用版本,對應的就是一個 ReplicaSet,而這個版本應有的 Pod 數量,是通過 ReplicaSet 自己的控制器來管理。

回滾

有更新,就有回滾,它們是苦命鴛鴦。

在 Kubernetes 中,回滾使用kubectl rollout命令。在滾動更新的章節,我們更新了 Nginx 應用,現在新版本如果有問題,需要進行回滾操作。

(1)查看可以回滾的歷史版本

$ kubectl rollout history deployment nginx-deployment 
deployment.apps/nginx-deployment 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

發現有兩個版本,現在使用的就是 2 版本,我們需要回滾到 1 版本 。

(2)執行以下命令回滾到老版本

$ kubectl rollout undo deployment nginx-deployment --to-revision 1
deployment.apps/nginx-deployment rolled back

(3)通過查看 RS,查看是否回滾到老版本

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6c74f576b9   3         3         3       27m
nginx-deployment-778d9f5866   0         0         0       25m

如果可以明確直接回滾到上一個版本,可以直接使用kubectl rollout undo deployment nginx-deployment

回滾的操作比較簡單,但是如果發佈比較頻繁,歷史數據超過一定版本(默認 10 個版本)後,就無法回滾到更老的版本了。

當然,我們可以通過定義spec.revisionHistoryLimit來定義保留多少版本號,默認是 10 個,如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.8
        imagePullPolicy: IfNotPresent
        name: nginx
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

以上就是 Deployment 的回滾操作,操作命令比較簡單,主要是瞭解到 Kubernetes 爲我們提供了這個功能,以備不時之需。

總結

從全文可知,Deployment 實際是一個兩層控制器:(1)、它通過 ReplicaSet 的個數來描述應用版本個數;(2)、它通過 ReplicaSet 的屬性來保證 Pod 的副本數;而且 Deployment 的靈活控制,很方便水平擴展 / 收縮還有滾動更新以及回滾操作。

DaemonSet

DaemonSet 保證在每個 Node 上都運行一個 Pod,如果新增一個 Node,這個 Pod 也會運行在新增的 Node 上,如果刪除這個 DadmonSet,就會清除它所創建的 Pod。常用來部署一些集羣日誌收集,監控等全局應用。

常見的場景如下:
1、運行存儲集羣 daemon,比如 ceph,glusterd 等;
2、運行一個日誌收集 daemon,比如 logstash,fluentd 等;
3、運行監控 daemon,比如 Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond 等;

比如運行一個 filebeat 的 DaemonSet,定義如下:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat-ds
  namespace: default
spec:
  selector:
    matchLabels:
      app: filebeat
      role: logstorage
  template:
    metadata:
      labels:
        app: filebeat
        role: logstorage
    spec:
      containers:
      - name: filebeat
        image: ikubernetes/filebeat:5.6.5-alpine
        env:
        - name: REDIS_HOST
          value: redis.default.svc.cluster.local

執行過後,就可以看到在 kk-node01 節點上運行了 filebeat,如下:

$ kubectl get po -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
filebeat-ds-kgqcq   1/1     Running   0          28s   172.16.51.212   kk-node01   <none>           <none>

可能有人好奇,集羣本身有兩個節點,爲何只部署了一個 Pod?

那是因爲我 master(控制節點)有污點,而我上面的 DaemonSet 沒有容忍這個污點,所以就沒有調度上去,具體的調度策略,我們留到kubernetes調度管理章節進行講解。

DaemonSet 也是支持更新和回滾的,具體操作和 Deployment 類似,這裏就不再贅述。

不過,這裏要介紹一下 DaemonSet 的更新策略,目前支持兩種更新策略:

值得一提的是rollingUpdate的更新策略,在老的 Kubernetes 版本中只有 maxUnavailable 而沒有 maxSurge,因爲 DaemonSet 只允許在 Node 上運行一個。但是在新版本中有了 maxSurge 這個參數,由它來控制多少個節點可用,比如總共有 100 個節點,maxSurge 配置 30%,則表示至少保證 30 個節點可用。

Job/CronJob

Kubernetes 的主要任務是保證 Pod 中的應用長久穩定的運行,但是我們有時候也需要一些只需要運行一次,執行完就退出了的 "短時" 任務,這時候使用 Deployment 等這類控制器就無法滿足我們的需求,Kubernetes 就誕生了 Job Controller,專門用來處理這類需求。

Job

Job 負責處理僅執行一次的任務,它保證批處理的任務的一個或多個成功結束,我們可以通過kubectl explain job來查看具體語法。

基本操作

Job 的定義語法和 Deployment、Pod 差不多,定義一個簡單的 Job,如下:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
  namespace: default
spec:
  template:
    metadata:
      name: job-demo
    spec:
      containers:
      - name: test-job
        image: busybox
        imagePullPolicy: IfNotPresent
        command:
        - "/bin/sh"
        - "-c"
        args:
        - "for i in $(seq 10); do echo $i; done"
      restartPolicy: Never
  backoffLimit: 4

這個 Job 簡單執行一個腳本,循環 10 次並輸出,通過kubectl apply -f job-demo.yaml創建 Job,如下:

$ kubectl apply -f job-demo.yaml 
job.batch/job-demo created

然後可以通過 kubectl logs 來查看日誌輸出,如下:

$ kubectl logs job-demo-wd67s 
1
2
3
4
5
6
7
8
9
10

一切都符合我們的預期,現在再來看看 Job 和 Pod 的狀態,如下:

$ kubectl get jobs.batch 
NAME       COMPLETIONS   DURATION   AGE
job-demo   1/1           23s        112s
$ kubectl get po
NAME             READY   STATUS      RESTARTS   AGE
job-demo-wd67s   0/1     Completed   0          114s

Job 的狀態沒有類似 Deployment 的 Ready 關鍵字,而是 COMPLETIONS(完成),1/1表示完成了這個 Job。而 Job 所控制的 Pod 的狀態也是 Completed,如果是這種狀態,就表示這個 Job 是成功的。

如果成功,Job 的 Pod 運行一次就結束了,如果失敗呢?

現在將剛纔的 Job 的 YAML 改成如下:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
  namespace: default
spec:
  template:
    metadata:
      name: job-demo
    spec:
      containers:
      - name: test-job
        image: busybox
        imagePullPolicy: IfNotPresent
        command:
        - "/bin/sh"
        - "-c"
        args:
        - "xxxxx"
      restartPolicy: Never
  backoffLimit: 4

執行過後可以看到 Pod 在不斷的重建,如下:

$ kubectl get po
NAME             READY   STATUS              RESTARTS   AGE
job-demo-kwsl8   0/1     Error               0          3s
job-demo-ltsvq   0/1     ContainerCreating   0          0s
job-demo-w54s4   0/1     Error               0          6s

爲什麼是重建而不是重啓呢?

因爲我們在上面的 YAML 裏配置了restartPolicy: Never,如果 Job 失敗就只會重建,如果要使用重啓,可以配置restartPolicy: OnFaliure,表示只有在狀態爲 Failure 的時候纔會重啓,Job 沒有 Always 參數。

把上面 YAML 中 restartPolicy 改成OnFaliure,效果如下:

$ kubectl get po
NAME             READY   STATUS             RESTARTS      AGE
job-demo-p9dkp   0/1     CrashLoopBackOff   3 (24s ago)   68s

可以看到該 Job 在不斷的重啓。

還有一種情況,如果這個 Job 一直不肯結束怎麼辦呢?比如我們將上面的 YAML 文件做如下修改:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
  namespace: default
spec:
  template:
    metadata:
      name: job-demo
    spec:
      containers:
      - name: test-job
        image: busybox
        imagePullPolicy: IfNotPresent
        command:
        - "/bin/sh"
        - "-c"
        args:
        - "sleep 3600"
      restartPolicy: OnFailure
  backoffLimit: 4

爲了避免這種情況,可以在 YAML 里加入activeDeadlineSeconds參數來指定 Pod 的存活時間,如下:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
  namespace: default
spec:
  template:
    metadata:
      name: job-demo
    spec:
      containers:
      - name: test-job
        image: busybox
        imagePullPolicy: IfNotPresent
        command:
        - "/bin/sh"
        - "-c"
        args:
        - "sleep 3600"
      restartPolicy: OnFailure
  backoffLimit: 4
  activeDeadlineSeconds: 10

該值適用於 Job 的整個生命期,無論 Job 創建了多少個 Pod。一旦 Job 運行時間達到 activeDeadlineSeconds 秒,其所有運行中的 Pod 都會被終止, 並且 Job 的狀態更新爲 type: Failed 及 reason: DeadlineExceeded。

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

並行控制

在 Job 對象中,負責控制並行的參數爲:

我們定義下面一個 Job 的 YAML 文件:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
  namespace: default
spec:
  parallelism: 2
  completions: 4
  template:
    metadata:
      name: job-demo
    spec:
      containers:
      - name: test-job
        image: busybox
        imagePullPolicy: IfNotPresent
        command:
        - "/bin/sh"
        - "-c"
        args:
        - "for i in $(seq 10); do echo $i; done"
      restartPolicy: OnFailure
  backoffLimit: 4
  activeDeadlineSeconds: 100

parallelism: 2 和 completions: 4 表示要完成 4 個 pod,每次可以同時運行兩個 Pod,我們創建這個 Job,觀察結果如下:

$ kubectl get po
NAME             READY   STATUS      RESTARTS   AGE
job-demo-5wlp8   0/1     Completed   0          2s
job-demo-6wfkw   0/1     Completed   0          2s
job-demo-d54vz   0/1     Completed   0          5s
job-demo-x5mpz   0/1     Completed   0          5s

從上面可以知道,Job Controller 實際控制的就是 Pod,它在創建的時候會在 Job 和 Pod 裏自動生成隨機字符串的 label,然後將它們進行綁定。

Job Controller 在實際的調諧操作是根據實際在 running 狀態的 Pod 數,還有已經退出的 Pod 數以及 parallelism 和 completions 的參數值共同計算出在 Job 週期內應該創建或者刪除多少 Pod,然後調用 kube-api 來執行這類操作。

所以 Job Controller 實際上是控制的 Pod 的並行度以及總共要完成的任務數這兩個重要的參數。

CronJob

CronJob 其實就在 Job 的基礎上加了時間調度,類似於用 Deployment 管理 Pod 一樣。它和我們 Linux 上的 Crontab 差不多。

比如定義簡單的 CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            command:
            - "/bin/sh"
            - "-c"
            args:
            - "for i in $(seq 10); do echo $i; done"
          restartPolicy: OnFailure

我們可以看到 spec 裏其實就是一個 Job Template。另外其 schedule 就是一個便準的 Cron 格式,如下:

分鐘   小時    日    月    星期
*      *      *     *      *

運行過後,查看狀態如下:

$ kubectl get cronjobs.batch 
NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        45s             69s
$ kubectl get po
NAME                   READY   STATUS      RESTARTS   AGE
hello-27628291-h8skg   0/1     Completed   0          50s

需要注意的是,由於 cron 的特殊性,有時候會存在由於上一個定時任務還沒有執行完成,新的定時任務又開始了的情況,我們可以通過定義 spec.concurrencyPolicy 字段來定義規則,比如:

如果某一個 Job 創建失敗,那麼這次創建就會被標記爲 miss,當在指定的時間窗口內,Miss 的數達到 100,那麼 CronJob 就會停止再創建這個 Job。這個時間窗口可以通過 spec.startingDeadlineSeconds 來指定。

總結

上面介紹的是日常工作中常用的控制器,其中 Deployment 和 DaemonSet 的使用頻率最高,熟練掌握這些控制器,並且學會在什麼時候選擇什麼樣的控制器,合理使用使工作效率最高。

我是 喬克,《運維開發故事》公衆號團隊中的一員,一線運維農民工,雲原生實踐者,這裏不僅有硬核的技術乾貨,還有我們對技術的思考和感悟,歡迎關注我們的公衆號,期待和你一起成長!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0NrdWPdHsXwCnjfw7XJNCA