angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Ejecutar un comando Docker dentro de un contenedor Cron Docker

El Alpine Docker image hace que sea muy fácil construir un contenedor Cron.

18 abril 2023
En Docker
post main image
https://unsplash.com/@unsplash

Al utilizar Docker, su aplicación suele constar de varios contenedores Docker . A menudo, quieres ejecutar scripts dentro de estos contenedores en determinados momentos, por ejemplo, cada cinco minutos, una vez por hora, una vez al día.

Aquí es donde el programador de tareas Cron entra en juego, y hay varias opciones sobre cómo hacer esto. En este post creo un contenedor Cron separado, y uso el comando Docker Exec para ejecutar comandos y scripts en otro contenedor.

Yo tenía una solución de trabajo utilizando la imagen Ubuntu base, pero quería una configuración más sencilla y menor huella. Es por eso que estoy usando el Alpine Docker image aquí. Como siempre, estoy ejecutando esto en Ubuntu 22.04.

Alpine y Cron

Alpine ya viene con Cron configurado. Dentro del contenedor, hay directorios donde podemos poner nuestros scripts:

docker run alpine:3.17 ls -l /etc/periodic

Resultado:

/etc/periodic:
total 20
drwxr-xr-x    2 root     root          4096 Mar 29 14:45 15min
drwxr-xr-x    2 root     root          4096 Mar 29 14:45 daily
drwxr-xr-x    2 root     root          4096 Mar 29 14:45 hourly
drwxr-xr-x    2 root     root          4096 Mar 29 14:45 monthly
drwxr-xr-x    2 root     root          4096 Mar 29 14:45 weekly

Los scripts en estos directorios, son (periódicamente) ejecutados por Cron de la siguiente manera:

docker run alpine:3.17 crontab -l

Resultado:

# do daily/weekly/monthly maintenance
# min	hour	day	month	weekday	command
*/15	*	*	*	*	run-parts /etc/periodic/15min
0	*	*	*	*	run-parts /etc/periodic/hourly
0	2	*	*	*	run-parts /etc/periodic/daily
0	3	*	*	6	run-parts /etc/periodic/weekly
0	5	1	*	*	run-parts /etc/periodic/monthly

Usando Docker Volumes, podemos mapear (montar) estos directorios en nuestro host. Probablemente quieras ejecutar algunos comandos o scripts en otros momentos, por ejemplo:

  • Cada minuto.
  • Cada hora entre las 6 y las 23.
  • A medianoche.

Esto significa que debemos añadir líneas a nuestro archivo crontab y crear nuevos directorios dentro del contenedor. Podemos hacerlo en un archivo de inicio o en el Dockerfile. Por ejemplo, añadir una línea para un directorio con scripts que se ejecutarán cada hora entre 6 y 23 en el Dockerfile:

RUN echo "0       6-23    *       *       *       run-parts /etc/periodic/hourly_0600_2300" >> /etc/crontabs/root

También queremos usar Bash. Y para poder ejecutar comandos Docker en nuestro contenedor que puedan hacer referencia a otros contenedores Docker , añadimos Docker a nuestro Dockerfile y una asignación de volúmenes en nuestro archivo docker-compose.yml:

RUN apk add --update --no-cache bash && \
    apk add --update --no-cache docker && \
    ...
    volumes:
      # access docker containers outside this container
      - /var/run/docker.sock:/var/run/docker.sock

Directorios y archivos

Aquí hay una lista de árbol de todos los directorios y archivos en el proyecto:

├── project
│   ├── cron
│   │   ├── jobs
│   │   │   ├── 15min
│   │   │   ├── 1min
│   │   │   │   └── run_every_minute
│   │   │   ├── 5min
│   │   │   │   └── run_every_5_minutes
│   │   │   ├── daily
│   │   │   ├── daily_0000
│   │   │   ├── hourly
│   │   │   │   └── run_every_hour
│   │   │   └── hourly_0600_2300
│   │   │       └── run_every_hour_0600_2300
│   │   └── Dockerfile
│   ├── docker-compose.yml
│   └── .env

Para las pruebas, he añadido algunos scripts Bash. Importante: De acuerdo con la documentación de Alpine : 'No uses puntos en los nombres de los archivos de script - esto hace que no funcionen'.

- run_every_minute
- run_every_5_minutes
- run_every_hour
- run_every_hour_0600_2300

El contenido de todos estos scripts es el mismo:

#!/bin/bash
script_name=`basename "$0"`
echo "Running ${script_name} ..."

Asegúrate de que todos estos scripts Bash son ejecutables, por ejemplo:

chmod a+x run_every_5_minutes

Los archivos

Aquí están los otros archivos en el proyecto:

  • Dockerfile
  • docker-compose.yml
  • .env

Archivo: Dockerfile

Tenga en cuenta que Cron debe ejecutarse como root, no como user.

# Dockerfile
FROM alpine:3.17
MAINTAINER Peter Mooring peterpm@xs4all.nl peter@petermooring.com

