1 왜 YAML을 알아야 하는가
MINERVA 시리즈와 블로그 인프라 전반에 YAML이 분포한다.
| 용도 | 파일 | 의존 글 |
|---|---|---|
| RAGConfig 프로파일 | data/configs/default.yaml |
11-0편 |
| A/B 실험 정의 | data/experiments/*.yaml |
06편 |
| Docker Compose | docker-compose.yml |
07-0편 |
| GitHub Actions 워크플로 | .github/workflows/*.yml |
07-1편 |
| Kubernetes Deployment | k8s/*.yaml |
11-1편 |
| 블로그 사이트 설정 | _quarto.yml, _metadata.yml |
(블로그 자체) |
| Quarto 포스트 frontmatter | 모든 .qmd 파일 상단 |
(블로그 자체) |
같은 형식이지만 들여쓰기 함정·multiline string·anchor 같은 세부에서 자주 막힌다. 본 글이 그 토대를 한 호흡으로 정리한다.
2 핵심 데이터 타입
YAML(YAML Ain’t Markup Language)은 사람이 읽기 쉬운 데이터 직렬화 형식. 핵심 데이터 모델:
- scalar: 단일 값 (string, number, boolean, null)
- sequence: 순서 있는 리스트
- mapping: 키-값 쌍 (dict)
# scalar
name: minerva # string
version: 0.1.0 # string (자동 추론)
port: 8000 # int
temperature: 0.7 # float
debug: true # boolean
description: null # null (또는 ~ 또는 빈 값)
# sequence (list)
agents:
- qna_chatbot
- data_standardizer
- insilico_analyzer
# mapping (dict)
azure:
endpoint: https://my-resource.openai.azure.com/
api_version: "2024-12-01-preview"
models:
- gpt-4.1
- gpt-5-mini
# inline 형식 (JSON 같음)
inline_list: [a, b, c]
inline_dict: {host: localhost, port: 8000}scalar는 자동 타입 추론된다 — 8000은 int, "8000"은 string. 명확히 string으로 두려면 따옴표를 둘러싼다.
3 들여쓰기 — 공백만, 일관되게
# CORRECT — 공백 2칸
parent:
child:
grandchild: value
# WRONG — tab 사용 (YAML은 tab 금지)
parent:
child: valueYAML은 tab을 절대 사용 금지한다 (스펙 명시). 공백 2칸이 관행이고 4칸도 가능하나 한 파일 내에서 일관되어야 한다.
대부분의 IDE(VS Code, IntelliJ)는 YAML 플러그인으로 들여쓰기 오류를 즉시 시각화한다. 사용을 강력 권장.
4 따옴표 — 언제 쓰는가
# 1. 자동 추론 — 따옴표 없어도 됨
name: minerva
port: 8000
debug: true
# 2. 의도한 string인데 자동 추론으로 다른 타입이 되는 경우
version: "0.1.0" # 따옴표 없으면 일부 파서가 0.1로 해석
phone: "010-1234-5678" # 일부 파서가 산술식으로 해석 시도
boolean_string: "true" # 따옴표 없으면 boolean True
# 3. 특수 문자 포함
description: "콜론: 포함" # 따옴표 없으면 mapping으로 오인
template: "변수 ${VAR} 포함"
# 4. 작은따옴표 vs 큰따옴표
single: 'hello\nworld' # \n이 그대로 두 글자
double: "hello\nworld" # \n이 줄바꿈으로 해석관행: scalar에 의심이 가면 따옴표 두기. 큰따옴표는 escape 가능, 작은따옴표는 literal.
5 multiline string — | > 차이
긴 텍스트를 여러 줄로 작성할 때 4가지 스타일이 있다.
# 1. literal block — | (줄바꿈 보존)
description: |
Phase B에서 만든 LCEL Chain 파이프라인의 한계를 짚고,
LangGraph StateGraph가 그 한계를 어떻게 푸는지 정리한다.
Phase C-2 전환의 이론적 출발점이다.
# 결과: "Phase B에서 만든 LCEL Chain 파이프라인의 한계를 짚고,\nLangGraph...\nPhase..."
# 끝에 줄바꿈 1개 추가됨
# 2. folded block — > (줄바꿈을 공백으로 폴드)
description: >
Phase B에서 만든 LCEL Chain 파이프라인의 한계를 짚고,
LangGraph StateGraph가 그 한계를 어떻게 푸는지 정리한다.
# 결과: "Phase B에서 만든... 짚고, LangGraph... 정리한다.\n"
# 줄바꿈이 공백으로 변환됨
# 3. literal strip — |- (끝 줄바꿈 제거)
content: |-
no trailing newline
# 4. folded strip — >- (끝 줄바꿈 제거 + 폴드)
brief: >-
한 줄로 합쳐지고
끝에 줄바꿈 없음| 스타일 | 줄바꿈 | 끝 줄바꿈 | 사용처 |
|---|---|---|---|
\| |
보존 | 1개 추가 | 코드 블록, 시·구조 있는 텍스트 |
> |
공백으로 폴드 | 1개 추가 | 긴 설명문 (한 단락) |
\|- |
보존 | 제거 | 줄바꿈 의미 있되 끝 줄바꿈은 불필요 |
>- |
폴드 | 제거 | 한 줄로 합치고 끝 깔끔하게 |
Quarto 포스트 frontmatter의 description: | 패턴이 literal block이다. MINERVA 시리즈 모든 글이 이 패턴.
6 anchor & alias — & *
같은 값을 여러 곳에서 재사용하려면 anchor(&)로 정의하고 alias(*)로 참조한다.
# anchor 정의
defaults: &default_config
timeout: 60
max_retries: 3
log_level: INFO
# alias 사용 — 위 dict를 그대로 가져옴
production_config:
<<: *default_config # merge key (아래 절 참조)
timeout: 120 # override
development_config:
<<: *default_config
log_level: DEBUGGitHub Actions·docker-compose에서 자주 쓰는 패턴. 같은 환경변수를 여러 job에 반복 작성하지 않고 한 번 정의 후 alias.
# .github/workflows/example.yml
common-env: &common
PYTHONPATH: ./src
LOG_LEVEL: INFO
jobs:
test:
env:
<<: *common
DEBUG: true # 추가
build:
env:
<<: *common # 같은 env 재사용GitHub Actions는 anchor를 지원하지만, 다른 일부 CI(예: 일부 GitLab CI 버전)는 제한적. 사용 전 도구 문서 확인. 또한 yaml.safe_load(content)(Python PyYAML)는 anchor 지원 — 표준 YAML 1.1/1.2의 일부.
7 merge key — <<:
<<: *anchor는 anchor의 dict를 현재 dict에 병합한다.
base: &base
a: 1
b: 2
merged:
<<: *base # a, b 가져옴
c: 3 # 추가
b: 999 # b override
# 결과: {a: 1, b: 999, c: 3}여러 anchor를 동시에 병합:
defaults_a: &a
x: 1
defaults_b: &b
y: 2
combined:
<<: [*a, *b] # 둘 다 병합
z: 3
# 결과: {x: 1, y: 2, z: 3}merge key는 YAML 1.1 스펙의 확장 기능. 대부분 라이브러리가 지원하지만 엄격 1.2 모드에서는 제외될 수 있다.
8 환경변수 보간 — 도구별 차이
YAML 자체는 환경변수 보간을 정의하지 않는다. 도구마다 다른 문법을 추가한다.
| 도구 | 문법 | 예시 |
|---|---|---|
| docker-compose | ${VAR} 또는 ${VAR:-default} |
image: ${IMAGE:-minerva:latest} |
| GitHub Actions | ${{ env.VAR }} 또는 ${{ secrets.X }} |
key: ${{ secrets.AZURE_API_KEY }} |
| ansible | { var } (Jinja2) |
host: {{ inventory_hostname }} |
| MINERVA RAGConfig | ${VAR:default} (자체 구현) |
model: ${AZURE_DEPLOY:gpt-4.1} |
MINERVA 11-0편의 ${VAR:default}는 표준 docker-compose 문법(${VAR:-default})과 살짝 다른 자체 구현이다. 도구마다 정확한 문법 확인 필수.
9 JSON과의 관계
YAML 1.2부터 JSON은 YAML의 부분집합이다. 즉:
이 JSON은 그대로 유효한 YAML이다. inline mapping/sequence가 JSON 문법과 동일하기 때문.
이 호환성 덕분에:
- JSON 설정 파일을 YAML로 점진 마이그레이션 가능
- YAML 파서가 JSON도 읽음
- 일부 도구(GitHub Actions)는 yml 또는 json 둘 다 받음
차이점 — 둘 중 무엇을 쓸지:
| 기준 | YAML | JSON |
|---|---|---|
| 사람이 작성 | 들여쓰기 + multiline string으로 가독성 우수 | 따옴표·콤마 부담 |
| 기계 파싱 속도 | 느림 (들여쓰기·anchor 처리) | 빠름 |
| 주석 | # 가능 |
불가 |
| 사람이 직접 편집하는 설정 | 권장 | 비권장 |
| API 응답·로그 | 비권장 | 권장 |
운영 사용 — 사람이 작성·편집하는 설정은 YAML, 기계 간 데이터 교환은 JSON.
10 Python에서 YAML 읽기·쓰기
11 자주 발생하는 오류 패턴
CORRECT:
YAML은 tab을 절대 허용하지 않는다. IDE의 “Insert Spaces” 설정을 켜고 .editorconfig로 강제. 들여쓰기 보이지 않는 차이는 가장 흔한 함정.
CORRECT:
버전 번호·날짜·전화번호 같은 string은 따옴표로 감싼다. 자동 타입 추론이 의도와 다르게 동작.
CORRECT:
YAML 1.1은 yes/no/on/off도 boolean으로 추론한다. 1.2부터는 true/false만 boolean이지만 여전히 일부 파서가 1.1 호환 모드. 의심스러우면 따옴표.
CORRECT:
literal block(|) 안에서는 첫 줄의 들여쓰기 깊이가 기준이 되고, 그 이상 들여쓴 부분은 들여쓰기 자체가 본문에 포함된다. 의도가 아닌 한 모든 줄을 같은 깊이로.
import yaml
with open(user_uploaded_file) as f:
data = yaml.load(f, Loader=yaml.Loader) # !!python/object 태그로 코드 실행 가능CORRECT:
신뢰하지 않는 YAML(사용자 업로드, 외부 API 응답 등)은 safe_load를 사용한다. CVE 패턴.
12 정리
| 영역 | 핵심 |
|---|---|
| 데이터 타입 | scalar / sequence / mapping. 자동 타입 추론 |
| 들여쓰기 | 공백만 (tab 금지), 일관된 깊이 |
| 따옴표 | 의심 시 둘러싸기. 큰따옴표는 escape, 작은따옴표는 literal |
| multiline | \| 줄바꿈 보존, > 폴드, - 끝 줄바꿈 제거 |
| anchor·alias | & *로 재사용, <<: merge key로 병합 |
| 환경변수 보간 | 도구마다 문법 다름 (${VAR}, ${{ secrets.X }}, { var }) |
| JSON 호환 | JSON은 YAML 1.2의 부분집합 |
| Python 사용 | yaml.safe_load 필수 (보안) |
13 응용 분야
| MINERVA 사용처 | 본 글 절 |
|---|---|
data/configs/*.yaml (RAGConfig) |
기본 데이터 타입, multiline, 환경변수 보간 |
data/experiments/*.yaml (A/B arm) |
mapping + sequence, anchor로 공통 arm 추출 |
.github/workflows/*.yml |
merge key로 공통 env 재사용, 환경변수 보간 |
docker-compose.yml |
환경변수 보간 + anchor |
.qmd frontmatter |
multiline description: \| |
_quarto.yml |
깊은 mapping + sequence |
14 관련 주제
선행 학습
- 환경변수와 dotenv – YAML과 결합되는 환경변수 운영
- Python typing 심화 – YAML → Pydantic 모델 매핑
바로 이어 읽을 글 (Tier 2 다음 편)
- Python logging + structured log — 작성 예정
- Docker Compose 기초 — 작성 예정
MINERVA 시리즈 응용
- MINERVA Config 의존성 (11-0) – RAGConfig YAML 구조 +
${VAR:default}치환 - MINERVA A/B 실험 (06) – 실험 정의 YAML 구조
- MINERVA CI/CD GitHub Actions (07-1) – 워크플로 yml 4개
- GitHub Actions 워크플로 기초 – yml 기반 워크플로 정의