서울과 다른 지역의 현지 시각 예약을 상징하는 세 개의 원형 시계

GitHub Actions cron timezone: UTC 변환 없이 한국 시간 schedule 검증하기

기존 30 0 * * *를 보고 한국 시간 오전 9시 30분이라고 해석하는 설정은 실행 순간보다 환산식을 기억하게 만든다. 2026년 3월 19일 공개된 GitHub Actions cron timezone 지원을 쓰면 30 9 * * * 옆에 Asia/Seoul을 두어 같은 의도를 현지 시각으로 남길 수 있다.

이 글은 2026년 8월 10일 공식 문서를 다시 확인하고, Ubuntu 계열 Linux의 Python 3.12.3·PyYAML 6.0.3·시스템 IANA tzdata로 YAML 구조와 서울·뉴욕의 다음 실행 시각을 대조한 결과를 다룬다. 실제 GitHub 저장소에 workflow를 커밋하거나 예약 실행을 기다리지는 않았으므로, 서버 측 스키마 메시지와 큐 동작은 공식 문서에 명시된 범위까지만 설명한다.

20초 핵심 요약

  • 무엇:on.schedule 목록 항목에서 crontimezone: 'Asia/Seoul'을 나란히 쓴다.
  • 왜: UTC 환산식을 계속 관리하면 현지 시각 의도가 설정에서 사라지고, DST 지역에서는 계절이 바뀔 때 실행 시각까지 밀릴 수 있다.
  • 어떻게: 기존 UTC 표현과 다음 실행 시각을 대조하고, 잘못된 키 위치와 IANA 이름을 검사한 뒤 Actions run history에서 실제 실행을 확인한다.

UTC 숫자를 바꾸기 전에 timezone의 위치부터 고친다

한국 시간으로 매일 오전 9시 30분에 예약하려면 timezone을 workflow 최상위나 schedule의 공통 속성으로 두지 않는다. GitHub Changelogworkflow syntax가 제시하는 위치는 각 목록 항목의 cron 바로 옆이다.

on:
  schedule:
    - cron: '30 9 * * *'
      timezone: 'Asia/Seoul'

여기서 9는 UTC가 아니라 Asia/Seoul의 오전 9시다. 한 workflow에 예약이 여러 개라면 각 목록 항목에 필요한 timezone을 적는다.

아래 구조는 schedule을 목록이 아닌 매핑으로 바꾸므로 사용하지 않는다.

on:
  schedule:
    timezone: 'Asia/Seoul'
    cron: '30 9 * * *'

로컬 fixture는 이 구조를 on.schedule must be a list로 거부했다. 다만 이 문구는 이번 검사기가 만든 판정이며 GitHub 화면에 표시되는 서버 측 오류 문구는 확인하지 않았다.

같은 09:30이라도 서울과 뉴욕의 UTC 결과는 다르다

기존 설정을 바꿀 때는 cron 숫자만 보고 맞다고 판단하지 말고, 두 표현이 같은 순간을 가리키는지 한 번 대조해야 한다. 2026년 3월 7일 00:00 UTC부터 다음 09:30 세 번을 분 단위로 계산한 결과는 다음과 같다.

운영 의도 기존 UTC cron timezone 방식 관측 결과
서울 매일 09:30 30 0 * * * 30 9 * * * + Asia/Seoul 3일 모두 00:30Z와 09:30+09:00으로 같은 순간이었다.
뉴욕 매일 09:30 30 14 * * * 30 9 * * * + America/New_York 고정 UTC 방식은 DST 뒤 현지 10:30이 됐지만 timezone 방식은 09:30을 유지했다.

실제 성공 실행의 판정 출력에는 유효 YAML, 잘못된 키 위치, timezone 오타, 두 지역의 다음 실행 시각을 함께 남겼다.

environment: Python 3.12.3 PyYAML 6.0.3
schema.valid: VALID
schema.bad_key_position: REJECTED - on.schedule must be a list; timezone belongs beside cron in each list item
schema.bad_timezone: REJECTED - 'No time zone found with key Asia/Seooul'
next_runs.Asia/Seoul:
  2026-03-07T09:30:00+09:00 = 2026-03-07T00:30:00Z
  2026-03-08T09:30:00+09:00 = 2026-03-08T00:30:00Z
  2026-03-09T09:30:00+09:00 = 2026-03-09T00:30:00Z
next_runs.America/New_York:
  2026-03-07T09:30:00-05:00 = 2026-03-07T14:30:00Z
  2026-03-08T09:30:00-04:00 = 2026-03-08T13:30:00Z
  2026-03-09T09:30:00-04:00 = 2026-03-09T13:30:00Z
before_after.Seoul: legacy UTC cron 30 0 * * * == timezone cron 30 9 * * * Asia/Seoul (00:30Z / 09:30+09:00)
before_after.New_York: legacy 30 14 UTC => 09:30 EST then 10:30 EDT; timezone 30 9 America/New_York => 09:30 local, UTC changes 14:30Z to 13:30Z
exit_status=0

