Cron Jobs в Kubernetes - подключиться к существующему Pod, выполнить скрипт

Я уверен, что упускаю что-то очевидное. Я просмотрел документацию для ScheduledJobs / CronJobs на Kubernetes, но не могу найти способ сделать следующее по расписанию:

  1. Подключиться к существующему модулю
  2. Выполнить скрипт
  3. Отключить

У меня есть альтернативные способы сделать это, но они не чувствуют себя хорошо.

  1. Запланируйте задачу cron для: kubectl exec -it $(kubectl get pods --selector=some-selector | head -1) /path/to/script

  2. Создайте одно развертывание, в котором есть "Cron Pod", в котором также находится приложение, и множество "Non Cron Pod", которые являются просто приложением. Cron Pod будет использовать другое изображение (на котором запланированы задачи cron).

Я бы предпочел использовать запланированные задания Kubernetes, если это возможно, чтобы предотвратить одновременное выполнение одного и того же задания, а также потому, что оно представляется мне более подходящим способом сделать это.

Есть ли способ сделать это ScheduledJobs / CronJobs?

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

6 ответов

Насколько я знаю, не существует "официального" способа сделать это так, как вы хотите, и я верю в это по замыслу. Стручки должны быть эфемерными и горизонтально масштабируемыми, а Джобс предназначен для выхода. Работа cron, "прикрепленная" к существующему модулю, не подходит для этого модуля. Планировщик не будет знать, если работа завершена.

Вместо этого, задание может вызвать экземпляр вашего приложения специально для запуска задания, а затем закрыть его после завершения задания. Для этого вы можете использовать тот же образ для задания, что и для вашего развертывания, но использовать другую "точку входа", установив command:,

Если для их работы требуется доступ к данным, созданным вашим приложением, то эти данные нужно будет сохранить вне приложения / модуля, вы можете сделать это несколькими способами, но очевидными способами будут база данных или постоянный том. Например, использование базы данных будет выглядеть примерно так:

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

Создайте запланированный модуль, который использует API Kubernetes для запуска нужной команды на целевых модулях через exec функция. Образ модуля должен содержать клиентские библиотеки для доступа к API - многие из них доступны, или вы можете создать свою собственную.

Например, вот решение, использующее клиент Python, который запускается для каждого модуля ZooKeeper и запускает команду обслуживания базы данных:

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
LABEL maintainer "reDock Inc."

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 возможным. Для перечисления модулей и запуска 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

Похоже, вы захотите выполнять запланированную работу внутри самого модуля, а не делать это на уровне Kubernetes. Я бы подошел к этому как к cronjob внутри контейнера, используя традиционный crontab Linux. Рассмотреть возможность:

      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

Если вы хотите отслеживать журналы этих процессов, для отслеживания этих файлов журналов потребуется механизм типа Fluent.

Это похоже на анти-шаблон. Почему вы не можете просто запустить свой рабочий модуль как рабочий модуль?

Независимо от того, вы, кажется, уверены, что вам нужно это сделать. Вот что я бы сделал.

Возьмите свой рабочий модуль и оберните выполнение оболочки в простой веб-сервис, это 10 минут работы практически с любым языком. Разоблачите порт и поставьте службу перед этим работником. Тогда ваши рабочие модули могут просто свернуться..svc.cluster.local:/ (если вы не зациклены на dns).

Это должно помочь.

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

Мне удалось это сделать, создав собственный образ с помощью 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

Вот образ Docker, который использует CronJob: https://hub.docker.com/repository/docker/juampynr/digital-ocean-cronjob

Если вы не используете DigitalOcean, выясните, как загрузить конфигурацию кластера, чтобы kubectlможно использовать. Например, с Google Cloud вам нужно будет загрузитьgcloud.

Вот репозиторий проекта, в котором я реализовал это https://github.com/juampynr/drupal8-do.

Другие вопросы по тегам