# Install required packages
RUN apk add --update --no-cache bash && \
    apk add --update --no-cache docker && \
    # every minute
    echo "*       *       *       *       *       run-parts /etc/periodic/1min" >> /etc/crontabs/root && \
	# every 5 minutes
    echo "*/5     *       *       *       *       run-parts /etc/periodic/5min" >> /etc/crontabs/root && \
	# every hour between 06:00 and 23:00
    echo "0       6-23    *       *       *       run-parts /etc/periodic/hourly_0600_2300" >> /etc/crontabs/root && \
	# every day at 00:00
    echo "0       0       *       *       *       run-parts /etc/periodic/daily_0000" >> /etc/crontabs/root

# show all run-parts
# RUN crontab -l

WORKDIR /

File: docker-compose.yml

Observe que hemos establecido el contexto en './cron'. En este directorio están todos los ficheros asociados a este contenedor.

# docker-compose.yml
version: "3.7"

x-service_defaults: &service_defaults
  env_file:
    - ./.env
  restart: always
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "5"

services:
  cron:
    << : *service_defaults
    build:
      context: ./cron
      dockerfile: Dockerfile
    volumes:
      - ./cron/jobs/1min:/etc/periodic/1min/:ro
      - ./cron/jobs/5min:/etc/periodic/5min/:ro
      - ./cron/jobs/15min:/etc/periodic/15min/:ro
      - ./cron/jobs/hourly:/etc/periodic/hourly/:ro
      - ./cron/jobs/hourly_0600_2300:/etc/periodic/hourly_0600_2300/:ro
      - ./cron/jobs/daily:/etc/periodic/daily/:ro
      - ./cron/jobs/daily_0000:/etc/periodic/daily_0000/:ro
      # access docker containers outside this container
      - /var/run/docker.sock:/var/run/docker.sock
    command: crond -f -l 8

Fichero: .env

En el fichero .env sólo especificamos el nombre del proyecto:

# .env
COMPOSE_PROJECT_NAME=myapp

Construcción, arranque y algunas pruebas

Para (re)construir el contenedor:

docker-compose build --no-cache

Para iniciar el contenedor Cron:

docker-compose up

La salida será algo como:

Attaching to myapp_cron_1
cron_1  | Running run_every_minute ...
cron_1  | Running run_every_minute ...
cron_1  | Running run_every_minute ...
cron_1  | Running run_every_minute ...
cron_1  | Running run_every_minute ...
cron_1  | Running run_every_5_minutes ...
cron_1  | Running run_every_minute ...
...

El nombre del contenedor Cron es:

myapp_cron_1

Para mostrar los logs del contenedor:

docker logs -t -f myapp_cron_1

Resultado:

2023-04-18T11:52:00.871554414Z Running run_every_minute ...
2023-04-18T11:53:00.872386069Z Running run_every_minute ...
2023-04-18T11:54:00.872337812Z Running run_every_minute ...
2023-04-18T11:55:00.872792007Z Running run_every_minute ...
2023-04-18T11:55:00.872825556Z Running run_every_5_minutes ...
2023-04-18T11:56:00.875379199Z Running run_every_minute ...

Para comprobar qué scripts se ejecutarán desde el directorio ./cron/jobs/hourly_0600_2300, ejecute el siguiente comando en otro terminal:

docker exec myapp_cron_1 run-parts --test /etc/periodic/hourly_0600_2300

Esto no ejecutará los scripts Resultado:

/etc/periodic/hourly_0600_2300/run_every_hour_0600_2300

Ejecutar un script o comando en otro contenedor Docker

Vamos a ejecutar un comando en un contenedor BusyBox. En otro terminal, inicia este contenedor BusyBox y nómbralo 'my_busybox':

docker run -it --name my_busybox --rm busybox

Crea un nuevo script 'run_busybox_env' en el directorio './cron/jobs/1min' y hazlo ejecutable:

#!/bin/bash
# run_busybox_env
DOCKER=/usr/bin/docker

${DOCKER} exec my_busybox env

Este script ejecuta el comando 'env' en el contenedor 'my_busybox' y lista las variables de entorno del contenedor BusyBox.
En un minuto, el resultado debería aparecer en nuestro contenedor Cron:

cron_1  | Running run_every_minute ...
cron_1  | Running run_every_5_minutes ...
cron_1  | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cron_1  | HOSTNAME=dc74e9704d49
cron_1  | HOME=/root
cron_1  | Running run_every_minute ...

HOSTNAME tiene el ContainerId del contenedor BusyBox, esto diferirá en tu caso. Puedes obtener el ContainerId del contenedor BusyBox ejecutando:

docker ps | grep my_busybox

Resumen

Usar el Alpine Docker image para nuestro contenedor Cron fue muy fácil. Todo funcionó a la primera. El resultado es menos configuración y una huella más pequeña.

Enlaces / créditos

Alpine Linux:FAQ
https://wiki.alpinelinux.org/wiki/Alpine_Linux:FAQ

CRON Jobs with Alpine Linux and Docker
https://lucshelton.com/blog/cron-jobs-with-alpine-linux-and-docker

Docker Tip #40: Running Cron Jobs on the Host vs. in a Container
https://nickjanetakis.com/blog/docker-tip-40-running-cron-jobs-on-the-host-vs-in-a-container

Running cron jobs in a Docker Alpine container
https://devopscell.com/cron/docker/alpine/linux/2017/10/30/run-cron-docker-alpine.html

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.