Tareas programadas con Cron
Cómo programar y gestionar tareas recurrentes usando sintaxis cron en Linux, Python y Node.js.
Visión general
Cron es el planificador de trabajos estándar de Unix para ejecutar comandos en intervalos específicos. Ya sea que necesites respaldar bases de datos, enviar emails, limpiar logs o obtener datos, cron proporciona un mecanismo confiable para la automatización recurrente.
Más allá del cron del sistema, la mayoría de los ecosistemas de programación ofrecen librerías de scheduling que traen funcionalidad tipo cron directamente a tu aplicación.
Cuándo usarlo
Usa esta recipe cuando:
- Ejecutas tareas periódicas en un servidor (backups, limpiezas, reportes). Consulta Background Jobs para patrones de task queues.
- Programas trabajos en background dentro de una aplicación. Consulta Scheduled Jobs para cron serverless.
- Reemplazas procesos manuales con scripts automatizados. Consulta Bash Scripting Automation para automatización de scripts.
- Coordinas ejecución de trabajos distribuidos. Consulta RabbitMQ Task Queue para coordinación de tareas distribuidas.
Solución
Linux (System Cron)
# Editar crontab
crontab -e
# Todos los días a las 3:00 AM
0 3 * * * /usr/local/bin/backup.sh
# Cada 15 minutos
*/15 * * * * /usr/local/bin/check-health.sh
# Cada lunes a las 9:00 AM
0 9 * * 1 /usr/local/bin/weekly-report.sh
# Primer día de cada mes a medianoche
0 0 1 * * /usr/local/bin/monthly-cleanup.sh
Python
import schedule
import time
def job():
print("Running scheduled task...")
# Cada 10 minutos
schedule.every(10).minutes.do(job)
# Todos los días a las 9:30 AM
schedule.every().day.at("09:30").do(job)
# Cada lunes
schedule.every().monday.do(job)
while True:
schedule.run_pending()
time.sleep(1)
JavaScript (Node.js)
const cron = require('node-cron');
// Cada 15 minutos
cron.schedule('*/15 * * * *', () => {
console.log('Running every 15 minutes');
});
// Todos los días a las 3:00 AM
cron.schedule('0 3 * * *', () => {
console.log('Running daily backup');
});
// Cada lunes a las 9:00 AM
cron.schedule('0 9 * * 1', () => {
console.log('Running weekly report');
});
Explicación
Las expresiones cron usan 5 campos:
| Campo | Valores permitidos | Descripción |
|---|---|---|
| Minuto | 0-59 | Minuto de la hora |
| Hora | 0-23 | Hora del día |
| Día del mes | 1-31 | Día del mes |
| Mes | 1-12 | Mes del año |
| Día de la semana | 0-7 (0 y 7 = domingo) | Día de la semana |
Caracteres especiales:
*— cualquier valor,— separador de lista de valores |-— rango de valores | |*/n— cada n pasos |
Horarios comunes
| Expresión | Horario |
|---|---|
*/5 * * * * | Cada 5 minutos |
0 * * * * | Cada hora |
0 0 * * * | Todos los días a medianoche |
0 9 * * 1 | Cada lunes a las 9 AM |
0 0 1 * * | Primer día de cada mes |
0 0 * * 0 | Cada domingo a medianoche |
Lo que funciona
- Usa rutas absolutas para comandos y scripts en crontab
- Redirecciona la salida a un archivo de log o
/dev/nullpara evitar spam de mail - Establece una zona horaria específica si tus trabajos dependen de horarios laborales
- Usa un process manager (systemd, PM2) para schedulers a nivel de aplicación
- Agrega manejo de errores y alertas para tareas programadas fallidas
- Testea expresiones con validadores de cron online antes de deployar
Errores comunes
- Olvidar hacer scripts ejecutables (
chmod +x) - Usar rutas relativas que fallan en el entorno mínimo de cron
- No manejar ejecuciones superpuestas de trabajos (usa locking)
- Ignorar cambios de horario de verano
- Ejecutar trabajos demasiado frecuentes sin rate limiting o backoff
Tips de Rendimiento
- Escalona los schedules de jobs. Evita ejecutar múltiples jobs pesados al mismo tiempo:
# Mal: todos a medianoche
0 0 * * * /usr/local/bin/backup.sh
0 0 * * * /usr/local/bin/cleanup.sh
0 0 * * * /usr/local/bin/report.sh
# Bien: escalar por 30 minutos
0 0 * * * /usr/local/bin/backup.sh
30 0 * * * /usr/local/bin/cleanup.sh
0 1 * * * /usr/local/bin/report.sh
- Usa jitter para jobs distribuidos. Añade delay aleatorio para prevenir thundering herd:
import random
import time
def run_with_jitter(max_delay=300):
delay = random.randint(0, max_delay)
time.sleep(delay)
run_job()
- Setea timeouts. Previene jobs desbocados que consuman recursos:
# Matar job después de 1 hora
0 3 * * * timeout 3600 /usr/local/bin/backup.sh
# Kubernetes CronJob
spec:
jobTemplate:
spec:
activeDeadlineSeconds: 3600
- Limpia artefactos antiguos de jobs. Setea políticas de retención:
# Borrar backups mayores a 30 días
0 4 * * * find /backup -name "*.sql" -mtime +30 -delete
# Kubernetes CronJob
spec:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5 Preguntas frecuentes
¿Esta solución está lista para producción?
Sí. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno específico antes de desplegar.
¿Cuáles son las características de rendimiento?
El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.
¿Cómo depuro problemas con este enfoque?
Empieza con el ejemplo mínimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.
Java (Quartz Scheduler)
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class CronScheduler {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail job = JobBuilder.newJob(BackupJob.class)
.withIdentity("backupJob", "group1")
.build();
// Todos los días a las 3:00 AM
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("backupTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 3 * * ?"))
.build();
scheduler.scheduleJob(job, trigger);
scheduler.start();
}
public static class BackupJob implements Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println("Ejecutando backup: " + java.time.LocalDateTime.now());
}
}
}
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
spec:
schedule: "0 3 * * *" # Todos los días a las 3:00 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup-tool:v1.2
command: ["/bin/sh", "-c"]
args: ["pg_dump $DATABASE_URL > /backup/$(date +%Y%m%d).sql"]
envFrom:
- secretRef:
name: db-credentials
restartPolicy: OnFailure
activeDeadlineSeconds: 3600 # Matar después de 1 hora
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
concurrencyPolicy: Forbid # Prevenir ejecuciones superpuestas
Go (robfig/cron)
package main
import (
"fmt"
"github.com/robfig/cron/v3"
)
func main() {
c := cron.New()
// Cada 15 minutos
c.AddFunc("*/15 * * * *", func() {
fmt.Println("Health check ejecutando...")
})
// Todos los días a las 3:00 AM
c.AddFunc("0 3 * * *", func() {
fmt.Println("Backup diario ejecutando...")
})
c.Start()
select {} // Bloquear para siempre
}
systemd Timer (Alternativa a Cron)
# /etc/systemd/system/backup.service
[Unit]
Description=Database Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Environment=DATABASE_URL=postgres://localhost/mydb
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3:00 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true # Ejecutar jobs perdidos después del boot
[Install]
WantedBy=timers.target
# Habilitar e iniciar el timer
$ sudo systemctl enable backup.timer
$ sudo systemctl start backup.timer
$ systemctl list-timers --all
Prevenir Ejecuciones Superpuestas con flock
# Entrada crontab con flock para prevenir superposición
0 3 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh
# -n: non-blocking, falla si el lock está tomado
# -w 60: esperar hasta 60 segundos por el lock
0 * * * * /usr/bin/flock -w 60 /tmp/cleanup.lock /usr/local/bin/cleanup.sh
Variables de Entorno en Cron
# Cron tiene un PATH mínimo. Setéalo explícitamente en crontab:
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
MAILTO=alerts@example.com
# O sourcea un profile al inicio de cada job
0 3 * * * . /home/user/.bashrc && /usr/local/bin/backup.sh
Python con APScheduler (Avanzado)
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def backup_job():
logger.info("Iniciando backup de base de datos...")
# Lógica de backup aquí
logger.info("Backup completado.")
scheduler = BackgroundScheduler(timezone="UTC")
# Todos los días a las 3:00 AM UTC
scheduler.add_job(
backup_job,
CronTrigger(hour=3, minute=0),
id="backup",
max_instances=1, # Prevenir superposición
coalesce=True, # Fusionar ejecuciones perdidas
)
# Cada 15 minutos con jitter
scheduler.add_job(
health_check,
CronTrigger(minute="*/15"),
id="health_check",
max_instances=1,
jitter=60, # Delay aleatorio de hasta 60 segundos
)
scheduler.start()
Recursos Relacionados
Programar y Monitorear DAGs con Apache Airflow
Definí, programá y monitoreá DAGs de Airflow con operators, sensors, XCom, dependencias de tareas y la TaskFlow API.
RecipeFlujo de Trabajo Git
Una estrategia de branching práctica para equipos: ramas de feature, pull requests e historial limpio de commits.
PatternPatrón Observer
Define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos. Patrón de diseño conductual para comunicación basada en eventos.
RecipeAPScheduler BackgroundScheduler: Evitar Tareas Superpuestas
Ejecuta tareas tipo cron en Python con APScheduler. Cubre disparadores de intervalo, cron y fecha, almacenes de tareas y planificación en segundo plano.
RecipeTareas en Segundo Plano (Background Jobs)
Cómo programar y ejecutar tareas en segundo plano usando cron, colas de trabajo y workers.
RecipeConfiguración de Pipelines CI/CD
Configura pipelines CI/CD automatizados para testing, building y deployment de aplicaciones con GitHub Actions y lo que funciona.