Cron en Kubernetes vs crontab Linux: trampas de zona horaria
Por qué la misma cadena cron de cinco campos dispara a horas distintas en GitHub Actions, K8s CronJob y crontab de servidor, y cómo previsualizar con seguridad.
0 9 * * 1-5 parece inequívoco: minuto cero, hora nueve, días laborables. Hasta que lo despliegas en tres plataformas y los backups corren a las 09:00 local, 09:00 UTC y 09:00 en la zona del control plane, todo con la misma cadena.
Esta comparación es para ingenieros que copian líneas cron entre servidores Linux, Kubernetes y CI sin releer las reglas de zona horaria.
Misma gramática, relojes distintos
El cron Unix clásico usa cinco campos (minuto, hora, día del mes, mes, día de la semana). La gramática es familiar; el reloj no se comparte:
| Plataforma | Comportamiento típico de zona horaria |
|---|---|
crontab de usuario Linux | Zona local del servidor (timedatectl) |
CronJob de Kubernetes | Por defecto del controller salvo timeZone (1.25+) |
schedule de GitHub Actions | Siempre UTC |
| Quartz / algunas reglas cloud | A menudo seis campos (segundos primero) |
Una línea copiada de /etc/crontab en una VM de Shanghai a un workflow de GitHub se desplazará ocho horas salvo que conviertas.
crontab Linux: conoce el servidor
crontab -e en una VM evalúa expresiones en la zona local de la máquina. Los cambios de DST pueden saltar o repetir una hora en horarios de reloj de pared como 30 2 * * *.
Los runbooks deberían registrar:
- Expresión
- Salida de
timedatectldel servidor - Equivalente UTC esperado para correlación de logs
Al correlacionar con timestamps de CloudWatch o Loki, convierte con un Timestamp Converter en lugar de cálculo mental durante un incidente.
Kubernetes: define timeZone explícitamente
Desde Kubernetes 1.25, CronJob soporta:
spec:
timeZone: "Asia/Shanghai"
schedule: "0 9 * * 1-5"
Sin timeZone, el comportamiento depende de la configuración del controller: no asumas que coincide con tu portátil. Trata la ausencia de zona horaria en YAML como bloqueador de review.
Actualizaciones de cluster y control planes multiregión son por qué zonas IANA explícitas ganan a "siempre usamos local".
GitHub Actions: solo UTC
on:
schedule:
- cron: "0 9 * * 1-5"
Esto es 09:00 UTC de lunes a viernes. Para 09:00 laborable en Shanghai necesitas 0 1 * * 1-5 en UTC durante el offset estándar, o documenta la tabla de offsets en el README del workflow.
Muchos tickets de "¿por qué corrió CI de noche?" empiezan aquí.
Previsualiza antes del merge
Independientemente de la plataforma:
- Parsea la expresión en un Cron Expression Parser
- Lee la descripción humana (cuidado con la semántica día-del-mes OR día-de-la-semana)
- Previsualiza próximas ejecuciones en UTC y en la zona objetivo
- Añade zona horaria + plataforma a la línea del runbook
Fallos habituales al copiar y pegar
| Error | Resultado |
|---|---|
| Cron de Actions pensado como 9am local | Job a hora de pared incorrecta |
K8s sin timeZone | Deriva tras mover el cluster |
| Quartz de seis campos pegado como cinco | Schedule inválido o incorrecto |
| DOM + DOW ambos definidos esperando AND | Ejecuciones extra inesperadas |
Aprendizaje relacionado
Sintaxis de campos, tablas por plataforma y checklist completo de depuración están en el curso Cron Expressions. Para correlación de logs entre zonas, combínalo con Timestamps in logs across timezones.
Idea clave
Las cadenas cron son agnósticas de zona horaria; los schedulers no lo son. Documenta plataforma + zona junto a cada línea, previsualiza en UTC y local, y configura timeZone de Kubernetes a propósito, no por accidente.