Ver3.0 ๐ฏ YAML ์ค์ ํ์ผ๊ณผ ๋ฐ์ดํฐ ์ง๋ ฌํ๋ฅผ ์ํ ์ธ๊ฐ ์นํ์ ํฌ๋งท ์๋ฒฝ ๊ฐ์ด๋

๐ฏ YAML ์ค์ ํ์ผ๊ณผ ๋ฐ์ดํฐ ์ง๋ ฌํ๋ฅผ ์ํ ์ธ๊ฐ ์นํ์ ํฌ๋งท ์๋ฒฝ ๊ฐ์ด๋
ํ๋ก๊ทธ๋๋ฐ ์ธ๊ณ์์ ๊ฐ์ฅ ์ฝ๊ธฐ ์ฌ์ด ๋ฐ์ดํฐ ํ์์ ๋ง์คํฐํ์! ๐
๐ YAML์ด ๋ญ๊ธธ๋ ์ด๋ ๊ฒ ์ธ๊ธฐ๊ฐ ๋ง์๊น?
์น๊ตฌ์ผ, ํ๋ก๊ทธ๋๋ฐ ํ๋ค ๋ณด๋ฉด ์ค์ ํ์ผ ๋๋ฌธ์ ๋จธ๋ฆฌ ์ํ ์ ๋ง์ง? JSON์ ๊ดํธ์ ๋ฐ์ดํ ๋๋ฌธ์ ๋์ด ์ํ๊ณ , XML์ ํ๊ทธ๊ฐ ๋๋ฌด ๋ง์์ ์ ์ ์ค์ํ ๋ด์ฉ์ด ๋ญ์ง ์ฐพ๊ธฐ ํ๋ค๊ณ ๋ง์ด์ผ. ๐ต
๊ทธ๋ฐ๋ฐ YAML(YAML Ain't Markup Language)์ด๋ผ๋ ๋
์์ด ๋ฑ์ฅํ๋ฉด์ ๋ชจ๋ ๊ฒ ๋ฌ๋ผ์ก์ด! ์ด๋ฆ๋ถํฐ๊ฐ ์ฌ๋ฏธ์์ง? "YAML์ ๋งํฌ์
์ธ์ด๊ฐ ์๋๋ค"๋ผ๋ ์ฌ๊ท์ ์ฝ์ด์ธ๋ฐ, ์๋๋ "Yet Another Markup Language"์๋ค๊ฐ ๋์ค์ ๋ฐ๋ ๊ฑฐ์ผ. ์ด๋ฆ๋ง ๋ด๋ ๊ฐ๋ฐ์๋ค์ ์ ๋จธ ๊ฐ๊ฐ์ด ๋๊ปด์ง์ง ์์? ๐ญ
YAML์ 1990๋
๋ ํ๋ฐ๋ถํฐ ๊ฐ๋ฐ๋๊ธฐ ์์ํด์ 2001๋
์ ์ฒ์ ๊ณต๊ฐ๋์ด. ํด๋ผํฌ ์๋ฐ์ค(Clark Evans), ์ด๋
ธ ๋ํธ ๋ท(Ingy dรถt Net), ์ค๋ ๋ฒค-์ผ์ดํค(Oren Ben-Kiki) ์ด ์ธ ๋ช
์ ๊ฐ๋ฐ์๊ฐ ๋ง๋ค์๋๋ฐ, ์ด๋ค์ ๋ชฉํ๋ ๋จ์ํ์ด. "์ฌ๋์ด ์ฝ๊ธฐ ์ฝ๊ณ , ๊ธฐ๊ณ๋ ์ฒ๋ฆฌํ๊ธฐ ์ฌ์ด ๋ฐ์ดํฐ ์ง๋ ฌํ ํฌ๋งท์ ๋ง๋ค์!" ๐ฏ
์๋ฅผ ๋ค์ด, ๊ฒ์ ์บ๋ฆญํฐ์ ๋ ๋ฒจ, ์์ดํ , ์์น ์ ๋ณด ๊ฐ์ ๊ฑธ ํ์ผ๋ก ์ ์ฅํ๋ค๊ฐ ๋์ค์ ๋ถ๋ฌ์ค๋ ๊ฒ๋ ์ง๋ ฌํ์ ํ ์์์ผ. ๐ฎ
YAML์ ๊ฐ์ฅ ํฐ ํน์ง์ ๋ค์ฌ์ฐ๊ธฐ(indentation)๋ก ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ํํํ๋ค๋ ๊ฑฐ์ผ. Python ์ข์ํ๋ ์ฌ๋๋ค์ ๋ฐ๋ก ์น์ํ๊ฒ ๋๊ปด์ง ๊ฑฐ์ผ! ์ค๊ดํธ๋ ๋๊ดํธ ๊ฐ์ ํน์ ๋ฌธ์๋ฅผ ์ต์ํํ๊ณ , ๊ณต๋ฐฑ๊ณผ ์ค๋ฐ๊ฟ๋ง์ผ๋ก ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋๊น ์ ๋ง ๊น๋ํ์ง. โจ
๐ YAML์ ๊ธฐ๋ณธ ๋ฌธ๋ฒ - ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐฐ์๋ณด์!
YAML์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ํค-๊ฐ ์์ด์ผ. ์ฝ๋ก (:)์ผ๋ก ๊ตฌ๋ถํ๊ณ , ์ฝ๋ก ๋ค์๋ ๋ฐ๋์ ๊ณต๋ฐฑ์ด ํ๋ ์์ด์ผ ํด!
name: ํ๊ธธ๋
age: 25
city: ์์ธ
is_student: true
score: 95.5
๋ณด๋ค์ํผ ๋ฐ์ดํ ์์ด๋ ๋ฌธ์์ด์ ์ธ ์ ์์ด. ์ซ์๋ ์๋์ผ๋ก ์ซ์ ํ์ ์ผ๋ก ์ธ์๋๊ณ , true/false๋ ๋ถ๋ฆฌ์ธ ๊ฐ์ผ๋ก ์ฒ๋ฆฌ๋ผ. ์ ๋ง ์ง๊ด์ ์ด์ง? ๐
๋ฆฌ์คํธ๋ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ํํํ ์ ์์ด. ํ์ดํ(-)์ ์ฌ์ฉํ๋ ๋ธ๋ก ์คํ์ผ๊ณผ ๋๊ดํธ๋ฅผ ์ฌ์ฉํ๋ ํ๋ก์ฐ ์คํ์ผ์ด์ผ.
# ๋ธ๋ก ์คํ์ผ (๋ ์ฝ๊ธฐ ์ฌ์!)
fruits:
- ์ฌ๊ณผ
- ๋ฐ๋๋
- ์ค๋ ์ง
- ํฌ๋
# ํ๋ก์ฐ ์คํ์ผ (ํ ์ค๋ก ๊ฐ๋จํ๊ฒ)
colors: [๋นจ๊ฐ, ํ๋, ๋
ธ๋, ์ด๋ก]
๊ฐ์ธ์ ์ผ๋ก๋ ๋ธ๋ก ์คํ์ผ์ ์ถ์ฒํด! ํญ๋ชฉ์ด ๋ง์์ง๋ฉด ํ๋ก์ฐ ์คํ์ผ์ ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง๊ฑฐ๋ . ๐
YAML์ ์ง์ง ๊ฐ๋ ฅํจ์ ์ฌ๊ธฐ์ ๋ํ๋! ๋ค์ฌ์ฐ๊ธฐ๋ก ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํํํ ์ ์์ด. ๋ณดํต 2์นธ ๋๋ 4์นธ ๋ค์ฌ์ฐ๊ธฐ๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ผ๊ด์ฑ๋ง ์ ์งํ๋ฉด ๋ผ.
person:
name: ๊น๊ฐ๋ฐ
age: 30
address:
city: ๋ถ์ฐ
district: ํด์ด๋๊ตฌ
zipcode: 12345
hobbies:
- ์ฝ๋ฉ
- ๋
์
- ์ฌํ
skills:
programming:
- Python
- JavaScript
- Java
languages:
- ํ๊ตญ์ด
- ์์ด
์ด๋ ๊ฒ ๋ณต์กํ ๊ตฌ์กฐ๋ ๋ค์ฌ์ฐ๊ธฐ๋ง์ผ๋ก ๋ช ํํ๊ฒ ํํํ ์ ์์ด! JSON์ผ๋ก ๊ฐ์ ๊ฑธ ํํํ๋ฉด ๊ดํธ ์ง์ฅ์ด ํผ์ณ์ง๋๋ฐ ๋ง์ด์ผ. ๐ฅ
YAML์ ๋ฌธ์์ด์ ๋ค๋ฃจ๋ ๋ฐฉ์์ด ์ ๋ง ์ ์ฐํด! ์ฌ๋ฌ ์ค ๋ฌธ์์ด์ ํํํ๋ ๋ฐฉ๋ฒ์ด ๋ช ๊ฐ์ง ์์ด.
# ํ์ดํ(|) - ์ค๋ฐ๊ฟ ์ ์ง
description: |
์ด๊ฒ์ ์ฌ๋ฌ ์ค๋ก ๋
์ค๋ช
ํ
์คํธ์
๋๋ค.
์ค๋ฐ๊ฟ์ด ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.
# ๊บพ์ (>) - ์ค๋ฐ๊ฟ์ ๊ณต๋ฐฑ์ผ๋ก ๋ณํ
summary: >
์ด ํ
์คํธ๋ ์ฌ๋ฌ ์ค๋ก
์์ฑ๋์์ง๋ง ์ค์ ๋ก๋
ํ ์ค๋ก ํฉ์ณ์ง๋๋ค.
# ๋ฐ์ดํ ์ฌ์ฉ
message: "ํน์๋ฌธ์๋ฅผ ํฌํจํ ๋ฌธ์์ด: ์ฝ๋ก (:)์ด๋ ํด์(#) ๊ฐ์ ๊ฒ๋ค"
path: 'C:\Users\Documents\file.txt' # ์ญ์ฌ๋์๋ ๋ฌธ์ ์์ด!
ํนํ ํ์ดํ(|)์ ๊บพ์ (>) ๊ธฐํธ๋ ๊ธด ํ ์คํธ๋ฅผ ๋ค๋ฃฐ ๋ ์ ๋ง ์ ์ฉํด. ์ค์ ํ์ผ์ SQL ์ฟผ๋ฆฌ๋ ๊ธด ์ค๋ช ์ ๋ฃ์ ๋ ์์ฃผ ์ฌ์ฉํ์ง! ๐ช
YAML์์ ์ฃผ์์ ํด์(#) ๊ธฐํธ๋ก ์์ํด. ์ฝ๋๋ฅผ ์ค๋ช ํ๊ฑฐ๋ ์์๋ก ๋นํ์ฑํํ ๋ ์ ์ฉํ์ง!
# ์ด๊ฒ์ ์ฃผ์์
๋๋ค
name: ํ๋ก์ ํธ๋ช
# ์ธ๋ผ์ธ ์ฃผ์๋ ๊ฐ๋ฅํด์
# ์ฌ๋ฌ ์ค ์ฃผ์์ ์ฐ๊ณ ์ถ๋ค๋ฉด
# ๊ฐ ์ค๋ง๋ค # ๊ธฐํธ๋ฅผ ๋ถ์ฌ์ผ ํด์
# ํ์ง๋ง ์ฝ๊ธฐ๋ ์ฝ์ฃ ?
database:
host: localhost
port: 5432
# username: admin # ์์๋ก ๋นํ์ฑํ
๐ง YAML์ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค
๊ฐ์ ๋ด์ฉ์ ์ฌ๋ฌ ๋ฒ ๋ฐ๋ณตํด์ ์ฐ๊ธฐ ๊ท์ฐฎ์ ๋๊ฐ ์์ง? YAML์ ์ต์ปค(&)์ ๋ณ์นญ(*)์ ์ฌ์ฉํด์ ์ค๋ณต์ ์ ๊ฑฐํ ์ ์์ด!
# ๊ธฐ๋ณธ ์ค์ ์ ์ต์ปค๋ก ์ ์
defaults: &default_settings
timeout: 30
retry: 3
log_level: info
# ๊ฐ๋ฐ ํ๊ฒฝ - ๊ธฐ๋ณธ ์ค์ ์ฌ์ฉ
development:
<<: *default_settings
debug: true
# ํ๋ก๋์
ํ๊ฒฝ - ๊ธฐ๋ณธ ์ค์ ์ฌ์ฉํ๋ ์ผ๋ถ ๋ฎ์ด์ฐ๊ธฐ
production:
<<: *default_settings
timeout: 60
log_level: warning
&default_settings๋ก ์ต์ปค๋ฅผ ๋ง๋ค๊ณ , *default_settings๋ก ์ฐธ์กฐํ๋ ๊ฑฐ์ผ. <<:๋ ๋ณํฉ(merge) ํค๋ก, ์ฐธ์กฐํ ๋ด์ฉ์ ํ์ฌ ๋งต์ ํฉ์ณ์ฃผ์ง. ์ ๋ง ๋๋ํ์ง? ๐ง
YAML์ ์๋์ผ๋ก ํ์ ์ ์ถ๋ก ํ์ง๋ง, ๋๋ก๋ ๋ช ์์ ์ผ๋ก ์ง์ ํด์ผ ํ ๋๊ฐ ์์ด.
# ๋ฌธ์์ด๋ก ๊ฐ์ ๋ณํ
version: !!str 1.0
port: !!str 8080
# ์ ์๋ก ๋ช
์
count: !!int "100"
# ๋ถ๋์์์
price: !!float 19.99
# null ๊ฐ
empty_value: !!null
# ๋ ์ง์ ์๊ฐ
created_at: 2024-01-15
timestamp: 2024-01-15T10:30:00Z
์ผ๋ฐ์ ์ผ๋ก๋ ๋จ์ํ ๋ฌธ์์ด์ ํค๋ก ์ฌ์ฉํ์ง๋ง, ๋ณต์กํ ํค๋ ๊ฐ๋ฅํด!
? - ๋ณต์กํ
- ํค
: ๊ฐ
? [๋ฐฐ์ด, ํค]
: ๋ฐฐ์ด์ ํค๋ก ์ฌ์ฉ
# ๋ ์ค์ฉ์ ์ธ ์์
? !!python/tuple [์์ธ, ๊ฐ๋จ๊ตฌ]
: ์ฐํธ๋ฒํธ: 12345
๋ฌผ๋ก ์ด๋ฐ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ์ค๋ฌด์์ ์์ฃผ ์ฐ์ด์ง ์์. ํ์ง๋ง ์์๋๋ฉด ์ธ์ ๊ฐ ์ ์ฉํ ๋๊ฐ ์์ ๊ฑฐ์ผ! ๐
๐ YAML vs JSON vs XML - ๋ญ ์จ์ผ ํ ๊น?
๋ฐ์ดํฐ ์ง๋ ฌํ ํฌ๋งท์ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์์ด. ๊ฐ๊ฐ์ ์ฅ๋จ์ ์ ๋น๊ตํด๋ณด์!
| ํน์ง | YAML | JSON | XML |
|---|---|---|---|
| ๊ฐ๋ ์ฑ | โญโญโญโญโญ ์ต๊ณ ! | โญโญโญ ๊ด์ฐฎ์ | โญโญ ํ๊ทธ๊ฐ ๋ง์ ๋ณต์ก |
| ์์ฑ ํธ์์ฑ | โญโญโญโญโญ ๋งค์ฐ ์ฌ์ | โญโญโญ ๊ดํธ ๊ด๋ฆฌ ํ์ | โญโญ ํ๊ทธ ์์ฑ ๋ฒ๊ฑฐ๋ก์ |
| ํ์ฑ ์๋ | โญโญโญ ๋ณดํต | โญโญโญโญโญ ๋งค์ฐ ๋น ๋ฆ | โญโญ ๋๋ฆผ |
| ์ฃผ์ ์ง์ | โ ์ง์ | โ ๋ฏธ์ง์ | โ ์ง์ |
| ๋ฐ์ดํฐ ํฌ๊ธฐ | โญโญโญโญ ์์ | โญโญโญ ๋ณดํต | โญโญ ํผ |
| ๋ธ๋ผ์ฐ์ ์ง์ | โ ๋ณ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ | โ ๋ค์ดํฐ๋ธ ์ง์ | โ ๋ค์ดํฐ๋ธ ์ง์ |
| ์ฃผ์ ์ฉ๋ | ์ค์ ํ์ผ, CI/CD | API, ์น ํต์ | ๋ฌธ์, ๋ ๊ฑฐ์ ์์คํ |
โข ์ค์ ํ์ผ์ ์์ฑํ ๋ (Docker, Kubernetes, CI/CD ํ์ดํ๋ผ์ธ)
โข ์ฌ๋์ด ์ง์ ํธ์งํด์ผ ํ๋ ํ์ผ
โข ์ฃผ์์ด ํ์ํ ๊ฒฝ์ฐ
โข ๋ณต์กํ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํํํ ๋
JSON์ ์ ํํด์ผ ํ ๋:
โข REST API ์๋ต/์์ฒญ ๋ฐ์ดํฐ
โข ์น ๋ธ๋ผ์ฐ์ ์ ํต์ ํ ๋
โข ํ์ฑ ์๋๊ฐ ์ค์ํ ๊ฒฝ์ฐ
โข ํ๋ก๊ทธ๋จ ๊ฐ ๋ฐ์ดํฐ ๊ตํ
XML์ ์ ํํด์ผ ํ ๋:
โข ๋ ๊ฑฐ์ ์์คํ ๊ณผ์ ํธํ์ฑ์ด ํ์ํ ๋
โข ๋ณต์กํ ๋ฌธ์ ๊ตฌ์กฐ (SOAP, RSS)
โข ์คํค๋ง ๊ฒ์ฆ์ด ์ค์ํ ๊ฒฝ์ฐ
โข ๋ค์์คํ์ด์ค๊ฐ ํ์ํ ๊ฒฝ์ฐ
์ค์ ๋ก ํ๋ ๊ฐ๋ฐ ํ๊ฒฝ์์๋ YAML๊ณผ JSON์ ์ํฉ์ ๋ง๊ฒ ํผ์ฉํ๋ ๊ฒ ์ผ๋ฐ์ ์ด์ผ. ์ค์ ์ YAML๋ก, API ํต์ ์ JSON์ผ๋ก ํ๋ ์์ด์ง! ๐
๐ ๏ธ ์ค์ ! YAML ํ์ฉ ์ฌ๋ก
Docker Compose๋ YAML์ ๋ํ์ ์ธ ํ์ฉ ์ฌ๋ก์ผ. ์ฌ๋ฌ ์ปจํ ์ด๋๋ฅผ ์ ์ํ๊ณ ๊ด๋ฆฌํ๋ ๋ฐ YAML๋ง ํ ๊ฒ ์์ง!
version: '3.8'
services:
# ์น ์ ํ๋ฆฌ์ผ์ด์
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- DEBUG=true
- DATABASE_URL=postgresql://db:5432/myapp
depends_on:
- db
- redis
networks:
- backend
# ๋ฐ์ดํฐ๋ฒ ์ด์ค
db:
image: postgres:14
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret123
networks:
- backend
# ์บ์ ์๋ฒ
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- backend
volumes:
postgres_data:
networks:
backend:
driver: bridge
์ด๋ ๊ฒ ๋ณต์กํ ์ธํ๋ผ ๊ตฌ์ฑ์ YAML ํ๋๋ก ๊น๋ํ๊ฒ ์ ์ํ ์ ์์ด! ๋ค์ฌ์ฐ๊ธฐ๋ง ๋ด๋ ์ด๋ค ์๋น์ค๊ฐ ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋๋์ง ํ๋์ ๋ณด์ด์ง? ๐
Kubernetes๋ YAML์ ์ ์ฉํ๋ ๋ํ์ ์ธ ํ๋ซํผ์ด์ผ. ํด๋ผ์ฐ๋ ๋ค์ดํฐ๋ธ ์๋์ ํ์ ๋๊ตฌ์ง!
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
version: v1.0
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: myregistry/my-app:1.0
ports:
- containerPort: 8080
env:
- name: ENV
value: "production"
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
--- ๊ตฌ๋ถ์๋ก ์ฌ๋ฌ ๋ฆฌ์์ค๋ฅผ ํ ํ์ผ์ ์ ์ํ ์ ์์ด. ์ ๋ง ํธ๋ฆฌํ์ง! ๐
GitHub Actions๋ YAML๋ก ์ํฌํ๋ก์ฐ๋ฅผ ์ ์ํด. ์๋ํ์ ํต์ฌ์ด์ง!
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
steps:
- name: ์ฝ๋ ์ฒดํฌ์์
uses: actions/checkout@v3
- name: Node.js ์ค์
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: ์์กด์ฑ ์ค์น
run: npm ci
- name: ๋ฆฐํธ ๊ฒ์ฌ
run: npm run lint
- name: ํ
์คํธ ์คํ
run: npm test
- name: ์ปค๋ฒ๋ฆฌ์ง ์
๋ก๋
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: ์ฝ๋ ์ฒดํฌ์์
uses: actions/checkout@v3
- name: ํ๋ก๋์
๋ฐฐํฌ
run: |
echo "ํ๋ก๋์
ํ๊ฒฝ์ ๋ฐฐํฌ ์ค..."
# ๋ฐฐํฌ ์คํฌ๋ฆฝํธ ์คํ
- name: ์ฌ๋ ์๋ฆผ
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค!'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
์ด๋ ๊ฒ ๋ณต์กํ CI/CD ํ์ดํ๋ผ์ธ๋ YAML๋ก ๋ช ํํ๊ฒ ํํํ ์ ์์ด. ๊ฐ ๋จ๊ณ๊ฐ ๋ฌด์์ ํ๋์ง ์ฃผ์ ์์ด๋ ์ดํดํ ์ ์์ง! ๐ก
๋ง์ ํ๋ ์์ํฌ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ YAML์ ์ค์ ํ์ผ ํ์์ผ๋ก ์ฑํํ๊ณ ์์ด. Spring Boot ์์๋ฅผ ๋ณผ๊น?
# application.yml
spring:
application:
name: my-awesome-app
profiles:
active: development
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: ${DB_USER:admin}
password: ${DB_PASSWORD:secret}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
timeout: 2000ms
cache:
type: redis
redis:
time-to-live: 600000
logging:
level:
root: INFO
com.mycompany: DEBUG
org.hibernate.SQL: DEBUG
file:
name: logs/application.log
max-size: 10MB
max-history: 30
server:
port: 8080
compression:
enabled: true
error:
include-message: always
include-stacktrace: on_param
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
${DB_USER:admin} ๊ฐ์ ํ๊ฒฝ ๋ณ์ ์ฐธ์กฐ๋ ๊ฐ๋ฅํด! ๊ธฐ๋ณธ๊ฐ๋ ์ค์ ํ ์ ์์ด์ ์ ๋ง ์ ์ฐํ์ง. ๐จ
์ธํ๋ผ ์๋ํ ๋๊ตฌ์ธ Ansible๋ YAML์ ์ฌ์ฉํด. ํ๋ ์ด๋ถ(Playbook)์ด๋ผ๊ณ ๋ถ๋ฅด์ง!
---
- name: ์น ์๋ฒ ์ค์ ๋ฐ ๋ฐฐํฌ
hosts: webservers
become: yes
vars:
app_name: my-web-app
app_version: 1.2.3
deploy_dir: /var/www/{{ app_name }}
tasks:
- name: ํ์ ํจํค์ง ์ค์น
apt:
name:
- nginx
- python3
- python3-pip
state: present
update_cache: yes
- name: ์ ํ๋ฆฌ์ผ์ด์
๋๋ ํ ๋ฆฌ ์์ฑ
file:
path: "{{ deploy_dir }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ฐฐํฌ
git:
repo: https://github.com/mycompany/{{ app_name }}.git
dest: "{{ deploy_dir }}"
version: "v{{ app_version }}"
notify: restart nginx
- name: Python ์์กด์ฑ ์ค์น
pip:
requirements: "{{ deploy_dir }}/requirements.txt"
virtualenv: "{{ deploy_dir }}/venv"
- name: Nginx ์ค์ ํ์ผ ๋ณต์ฌ
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: restart nginx
- name: Nginx ์ฌ์ดํธ ํ์ฑํ
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
์๋ฒ ์ค์ ๋ถํฐ ๋ฐฐํฌ๊น์ง ๋ชจ๋ ๊ณผ์ ์ YAML๋ก ์ ์ํ๋๊น ์ ๋ง ๋ช ํํ์ง? ์ด๊ฒ ๋ฐ๋ก Infrastructure as Code(IaC)์ ํ์ด์ผ! ๐ช
โ ๏ธ YAML ์ฌ์ฉ ์ ์ฃผ์์ฌํญ๊ณผ ํจ์ ๋ค
YAML์ ์ ๋ง ์ข์ ํฌ๋งท์ด์ง๋ง, ๋ช ๊ฐ์ง ์ฃผ์ํ ์ ์ด ์์ด. ์ด๊ฑธ ๋ชจ๋ฅด๋ฉด ๋๋ฒ๊น ์ง์ฅ์ ๋น ์ง ์ ์์ด! ๐ฑ
YAML์ ๊ฐ์ฅ ํฐ ์ฝ์ ์ด์ ๊ฐ์ฅ ํํ ์๋ฌ ์์ธ์ด์ผ. ๋ค์ฌ์ฐ๊ธฐ๊ฐ ํ๋๋ผ๋ ํ๋ฆฌ๋ฉด ํ์ฑ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฑฐ๋ !
# โ ์๋ชป๋ ์์ - ๋ค์ฌ์ฐ๊ธฐ ๋ถ์ผ์น
person:
name: ํ๊ธธ๋
age: 25 # 3์นธ ๋ค์ฌ์ฐ๊ธฐ (์๋ฌ!)
city: ์์ธ # 2์นธ ๋ค์ฌ์ฐ๊ธฐ
# โ
์ฌ๋ฐ๋ฅธ ์์ - ์ผ๊ด๋ ๋ค์ฌ์ฐ๊ธฐ
person:
name: ํ๊ธธ๋
age: 25
city: ์์ธ
ํด๊ฒฐ์ฑ
: ์ข์ ์๋ํฐ๋ฅผ ์ฌ์ฉํ์! VS Code, IntelliJ ๊ฐ์ IDE๋ YAML ํ๋ฌ๊ทธ์ธ์ด ์์ด์ ๋ค์ฌ์ฐ๊ธฐ๋ฅผ ์๋์ผ๋ก ๋ง์ถฐ์ฃผ๊ณ ์๋ฌ๋ ํ์ํด์ค. ๐ ๏ธ
YAML์ ์ฌ๋ฌ ๊ฐ์ง ๊ฐ์ ๋ถ๋ฆฌ์ธ์ผ๋ก ํด์ํด. ์ด๊ฒ ๋๋ก๋ ๋ฌธ์ ๊ฐ ๋ผ!
# ์ด ๊ฐ๋ค์ ๋ชจ๋ true๋ก ํด์๋จ
value1: yes
value2: on
value3: true
value4: True
value5: TRUE
# ์ด ๊ฐ๋ค์ ๋ชจ๋ false๋ก ํด์๋จ
value6: no
value7: off
value8: false
value9: False
value10: FALSE
# ๋ฌธ์ ์ํฉ!
country: NO # ๋
ธ๋ฅด์จ์ด๋ฅผ ์๋ฏธํ์ง๋ง false๋ก ํด์๋จ!
# ํด๊ฒฐ์ฑ
: ๋ฐ์ดํ ์ฌ์ฉ
country: "NO" # ์ด์ ๋ฌธ์์ด๋ก ์ธ์๋จ
ํนํ ๊ตญ๊ฐ ์ฝ๋๋ ํน์ ํค์๋๋ฅผ ์ฌ์ฉํ ๋ ์กฐ์ฌํด์ผ ํด! ๐
YAML์ ํญ ๋ฌธ์๋ฅผ ํ์ฉํ์ง ์์! ๋ฐ๋์ ์คํ์ด์ค๋ฅผ ์ฌ์ฉํด์ผ ํด.
# โ ํญ ๋ฌธ์ ์ฌ์ฉ (๋ณด์ด์ง ์์ง๋ง ์๋ฌ ๋ฐ์!)
person:
โname: ํ๊ธธ๋ # ํญ ๋ฌธ์
# โ
์คํ์ด์ค ์ฌ์ฉ
person:
name: ํ๊ธธ๋ # ์คํ์ด์ค 2๊ฐ
๋๋ถ๋ถ์ ์๋ํฐ๋ ์ค์ ์ผ๋ก ํญ์ ์คํ์ด์ค๋ก ์๋ ๋ณํํ ์ ์์ด. ๊ผญ ์ค์ ํด๋์! โ๏ธ
์ฝ๋ก (:)์ ํค-๊ฐ ๊ตฌ๋ถ์๋ผ์ ๊ฐ์ ํฌํจ๋๋ฉด ๋ฌธ์ ๊ฐ ์๊ฒจ!
# โ ์๋ฌ ๋ฐ์
url: http://example.com:8080/path
# โ
๋ฐ์ดํ๋ก ๊ฐ์ธ๊ธฐ
url: "http://example.com:8080/path"
# ๋๋
url: 'http://example.com:8080/path'
# ํด์(#)๋ ์ฃผ์์ผ๋ก ์ธ์๋๋ ์ฃผ์!
password: secret#123 # 123์ ์ฃผ์์ผ๋ก ์ธ์๋จ!
password: "secret#123" # ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
YAML์ ๋๋ํ๊ฒ ํ์ ์ ์ถ๋ก ํ์ง๋ง, ๋๋ก๋ ๋๋ฌด ๋๋ํด์ ๋ฌธ์ ๊ฐ ๋ผ!
# ์๋ํ์ง ์์ ํ์
๋ณํ
version: 1.0 # ์ซ์ 1.0์ผ๋ก ํด์๋จ
version: 1.10 # ์ซ์ 1.1๋ก ํด์๋จ (0์ด ์ฌ๋ผ์ง!)
# ๋ฌธ์์ด๋ก ์ ์งํ๋ ค๋ฉด
version: "1.0"
version: "1.10"
# ์ฐํธ๋ฒํธ๋ ์กฐ์ฌ!
zipcode: 01234 # ์ซ์ 1234๋ก ํด์๋จ (์์ 0์ด ์ฌ๋ผ์ง!)
zipcode: "01234" # ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
๋ฒ์ ๋ฒํธ๋ ์ฐํธ๋ฒํธ์ฒ๋ผ ์์ 0์ด ์๋ ์ซ์๋ ํญ์ ๋ฐ์ดํ๋ก ๊ฐ์ธ์! ๐ฎ
โข yamllint: ์ปค๋งจ๋๋ผ์ธ ๋ฆฐํฐ
โข YAML Validator: ์จ๋ผ์ธ ๊ฒ์ฆ ๋๊ตฌ
โข IDE ํ๋ฌ๊ทธ์ธ: VS Code, IntelliJ ๋ฑ์ YAML ํ๋ฌ๊ทธ์ธ
โข CI/CD ํ์ดํ๋ผ์ธ: ์๋์ผ๋ก YAML ๊ฒ์ฆ ๋จ๊ณ ์ถ๊ฐ
ํนํ ์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ๊ด๋ จ ์ฌ๋ฅ์ ๊ฑฐ๋ํ ๋, YAML ์ค์ ํ์ผ ์์ฑ์ด๋ ๊ฒํ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ค๋ฉด ์ด๋ฐ ๋๊ตฌ๋ค์ ํ์ฉํ๋ฉด ์ ๋ง ์ ์ฉํ ๊ฑฐ์ผ! ๐ฏ
๐จ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ณ YAML ์ฌ์ฉ๋ฒ
๊ฑฐ์ ๋ชจ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ YAML์ ์ฌ์ฉํ ์ ์์ด! ์ฃผ์ ์ธ์ด๋ณ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ณด์. ๐ป
Python์์๋ PyYAML ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฃผ๋ก ์ฌ์ฉํด. ์ ๋ง ๊ฐ๋จํ์ง!
import yaml
# YAML ํ์ผ ์ฝ๊ธฐ
with open('config.yaml', 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
print(config['database']['host'])
# YAML ํ์ผ ์ฐ๊ธฐ
data = {
'name': '๋ด ํ๋ก์ ํธ',
'version': '1.0',
'dependencies': ['requests', 'flask', 'sqlalchemy']
}
with open('output.yaml', 'w', encoding='utf-8') as file:
yaml.dump(data, file, allow_unicode=True, default_flow_style=False)
# ๋ฌธ์์ด๋ก ๋ณํ
yaml_string = yaml.dump(data, allow_unicode=True)
print(yaml_string)
# ๋ฌธ์์ด์์ ํ์ฑ
parsed_data = yaml.safe_load(yaml_string)
safe_load()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ๋ณด์์ ์์ ํด! load()๋ ์์์ Python ๊ฐ์ฒด๋ฅผ ์คํํ ์ ์์ด์ ์ํํ๊ฑฐ๋ . ๐
JavaScript์์๋ js-yaml ํจํค์ง๋ฅผ ๋ง์ด ์จ!
const yaml = require('js-yaml');
const fs = require('fs');
// YAML ํ์ผ ์ฝ๊ธฐ
try {
const config = yaml.load(fs.readFileSync('config.yaml', 'utf8'));
console.log(config.database.host);
} catch (e) {
console.error('YAML ํ์ฑ ์๋ฌ:', e);
}
// YAML ํ์ผ ์ฐ๊ธฐ
const data = {
name: '๋ด ํ๋ก์ ํธ',
version: '1.0',
dependencies: ['express', 'mongoose', 'dotenv']
};
try {
const yamlStr = yaml.dump(data);
fs.writeFileSync('output.yaml', yamlStr, 'utf8');
} catch (e) {
console.error('YAML ์์ฑ ์๋ฌ:', e);
}
// ์ต์
์ฌ์ฉํ๊ธฐ
const yamlWithOptions = yaml.dump(data, {
indent: 2,
lineWidth: 80,
noRefs: true
});
Java์์๋ SnakeYAML ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ค์ด์ผ!
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.util.*;
public class YamlExample {
public static void main(String[] args) {
Yaml yaml = new Yaml();
// YAML ํ์ผ ์ฝ๊ธฐ
try (InputStream inputStream = new FileInputStream("config.yaml")) {
Map<String, Object> config = yaml.load(inputStream);
System.out.println(config.get("database"));
} catch (IOException e) {
e.printStackTrace();
}
// YAML ํ์ผ ์ฐ๊ธฐ
Map<String, Object> data = new HashMap<>();
data.put("name", "๋ด ํ๋ก์ ํธ");
data.put("version", "1.0");
data.put("dependencies", Arrays.asList("spring-boot", "hibernate", "lombok"));
try (FileWriter writer = new FileWriter("output.yaml")) {
yaml.dump(data, writer);
} catch (IOException e) {
e.printStackTrace();
}
// ๊ฐ์ฒด๋ฅผ YAML๋ก ๋ณํ
String yamlString = yaml.dump(data);
System.out.println(yamlString);
}
}
Go์์๋ gopkg.in/yaml.v3 ํจํค์ง๋ฅผ ์ฌ์ฉํด!
package main
import (
"fmt"
"io/ioutil"
"log"
"gopkg.in/yaml.v3"
)
type Config struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Database struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"database"`
Features []string `yaml:"features"`
}
func main() {
// YAML ํ์ผ ์ฝ๊ธฐ
data, err := ioutil.ReadFile("config.yaml")
if err != nil {
log.Fatal(err)
}
var config Config
err = yaml.Unmarshal(data, &config)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Name: %s\n", config.Name)
fmt.Printf("Database Host: %s\n", config.Database.Host)
// YAML ํ์ผ ์ฐ๊ธฐ
newConfig := Config{
Name: "๋ด ํ๋ก์ ํธ",
Version: "1.0",
}
newConfig.Database.Host = "localhost"
newConfig.Database.Port = 5432
newConfig.Features = []string{"auth", "logging"}
yamlData, err := yaml.Marshal(&newConfig)
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile("output.yaml", yamlData, 0644)
if err != nil {
log.Fatal(err)
}
}
Ruby๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ YAML ์ง์์ด ํฌํจ๋์ด ์์ด!
require 'yaml'
# YAML ํ์ผ ์ฝ๊ธฐ
config = YAML.load_file('config.yaml')
puts config['database']['host']
# YAML ํ์ผ ์ฐ๊ธฐ
data = {
'name' => '๋ด ํ๋ก์ ํธ',
'version' => '1.0',
'dependencies' => ['rails', 'sidekiq', 'redis']
}
File.open('output.yaml', 'w') do |file|
file.write(data.to_yaml)
end
# ๋ฌธ์์ด๋ก ๋ณํ
yaml_string = data.to_yaml
puts yaml_string
# ๋ฌธ์์ด์์ ํ์ฑ
parsed_data = YAML.load(yaml_string)
๊ฐ ์ธ์ด๋ง๋ค ์กฐ๊ธ์ฉ ๋ค๋ฅด์ง๋ง, ๊ธฐ๋ณธ ๊ฐ๋ ์ ๋น์ทํด! ์ฝ๊ณ ์ฐ๋ ๋ฐฉ๋ฒ๋ง ์ตํ๋ฉด ๊ธ๋ฐฉ ์ฌ์ฉํ ์ ์์ด. ๐
๐ YAML ๋ฒ ์คํธ ํ๋ํฐ์ค
์ค๋ฌด์์ YAML์ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํ ํ๋ค์ ์ ๋ฆฌํด๋ดค์ด! ์ด๊ฒ๋ง ๋ฐ๋ผํด๋ ํ๋ก์ฒ๋ผ ๋ณด์ผ ๊ฑฐ์ผ. ๐
ํ๋ก์ ํธ ์ ์ฒด์์ 2์นธ ๋๋ 4์นธ ์ค ํ๋๋ฅผ ์ ํํ๊ณ ์ผ๊ด๋๊ฒ ์ฌ์ฉํด. ์์ด ์ฐ๋ฉด ํผ๋์ค๋ฌ์!
# โ
์ข์ ์์ - ์ผ๊ด๋ 2์นธ ๋ค์ฌ์ฐ๊ธฐ
database:
host: localhost
credentials:
username: admin
password: secret
# โ ๋์ ์์ - ๋ค์ฌ์ฐ๊ธฐ ํผ์ฉ
database:
host: localhost
credentials:
username: admin # 4์นธ
password: secret # 2์นธ
YAML์ ํฐ ์ฅ์ ์ค ํ๋๊ฐ ์ฃผ์์ ์ธ ์ ์๋ค๋ ๊ฑฐ์ผ. ์ ๊ทน ํ์ฉํ์!
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ค์
# ํ๋ก๋์
ํ๊ฒฝ์์๋ ํ๊ฒฝ ๋ณ์๋ฅผ ์ฌ์ฉํ์ธ์
database:
host: localhost # ๊ฐ๋ฐ ํ๊ฒฝ์ฉ
port: 5432
# ์ฐ๊ฒฐ ํ ์ค์
pool:
min: 5 # ์ต์ ์ฐ๊ฒฐ ์
max: 20 # ์ต๋ ์ฐ๊ฒฐ ์
timeout: 30000 # ๋ฐ๋ฆฌ์ด ๋จ์
๊ฐ๋ฐ, ์คํ ์ด์ง, ํ๋ก๋์ ํ๊ฒฝ๋ณ๋ก ํ์ผ์ ๋ถ๋ฆฌํ๊ฑฐ๋ ์ต์ปค๋ฅผ ํ์ฉํด!
# config/base.yaml
defaults: &defaults
timeout: 30
retry: 3
log_level: info
# config/development.yaml
<<: *defaults
database:
host: localhost
port: 5432
debug: true
# config/production.yaml
<<: *defaults
database:
host: prod-db.example.com
port: 5432
log_level: warning
debug: false
๋น๋ฐ๋ฒํธ๋ API ํค ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ YAML ํ์ผ์ ์ง์ ์ฐ์ง ๋ง๊ณ ํ๊ฒฝ ๋ณ์๋ฅผ ์ฐธ์กฐํด!
# โ ๋์ ์์
database:
password: "mySecretPassword123!"
# โ
์ข์ ์์
database:
password: ${DB_PASSWORD}
api:
key: ${API_KEY}
secret: ${API_SECRET}
ํ๋์ YAML ํ์ผ์ด ๋๋ฌด ๊ธธ์ด์ง๋ฉด ๊ด๋ฆฌ๊ฐ ์ด๋ ค์. ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ถํ ํ์!
# main.yaml
database: !include database.yaml
cache: !include cache.yaml
logging: !include logging.yaml
services: !include services.yaml
๋ชจ๋ YAML ํ์๊ฐ !include๋ฅผ ์ง์ํ๋ ๊ฑด ์๋์ง๋ง, ๋ง์ ๋๊ตฌ๋ค์ด ๋น์ทํ ๊ธฐ๋ฅ์ ์ ๊ณตํด!
JSON Schema๋ฅผ ์ฌ์ฉํด์ YAML ํ์ผ์ ๊ตฌ์กฐ๋ฅผ ๊ฒ์ฆํ ์ ์์ด!
# schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["name", "version"],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"database": {
"type": "object",
"required": ["host", "port"],
"properties": {
"host": {"type": "string"},
"port": {"type": "integer"}
}
}
}
}
์ด๋ ๊ฒ ํ๋ฉด ์๋ชป๋ ์ค์ ์ ๋ฏธ๋ฆฌ ์ก์๋ผ ์ ์์ด! ๐ฏ
YAML ํ์ผ๋ ์ฝ๋์ฒ๋ผ ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ํ๊ณ , ๋ณ๊ฒฝ ์ฌํญ์ ๋ฌธ์ํํด!
# config.yaml
# Version: 2.1.0
# Last Updated: 2024-01-15
# Changes:
# - Added Redis cache configuration
# - Updated database pool settings
# - Removed deprecated feature flags
version: "2.1.0"
# ... ๋๋จธ์ง ์ค์ ...
์ ํ์ ์ค์ ์๋ ํฉ๋ฆฌ์ ์ธ ๊ธฐ๋ณธ๊ฐ์ ์ ๊ณตํด!
server:
port: ${PORT:-8080} # ํ๊ฒฝ ๋ณ์๊ฐ ์์ผ๋ฉด 8080 ์ฌ์ฉ
host: ${HOST:-0.0.0.0}
logging:
level: ${LOG_LEVEL:-info}
format: ${LOG_FORMAT:-json}
๐ YAML vs TOML vs JSON - ์ฌํ ๋น๊ต
์ต๊ทผ์๋ TOML์ด๋ผ๋ ์๋ก์ด ์ค์ ํ์ผ ํฌ๋งท๋ ์ธ๊ธฐ๋ฅผ ์ป๊ณ ์์ด. ์ธ ๊ฐ์ง๋ฅผ ๋ ์์ธํ ๋น๊ตํด๋ณด์! ๐ฌ
# YAML
project:
name: "My Awesome Project"
version: "1.0.0"
authors:
- name: "ํ๊ธธ๋"
email: "hong@example.com"
- name: "๊น์ฒ ์"
email: "kim@example.com"
dependencies:
production:
- express: "^4.18.0"
- mongoose: "^7.0.0"
development:
- jest: "^29.0.0"
- eslint: "^8.0.0"
scripts:
start: "node server.js"
test: "jest"
build: "webpack --mode production"
// JSON
{
"project": {
"name": "My Awesome Project",
"version": "1.0.0",
"authors": [
{
"name": "ํ๊ธธ๋",
"email": "hong@example.com"
},
{
"name": "๊น์ฒ ์",
"email": "kim@example.com"
}
],
"dependencies": {
"production": [
{"express": "^4.18.0"},
{"mongoose": "^7.0.0"}
],
"development": [
{"jest": "^29.0.0"},
{"eslint": "^8.0.0"}
]
},
"scripts": {
"start": "node server.js",
"test": "jest",
"build": "webpack --mode production"
}
}
}
# TOML
[project]
name = "My Awesome Project"
version = "1.0.0"
[[project.authors]]
name = "ํ๊ธธ๋"
email = "hong@example.com"
[[project.authors]]
name = "๊น์ฒ ์"
email = "kim@example.com"
[project.dependencies.production]
express = "^4.18.0"
mongoose = "^7.0.0"
[project.dependencies.development]
jest = "^29.0.0"
eslint = "^8.0.0"
[project.scripts]
start = "node server.js"
test = "jest"
build = "webpack --mode production"
| ํน์ง | YAML | JSON | TOML |
|---|---|---|---|
| ๊ฐ๋ ์ฑ | โญโญโญโญโญ | โญโญโญ | โญโญโญโญ |
| ์์ฑ ์ฉ์ด์ฑ | โญโญโญโญโญ | โญโญโญ | โญโญโญโญ |
| ํ์ฑ ์๋ | โญโญโญ | โญโญโญโญโญ | โญโญโญโญ |
| ๋ช ํ์ฑ | โญโญโญ | โญโญโญโญโญ | โญโญโญโญโญ |
| ๋ณต์กํ ๊ตฌ์กฐ | โญโญโญโญโญ | โญโญโญโญ | โญโญโญ |
| ์ฃผ์ ์ง์ | โ | โ | โ |
| ํ์ ์์ ์ฑ | โญโญโญ | โญโญโญโญ | โญโญโญโญโญ |
| ์๋ฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ | ๋์ (๋ค์ฌ์ฐ๊ธฐ) | ์ค๊ฐ (๊ดํธ) | ๋ฎ์ |
โข ๋ณต์กํ ๊ณ์ธต ๊ตฌ์กฐ๊ฐ ํ์ํ ๋
โข ์ฌ๋์ด ์์ฃผ ํธ์งํด์ผ ํ ๋
โข CI/CD ํ์ดํ๋ผ์ธ ์ค์
โข Kubernetes, Docker Compose ๊ฐ์ ์ธํ๋ผ ์ค์
โข ์ฃผ์์ด ์ค์ํ ์ค์ ํ์ผ
JSON์ ์ ํํ์ธ์:
โข API ํต์ (REST, GraphQL)
โข ์น ๋ธ๋ผ์ฐ์ ์์ ๋ฐ์ดํฐ ๊ตํ
โข ํ์ฑ ์๋๊ฐ ์ค์ํ ๋
โข ํ๋ก๊ทธ๋จ ๊ฐ ๋ฐ์ดํฐ ์ ์ก
โข NoSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค (MongoDB ๋ฑ)
TOML์ ์ ํํ์ธ์:
โข ๊ฐ๋จํ๊ณ ๋ช ํํ ์ค์ ํ์ผ
โข Rust ํ๋ก์ ํธ (Cargo.toml)
โข Python ํ๋ก์ ํธ (pyproject.toml)
โข ํ์ ์์ ์ฑ์ด ์ค์ํ ๋
โข ๋ค์ฌ์ฐ๊ธฐ ์๋ฌ๋ฅผ ํผํ๊ณ ์ถ์ ๋
๐ ์ค์ ํ๋ก์ ํธ: YAML ๊ธฐ๋ฐ ์ค์ ์์คํ ๊ตฌ์ถํ๊ธฐ
์ด์ ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ ์ ์๋ ์ค์ ์์คํ ์ ๋ง๋ค์ด๋ณด์! ์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ ๋๋ ์ด๋ฐ ์ค์ ์์ ๊ฐ ์ ๋ง ์ ์ฉํ ๊ฑฐ์ผ. ๐ผ
my-project/
โโโ config/
โ โโโ default.yaml # ๊ธฐ๋ณธ ์ค์
โ โโโ development.yaml # ๊ฐ๋ฐ ํ๊ฒฝ
โ โโโ staging.yaml # ์คํ
์ด์ง ํ๊ฒฝ
โ โโโ production.yaml # ํ๋ก๋์
ํ๊ฒฝ
โ โโโ secrets.yaml.example # ๋ฏผ๊ฐ ์ ๋ณด ํ
ํ๋ฆฟ
โโโ src/
โ โโโ config-loader.js # ์ค์ ๋ก๋
โ โโโ app.js # ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์
โโโ package.json
# config/default.yaml
app:
name: "My Application"
version: "1.0.0"
description: "๋ฉ์ง ์ ํ๋ฆฌ์ผ์ด์
์
๋๋ค"
server:
host: "0.0.0.0"
port: 3000
timeout: 30000
# CORS ์ค์
cors:
enabled: true
origins:
- "http://localhost:3000"
- "http://localhost:8080"
methods:
- GET
- POST
- PUT
- DELETE
credentials: true
database:
type: "postgresql"
host: "localhost"
port: 5432
name: "myapp_db"
# ์ฐ๊ฒฐ ํ ์ค์
pool:
min: 2
max: 10
idle_timeout: 10000
acquire_timeout: 30000
# ์ฟผ๋ฆฌ ๋ก๊น
logging:
enabled: false
slow_query_threshold: 1000 # ms
cache:
type: "redis"
host: "localhost"
port: 6379
ttl: 3600 # ๊ธฐ๋ณธ TTL (์ด)
# ์บ์ ์ ๋ต
strategies:
user_data:
ttl: 1800
prefix: "user:"
session:
ttl: 7200
prefix: "session:"
logging:
level: "info"
format: "json"
# ๋ก๊ทธ ์ถ๋ ฅ ๋์
transports:
- type: "console"
enabled: true
- type: "file"
enabled: true
filename: "logs/app.log"
max_size: "10m"
max_files: 30
# ๋ก๊ทธ ๋ ๋ฒจ๋ณ ์ค์
levels:
error: true
warn: true
info: true
debug: false
trace: false
security:
# JWT ์ค์
jwt:
algorithm: "HS256"
expires_in: "24h"
refresh_expires_in: "7d"
# ๋น๋ฐ๋ฒํธ ์ ์ฑ
password:
min_length: 8
require_uppercase: true
require_lowercase: true
require_numbers: true
require_special: true
# Rate limiting
rate_limit:
enabled: true
window_ms: 900000 # 15๋ถ
max_requests: 100
features:
# ๊ธฐ๋ฅ ํ๋๊ทธ
user_registration: true
email_verification: true
two_factor_auth: false
social_login: true
api_v2: false
integrations:
# ์ธ๋ถ ์๋น์ค ์ฐ๋
email:
provider: "sendgrid"
from: "noreply@example.com"
storage:
provider: "s3"
bucket: "my-app-uploads"
analytics:
provider: "google"
enabled: true
# config/development.yaml
# ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ (default.yaml ๋ฎ์ด์ฐ๊ธฐ)
server:
port: 3001
database:
name: "myapp_dev"
logging:
enabled: true # ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์ฟผ๋ฆฌ ๋ก๊น
ํ์ฑํ
logging:
level: "debug"
levels:
debug: true
trace: true
security:
rate_limit:
enabled: false # ๊ฐ๋ฐ ์ค์๋ rate limit ๋นํ์ฑํ
features:
api_v2: true # ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ ๊ธฐ๋ฅ ํ
์คํธ
# config/production.yaml
# ํ๋ก๋์
ํ๊ฒฝ ์ค์
server:
port: ${PORT} # ํ๊ฒฝ ๋ณ์์์ ๊ฐ์ ธ์ค๊ธฐ
host: "0.0.0.0"
database:
host: ${DB_HOST}
port: ${DB_PORT}
name: ${DB_NAME}
username: ${DB_USER}
password: ${DB_PASSWORD}
pool:
min: 5
max: 50 # ํ๋ก๋์
์์๋ ๋ ํฐ ํ ์ฌ์ฉ
cache:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}
logging:
level: "warn"
transports:
- type: "console"
enabled: false # ํ๋ก๋์
์์๋ ์ฝ์ ๋ก๊ทธ ๋นํ์ฑํ
- type: "file"
enabled: true
filename: "/var/log/myapp/app.log"
security:
jwt:
secret: ${JWT_SECRET}
rate_limit:
enabled: true
max_requests: 50 # ํ๋ก๋์
์์๋ ๋ ์๊ฒฉํ๊ฒ
// src/config-loader.js
const fs = require('fs');
const yaml = require('js-yaml');
const path = require('path');
class ConfigLoader {
constructor() {
this.config = {};
this.environment = process.env.NODE_ENV || 'development';
}
/**
* ์ค์ ํ์ผ ๋ก๋
*/
load() {
try {
// 1. ๊ธฐ๋ณธ ์ค์ ๋ก๋
const defaultConfig = this.loadYamlFile('default.yaml');
// 2. ํ๊ฒฝ๋ณ ์ค์ ๋ก๋
const envConfig = this.loadYamlFile(`${this.environment}.yaml`);
// 3. ๋ก์ปฌ ์ค๋ฒ๋ผ์ด๋ ์ค์ (์ ํ์ )
const localConfig = this.loadYamlFile('local.yaml', true);
// 4. ์ค์ ๋ณํฉ (๊น์ ๋ณํฉ)
this.config = this.deepMerge(
defaultConfig,
envConfig,
localConfig
);
// 5. ํ๊ฒฝ ๋ณ์ ์นํ
this.config = this.replaceEnvVariables(this.config);
// 6. ์ค์ ๊ฒ์ฆ
this.validate();
console.log(`โ
Configuration loaded for ${this.environment} environment`);
return this.config;
} catch (error) {
console.error('โ Failed to load configuration:', error.message);
throw error;
}
}
/**
* YAML ํ์ผ ๋ก๋
*/
loadYamlFile(filename, optional = false) {
const filePath = path.join(__dirname, '../config', filename);
if (!fs.existsSync(filePath)) {
if (optional) {
return {};
}
throw new Error(`Configuration file not found: ${filename}`);
}
try {
const fileContents = fs.readFileSync(filePath, 'utf8');
return yaml.load(fileContents) || {};
} catch (error) {
throw new Error(`Failed to parse ${filename}: ${error.message}`);
}
}
/**
* ๊น์ ๊ฐ์ฒด ๋ณํฉ
*/
deepMerge(...objects) {
const isObject = obj => obj && typeof obj === 'object' && !Array.isArray(obj);
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = oVal; // ๋ฐฐ์ด์ ๋ฎ์ด์ฐ๊ธฐ
} else if (isObject(pVal) && isObject(oVal)) {
prev[key] = this.deepMerge(pVal, oVal); // ์ฌ๊ท์ ๋ณํฉ
} else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
/**
* ํ๊ฒฝ ๋ณ์ ์นํ
*/
replaceEnvVariables(obj) {
const envVarPattern = /\$\{([^}]+)\}/g;
const replace = (value) => {
if (typeof value === 'string') {
return value.replace(envVarPattern, (match, envVar) => {
// ๊ธฐ๋ณธ๊ฐ ์ง์: ${VAR:-default}
const [varName, defaultValue] = envVar.split(':-');
return process.env[varName.trim()] || defaultValue || match;
});
} else if (Array.isArray(value)) {
return value.map(replace);
} else if (typeof value === 'object' && value !== null) {
const result = {};
for (const [key, val] of Object.entries(value)) {
result[key] = replace(val);
}
return result;
}
return value;
};
return replace(obj);
}
/**
* ์ค์ ๊ฒ์ฆ
*/
validate() {
const required = [
'app.name',
'server.port',
'database.host'
];
for (const path of required) {
if (!this.get(path)) {
throw new Error(`Required configuration missing: ${path}`);
}
}
// ํฌํธ ๋ฒํธ ๊ฒ์ฆ
const port = this.get('server.port');
if (isNaN(port) || port < 1 || port > 65535) {
throw new Error(`Invalid port number: ${port}`);
}
}
/**
* ์ค์ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ (์ ํ๊ธฐ๋ฒ ์ง์)
*/
get(path, defaultValue = undefined) {
const keys = path.split('.');
let value = this.config;
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key];
} else {
return defaultValue;
}
}
return value;
}
/**
* ์ ์ฒด ์ค์ ๋ฐํ
*/
getAll() {
return this.config;
}
/**
* ์ค์ ๊ฐ ์ค์ (๋ฐํ์ ๋ณ๊ฒฝ)
*/
set(path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
let obj = this.config;
for (const key of keys) {
if (!(key in obj)) {
obj[key] = {};
}
obj = obj[key];
}
obj[lastKey] = value;
}
}
// ์ฑ๊ธํค ์ธ์คํด์ค
const configLoader = new ConfigLoader();
module.exports = configLoader;
// src/app.js
const express = require('express');
const config = require('./config-loader');
// ์ค์ ๋ก๋
config.load();
const app = express();
// ์ค์ ๊ฐ ์ฌ์ฉ
const port = config.get('server.port');
const appName = config.get('app.name');
const dbConfig = config.get('database');
console.log(`๐ Starting ${appName}...`);
// ๋ฏธ๋ค์จ์ด ์ค์
if (config.get('server.cors.enabled')) {
const cors = require('cors');
app.use(cors({
origin: config.get('server.cors.origins'),
credentials: config.get('server.cors.credentials')
}));
}
// Rate limiting
if (config.get('security.rate_limit.enabled')) {
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: config.get('security.rate_limit.window_ms'),
max: config.get('security.rate_limit.max_requests')
});
app.use(limiter);
}
// ๋ผ์ฐํธ
app.get('/config', (req, res) => {
// ๋ฏผ๊ฐํ ์ ๋ณด๋ ์ ์ธํ๊ณ ๋ฐํ
const safeConfig = {
app: config.get('app'),
features: config.get('features'),
environment: process.env.NODE_ENV
};
res.json(safeConfig);
});
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// ์๋ฒ ์์
app.listen(port, () => {
console.log(`โ
Server running on port ${port}`);
console.log(`๐ Environment: ${process.env.NODE_ENV}`);
console.log(`๐ง Database: ${dbConfig.type} at ${dbConfig.host}:${dbConfig.port}`);
// ํ์ฑํ๋ ๊ธฐ๋ฅ ์ถ๋ ฅ
const features = config.get('features');
const enabledFeatures = Object.entries(features)
.filter(([_, enabled]) => enabled)
.map(([name]) => name);
console.log(`โจ Enabled features: ${enabledFeatures.join(', ')}`);
});
2. ํ๊ฒฝ ๋ณ์ ์ง์: ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ๊ด๋ฆฌ
3. ๊ธฐ๋ณธ๊ฐ ์ ๊ณต: ๋๋ฝ๋ ์ค์ ์ ๋ํ ํด๋ฐฑ
4. ๊ฒ์ฆ ๊ธฐ๋ฅ: ํ์ ์ค์ ๋๋ฝ ๋ฐฉ์ง
5. ์ ํ๊ธฐ๋ฒ: ์ค์ฒฉ๋ ์ค์ ์ ์ฝ๊ฒ ์ ๊ทผ
6. ๋ฐํ์ ๋ณ๊ฒฝ: ํ์์ ์ค์ ๋์ ์์ ๊ฐ๋ฅ
7. ํ์ ์์ ์ฑ: TypeScript์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋์ฑ ๊ฐ๋ ฅ!
์ฌ๋ฅ๋ท์์ ์ด๋ฐ ์ค์ ์์คํ ๊ตฌ์ถ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ค๋ฉด, ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ด์ฌ์ ๊ฐ์ง ๊ฑฐ์ผ! ๐ฏ
๐ YAML ๋๋ฒ๊น ๊ณผ ๋ฌธ์ ํด๊ฒฐ
YAML ํ์ผ์์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ์ด๋ป๊ฒ ํด๊ฒฐํ ๊น? ์ค์ ๋๋ฒ๊น ํ์ ์๋ ค์ค๊ฒ! ๐
# โ ๋ฌธ์ ์ฝ๋
database:
host: localhost:5432 # ์ฝ๋ก ์ด ๊ฐ์ ํฌํจ๋จ!
# โ
ํด๊ฒฐ ๋ฐฉ๋ฒ
database:
host: "localhost:5432" # ๋ฐ์ดํ๋ก ๊ฐ์ธ๊ธฐ
# ๋๋
database:
host: localhost
port: 5432 # ๋ถ๋ฆฌํ๊ธฐ
์์ธ: YAML ํ์๊ฐ ์ฝ๋ก ์ ํค-๊ฐ ๊ตฌ๋ถ์๋ก ์ธ์ํด๊ฒฐ: ์ฝ๋ก ์ด ํฌํจ๋ ๊ฐ์ ๋ฐ์ดํ๋ก ๊ฐ์ธ๊ธฐ
# โ ๋ฌธ์ ์ฝ๋ (ํญ ๋ฌธ์ ์ฌ์ฉ)
database:
โhost: localhost # ํญ ๋ฌธ์!
# โ
ํด๊ฒฐ ๋ฐฉ๋ฒ
database:
host: localhost # ์คํ์ด์ค ์ฌ์ฉ
์์ธ: YAML์ ํญ ๋ฌธ์๋ฅผ ํ์ฉํ์ง ์์ํด๊ฒฐ: ์๋ํฐ ์ค์ ์์ "ํญ์ ์คํ์ด์ค๋ก ๋ณํ" ์ต์ ํ์ฑํ
# โ ๋ฌธ์ ์ฝ๋
database
host: localhost # ์ฝ๋ก ๋๋ฝ!
# โ
ํด๊ฒฐ ๋ฐฉ๋ฒ
database: # ์ฝ๋ก ์ถ๊ฐ
host: localhost
์์ธ: ํค ๋ค์ ์ฝ๋ก ๋๋ฝํด๊ฒฐ: ๋ชจ๋ ํค ๋ค์ ์ฝ๋ก ํ์ธ
# โ ๋ฌธ์ ์ฝ๋ (๋ค์ฌ์ฐ๊ธฐ ๋ถ์ผ์น)
database:
host: localhost
port: 5432 # 3์นธ ๋ค์ฌ์ฐ๊ธฐ
name: mydb # 2์นธ ๋ค์ฌ์ฐ๊ธฐ
# โ
ํด๊ฒฐ ๋ฐฉ๋ฒ
database:
host: localhost
port: 5432 # ์ผ๊ด๋ 2์นธ
name: mydb
์์ธ: ๋ค์ฌ์ฐ๊ธฐ ๋ ๋ฒจ ๋ถ์ผ์นํด๊ฒฐ: ๊ฐ์ ๋ ๋ฒจ์ ํญ๋ชฉ์ ๋์ผํ ๋ค์ฌ์ฐ๊ธฐ ์ฌ์ฉ
โข YAML Validator (codebeautify.org): ํฌ๋งทํ ๊ณผ ๊ฒ์ฆ
โข JSON to YAML: JSON๊ณผ YAML ์ํธ ๋ณํ
์ด๋ฐ ๋๊ตฌ๋ค๋ก ๋น ๋ฅด๊ฒ ๋ฌธ๋ฒ ์ค๋ฅ๋ฅผ ์ฐพ์ ์ ์์ด! ๐ง
# yamllint ์ค์น (Python)
pip install yamllint
# YAML ํ์ผ ๊ฒ์ฆ
yamllint config.yaml
# ์์ธํ ์๋ฌ ๋ฉ์์ง์ ํจ๊ป
yamllint -f parsable config.yaml
# ํน์ ๊ท์น ๋นํ์ฑํ
yamllint -d '{extends: default, rules: {line-length: disable}}' config.yaml
โข YAML by Red Hat: ์๋ ์์ฑ, ๊ฒ์ฆ, ํฌ๋งทํ
โข YAML Language Support: ๋ฌธ๋ฒ ํ์ด๋ผ์ดํ
โข Prettier: ์๋ ํฌ๋งทํ
VS Code ์ค์ ์์:
{
"yaml.validate": true,
"yaml.format.enable": true,
"yaml.completion": true,
"editor.formatOnSave": true,
"[yaml]": {
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.autoIndent": "advanced"
}
}
// Node.js์์ YAML ๊ฒ์ฆ
const yaml = require('js-yaml');
const fs = require('fs');
function validateYaml(filePath) {
try {
const fileContents = fs.readFileSync(filePath, 'utf8');
const data = yaml.load(fileContents);
console.log('โ
YAML is valid!');
console.log('Parsed data:', JSON.stringify(data, null, 2));
return { valid: true, data };
} catch (error) {
console.error('โ YAML validation failed:');
console.error(`Error: ${error.message}`);
if (error.mark) {
console.error(`Line: ${error.mark.line + 1}`);
console.error(`Column: ${error.mark.column + 1}`);
console.error(`Snippet: ${error.mark.snippet}`);
}
return { valid: false, error };
}
}
// ์ฌ์ฉ ์์
validateYaml('./config/production.yaml');
โ 1. ๋ค์ฌ์ฐ๊ธฐ๊ฐ ์ผ๊ด๋๊ฒ ์คํ์ด์ค๋ก๋ง ๋์ด ์๋๊ฐ?
โ 2. ๋ชจ๋ ํค ๋ค์ ์ฝ๋ก ์ด ์๋๊ฐ?
โ 3. ์ฝ๋ก ๋ค์ ๊ณต๋ฐฑ์ด ์๋๊ฐ?
โ 4. ํน์ ๋ฌธ์(์ฝ๋ก , ํด์ ๋ฑ)๊ฐ ํฌํจ๋ ๊ฐ์ ๋ฐ์ดํ๋ก ๊ฐ์๋๊ฐ?
โ 5. ๋ฆฌ์คํธ ํญ๋ชฉ์ด ์ฌ๋ฐ๋ฅธ ํ์์ธ๊ฐ? (ํ์ดํ + ๊ณต๋ฐฑ)
โ 6. ์ฌ๋ฌ ์ค ๋ฌธ์์ด์ ํ์ดํ(|)๋ ๊บพ์ (>)๋ฅผ ์ฌ์ฉํ๋๊ฐ?
โ 7. ์ต์ปค์ ๋ณ์นญ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์๋์๋๊ฐ?
โ 8. ํ์ผ ์ธ์ฝ๋ฉ์ด UTF-8์ธ๊ฐ?
โ 9. ๋ถ๋ฆฌ์ธ ๊ฐ์ด ์๋ํ ๋๋ก ํด์๋๋๊ฐ?
โ 10. ํ๊ฒฝ ๋ณ์ ์ฐธ์กฐ๊ฐ ์ฌ๋ฐ๋ฅธ ํ์์ธ๊ฐ?
๐จ YAML ๊ณ ๊ธ ํจํด๊ณผ ํธ๋ฆญ
์ด์ YAML ๋ง์คํฐ๊ฐ ๋๊ธฐ ์ํ ๊ณ ๊ธ ๊ธฐ๋ฒ๋ค์ ๋ฐฐ์๋ณด์! ์ด๋ฐ ๊ฑธ ์๋ฉด ์ ๋ง ํ๋ก์ฒ๋ผ ๋ณด์ผ ๊ฑฐ์ผ. ๐
# ๊ณตํต ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ ์ ์ต์ปค๋ก ์ ์
.database_defaults: &db_defaults
pool:
min: 2
max: 10
idle_timeout: 10000
retry:
attempts: 3
delay: 1000
ssl:
enabled: true
reject_unauthorized: true
# ๋ง์ดํฌ๋ก์๋น์ค๋ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
services:
user_service:
database:
<<: *db_defaults
host: user-db.example.com
name: users_db
order_service:
database:
<<: *db_defaults
host: order-db.example.com
name: orders_db
pool:
max: 20 # ์ฃผ๋ฌธ ์๋น์ค๋ ๋ ํฐ ํ ํ์
analytics_service:
database:
<<: *db_defaults
host: analytics-db.example.com
name: analytics_db
ssl:
enabled: false # ๋ด๋ถ ๋คํธ์ํฌ์์๋ SSL ๋ถํ์
# ํ๊ฒฝ๋ณ ์ค์ ์ ํ๋์ ํ์ผ์์ ๊ด๋ฆฌ
environments:
development: &dev
debug: true
log_level: debug
cache_enabled: false
staging: &staging
debug: false
log_level: info
cache_enabled: true
production: &prod
debug: false
log_level: warn
cache_enabled: true
rate_limit_strict: true
# ํ์ฌ ํ๊ฒฝ ์ ํ (ํ๊ฒฝ ๋ณ์๋ก ์ ์ด)
current: *${ENV:-dev} # ๊ธฐ๋ณธ๊ฐ์ dev
# ๋๋ ๊ฐ ํ๊ฒฝ์ ๋ช
์์ ์ผ๋ก ์ ์
app:
development:
<<: *dev
api_url: http://localhost:3000
staging:
<<: *staging
api_url: https://staging-api.example.com
production:
<<: *prod
api_url: https://api.example.com
# ๊ธฐ๋ฅ ํ๋๊ทธ์ ์กฐ๊ฑด๋ถ ์ค์
features:
# ๊ธฐ๋ณธ ๊ธฐ๋ฅ
core: &core_features
authentication: true
authorization: true
logging: true
# ํ๋ฆฌ๋ฏธ์ ๊ธฐ๋ฅ
premium: &premium_features
<<: *core_features
advanced_analytics: true
custom_reports: true
api_access: true
priority_support: true
# ์ํฐํ๋ผ์ด์ฆ ๊ธฐ๋ฅ
enterprise: &enterprise_features
<<: *premium_features
sso: true
audit_logs: true
custom_integrations: true
dedicated_support: true
# ํ๋๋ณ ์ค์
plans:
free:
features: *core_features
limits:
users: 5
storage: 1GB
api_calls: 1000
pro:
features: *premium_features
limits:
users: 50
storage: 100GB
api_calls: 100000
enterprise:
features: *enterprise_features
limits:
users: unlimited
storage: unlimited
api_calls: unlimited
# ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ
์ฒ ์ค์
microservices:
# API Gateway
- name: api-gateway
type: gateway
port: 8080
routes:
- path: /users/*
service: user-service
methods: [GET, POST, PUT, DELETE]
- path: /orders/*
service: order-service
methods: [GET, POST]
- path: /products/*
service: product-service
methods: [GET]
middleware:
- auth
- rate-limit
- cors
# User Service
- name: user-service
type: service
port: 8081
replicas: 3
database:
type: postgresql
connection: ${USER_DB_URL}
cache:
type: redis
connection: ${REDIS_URL}
dependencies:
- auth-service
- notification-service
health_check:
path: /health
interval: 30s
timeout: 5s
# Order Service
- name: order-service
type: service
port: 8082
replicas: 5
database:
type: postgresql
connection: ${ORDER_DB_URL}
message_queue:
type: rabbitmq
connection: ${RABBITMQ_URL}
queues:
- order-created
- order-updated
- order-cancelled
dependencies:
- user-service
- product-service
- payment-service
# Product Service
- name: product-service
type: service
port: 8083
replicas: 2
database:
type: mongodb
connection: ${MONGO_URL}
search:
type: elasticsearch
connection: ${ES_URL}
indices:
- products
- categories
# ์ค์ ์คํค๋ง ์ ์
schema:
type: object
required:
- app
- server
- database
properties:
app:
type: object
required: [name, version]
properties:
name:
type: string
minLength: 1
version:
type: string
pattern: '^\d+\.\d+\.\d+$'
server:
type: object
required: [port]
properties:
port:
type: integer
minimum: 1
maximum: 65535
host:
type: string
format: hostname
database:
type: object
required: [type, host, port]
properties:
type:
type: string
enum: [postgresql, mysql, mongodb]
host:
type: string
port:
type: integer
pool:
type: object
properties:
min:
type: integer
minimum: 1
max:
type: integer
minimum: 1
# ์ค์ ์ค์ (์ ์คํค๋ง๋ฅผ ๋ฐ๋ฆ)
app:
name: "My Application"
version: "1.2.3"
server:
port: 8080
host: "0.0.0.0"
database:
type: postgresql
host: localhost
port: 5432
pool:
min: 2
max: 10
2. ๋ณํฉ ํค(<<:)๋ ๋งต์๋ง ์ฌ์ฉ ๊ฐ๋ฅ: ๋ฆฌ์คํธ๋ ์ค์นผ๋ผ ๊ฐ์๋ ์ฌ์ฉ ๋ถ๊ฐ
3. ๋ณต์กํ ๊ตฌ์กฐ๋ ์ฃผ์์ผ๋ก ์ค๋ช : ๋์ค์ ๋ณด๋ ์ฌ๋(๋๋ ๋ฏธ๋์ ๋)์ ์ํด
4. ํ๊ฒฝ ๋ณ์๋ ๋ฐํ์์ ์นํ: ํ์ผ ์์ฒด์๋ ํ ํ๋ฆฟ๋ง ์์
5. ์คํค๋ง ๊ฒ์ฆ์ CI/CD์ ํตํฉ: ์๋ชป๋ ์ค์ ์ผ๋ก ๋ฐฐํฌ๋๋ ๊ฒ ๋ฐฉ์ง
์ฌ๋ฅ๋ท์์ ์ด๋ฐ ๊ณ ๊ธ YAML ํจํด์ ํ์ฉํ ์ปจ์คํ ์ด๋ ์ฝ๋ ๋ฆฌ๋ทฐ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ฉด ์ข์ ๊ฒ ๊ฐ์! ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ด๋ฐ ๋ถ๋ถ์์ ์ด๋ ค์์ ๊ฒช๊ฑฐ๋ . ๐ฏ
๐ YAML์ ๋ฏธ๋์ ํธ๋ ๋
YAML์ ๊ณ์ ์งํํ๊ณ ์์ด! ์ต์ ํธ๋ ๋์ ๋ฏธ๋ ๋ฐฉํฅ์ ์์๋ณด์. ๐ฎ
ํ์ฌ YAML 1.2๊ฐ ์ต์ ๋ฒ์ ์ด์ผ. 1.1๊ณผ์ ์ฃผ์ ์ฐจ์ด์ ์ ์์๋๋ฉด ์ข์!
| ํน์ง | YAML 1.1 | YAML 1.2 |
|---|---|---|
| ๋ถ๋ฆฌ์ธ ๊ฐ | yes/no, on/off ๋ฑ ๋ค์ | true/false๋ง ๊ถ์ฅ |
| 8์ง์ ํ๊ธฐ | 0755 (์๋ ์ธ์) | 0o755 (๋ช ์์ ํ๊ธฐ) |
| JSON ํธํ์ฑ | ๋ถ๋ถ์ | ์์ ํธํ |
| ์ ๋์ฝ๋ | ์ ํ์ | ์์ ์ง์ |
Kubernetes, Docker, CI/CD ๋๊ตฌ๋ค์ด YAML์ ํ์ค์ผ๋ก ์ฑํํ๋ฉด์ YAML์ ์ค์์ฑ์ด ๋์ฑ ์ปค์ง๊ณ ์์ด!
๋ชจ๋ ์ธํ๋ผ ์ค์ ์ YAML๋ก ๊ด๋ฆฌํ๊ณ Git์ผ๋ก ๋ฒ์ ๊ด๋ฆฌํ๋ ์ถ์ธ
2. Helm Charts
Kubernetes ์ ํ๋ฆฌ์ผ์ด์ ์ ํจํค์งํ๋ ํ์ค ๋ฐฉ์
3. GitHub Actions, GitLab CI
CI/CD ํ์ดํ๋ผ์ธ์ YAML๋ก ์ ์
4. Serverless Framework
์๋ฒ๋ฆฌ์ค ์ ํ๋ฆฌ์ผ์ด์ ์ค์ ๋ YAML๋ก
5. API ๋ช ์ธ (OpenAPI/Swagger)
REST API ๋ฌธ์ํ์๋ YAML ํ์ฉ
YAML์ ๋จ์ ์ ๋ณด์ํ๋ ์๋ก์ด ํฌ๋งท๋ค๋ ๋ฑ์ฅํ๊ณ ์์ด!
Google์์ ๊ฐ๋ฐํ ์ค์ ์ธ์ด๋ก, YAML์ ์ ์ฐ์ฑ๊ณผ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ํ์ ์์ ์ฑ์ ๊ฒฐํฉํ์ด.
// CUE ์์
#Config: {
name: string
port: int & >0 & <65536
debug: bool | *false // ๊ธฐ๋ณธ๊ฐ false
}
myConfig: #Config & {
name: "my-app"
port: 8080
}
ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ ์ ๋์ ํ ์ค์ ์ธ์ด. ํ์ ์์ ์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ์ด ๋ฐ์ด๋!
-- Dhall ์์
let Config = { name : Text, port : Natural }
let makeConfig = \(name : Text) -> \(port : Natural) ->
{ name = name, port = port }
in makeConfig "my-app" 8080
JSON์ ํ์ฅํ ํ ํ๋ฆฟ ์ธ์ด. ๋ณ์, ํจ์, ์กฐ๊ฑด๋ฌธ ๋ฑ์ ์ง์ํด!
// Jsonnet ์์
local port = 8080;
local debug = std.extVar('DEBUG');
{
name: 'my-app',
port: port,
debug: debug == 'true',
url: 'http://localhost:' + port
}
YAML์ ๋ ์ฝ๊ณ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋๊ตฌ๋ค์ด ๊ณ์ ๋์ค๊ณ ์์ด!
โข Kustomize: Kubernetes YAML์ ํ ํ๋ฆฟ ์์ด ์ปค์คํฐ๋ง์ด์ง
โข Datree: Kubernetes YAML ์ ์ฑ ๊ฒ์ฆ
โข Monokle: Kubernetes YAML ์๊ฐํ ๋ฐ ํธ์ง ๋๊ตฌ
โข Kubeconform: Kubernetes ๋ฆฌ์์ค ๊ฒ์ฆ
โข Pluto: ๋ ์ด์ ์ฌ์ฉ๋์ง ์๋ Kubernetes API ๋ฒ์ ๊ฐ์ง
2. ํ์ ์์ ์ฑ ๊ฐํ: ์คํค๋ง ๊ฒ์ฆ์ด ๋์ฑ ์ค์ํด์ง ๊ฒ
3. AI ๊ธฐ๋ฐ ์๋ ์์ฑ: GitHub Copilot ๊ฐ์ ๋๊ตฌ๊ฐ YAML ์์ฑ์ ๋์์ค ๊ฑฐ์ผ
4. ์๊ฐํ ๋๊ตฌ ๋ฐ์ : ๋ณต์กํ YAML์ ๊ทธ๋ํฝ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๋๊ตฌ๋ค์ด ๋์ด๋ ๊ฒ
5. ๋ณด์ ๊ฐํ: ๋ฏผ๊ฐํ ์ ๋ณด ๊ด๋ฆฌ๋ฅผ ์ํ ์ํธํ ๊ธฐ๋ฅ ํตํฉ
YAML์ ์์ผ๋ก๋ ๊ณ์ ๋ฐ์ ํ๋ฉด์ ๊ฐ๋ฐ์๋ค์ ํ์ ๋๊ตฌ๋ก ๋จ์ ๊ฑฐ์ผ! ๐
๐ ํ์ต ๋ฆฌ์์ค์ ์ถ๊ฐ ์๋ฃ
YAML์ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด ๋ฆฌ์์ค๋ค์ ํ์ฉํด๋ด! ๐
โข YAML 1.2 ๋ช ์ธ: ์์ ํ ๋ฌธ๋ฒ ์ ์
โข YAML RFC: ํ์ค ๋ฌธ์
โข YAML Tutorial: ๋จ๊ณ๋ณ ํํ ๋ฆฌ์ผ
โข Kubernetes YAML ๊ฐ์ด๋: ์ค์ ์์ ์ค์ฌ
โข Docker Compose ๋ฌธ์: YAML ํ์ฉ ์ฌ๋ก
โข Katacoda: ์ธํฐ๋ํฐ๋ธ Kubernetes ํ์ต
โข Play with Docker: Docker Compose ์ค์ต ํ๊ฒฝ
โข "Docker Deep Dive" - Docker Compose YAML
โข "Infrastructure as Code" - ์ค์ ๊ด๋ฆฌ ๋ฒ ์คํธ ํ๋ํฐ์ค
โข Reddit r/kubernetes: YAML ๊ด๋ จ ํ ๋ก
โข YAML GitHub Discussions: ๊ณต์ ํ ๋ก ํฌ๋ผ
โข DevOps Korea: ํ๊ตญ์ด ์ปค๋ฎค๋ํฐ
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ฌ๊ธฐ๊น์ง YAML์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ฐฐ์ ์ด! ์ฒ์์๋ ๋จ์ํ ์ค์ ํ์ผ ํฌ๋งท์ผ๋ก ์์ํ์ง๋ง, ์ด์ ๋ ํ๋ ์ํํธ์จ์ด ๊ฐ๋ฐ์์ ๋นผ๋์ ์ ์๋ ํต์ฌ ๋๊ตฌ๊ฐ ๋์ง. ๐
YAML์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ ์ธ๊ฐ ์นํ์ ์ด๋ผ๋ ๊ฑฐ์ผ. ์ฝ๋๋ฅผ ์ฝ๋ ์ฌ๋์ด ์ฝ๊ฒ ์ดํดํ ์ ์๊ณ , ์์ฑํ๊ธฐ๋ ํธํ์ง. ๋ฌผ๋ก ๋ค์ฌ์ฐ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ ๊ณจ์น ์ํ ์ ์์ง๋ง, ์ข์ ์๋ํฐ์ ๋ฆฐํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ถฉ๋ถํ ๊ทน๋ณตํ ์ ์์ด! ๐ช
Docker, Kubernetes, CI/CD ํ์ดํ๋ผ์ธ, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ
์ฒ... ํ๋ ๊ฐ๋ฐ ํ๊ฒฝ์์ YAML์ ์ด๋์๋ ์์ด. ํด๋ผ์ฐ๋ ๋ค์ดํฐ๋ธ ์๋๋ฅผ ์ด์๊ฐ๋ ๊ฐ๋ฐ์๋ผ๋ฉด YAML์ ๋ง์คํฐํ๋ ๊ฑด ์ ํ์ด ์๋๋ผ ํ์์ผ! โ๏ธ
์ฌ๋ฅ๋ท์์๋ YAML ๊ด๋ จ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋ ๋ฐฐ์ธ ์ ์์ด. ์ค์ ํ์ผ ์์ฑ, ์ฝ๋ ๋ฆฌ๋ทฐ, Kubernetes ์ค์ ์ปจ์คํ
๋ฑ ๋ค์ํ ์๋น์ค๋ฅผ ์ฐพ์๋ณผ ์ ์์ผ๋๊น, ํ์ํ๋ฉด ํ์ฉํด๋ณด๋ ๊ฒ๋ ์ข์ ๊ฑฐ์ผ! ๐ฏ
๊ธฐ์ตํด! YAML์ ๋๊ตฌ์ผ ๋ฟ์ด์ผ. ์ค์ํ ๊ฑด ์ด ๋๊ตฌ๋ฅผ ์ด๋ป๊ฒ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๋๋์ง. ๋ฒ ์คํธ ํ๋ํฐ์ค๋ฅผ ๋ฐ๋ฅด๊ณ , ํ๊ณผ ์ผ๊ด๋ ์คํ์ผ์ ์ ์งํ๊ณ , ํญ์ ๊ฐ๋
์ฑ์ ์ต์ฐ์ ์ผ๋ก ์๊ฐํ์. ๊ทธ๋ฌ๋ฉด YAML์ ๋น์ ์ ๋ ๋ ํ ๋๋ฐ์๊ฐ ๋ ๊ฑฐ์ผ! ๐ค
์ด ๊ธ์ด YAML์ ์ดํดํ๊ณ ํ์ฉํ๋ ๋ฐ ๋์์ด ๋์๊ธธ ๋ฐ๋ผ! ๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ๊น์ด ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด, ์์์ ์๊ฐํ ๋ฆฌ์์ค๋ค์ ํ์ฉํด๋ด. ๊ทธ๋ฆฌ๊ณ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ฉด์ ๊ฒฝํ์ ์๋ ๊ฒ ๊ฐ์ฅ ์ค์ํด! ๐
ํดํผ ์ฝ๋ฉ! ๊ทธ๋ฆฌ๊ณ YAML๊ณผ ํจ๊ป ์ฆ๊ฑฐ์ด ๊ฐ๋ฐ ์ฌ์ ๋๊ธธ! ๐โจ
๐ YAML ๋ง์คํฐ์ ๊ธธ, ๋น์ ๋ ํ ์ ์์ด! ๐
Keep Learning, Keep Growing! ๐ป๐ฑ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.