如何在 K8S 的 Pod 内连续执行 Container?这种方法别错过

2022-05-23 00:00:00 多个 执行 指定 运行 依次

出于某些目的,有时需要在 Kubernetes 的一个 Pod 中,连续依次运行多个 Container。这种有明确结束预期的运行,即 Kubernetes 的 Job。但是,虽然一个 Job 可以在一个 Pod 内运行多个 Container,然而运行方式是并发的。

一种方法是在业务层处理。比如,通过共享的本地 Volume,使用文件锁的机制,可以实现多个并发的 Container 依次执行。但这需要在业务逻辑中,把并发强行改为同步,增加了开发复杂度。如果能使用 Kubernetes 本身的机制实现,则减免了很大的开发工作量。
以下是另外的三种方案。

Kubernetes Job

经过调查发现,虽然 containers 不能依次运行,但是 initContainers 可以。它是在containers 运行前,执行的初始化操作,依次结束运行并且无异常后,正式的containers 才会运行。利用这一点,可以实现多个任务的依次执行,把前面的任务写到initContainers、后一个任务写到 containers 即可。

以下为三个 Containter 依次执行的样例。

apiVersion: batch/v1kind: Jobmetadata:  name: sequential-jobsspec:  backoffLimit:   ttlSecondsAfterFinished: 3600  template:    spec:      activeDeadlinesSeconds: 60      restartPolicy: Never      initContainers:        - name: job-1          image: alpine:3.11          command:            - 'sh'            - '-c'            - >              for i in 1 2 3;              do                echo "job-1 `date`";                sleep 1s;              done;              echo code > /srv/input/code          volumeMounts:            - mountPath: /srv/input/              name: input        - name: job-2          image: alpine:3.11          command:            - 'sh'            - '-c'            - >              for i in 1 2 3;              do                echo "job-2 `date`";                sleep 1s;              done;              cat /srv/input/code &&              echo artifact > /srv/input/output/artifact          resources:            requests:              cpu: 3          volumeMounts:            - mountPath: /srv/input/              name: input            - mountPath: /srv/input/output/              name: output      containers:        - name: job-3          image: alpine:3.11          command:            - 'sh'            - '-c'            - >              echo "job-1 and job-2 completed";              sleep 3s;              cat /srv/output/artifact          volumeMounts:            - mountPath: /srv/output/              name: output      volumes:        - name: input          emptyDir: {}        - name: output          emptyDir: {}      securityContext:        runAsUser: 2000        runAsGroup: 2000        fsGroup: 2000
  • backoffLimit: 0,这句指定这个Job不要失败重启。

  • volumes 这部分,使用了 inputoutput 两个 emptyDir,作为输入输出。

  • securityContext 可以在镜像默认用户不确定的情况下,使用指定UID进行Volume操作,避免对 root 的依赖。

  • activeDeadlinesSeconds 指定了 Job 内 Pod 的超时时间。

    这个字段同样可以给到Job。

  • ttlSecondsAfterFinished 指定了在多久以后,Job会被自动删除。

运行完毕后,日志如下:

$ kubectl logs sequential-jobs-r4725 job-1job-1 Tue Jul 28 07:50:10 UTC 2020job-1 Tue Jul 28 07:50:11 UTC 2020job-1 Tue Jul 28 07:50:12 UTC 2020$ kubectl logs sequential-jobs-r4725 job-2job-2 Tue Jul 28 07:50:13 UTC 2020job-2 Tue Jul 28 07:50:14 UTC 2020job-2 Tue Jul 28 07:50:15 UTC 2020code$ kubectl logs sequential-jobs-r4725 job-3job-1 and job-2 completedartifact

Volcano

Volcano前身是kube-batch,声称在调度和管理方面,对原生Job进行了优化。但是在核心逻辑上,还是一样的,不能支持指定 Container 顺序执行。

状态转移图如下:
stateDiagram [*] → Pending Pending → Aborted Pending → Running Aborted → Pending Running → Aborted Running → Completed Running → Terminated Completed → [*] Terminated → [*]
在实际测试中,暂时没有发现在当前业务场景下,比原生Job有什么优势。以下是实测配置。
apiVersion: batch.volcano.sh/v1alpha1kind: Jobmetadata:  name: volcano-sequential-jobsspec:  minAvailable: 1  schedulerName: volcano  queue: default  tasks:    - replicas: 1      name: "task-1"      template:        spec:          restartPolicy: Never          initContainers:            - name: job-1              image: alpine:3.11              command:                - 'sh'                - '-c'                - >                  for i in 1 2 3;                  do                    echo "job-1 `date`";                    sleep 1s;                  done;                  echo code > /srv/input/code              volumeMounts:                - mountPath: /srv/input/                  name: input            - name: job-2              image: alpine:3.11              command:                - 'sh'                - '-c'                - >                  for i in 1 2 3;                  do                    echo "job-2 `date`";                    sleep 1s;                  done;                  cat /srv/input/code &&                  echo artifact > /srv/input/output/artifact              resources:                requests:                  cpu: 3              volumeMounts:                - mountPath: /srv/input/                  name: input                - mountPath: /srv/input/output/                  name: output          containers:            - name: job-done              image: alpine:3.11              command:                - 'sh'                - '-c'                - >                  echo "job-1 and job-2 completed";                  sleep 3s;                  cat /srv/output/artifact              volumeMounts:                - mountPath: /srv/output/                  name: output          volumes:            - name: input              emptyDir: {}            - name: output              emptyDir: {}          securityContext:            runAsUser: 2000            runAsGroup: 2000            fsGroup: 2000

上面与原生相比,虽然多了 tasks 这一层概念,但是在功能上并无帮助。

运行完毕后,日志如下:

$ kubectl logs volcano-sequential-jobs-task-1- job-1job-1 Tue Jul 28 07:53:17 UTC 2020job-1 Tue Jul 28 07:53:18 UTC 2020job-1 Tue Jul 28 07:53:20 UTC 2020$ kubectl logs volcano-sequential-jobs-task-1- job-2job-2 Tue Jul 28 07:53:21 UTC 2020job-2 Tue Jul 28 07:53:22 UTC 2020job-2 Tue Jul 28 07:53:23 UTC 2020code$ kubectl logs volcano-sequential-jobs-task-1- job-3job-1 and job-2 completedartifact
另外,Volcano 的文档缺失严重,与 kube-batch 也不兼容。目前看来有很多不清楚的问题。

argo

argo是更合适按顺序、依赖关系执行的。但是经评估,它有一个重大缺陷,不适合当前场景。
argo的每个Task都是独立的Pod,不同Pod未必在同一台机器上。而Volume则必须使用NFS之类的网络存储位置,性能不符合某些需要密集本地IO的场景。

总结

argo 是形式上合适的,可以避免使用 initContainers 这种邪道。但是独立 Pod 的问题导致它不适合这个场景。
目前看来,原生的 Job 合适。
选用Volcano还需要更深入的了解。
来源:https://note.qidong.name/2020/08/k8s-sequential-container-in-pod/

相关文章