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

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
Recientes
- Obtener una lista de YouTube vídeos de una persona
- De Docker-Composer a Docker Swarm: Configs
- Docker-Componer proyectos con nombres de servicio idénticos
- X Automatización web y scraping con Selenium
- Aiohttp con servidores DNS personalizados, Unbound y Docker
- Devuelve sólo los valores de una lista de registros de FastAPI
Más vistos
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando PyInstaller y Cython para crear un ejecutable de Python
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow