cron, 컨테이너, 그리고 아무도 정하지 않은 시간대
cron 표현식이 가리키는 것은 순간이 아니라 벽시계 시각이고, 그 벽시계는 스케줄러를 돌리는 기계의 것이기 때문이다. 컨테이너는 거의 언제나 UTC 로 뜨는데 일정을 적는 사람은 현지 시각으로 생각하므로, 09:00 으로 맞춘 작업이 서울에서 18:00 에 돈다. 표현식이 틀린 게 아니라, 머릿속의 시계와 다른 시계로 읽히고 있는 것이다.
표현식 안에는 시간대가 없다
다섯 필드는 분·시·일·월·요일을 적는다. 그 숫자들이 어느 시간대의 것인지 적을 자리가 문법에 없으므로, 스케줄러는 자기 것을 쓴다 — 유닉스 호스트의 시스템 시간대, Spring @Scheduled 의 JVM 기본값, 또는 오케스트레이터가 컨테이너에 넣어 준 무엇이든.
tzdata 를 설정하지 않은 베이스 이미지는 UTC 로 돈다. 일정이 정확히 현지 오프셋만큼 어긋나는 가장 흔한 원인이다. 한국이면 아홉 시간, 중부 유럽이면 계절에 따라 여덟 또는 아홉 시간.
해결은 보정이 아니라 명시다. 스케줄러가 지원하는 자리에 시간대를 적는다 — 쿠버네티스 CronJob 에는 timeZone, Quartz 에는 트리거의 TimeZone, Spring 에는 애너테이션의 zone 이 있다 — 또는 컨테이너의 TZ 를 정해 물려받는 것이 아니라 배포의 일부로 만든다.
서머타임은 어떤 시각을 없애고 어떤 시각을 두 번 오게 한다
서머타임이 있는 곳에서 현지 시각은 이어져 있지 않다. 봄 전환에는 시계가 01:59 에서 곧장 03:00 으로 뛰므로 02:00 으로 잡은 작업은 돌 순간 자체가 없어 그날 그냥 실행되지 않는다. 가을 전환에는 02:00 이 두 번 오고, 구현에 따라 한 번 돌거나 두 번 돈다.
구현마다 대응이 다르다. Vixie cron 은 사라진 구간에 걸린 작업을 밀어서 실행해 주지만 많은 컨테이너 스케줄러는 그러지 않는다. 특정 스케줄러의 동작에 기대는 것이, 플랫폼이 아래에서 바뀔 때 일정이 깨지는 경로다.
한국은 서머타임을 쓰지 않으므로 KST 로 맞춘 스케줄러는 이 문제를 겪지 않는다. 같은 표현식이 미국이나 유럽 리전에서 도는 순간부터 문제가 된다 — 서비스를 두 번째 클러스터에 올리면 바로 그 일이 생긴다.
어떻게 하나
스케줄러를 UTC 로 돌리고 표현식도 UTC 로 쓴다. 두 문제가 한꺼번에 사라진다 — 사라지는 구간도, 반복되는 구간도, 리전별 어긋남도 없다. 대가는 0 0 * * * 를 읽는 사람이 환산을 해야 한다는 것인데, 일 년에 두 번 조용히 하루를 건너뛰는 작업보다는 작은 대가다.
정말로 현지 시각을 따라야 하는 작업이라면 — 업무 시작 전에 도착해야 하는 리포트 같은 것 — 시간대를 명시하고, 현지 시각 01:00~03:00 사이에는 아무것도 두지 않는다. 전환이 일어나는 구간이다.
중요한 작업이라면 실제로 돈 시각을 예정된 일정과 함께 기록한다. 엉뚱한 시각에 도는 작업은 누가 그 둘을 대조하기 전까지 보이지 않는다.
관련 도구
이 도구가 따르는 규격
- crontab(5) — 리눅스 매뉴얼 — 필드 정의와 현지 시계를 쓰는 방식.
- POSIX.1-2024 — crontab — 이식 가능한 다섯 필드 형식과 그 시각 의미.
- Spring Framework — 스케줄링 — @Scheduled 가 시간대를 정하는 방식과 지정하는 자리.
고친 날 2026-09-05