【问题标题】:Cron Jobs in Kubernetes - connect to existing Pod, execute scriptKubernetes 中的 Cron 作业 - 连接到现有 Pod,执行脚本
【发布时间】:2017-05-02 16:51:18
【问题描述】:

我确定我遗漏了一些明显的东西。我查看了有关 Kubernetes 上的 ScheduledJobs / CronJobs 的文档,但我找不到按计划执行以下操作的方法:

  1. 连接到现有 Pod
  2. 执行脚本
  3. 断开连接

我有其他方法可以做到这一点,但它们感觉不对。

  1. 安排一个 cron 任务:kubectl exec -it $(kubectl get pods --selector=some-selector | head -1) /path/to/script

  2. 创建一个部署,其中包含一个“Cron Pod”,其中还包含应用程序,以及许多“非 Cron Pod”,它们只是应用程序。 Cron Pod 将使用不同的映像(一个安排了 cron 任务的映像)。

如果可能的话,我更喜欢使用 Kubernetes ScheduledJobs 来防止同一个 Job 一次运行多次,而且我觉得这样做更合适。

有没有办法通过 ScheduledJobs / CronJobs 做到这一点?

http://kubernetes.io/docs/user-guide/cron-jobs/

【问题讨论】:

  • 这是一个 Symfony 应用程序,我想在其上调用命令。服务器上有许多租户,ls -s */ | cut -f1 -d'/' 获取目录(安装)的可迭代列表比手动为每个安装创建一个 cron 条目更容易。它将以installation=$(ls -d*/ | cut -f1 -d'/'); cd /path/$installation; php app/console some:command 之类的形式结束。新的 pod 不会知道每个安装,并且如果不下拉并设置应用程序,就好像它是真实实例一样,将无法访问安装变量。
  • kubernetes.io/docs/tasks/job,我正在使用这个

标签: kubernetes kubernetes-cronjob


【解决方案1】:

据我所知,没有“官方”方式可以按照您想要的方式执行此操作,我相信这是设计使然。 Pod 应该是短暂的和水平可扩展的,而 Jobs 旨在退出。将 cron 作业“附加”到现有 pod 不适合该模块。调度器不知道作业是否完成。

相反,Job 可以启动应用程序的一个实例,专门用于运行 Job,然后在 Job 完成后将其删除。为此,您可以为作业使用与部署相同的映像,但通过设置 command: 使用不同的“入口点”。

如果他们的工作需要访问由您的应用程序创建的数据,那么这些数据将需要在应用程序/Pod 之外持久化,您可以通过以下几种方式,但显而易见的方式是数据库或持久卷。 例如,使用数据库看起来像这样:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: APP
spec:
  template:
    metadata:
      labels:
        name: THIS
        app: THAT
    spec:
      containers:
        - image: APP:IMAGE
          name: APP
          command:
          - app-start
          env:
            - name: DB_HOST
              value: "127.0.0.1"
            - name: DB_DATABASE
              value: "app_db"

还有一个作业连接到同一个数据库,但具有不同的“入口点”:

apiVersion: batch/v1
kind: Job
metadata:
  name: APP-JOB
spec:
  template:
    metadata:
      name: APP-JOB
      labels:
        app: THAT
    spec:
      containers:
      - image: APP:IMAGE
        name: APP-JOB
        command:
        - app-job
        env:
          - name: DB_HOST
            value: "127.0.0.1"
          - name: DB_DATABASE
            value: "app_db"

或者持久卷方法看起来像这样:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: APP
spec:
  template:
    metadata:
      labels:
        name: THIS
        app: THAT
    spec:
      containers:
        - image: APP:IMAGE
          name: APP
          command:
          - app-start
          volumeMounts:
          - mountPath: "/var/www/html"
            name: APP-VOLUME
      volumes:
        - name:  APP-VOLUME
          persistentVolumeClaim:
            claimName: APP-CLAIM

---

apiVersion: v1
kind: PersistentVolume
metadata:
  name: APP-VOLUME
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /app

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: APP-CLAIM
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
  selector:
    matchLabels:
      service: app

通过这样的作业,附加到同一个卷:

apiVersion: batch/v1
kind: Job
metadata:
  name: APP-JOB
spec:
  template:
    metadata:
      name: APP-JOB
      labels:
        app: THAT
    spec:
      containers:
      - image: APP:IMAGE
        name: APP-JOB
        command:
        - app-job
        volumeMounts:
        - mountPath: "/var/www/html"
          name: APP-VOLUME
    volumes:
      - name:  APP-VOLUME
        persistentVolumeClaim:
          claimName: APP-CLAIM

【讨论】:

  • 我和 OP 有同样的问题。我们的一个用例是运行在单独 pod 中运行的 gitlab 综合安装的备份。通过在 gitlab 的 pod 中运行命令来触发备份当然没有选择在 pod 之间共享其数据(即ReadWriteMany),当然也没有选择在同一卷上启动第二个 gitlab 实例(肯定会损坏它的内部数据库)。您对此有何看法?
【解决方案2】:

通过exec 函数创建一个使用 Kubernetes API 在目标 pod 上运行所需命令的计划 pod。 pod 映像应包含 client libraries 以访问 API - 其中许多可用,或者您可以构建自己的。

例如,这是一个使用 Python 客户端的解决方案,它执行到每个 ZooKeeper pod 并运行数据库维护命令:

import time

from kubernetes import config
from kubernetes.client import Configuration
from kubernetes.client.apis import core_v1_api
from kubernetes.client.rest import ApiException
from kubernetes.stream import stream
import urllib3

config.load_incluster_config()

configuration = Configuration()
configuration.verify_ssl = False
configuration.assert_hostname = False
urllib3.disable_warnings()
Configuration.set_default(configuration)

