Kubernetes工作负载管理(下)

fldj
发布于 2022-8-26 16:24
浏览
0收藏

作者 | 乔克

来源 |运维开发故事(ID:mygsdcsf)

转载请联系授权(微信ID:wanger5354)

回滚
有更新,就有回滚,它们是苦命鸳鸯。

在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的更新策略,目前支持两种更新策略:

  • 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

这个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 

一切都符合我们的预期,现在再来看看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对象中,负责控制并行的参数为:

  • 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

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字段来定义规则,比如:

  • concurrencyPolicy=Allow:表示这些Job可以同时存在
  • concurrencyPolicy=Firbid:表示不会创建新的Job,也就是这个定时任务被跳过
  • concurrencyPolicy=Replace:表示产生的新Job会替代旧的Job
    如果某一个Job创建失败,那么这次创建就会被标记为miss,当在指定的时间窗口内,Miss的数达到100,那么CronJob就会停止再创建这个Job。这个时间窗口可以通过spec.startingDeadlineSeconds来指定。

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

分类
标签
已于2022-8-26 16:24:51修改
收藏
回复
举报
回复