Cron Jobs в Kubernetes - подключиться к существующему Pod, выполнить скрипт
Я уверен, что упускаю что-то очевидное. Я просмотрел документацию для ScheduledJobs / CronJobs на Kubernetes, но не могу найти способ сделать следующее по расписанию:
- Подключиться к существующему модулю
- Выполнить скрипт
- Отключить
У меня есть альтернативные способы сделать это, но они не чувствуют себя хорошо.
Запланируйте задачу cron для: kubectl exec -it $(kubectl get pods --selector=some-selector | head -1) /path/to/script
Создайте одно развертывание, в котором есть "Cron Pod", в котором также находится приложение, и множество "Non Cron Pod", которые являются просто приложением. Cron Pod будет использовать другое изображение (на котором запланированы задачи cron).
Я бы предпочел использовать запланированные задания Kubernetes, если это возможно, чтобы предотвратить одновременное выполнение одного и того же задания, а также потому, что оно представляется мне более подходящим способом сделать это.
Есть ли способ сделать это ScheduledJobs / CronJobs?
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.