api = core_v1_api.CoreV1Api()
label_selector = 'app=zk,tier=backend'
namespace = 'default'

resp = api.list_namespaced_pod(namespace=namespace,
                               label_selector=label_selector)

for x in resp.items:
  name = x.spec.hostname

  resp = api.read_namespaced_pod(name=name,
                                 namespace=namespace)

  exec_command = [
  '/bin/sh',
  '-c',
  'opt/zookeeper/bin/zkCleanup.sh -n 10'
  ]

  resp = stream(api.connect_get_namespaced_pod_exec, name, namespace,
              command=exec_command,
              stderr=True, stdin=False,
              stdout=True, tty=False)

  print("============================ Cleanup %s: ============================\n%s\n" % (name, resp if resp else "<no output>"))

以及相关的 Dockerfile:

FROM ubuntu:18.04

ADD ./cleanupZk.py /

RUN apt-get update \
  && apt-get install -y python-pip \
  && pip install kubernetes \
  && chmod +x /cleanupZk.py

CMD /cleanupZk.py

请注意,如果您有一个启用 RBAC 的集群,您可能需要创建一个服务帐户和适当的角色才能使此 API 调用成为可能。如下所示的角色足以列出 pod 并运行 exec,例如上面的示例脚本需要:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-list-exec
  namespace: default
rules:
  - apiGroups: [""] # "" indicates the core API group
    resources: ["pods"]
    verbs: ["get", "list"]
  - apiGroups: [""] # "" indicates the core API group
    resources: ["pods/exec"]
    verbs: ["create", "get"]

相关 cron 作业的示例:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: zk-maint
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: zk-maint-pod-list-exec
  namespace: default
subjects:
- kind: ServiceAccount
  name: zk-maint
  namespace: default
roleRef:
  kind: Role
  name: pod-list-exec
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: zk-maint
  namespace: default
  labels:
    app: zk-maint
    tier: jobs
spec:
  schedule: "45 3 * * *"
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: zk-maint
            image: myorg/zkmaint:latest
          serviceAccountName: zk-maint
          restartPolicy: OnFailure
          imagePullSecrets:
          - name: azure-container-registry

【讨论】:

    【解决方案3】:

    这似乎是一种反模式。为什么不能将工作 pod 作为作业 pod 运行?

    不管你似乎很确信你需要这样做。这就是我要做的。

    使用您的工作容器并将您的 shell 执行包装在一个简单的 Web 服务中,使用几乎任何语言都需要 10 分钟。公开端口并将服务放在该工人/工人面前。然后你的工作 pod 可以简单地 curl ..svc.cluster.local:/ (除非你已经使用了 dns)。

    【讨论】:

    • 在 Pod“空间”内创建一个新容器来访问 pod 容器私有资源是有意义的。
    【解决方案4】:

    我设法通过使用 doctl(DigitalOcean 的命令行界面)和 kubectl 创建自定义映像来做到这一点。 CronJob 对象将使用这两个命令来下载集群配置并针对容器运行命令。

    这是一个 CronJob 示例:

    apiVersion: batch/v1beta1
    kind: CronJob
    metadata:
      name: drupal-cron
    spec:
      schedule: "*/5 * * * *"
      concurrencyPolicy: Forbid
      jobTemplate:
        spec:
          template:
            spec:
              containers:
                - name: drupal-cron
                  image: juampynr/digital-ocean-cronjob:latest
                  env:
                    - name: DIGITALOCEAN_ACCESS_TOKEN
                      valueFrom:
                        secretKeyRef:
                          name: api
                          key: key
                  command: ["/bin/bash","-c"]
                  args:
                    - doctl kubernetes cluster kubeconfig save drupster;
                      POD_NAME=$(kubectl get pods -l tier=frontend -o=jsonpath='{.items[0].metadata.name}');
                      kubectl exec $POD_NAME -c drupal -- vendor/bin/drush core:cron;
              restartPolicy: OnFailure
    

    这是 CronJob 使用的 Docker 映像:https://hub.docker.com/repository/docker/juampynr/digital-ocean-cronjob

    如果您不使用 DigitalOcean,请弄清楚如何下载集群配置,以便 kubectl 可以使用它。例如,使用 Google Cloud,您必须下载 gcloud

    这是我实现 https://github.com/juampynr/drupal8-do 的项目存储库。

    【讨论】:

      【解决方案5】:

      听起来您可能希望在 pod 本身内运行计划的工作,而不是在 Kubernetes 级别执行此操作。我会使用传统的 Linux crontab 将其作为容器内的 cronjob 来处理。考虑:

      kind: Pod
      apiVersion: v1
      metadata:
        name: shell
      spec:
        init-containers:
        - name: shell
          image: "nicolaka/netshoot"
          command:
          - /bin/sh
          - -c
          - |
            echo "0 */5 * * * /opt/whatever/bin/do-the-thing" | crontab -
            sleep infinity
      

      如果您想跟踪这些进程的日志,则需要一种流利的机制来跟踪这些日志文件。

      【讨论】:

        【解决方案6】:

        这个应该有帮助。

        apiVersion: batch/v1beta1
        kind: CronJob
        metadata:
          name: hello
        spec:
          schedule: "*/30 * * * *"
          jobTemplate:
            spec:
              template:
                spec:
                  containers:
                  - name: hello
                    image: busybox
                    args:
                    - /bin/sh
                    - -c
                    kubectl exec -it  <podname> "sh script.sh ";
                  restartPolicy: OnFailure
        

        【讨论】:

        • 除非容器图像具有docker.sock 可访问性,否则无法工作。
        • kubectl exec 不需要访问 docker.sock,但这也是一种反模式
        猜你喜欢
        • 2012-03-02
        • 1970-01-01
        • 2019-11-21
        • 2022-07-15
        • 2014-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多