-
1. YAML이란?
YAML은 "YAML Ain't Markup Language" 의 재귀 약어입니다. 초기에는 "Yet Another Markup Language"로 불렸으나, XML 같은 마크업 언어와 구분하기 위해 현재 이름으로 바뀌었습니다.
공식 정의: 모든 프로그래밍 언어를 위한 사람 친화적 데이터 직렬화 언어(human-friendly data serialization language)
역사
연도 사건 2001 Clark Evans, Oren Ben-Kiki, Ingy döt Net이 최초 YAML 프레임워크 작성 2004 YAML 1.0 공식 스펙 발표 2009 YAML 1.2 발표 — JSON 완전 호환 강화 2021 YAML 1.2.2 발표 (현재 최신) — 오픈 소스 개발 방식 전환 핵심 특징
- 들여쓰기 기반 구조 — 중괄호/꺾쇠 대신 스페이스(탭 금지)로 계층 표현
- 주석 지원 —
#이후는 주석 - 3가지 기본 자료형 — 스칼라(Scalar), 시퀀스(Sequence), 매핑(Mapping)
- 앵커/앨리어스 —
&로 앵커 정의,*로 참조하여 중복 제거 - 멀티라인 문자열 — 리터럴(
|)과 폴드(>) 두 가지 스타일 - JSON 상위 호환 — 유효한 JSON은 곧 유효한 YAML
2. 언제 쓰면 좋은가
주요 Use Cases
용도 예시 설정 파일 서버 설정, 앱 설정, 환경별 config CI/CD 파이프라인 GitHub Actions, GitLab CI, CircleCI 컨테이너 오케스트레이션 Kubernetes 매니페스트, Docker Compose IaC (인프라 코드화) Ansible 플레이북, Terraform 변수 파일 API 명세 OpenAPI/Swagger 스펙 테스트 데이터/픽스처 시드 데이터, mock 응답, 테스트 케이스 YAML이 적합하지 않은 경우
- 고성능 데이터 교환 API → JSON이 파싱 속도 우위
- 단순 키-값만 필요 → INI가 더 직관적
- 임의 입력 파싱 시 보안 주의 → Python
yaml.load()대신yaml.safe_load()필수
3. 다른 파일 포맷과의 비교
항목 YAML JSON TOML INI XML 주석 ✅ ( #)❌ ✅ ( #)✅ ✅ ( <!-- -->)가독성 매우 높음 중간 높음 높음 낮음 중첩 구조 무제한 무제한 제한적 미지원 무제한 파싱 속도 느림 빠름 중간 빠름 느림 앵커/재사용 ✅ ❌ ❌ ❌ ❌ 날짜/시간 타입 ✅ 기본 지원 ❌ ✅ 기본 지원 ❌ ❌ 대표 용도 설정, K8s API, 웹 Rust/Python 패키지 레거시 앱 설정 엔터프라이즈 교환 - 동일 데이터를 각 포맷으로 표현 — 비교 예시 **YAML** ```yaml # 서버 설정 database: host: localhost port: 5432 features: - authentication - logging debug: false ``` **JSON** ```json { "database": { "host": "localhost", "port": 5432 }, "features": ["authentication", "logging"], "debug": false } ``` **TOML** ```toml debug = false [database] host = "localhost" port = 5432 features = ["authentication", "logging"] ``` **INI** ``` [database] host = localhost port = 5432 ; features 배열 표현 불가 ``` **XML** ```xml <config> <database> <host>localhost</host> <port>5432</port> </database> <features> <feature>authentication</feature> <feature>logging</feature> </features> <debug>false</debug> </config> ```
4. YAML 기본 문법
4-1. 스칼라 (Scalars)
# 문자열 name: Alice greeting: "Hello,\nWorld!" # 큰따옴표: 이스케이프 해석 literal: 'No \n escape' # 작은따옴표: 이스케이프 없음 # 숫자 count: 42 ratio: 3.14 hex: 0xFF # 255 # 불리언 (YAML 1.2 기준) active: true disabled: false # null nothing: null also_null: ~ # 타입 강제 지정 (!!) as_string: !!str 3.14 # 숫자를 문자열로 as_int: !!int "80" # 문자열을 정수로4-2. 매핑 (Mappings) — 딕셔너리
# 블록 스타일 person: name: Bob age: 30 address: city: Seoul country: KR # 플로우 스타일 (한 줄, JSON과 유사) point: { x: 10, y: 20 }4-3. 시퀀스 (Sequences) — 배열
# 블록 스타일 fruits: - apple - banana - cherry # 객체 배열 users: - name: Alice role: admin - name: Bob role: user # 플로우 스타일 colors: [red, green, blue]위 내용을 json으로 변환하면 아래와 같습니다.
# 블록 스타일 { "fruits": [ "apple", "banana", "cherry" ] } # 객체 배열 { "users": [ { "name": "Alice", "role": "admin" }, { "name": "Bob", "role": "user" } ] }4-4. 멀티라인 문자열
# | (리터럴): 줄바꿈 그대로 보존 script: | #!/bin/bash echo "Hello" npm install # 결과: "#!/bin/bash\necho \"Hello\"\nnpm install\n" # > (폴드): 줄바꿈을 공백으로 변환 description: > This is a long description that spans multiple lines. # 결과: "This is a long description that spans multiple lines.\n"수정자 의미 ` />`` - />-`` + />+`4-5. 앵커 & 앨리어스 — 중복 제거
# &anchor_name 으로 정의, *anchor_name 으로 참조 defaults: &defaults adapter: postgres host: localhost pool: 5 development: <<: *defaults # defaults 전체를 병합 database: myapp_dev production: <<: *defaults database: myapp_prod host: prod-db.example.com # 로컬 값이 앵커 값을 덮어씀 # 시퀀스(배열)일 때, -common: &common - run: echo "Hello" jobs: lint: runs-on: ubuntu-latest steps: - *common - run: npm run lint4-6. 복수 문서 (
---)--- # 첫 번째 문서 name: Document One type: config --- # 두 번째 문서 name: Document Two type: data
5. Helm에서의 YAML 기법
타입 강제 (
!!)age: !!str 21 # 정수 → 문자열 port: !!int "80" # 문자열 → 정수멀티라인 문자열 (Helm 템플릿에서)
coffee: | Latte Cappuccino Espresso # >- : 단일 줄로 폴드 + 마지막 줄바꿈 제거 coffee: >- Latte Cappuccino파일 내용 삽입 시 들여쓰기
myfile: | {{ .Files.Get "myfile.txt" | indent 2 }}템플릿 표현식 자체를 들여쓰면 첫 줄이 이중 들여쓰기됩니다.
indent N함수에 들여쓰기를 위임하세요.JSON as YAML (인라인 스타일)
YAML은 JSON의 상위집합이므로 혼합 표현이 가능합니다.
# 블록 스타일 coffee: - Latte - Cappuccino # JSON 인라인 스타일 (동일한 의미) coffee: ["Latte", "Cappuccino"]Helm에서 앵커 주의사항
6. Kubernetes (K8s) 매니페스트
4가지 필수 필드
apiVersion: apps/v1 # 사용할 API 그룹/버전 kind: Deployment # 리소스 종류 metadata: # 리소스 식별 정보 name: my-app namespace: production spec: # 원하는 상태 (desired state) ...- Deployment 예시
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25.3 ports: - containerPort: 80 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" env: - name: SECRET_KEY valueFrom: secretKeyRef: name: my-secret key: secret-key readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 10- Service 예시 (ClusterIP / NodePort / LoadBalancer)
# ClusterIP (기본값, 클러스터 내부만) apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80 --- # LoadBalancer (클라우드 외부 노출) apiVersion: v1 kind: Service metadata: name: nginx-lb spec: type: LoadBalancer selector: app: nginx ports: - port: 80 targetPort: 80- ConfigMap & Secret
apiVersion: v1 kind: ConfigMap metadata: name: app-config data: DATABASE_HOST: "postgres.internal" config.yaml: | server: port: 8080 log_level: info --- apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: username: YWRtaW4= # echo -n 'admin' | base64 password: cGFzc3dvcmQ=
하나의 파일에 여러 리소스 정의 —
---로 구분apiVersion: apps/v1 kind: Deployment metadata: name: my-app ... --- apiVersion: v1 kind: Service metadata: name: my-app-svc ...
7. GitHub Actions / CI/CD 파이프라인
GitHub Actions 워크플로우는
.github/workflows/*.yml파일로 정의합니다.- 기본 워크플로우 구조
name: CI Pipeline on: push: branches: [main, 'release/**'] pull_request: branches: [main] schedule: - cron: '0 2 * * *' env: NODE_VERSION: '20' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npm run build - uses: actions/upload-artifact@v4 with: name: build-output path: dist/- 잡 의존성(needs)과 매트릭스 전략
jobs: build: runs-on: ubuntu-latest steps: - run: npm run build test: needs: build runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: [18, 20, 22] os: [ubuntu-latest, windows-latest] exclude: - os: windows-latest node-version: 18 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm test deploy: needs: [build, test] runs-on: ubuntu-latest environment: production if: github.ref == 'refs/heads/main' steps: - name: Deploy env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: ./scripts/deploy.sh- 앵커로 반복 스텝 재사용
x-common-steps: &common-setup - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci jobs: lint: runs-on: ubuntu-latest steps: - *common-setup - run: npm run lint test: runs-on: ubuntu-latest steps: - *common-setup - run: npm test
8. Docker Compose
- 기본 Compose 파일 구조
name: my-application services: web: image: nginx:alpine ports: - "8080:80" depends_on: app: condition: service_healthy networks: - frontend - backend app: build: context: . dockerfile: Dockerfile environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 networks: - backend restart: unless-stopped db: image: postgres:16-alpine volumes: - postgres-data:/var/lib/postgresql/data environment: POSTGRES_DB: mydb POSTGRES_USER: user POSTGRES_PASSWORD: pass networks: - backend volumes: postgres-data: networks: frontend: backend: internal: true- 앵커를 활용한 Compose 최적화
# x- 접두사는 Compose가 무시하는 확장 필드 x-common-env: &common-env LOG_LEVEL: info TIMEZONE: Asia/Seoul x-resource-limits: &resource-limits deploy: resources: limits: cpus: '0.5' memory: 512M services: service-a: image: my-image:latest environment: <<: *common-env SERVICE_NAME: service-a <<: *resource-limits service-b: image: my-image:latest environment: <<: *common-env SERVICE_NAME: service-b <<: *resource-limits
9. Ansible 플레이북
- 플레이북 기본 구조
--- - name: Configure web servers hosts: webservers become: true vars: http_port: 80 packages: - nginx - git tasks: - name: Install required packages ansible.builtin.apt: name: "{{ packages }}" state: present update_cache: yes - name: Start and enable nginx ansible.builtin.service: name: nginx state: started enabled: yes notify: Reload nginx handlers: - name: Reload nginx ansible.builtin.service: name: nginx state: reloaded- 변수, 조건문, 반복문, 블록
tasks: # loop — 반복 - name: Create user accounts ansible.builtin.user: name: "{{ item.name }}" groups: "{{ item.groups }}" loop: "{{ users }}" # when — 조건 - name: Install Apache (Debian 계열만) ansible.builtin.apt: name: apache2 when: ansible_os_family == "Debian" # block — 그룹화 + 에러 처리 - name: Deploy application block: - name: Pull code ansible.builtin.git: repo: https://github.com/org/app.git dest: /var/www/app rescue: - name: Notify failure ansible.builtin.debug: msg: "Deployment failed!" always: - name: Cleanup ansible.builtin.file: path: /tmp/deploy state: absent
10. OpenAPI / Swagger 스펙
- 기본 OpenAPI 3.x 구조
openapi: 3.0.4 info: title: User Management API version: 1.0.0 servers: - url: https://api.example.com/v1 paths: /users: get: summary: 사용자 목록 조회 parameters: - name: page in: query schema: type: integer default: 1 responses: '200': content: application/json: schema: $ref: '#/components/schemas/UserList' /users/{userId}: get: parameters: - name: userId in: path required: true schema: type: string format: uuid responses: '200': content: application/json: schema: $ref: '#/components/schemas/User' components: schemas: User: type: object properties: id: type: string format: uuid email: type: string format: email UserList: type: object properties: data: type: array items: $ref: '#/components/schemas/User' total: type: integer securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT security: - BearerAuth: []
11. 프로그래밍 언어별 YAML 라이브러리
Python — PyYAML
import yaml # ✅ 항상 safe_load 사용 (yaml.load()는 임의 코드 실행 위험) with open('config.yaml', 'r') as f: config = yaml.safe_load(f) # 직렬화 data = {'server': {'host': 'localhost', 'port': 8080}} yaml_string = yaml.dump(data, default_flow_style=False, allow_unicode=True) # 멀티 도큐먼트 docs = list(yaml.safe_load_all("---\nname: doc1\n---\nname: doc2\n"))JavaScript/TypeScript — js-yaml
import yaml from 'js-yaml'; import fs from 'fs'; const config = yaml.load(fs.readFileSync('config.yaml', 'utf8')); const yamlString = yaml.dump({ name: 'app', version: '1.0.0' }, { indent: 2 });Go — gopkg.in/yaml.v3
import "gopkg.in/yaml.v3" type Config struct { Server struct { Host string `yaml:"host"` Port int `yaml:"port"` } `yaml:"server"` } var cfg Config yaml.Unmarshal(data, &cfg) // Strict mode: 알 수 없는 필드 에러 처리 decoder := yaml.NewDecoder(reader) decoder.KnownFields(true)
12. 모범 사례 및 주의사항
흔한 함정
# ⚠️ 1. Norway Problem — YAML 1.1에서 국가 코드가 boolean으로 해석 country: NO # false로 해석될 수 있음! country: "NO" # ✅ 따옴표로 감싸기 # ⚠️ 2. 버전 번호가 부동소수점으로 처리 version: 1.10 # 1.1과 같아질 수 있음 version: "1.10" # ✅ 문자열로 강제 # ⚠️ 3. 탭 사용 금지 — 항상 스페이스 # ⚠️ 4. 콜론 포함 문자열 message: Hello: World # 파싱 오류 message: "Hello: World" # ✅ # ⚠️ 5. null vs 빈 문자열 value: # null value: "" # 빈 문자열 (null 아님)따옴표 가이드라인
# 일반 문자열 — 따옴표 불필요 name: John Doe # 작은따옴표 — 백슬래시 그대로 유지 path: 'C:\Users\John' regex: '^\d{3}-\d{4}$' # 큰따옴표 — 이스케이프 시퀀스 필요 시 newline: "Line 1\nLine 2" tab: "Col1\tCol2"보안
DRY 원칙 — 앵커/머지 키 활용
_defaults: &defaults timeout: 30 retries: 3 production: <<: *defaults host: prod.example.com staging: <<: *defaults host: staging.example.com timeout: 60 # 오버라이드
참고 자료