유효 설정과 키 위치 오류, 서울·뉴욕의 UTC 변환 및 DST 전후 차이를 검증한 터미널 캡처

서울 구간에서는 두 설정의 결과가 같았다. 차이는 실행 순간이 아니라 설정이 보존하는 의미다. 30 0은 UTC 환산 결과를 남기지만, 30 9Asia/Seoul은 운영자가 원하는 현지 시각을 직접 남긴다.

뉴욕에서는 그 차이가 실행 결과로 이어졌다. 3월 8일 DST가 시작되자 현지 09:30의 UTC 시각은 14:30Z에서 13:30Z로 바뀌었다. 고정 30 14 * * *는 현지 10:30으로 밀렸지만 America/New_York을 지정한 설정은 09:30을 유지했다.

IANA 이름 오타는 자동으로 보정되지 않는다

Asia/Seoul 대신 Asia/Seooul을 넣은 별도 실행은 Python zoneinfo에서 종료 상태 1로 실패했다. 긴 traceback 가운데 실패를 판정하는 마지막 부분은 다음과 같다.

  File "/usr/lib/python3.12/zoneinfo/_common.py", line 24, in load_tzdata
    raise ZoneInfoNotFoundError(f"No time zone found with key {key}")
zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key Asia/Seooul'
exit_status=1

잘못된 IANA timezone 이름이 자동 보정되지 않고 실패한 터미널 캡처

이 검사는 GitHub의 서버 측 validator를 대신하지 않는다. 배포 전 정적 검사에서 오타를 먼저 차단하기 위한 진단점이다. IANA 규칙은 정치·법률 변화와 tzdata 갱신에 따라 달라질 수 있으므로 장기 운영에서는 IANA Time Zone Database와 검사 환경의 tzdata도 함께 확인해야 한다.

DST는 현지 시각을 지키지만 경계 시각에는 별도 규칙이 있다

timezone을 쓰면 cron 숫자는 지정 지역의 wall-clock time으로 해석된다. 그래서 뉴욕의 존재하는 09:30은 DST 전후에도 현지 09:30으로 유지되고 UTC 쪽 시각이 바뀐다.

존재하지 않는 현지 시각은 별도 문제다. GitHub 공식 문서는 spring-forward 때 사라지는 02:30 예약을 다음 유효 시각인 03:00으로 전진시킨다고 명시한다. 이번 로컬 계산은 존재하는 09:30의 오프셋 변화만 다뤘으며, GitHub 실행 엔진의 이 전진 동작이나 fall-back 때 중복되는 현지 시각의 처리는 재현하지 않았다. 확인되지 않은 fall-back 동작은 일반 cron 지식으로 추정하면 안 된다.

시각이 맞는데 실행이 없으면 환산보다 운영 조건을 본다

timezone은 예정 시각의 해석 기준이지 정시 실행 SLA가 아니다. GitHub는 고부하 때 scheduled event가 지연될 수 있고, 특히 매시 정각에는 부하가 커지며, 부하가 충분히 높으면 queued job 일부가 drop될 수 있다고 안내한다. 업무상 허용된다면 0 9 * * * 대신 7 9 * * *처럼 정각을 피하는 선택이 가능하다.

예약이 늦거나 보이지 않을 때는 다음 순서로 범위를 좁힌다.

  1. workflow 파일이 default branch에 있는지 확인한다. 예약 실행은 default branch의 최신 커밋을 대상으로 한다.
  2. 예약 간격이 공식 최소값인 5분보다 짧지 않은지 확인한다.
  3. 공개 저장소가 60일 동안 활동이 없어 scheduled workflow가 비활성화되지 않았는지 확인한다.
  4. Actions run history에서 예정 시각 이후 지연 또는 누락 여부를 확인한다.

이 조건은 schedule 이벤트 문서scheduled workflow 문제 해결 문서에서 확인할 수 있다. workflow_dispatch를 함께 두면 job 자체는 수동으로 시험할 수 있지만, 수동 실행 성공이 schedule의 시각 해석까지 검증해 주지는 않는다.

전환은 다음 실행 대조와 복원값을 남긴 뒤 결정한다

한국 시각 기준 업무라면 Asia/Seoul 방식은 실행 결과를 새로 만드는 기능보다 UTC 환산 의도를 없애는 설정에 가깝다. 기존 30 0 * * *와 새 설정의 다음 실행이 같은 순간인지 대조하고, 원래 cron 줄을 복원값으로 기록한 뒤 전환하는 편이 안전하다. 문제가 생기면 timezone 항목을 제거하고 기록해 둔 UTC 표현으로 되돌릴 수 있다.

반대로 정확한 정시 실행 SLA가 필요하거나 queued job의 drop을 허용할 수 없다면 GitHub schedule 하나에만 의존해서는 안 된다. 이번 검증도 실제 GitHub 예약 실행과 지연 분포까지 확인한 것이 아니므로, 변경 후 판단은 Actions run history가 맡아야 한다.

설정 검증을 마쳤다면 GitHub Actions runner 업데이트 점검 글로 실행 환경의 변경 여부도 확인할 수 있다.

참고 링크

비슷한 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다