• 【K8S专栏】Kubernetes工作负载管理


    微信公众号:运维开发故事,作者:乔克

    在Kubernetes中,Pod是最小的管理单元,是一组紧密关联的容器组合。

    但是,单独的Pod并不能保障总是可用,比如我们创建一个nginx的Pod,因为某些原因,该Pod被意外删除,我们希望其能够自动新建一个同属性的Pod。很遗憾,单纯的Pod并不能满足需求。

    为此,Kubernetes实现了一系列控制器来管理Pod,使Pod的期望状态和实际状态保持一致。目前常用的控制器有:

    • Deployment

    • StatefulSet

    • DaemonSet

    • Job/CronJob

    这里只介绍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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    创建结果如下:

    $ 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到我们期望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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    启动过后可以看到如下信息:

    $ 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    从上面信息可知,如果创建一个Deployment对象,会自动创建一个RS对象,然后通过RS对象创建对应的Pod数。

    水平扩展/收缩

    上面我们创建一个3副本的Pod,如果现在需要对其进行扩展/收缩,则可以通过以下三种方式:

    • kubectl scale命令

    • kubectl edit运行中的Deployment

    • 通过修改YAML清单,然后使用kubectl apply进行更新

    具体采用哪种方式根据情况而定。

    1、通过kubectl scale命令进行扩缩

    扩展和收缩的命令是一样,扩展就是增加副本数,收缩就是减少副本数。
    (1)扩展 我们现在有3个副本,如果想要4个副本,则使用以下命令:

    $ kubectl scale deployment nginx-deployment --replicas 4
    deployment.apps/nginx-deployment scaled
    
    
    • 1
    • 2
    • 3

    可以看到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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)收缩 现在集群里有4个副本,如果只想要2个副本,则使用如下命令

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

    现在集群里就只有两个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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、通过kubectl edit直接编辑Deployment

    我们也可以直接通过kubectl edit直接编辑运行中的Deployment,修改其副本数,如下:

    $ kubectl edit deployments.apps nginx-deployment -oyaml
    
    
    • 1
    • 2

    编辑界面如下:图片

    修改过后使用: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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    滚动更新/回滚

    业务应用基本都是通过Deployment的方式部署在Kubernetes中的,应用的更新和回滚是常态的工作,特别是在互联网企业,快速迭代抓住用户的一个重要途径。

    但是,并不是每一次的迭代都是100%正常的,如果异常,如何快速恢复也是要考虑的事情。

    为适应这种场景,Deployment提供滚动更新和快速回滚的能力。

    滚动更新

    Deployment默认的更新方式就是滚动更新,可以通过strategy.type来指定更新方式。

    • Recreate:先删除所有的Pod,再创建

    • RollingUpdate:先启动新的Pod,再替换老的Pod

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

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      labels:
        app: nginx
    spec:
    ...
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1
          maxUnavailable: 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    说明:
    (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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    然后使用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"}]}}}}'
    
    
    • 1
    • 2

    然后可以从另一个窗口查看升级的过程,如下:

    $ 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    老的版本是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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这种滚动更新的好处是:如果在更新过程中,新版本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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    说明:
    (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         
    2         
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    发现有两个版本,现在使用的就是2版本,我们需要回滚到1版本 。

    (2)执行以下命令回滚到老版本

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

    (3)通过查看RS,查看是否回滚到老版本

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

    如果可以明确直接回滚到上一个版本,可以直接使用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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    以上就是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 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    执行过后,就可以看到在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              
    
    
    • 1
    • 2
    • 3
    • 4

    可能有人好奇,集群本身有两个节点,为何只部署了一个Pod?

    那是因为我master(控制节点)有污点,而我上面的DaemonSet没有容忍这个污点,所以就没有调度上去,具体的调度策略,我们留到kubernetes调度管理章节进行讲解。

    DaemonSet也是支持更新和回滚的,具体操作和Deployment类似,这里就不再赘述。

    不过,这里要介绍一下DaemonSet的更新策略,目前支持两种更新策略:

    • OnDelete:先删后起,也就是先删除老的Pod,再启动新的Pod,这种策略会导致节点在更新的过程中出现断连的情况。

    • RollingUpdate:滚动更新,和Deployment滚动方式一样,默认的策略。

    值得一提的是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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这个Job简单执行一个脚本,循环10次并输出,通过kubectl apply -f job-demo.yaml创建Job,如下:

    $ kubectl apply -f job-demo.yaml 
    job.batch/job-demo created
    
    
    • 1
    • 2
    • 3

    然后可以通过kubectl logs来查看日志输出,如下:

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

    一切都符合我们的预期,现在再来看看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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    执行过后可以看到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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    为什么是重建而不是重启呢?

    因为我们在上面的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
    
    
    • 1
    • 2
    • 3
    • 4

    可以看到该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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    为了避免这种情况,可以在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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。一旦 Job 运行时间达到 activeDeadlineSeconds 秒,其所有运行中的 Pod 都会被终止, 并且 Job 的状态更新为 type: Failed 及 reason: DeadlineExceeded。

    Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod, 即使其重试次数还未达到 backoffLimit 所设的限制。

    并行控制

    在Job对象中,负责控制并行的参数为:

    • completions:定义Job至少要完成的Pod数目,既Job的最小完成数;

    • parallelism:定义一个Job在任意时间最多可以启动多少个Pod;

    我们定义下面一个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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从上面可以知道,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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我们可以看到spec里其实就是一个Job Template。另外其schedule就是一个便准的Cron格式,如下:

    分钟   小时    日    月    星期
    *      *      *     *      *
    
    
    • 1
    • 2
    • 3

    运行过后,查看状态如下:

    $ 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    需要注意的是,由于cron的特殊性,有时候会存在由于上一个定时任务还没有执行完成,新的定时任务又开始了的情况,我们可以通过定义spec.concurrencyPolicy字段来定义规则,比如:

    • concurrencyPolicy=Allow:表示这些Job可以同时存在

    • concurrencyPolicy=Firbid:表示不会创建新的Job,也就是这个定时任务被跳过

    • concurrencyPolicy=Replace:表示产生的新Job会替代旧的Job

    如果某一个Job创建失败,那么这次创建就会被标记为miss,当在指定的时间窗口内,Miss的数达到100,那么CronJob就会停止再创建这个Job。这个时间窗口可以通过spec.startingDeadlineSeconds来指定。

    总结

    上面介绍的是日常工作中常用的控制器,其中Deployment和DaemonSet的使用频率最高,熟练掌握这些控制器,并且学会在什么时候选择什么样的控制器,合理使用使工作效率最高。


    我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

  • 相关阅读:
    docsify项目部署(华为云+宝塔+centos+docker+nginx)踩坑指南
    BurpSuit官方实验室之命令注入
    Postman —— 配置环境变量
    数字化新零售营销模式如何落地?数字化新零售营销功能推荐
    pandas:时间序列数据的周期转换
    【Django | 安全防护】CSRF跨站伪请求和SQL注入攻击
    Windows下安装Nginx
    Swift 单元测试
    Spring常见问题解决 - this指针造成AOP失效
    公司内部传文件怎么安全——「用绿盾透明加密软件」
  • 原文地址:https://blog.csdn.net/wanger5354/article/details/126524782