Kubernetes 클러스터를 관리하거나, GitHub Actions 워크플로를 설정하거나, Docker Compose 파일을 작성한 적이 있다면, 이미 YAML을 작성한 겁니다 — 아마 꽤 많이요. YAML은 현대 DevOps 도구의 대부분을 구동하는 설정 형식입니다. 하지만 어디에나 있음에도 불구하고, 사람들을 계속 놀라게 하는 형식입니다. 저는 배포가 막힌 채로 밤 11시에 YAML 들여쓰기 오류를 디버깅한 적이 한두 번이 아닙니다. 여러분은 그런 상황을 겪지 않도록 해봅시다.

YAML은 YAML Ain't Markup Language의 약자입니다 — 맞아요, 재귀 약자입니다. 원래는 "Yet Another Markup Language"였지만, 문서 마크업 언어가 아닌 데이터 직렬화 형식임을 강조하기 위해 이름이 바뀌었습니다. YAML 1.2 사양이 현재 표준이지만, 많은 도구들이 아직 YAML 1.1을 구현하고 있으며, 이는 아래에서 다룰 몇 가지 진정으로 위험한 차이점이 있습니다.

기초: YAML 문법 한눈에 보기

YAML은 들여쓰기(공백만 — 탭은 절대 안 됨)를 사용하여 구조를 표현합니다. YAML 문서는 매핑(키-값 쌍), 시퀀스(목록), 또는 스칼라(단일 값)입니다. 모든 기본 사항을 다루는 실제 Kubernetes 스타일 설정입니다:

yaml
# This is a comment — YAML supports them, JSON does not
app:
  name: payment-service
  version: "2.1.0"         # quoted to keep it a string
  port: 8080
  debug: false
  timeout: 30.5
  tags:
    - payments
    - backend
    - critical
  database:
    host: postgres.internal
    port: 5432
    ssl: true
    password: null          # explicitly no value

중괄호도 없고, 대부분의 문자열에 따옴표도 없고, 쉼표도 없습니다. 매일 편집하는 파일에서 그 명확성은 빠르게 쌓입니다. 하지만 YAML은 그 가독성의 대가로 복잡성을 치르게 됩니다 — 내부에서 많은 일이 일어나고 있습니다.

스칼라 타입: 문자열, 숫자, 불리언, Null

YAML은 값의 모양에서 타입을 추론합니다. 대부분의 경우 편리하지만, 몇 가지 특정 경우에는 조용히 재앙이 될 수 있습니다.

yaml
# Strings — quotes are optional unless value is ambiguous
name: Alice
greeting: "Hello, world"
message: 'single quotes work too'
path: /usr/local/bin           # unquoted — still a string

# Numbers
integer: 42
negative: -7
float: 3.14159
scientific: 6.022e23

# Booleans
ssl_enabled: true
verbose: false

# Null
api_key: null
legacy_field: ~               # tilde is also null in YAML
노르웨이 문제: YAML 1.1(PyYAML, 많은 구형 도구에서 사용)에서 따옴표 없는 값 yes, no, on, off, true, false, y, n, Y, N — 및 대문자 변형들 — 이 모두 불리언으로 파싱됩니다. 이는 ISO 국가 코드 NO(노르웨이)가 불리언 false가 된다는 것을 의미합니다. YAML 1.2에서 이를 수정했습니다. 불리언처럼 보이는 문자열은 항상 따옴표로 감싸세요. 전체 배경 이야기는 노르웨이 문제에 대한 위키피디아 항목을 참조하세요.
yaml
# YAML 1.1 implicit type coercion — these silently become booleans:
country: NO       # → false  (Norway Problem!)
enabled: yes      # → true
toggle: on        # → true

# The fix: always quote when there's any ambiguity
country: "NO"
enabled: "yes"
version: "1.0"    # also quote version numbers — 1.0 would be a float

시퀀스: YAML의 목록

목록은 하이픈-공백 접두사를 사용합니다. 각 항목은 스칼라, 매핑, 또는 다른 시퀀스가 될 수 있습니다:

yaml
# Simple list
languages:
  - Python
  - Go
  - TypeScript

# List of objects (common in Kubernetes)
containers:
  - name: api
    image: my-api:latest
    port: 8080
  - name: sidecar
    image: envoy:v1.28
    port: 9901

# Inline flow style (valid YAML, less readable)
tags: [payments, backend, v2]

여러 줄 문자열: 리터럴 블록 vs 접힌 블록

여기서 YAML이 설정 파일에서 JSON보다 앞서 나갑니다. 두 가지 특별 연산자가 여러 줄 문자열을 우아하게 처리합니다:

