Jeśli kiedykolwiek zarządzałeś klastrem Kubernetes, konfigurowałeś przepływ pracy GitHub Actions lub pisałeś plik Docker Compose, to już pisałeś YAML — zapewne całkiem sporo. YAML to format konfiguracyjny napędzający większość współczesnych narzędzi DevOps. Ale mimo że jest wszechobecny, wciąż potrafi zaskoczyć. Naprawiałem więcej niż swój udział w błędach wcięć YAML o 23:00 z zablokowanym deployem. Zadbajmy o to, żebyś nie musiał.
YAML to skrót od YAML Ain't Markup Language — tak, to rekurencyjny akronim. Pierwotnie brzmiał „Yet Another Markup Language", ale zmieniono go, aby podkreślić, że jest to format serializacji danych, a nie język znaczników dokumentów. Specyfikacja YAML 1.2 to aktualny standard, choć wiele narzędzi nadal implementuje YAML 1.1, który ma kilka naprawdę groźnych różnic, które omówimy poniżej.
Podstawy: Składnia YAML w pigułce
YAML używa wcięć (tylko spacje — nigdy tabulatory) do wyrażania struktury. Dokument YAML to odwzorowanie (pary klucz-wartość), sekwencja (lista) lub skalar (pojedyncza wartość). Oto prawdziwa konfiguracja w stylu Kubernetes, która obejmuje wszystkie podstawy:
# 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Żadnych nawiasów klamrowych, żadnych cudzysłowów przy większości łańcuchów, żadnych przecinków. W pliku, który ludzie edytują codziennie, ta czytelność naprawdę robi różnicę. Ale YAML płaci za tę czytelność złożonością — pod maską dzieje się całkiem sporo.
Typy skalarne: Łańcuchy, liczby, wartości logiczne, null
YAML wywnioskuje typy na podstawie wyglądu wartości. Jest to wygodne przez większość czasu i po cichu katastrofalne w kilku szczególnych przypadkach.
# 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 YAMLyes, no, on, off, true, false,
y, n, Y, N — i ich wielkie warianty —
są wszystkie parsowane jako wartości logiczne. Oznacza to, że kod kraju ISO NO (Norwegia) staje się
wartością logiczną false. YAML 1.2 to naprawił. Zawsze cytuj łańcuchy wyglądające jak wartości logiczne. Zobacz
artykuł Wikipedii o Problemie Norwegii
po pełną historię.# 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 floatSekwencje: Listy w YAML
Listy używają prefiksu myślnik-spacja. Każdy element może być skalarem, odwzorowaniem lub inną sekwencją:
# 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]Łańcuchy wieloliniowe: Dosłowny blok vs. złożony
To właśnie tutaj YAML przewyższa JSON dla plików konfiguracyjnych. Dwa specjalne operatory elegancko obsługują łańcuchy wieloliniowe:
# 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 twoOperator | sprawia, że wieloliniowe skrypty run: w GitHub Actions są
czytelne. Ściągawka yaml-multiline.info
to najszybszy sposób, by zapamiętać, który modyfikator chomp robi co. Bez tych bloków skalarnych
musiałbyś pisać escapeowane znaki nowej linii w jednym łańcuchu — do czego zmusza cię JSON.
Tabulatory vs. spacje — Kardynalna zasada
.yml i .yaml. Dwie spacje to
niemal powszechna konwencja.To jest przyczyna numer jeden tajemniczych błędów YAML. Tabulator wygląda identycznie jak spacje w większości edytorów. Włącz "pokaż znaki białe" w swoim edytorze podczas debugowania YAML, lub wklej plik do Walidatora YAML, aby uzyskać jasny komunikat błędu wskazujący dokładną linię.
Prawdziwy przykład: Przepływ pracy GitHub Actions
Oto prawdziwy fragment przepływu pracy GitHub Actions. Zwróć uwagę, jak wieloliniowy blok run:
używa dosłownego skalara blokowego i jak wcięcia determinują strukturę:
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 buildPrawdziwy przykład: Manifest wdrożenia Kubernetes
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"Parsowanie YAML w Pythonie i JavaScript
W Pythonie biblioteka PyYAML
to standardowy wybór. Zawsze używaj safe_load(), nie load() — niebezpieczna wersja
może wykonywać dowolny kod Pythona z pliku YAML:
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)W JavaScript/Node.js, js-yaml to najszerzej używana biblioteka:
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);Podsumowanie
Składnia oparta na wcięciach w YAML, obsługa komentarzy i czytelne łańcuchy wieloliniowe sprawiają, że jest właściwym wyborem dla plików konfiguracyjnych, które ludzie piszą i utrzymują. Ale wiąże się z nim prawdziwe ryzyko: Problem Norwegii, mylenie tabulatorów ze spacjami i niejawna konwersja typów z parserów YAML 1.1. Znaj zasady, cytuj wszystko niejednoznaczne, skonfiguruj edytor tak, aby pokazywał znaki białe, i używaj Formatera YAML, aby pliki były spójne. YAML to potężne narzędzie, gdy rozumiesz, gdzie może ugryźć.