The YAML Norway problem and cron's day-of-month trap: two config formats that lie to you
YAML의 노르웨이 문제와 cron의 날짜 함정 — 설정 파일이 값을 다르게 해석하는 이유
YAML은 파서와 스키마에 따라 NO, 1.10, 0755를 문자열이 아닌 값으로 읽고, cron은 날짜와 요일 조건을 AND가 아닌 OR로 처리할 수 있습니다. 글은 두 형식의 함정을 사례와 시뮬레이터로 설명하고, YAML 값 인용과 cron 조건 검증 등 실수를 막는 방법을 소개합니다.
- 주제
AI 요약
YAML과 cron은 작성한 설정을 예상과 다른 값이나 실행 시점으로 해석할 수 있습니다. 두 형식 모두 정해진 규칙을 따르지만, 그 규칙이 작성자가 짐작한 것과 다를 수 있습니다. 글은 YAML 파서별 차이와 cron의 날짜·요일 처리 방식을 구체적인 사례로 보여주고, 브라우저 시뮬레이터로 결과를 확인하는 방법을 소개합니다. 글쓴이는 두 시뮬레이터 제작에 참여했다고 밝힙니다.
YAML 버전과 파서가 값을 바꿉니다
PyYAML에 다음 내용을 넣으면 country: NO의 값이 False, version: 1.10이 실수 1.1, mode: 0755가 정수 493으로 바뀝니다. on: push도 키 True와 값 push로 해석됩니다. 국가 코드가 불리언으로 바뀌고 버전의 끝자리 0이 사라지며, GitHub Actions에서 쓰는 on 키까지 달라지는 사례입니다.
널리 쓰이는 YAML 1.1과 1.2는 따옴표 없는 값의 규칙이 다릅니다. YAML 1.1은 여러 대소문자 형태의 y, yes, on, n, no, off를 불리언으로 읽고, 앞에 0이 붙은 수를 8진수로 처리합니다. YAML 1.2의 core schema는 불리언을 true, false로 제한하고 0755를 10진수 755로 읽습니다. 어떤 결과를 얻는지는 파일 자체가 아니라 파서와 설정에 달려 있습니다.
PyYAML은 일부 예외를 빼고 YAML 1.1 규칙을 따릅니다. 예를 들어 소문자 y, n은 문자열로 남깁니다. 반면 js-yaml 5.4.2는 기본 스키마에서 YAML 1.2 규칙을 적용합니다. 같은 문서에서 NO를 문자열로, 0755를 755로 읽습니다.
YAML Parsing Simulator는 YAML 1.2 결과와 YAML 1.1을 따르는 구형 파서의 결과를 나란히 보여줍니다. 문자열에서 다른 타입으로 바뀐 항목을 나열하고, 두 규격이 다르게 읽는 값은 경로와 결과를 표로 비교합니다. 다만 이 도구는 전체 YAML 구현이 아니라 값을 확인하는 소형 파서입니다. 값을 해석하지만 키는 처리하지 않으므로, 앞선 예시의 on: 키 문제는 실제 파서에서 확인해야 합니다.
자주 만나는 YAML 함정과 예방책
글은 열 가지 학습 예제를 소개합니다. 국가 코드 NO는 YAML 1.1에서 false가 되지만 SE는 문자열로 남습니다. version: 1.10은 두 규격 모두 실수로 읽으므로 뒤의 0이 사라집니다. defaultMode: 0755는 YAML 1.1에서 493, YAML 1.2에서 755가 됩니다. Kubernetes는 defaultMode를 8진수 최대 0777 또는 10진수 최대 511로 받으므로, 어느 해석이 맞는지 필드의 규칙도 확인해야 합니다.
문자열로 유지할 값은 code: "NO", version: "1.10", mode: "0755"처럼 인용하면 두 규격에서 문자열로 읽힙니다. 반대로 숫자를 요구하는 필드에는 defaultMode: 493처럼 두 규격이 같은 값으로 읽는 10진수를 쓸 수 있습니다.
중복 키도 파서마다 결과가 다릅니다. replicas: 3 뒤에 replicas: 1이 나오면 PyYAML은 경고 없이 마지막 값을 사용합니다. js-yaml 5.4.2는 기본 설정에서 중복 매핑 키 오류로 문서를 거부합니다. 나머지 예제는 빈 값과 ~, null, 빈 문자열의 차이, 블록 스칼라의 줄바꿈 접기, 앵커와 병합 키, 들여쓰기 탭, 공백 없는 콜론 등을 다룹니다. 블록 스칼라가 줄바꿈을 공백으로 바꾸면 셸 스크립트가 깨질 수 있습니다. CI에서는 yamllint의 truthy 규칙으로 yes, off 같은 값을 검사할 수 있습니다.
cron의 날짜와 요일 조건은 OR입니다
매달 첫 번째 월요일 오전 9시에 실행하려고 0 9 1-7 * MON을 쓰면, 1일부터 7일까지이면서 월요일인 날만 선택한다고 생각하기 쉽습니다. 하지만 crontab(5)에 따르면 날짜(day-of-month)와 요일(day-of-week) 필드가 모두 제한되어 있으면 둘 중 하나라도 맞을 때 실행합니다. 따라서 두 조건은 AND가 아니라 OR입니다.
Cron Expression Simulator에서 시작일을 2026년 9월 23일로 두고 실행 10회를 확인하면 9월 28일 월요일 다음으로 10월 1일부터 7일까지 매일 실행됩니다. 시뮬레이터는 해당 날짜들을 풀어 보여주고 두 필드가 OR 조건이라는 설명도 붙입니다.
첫 번째 월요일을 만들려면 한 필드는 제한하고 다른 조건은 작업 안에서 검사합니다. 글의 예시는 0 9 1-7 * * [ "$(date +\%u)" = 1 ] && /usr/local/bin/monthly-report입니다. date +%u는 월요일을 1로 출력합니다. crontab에서 이스케이프하지 않은 %는 줄바꿈으로 처리되고 나머지 내용은 명령의 표준 입력으로 들어가므로, \% 표기가 필요합니다.
실행 간격과 서머타임도 확인해야 합니다
*/7 * * * *는 매 7분 간격으로 계속 실행되는 표현이 아닙니다. 매 시간 0, 7, 14분처럼 실행하다가 56분 뒤 다음 시간 0분까지 4분 간격이 생깁니다. 하루 실행 횟수는 216회입니다. 60이 5로 나누어떨어지므로 */5에는 같은 간격 차이가 없습니다.
서머타임도 실행 시점을 바꿉니다. 30 1 * * *를 Europe/London에서 실행하면 2026년 3월 29일에는 시계가 00:59에서 02:00으로 넘어가 01:30이 존재하지 않습니다. 반대로 10월 25일에는 01:30이 두 번 나타납니다. 2월 30일을 지정한 0 0 30 2 *는 문법상 유효하지만 실행되는 날은 없습니다. 시뮬레이터는 8년 앞까지 검색해 결과가 없음을 알립니다.
다만 서머타임 처리는 스케줄러마다 다릅니다. Debian cron(8)은 건너뛴 시간대의 작업을 시계 변경 직후 실행하고, 시계가 3시간 미만 뒤로 움직일 때 반복된 시간대의 작업은 다시 실행하지 않는다고 설명합니다. 안전한 방법으로는 서머타임을 적용하는 지역의 새벽 시간대를 피하거나 UTC로 예약하는 방법을 제시합니다.
YAML에서는 문자열을 인용하고, cron에서는 날짜 조건을 한 필드에만 두는 식으로 의미를 명시해야 합니다. 작성한 설정을 파서나 스케줄러가 실제로 어떻게 처리하는지도 확인해야 합니다. 글에는 YAML Parsing Simulator와 Cron Expression Simulator 외에도 무료 DevOps 게임·시뮬레이터 50여 개가 있다고 적혀 있습니다.
dev.to 반응
- @analista_83 — 스키마 차이는 누군가의 기억에 맡길 게 아니라 CI에 넣어야 합니다. 저장소에서 파서와 스키마를 고정한 다음, NO, 0755, on:이 있는 작은 fixture를 불러 결과 타입을 검증하세요. 그러면 이런 종류의 버그를 운영 환경에서 갑자기 발견하는 대신 빌드 실패로 잡을 수 있습니다.
원문: dev.to / 번역·요약: Trawling