yaml
# Literal block scalar (|) — preserves newlines exactly
startup_script: |
  #!/bin/bash
  set -e
  echo "Starting service..."
  npm start

# Folded scalar (>) — folds newlines into spaces (good for long descriptions)
description: >
  This service handles payment processing
  for all regions. It connects to Stripe
  and falls back to PayPal.
# Result: "This service handles payment processing for all regions. It connects to Stripe and falls back to PayPal."

# Chomp modifiers
keep_trailing: |+
  line one
  line two

strip_trailing: |-
  line one
  line two

| 연산자는 GitHub Actions의 여러 줄 run: 스크립트를 읽기 쉽게 만드는 것입니다. yaml-multiline.info 치트 시트는 어떤 청크 수정자가 무엇을 하는지 가장 빠르게 기억하는 방법입니다. 이러한 블록 스칼라 없이는 단일 문자열에 이스케이프된 줄 바꿈을 작성해야 했을 것입니다 — JSON이 강제하는 방식이죠.

탭 vs 공백 — 기본 규칙

YAML은 들여쓰기에 탭을 금지합니다. 완전히요. 에디터가 YAML 파일의 들여쓰기에 탭 문자를 삽입하면, 파일 파싱이 실패하거나 — 더 나쁘게는 — 잘못 파싱됩니다. 에디터를 .yml.yaml 파일에 공백을 사용하도록 설정하세요. 2칸 공백이 거의 보편적인 관례입니다.

이것은 모호한 YAML 오류의 #1 원인입니다. 탭은 대부분의 에디터에서 공백과 동일하게 보입니다. YAML을 디버깅할 때 에디터에서 "공백 문자 표시"를 활성화하거나, 파일을 YAML 유효성 검사기에 붙여넣어 정확한 줄을 가리키는 명확한 오류 메시지를 받으세요.

실제 예제: GitHub Actions 워크플로

실제 GitHub Actions 워크플로 스니펫입니다. 여러 줄 run: 블록이 리터럴 블록 스칼라를 사용하는 방식과, 들여쓰기가 구조를 결정하는 방식을 주목하세요:

yaml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: |
          npm run lint
          npm run test -- --coverage
          npm run build

실제 예제: Kubernetes 배포 매니페스트

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
  namespace: production
  labels:
    app: payment-api
    version: "2.1.0"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-api
  template:
    metadata:
      labels:
        app: payment-api
    spec:
      containers:
        - name: api
          image: payment-api:2.1.0
          ports:
            - containerPort: 8080
          env:
            - name: NODE_ENV
              value: production
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"

Python과 JavaScript에서 YAML 파싱

Python에서는 PyYAML 라이브러리가 표준 선택지입니다. 항상 safe_load()를 사용하고 load()는 사용하지 마세요 — 안전하지 않은 버전은 YAML 파일에서 임의의 Python 코드를 실행할 수 있습니다:

python
import yaml

# Reading a YAML config file
with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)

print(config['app']['name'])        # payment-service
print(config['app']['port'])        # 8080 (integer, not string)
print(config['app']['tags'])        # ['payments', 'backend', 'critical']

# Writing Python data to YAML
data = {
    'service': 'auth-api',
    'replicas': 2,
    'endpoints': ['/login', '/logout', '/refresh']
}
with open('output.yaml', 'w') as f:
    yaml.dump(data, f, default_flow_style=False)

JavaScript/Node.js에서는 js-yaml이 가장 널리 사용되는 라이브러리입니다:

js
import yaml from 'js-yaml';
import fs from 'fs';

// Parse YAML string
const raw = fs.readFileSync('config.yaml', 'utf8');
const config = yaml.load(raw);

console.log(config.app.name);     // payment-service
console.log(config.app.tags);     // ['payments', 'backend', 'critical']

// Dump an object to YAML string
const output = yaml.dump({
  name: 'my-service',
  port: 3000,
  tags: ['api', 'public']
});
console.log(output);

마무리

YAML의 들여쓰기 기반 문법, 주석 지원, 읽기 쉬운 여러 줄 문자열은 사람이 작성하고 유지 관리하는 설정 파일에 적합한 선택입니다. 하지만 진짜 함정들이 있습니다: 노르웨이 문제, 탭/공백 혼란, YAML 1.1 파서의 암묵적 타입 강제. 규칙을 알고, 모호한 것은 따옴표로 감싸고, 공백을 표시하도록 에디터를 설정하고, 파일을 일관되게 유지하기 위해 YAML 포매터를 사용하세요. YAML은 어디서 물리는지 이해하면 강력한 도구입니다.