Jeśli kiedykolwiek kopiowałeś blok YAML z jednego miejsca w pliku do innego, już natrafiłeś na problem, który rozwiązują kotwice i aliasy. Duże konfiguracje Kubernetes, wielośrodowiskowe pliki Docker Compose i złożone potoki CI mają tendencję do powtarzania tych samych bloków — limitów zasobów, zmiennych środowiskowych, montowań woluminów — w kółko. YAML ma wbudowaną odpowiedź na to, o której większość deweloperów nie wie, że istnieje.
Kotwice (&) i aliasy (*) to mechanizm YAML do definiowania wartości raz
i odwoływania się do niej w dowolnym innym miejscu tego samego pliku. Traktuj kotwicę jak deklarację
zmiennej, a alias jak jej użycie. Klucz scalania (<<) rozszerza to o scalanie obiektów —
podobne do operatora spread w JavaScript. Obie funkcje są zdefiniowane w
sekcji specyfikacji YAML 1.2 o kotwicach węzłów.
Podstawowa składnia kotwic i aliasów
Składnia jest minimalna. Poprzedź wartość przez &name, aby utworzyć kotwicę,
a następnie użyj *name w dowolnym miejscu dokumentu, aby się do niej odwołać:
# Define the anchor once
default_timeout: &timeout 30
# Reference it multiple times
services:
api:
timeout: *timeout # → 30
worker:
timeout: *timeout # → 30
scheduler:
timeout: *timeout # → 30Gdy parser YAML odczyta to, każdy alias rozwiązuje się do dokładnie tej samej wartości co kotwica. Zmień kotwicę, a wszystkie aliasy zostaną zaktualizowane. Nie potrzeba ręcznej zamiany.
Scalanie obiektów kluczem <<
Ponowne użycie skalara jest przydatne, ale prawdziwa moc pochodzi ze scalania całych bloków odwzorowań.
Klucz scalania << wchłania wszystkie pary klucz-wartość z kotwicy do bieżącego odwzorowania:
# Define a base configuration block
base_service: &base
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- app-network
# Merge it into specific services
services:
api:
<<: *base # merge all keys from base_service
image: my-api:latest
ports:
- "8080:8080"
worker:
<<: *base # same base config
image: my-worker:latest
command: ["node", "worker.js"]
scheduler:
<<: *base # and again
image: my-scheduler:latest
command: ["node", "cron.js"]Klucze zdefiniowane bezpośrednio w odwzorowaniu nadpisują klucze ze scalonej kotwicy.
Jeśli więc base ustawia restart: unless-stopped, a serwis ustawia też
restart: no, wygrywa wartość na poziomie serwisu.
Przed i po: Docker Compose w praktyce
Oto jak wygląda typowy wielousługowy plik Docker Compose bez kotwic — pełen powtórzeń:
# BEFORE — repetitive and hard to maintain
version: "3.9"
services:
api:
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgres
worker:
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgres
mailer:
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgresTeraz z kotwicami i kluczem scalania — każdy powtarzający się blok jest zdefiniowany dokładnie raz:
# AFTER — DRY and easy to maintain
version: "3.9"
x-common: &common
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgres
services:
api:
<<: *common
image: my-api:2.4.1
ports:
- "8080:8080"
worker:
<<: *common
image: my-worker:2.4.1
command: ["node", "worker.js"]
mailer:
<<: *common
image: my-mailer:2.4.1
command: ["node", "mailer.js"]x- na
polach rozszerzeń
(jak x-common) to konwencja Docker Compose, która uniemożliwia Compose próbę parsowania bloku jako definicji serwisu.
Jest ignorowany przez silnik Compose, ale w pełni użyteczny jako kotwica YAML.GitHub Actions: Strategia matrix z kotwicami
Konfiguracja CI to kolejne miejsce, gdzie kotwice się opłacają. Oto przepływ pracy GitHub Actions, który ponownie używa wspólnej konfiguracji kroków w wielu zadaniach:
name: Build and Test
on: [push, pull_request]
# Shared setup steps
x-setup-steps: &setup-steps
- 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:
- *setup-steps
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- *setup-steps
- run: npm test -- --coverage
build:
runs-on: ubuntu-latest
steps:
- *setup-steps
- run: npm run buildUwaga: Parser YAML GitHub Actions
obsługuje kotwice i aliasy. Jednak niektóre systemy CI
(zwłaszcza CircleCI's config.yml przed wersją 2.1) miały własną składnię podobną do kotwic.
Zawsze sprawdzaj dokumentację swojego konkretnego narzędzia.
Kubernetes: Ponowne użycie limitów zasobów
W manifestach Kubernetes, limity zasobów i zmienne środowiskowe to główni kandydaci do kotwic. Oto wzorzec, którego regularnie używam:
# Anchors at the top of the file (or in a shared ConfigMap generator)
x-resources:
small: &resources-small
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
large: &resources-large
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
template:
spec:
containers:
- name: frontend
image: frontend:latest
resources: *resources-small # reuse small profile
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: api
image: api:latest
resources: *resources-large # reuse large profileWielokrotne scalania i priorytety nadpisywania
Możesz scalać z wielu kotwic w jednym odwzorowaniu. Podaj je jako listę pod <<.
Wcześniejsze wpisy na liście mają priorytet nad późniejszymi dla zduplikowanych kluczy:
defaults: &defaults
color: blue
size: medium
weight: light
overrides: &overrides
color: red # will override defaults.color
weight: heavy # will override defaults.weight
result:
<<: [*overrides, *defaults] # overrides takes priority
name: widget
# Result: color=red, size=medium, weight=heavy, name=widgetOgraniczenia, o których warto wiedzieć
- Tylko ten sam plik. Kotwice i aliasy działają tylko w obrębie pojedynczego strumienia dokumentu YAML. Nie można odwoływać się do kotwicy z innego pliku. Do ponownego użycia między plikami potrzebujesz własnego mechanizmu swojego narzędzia (np. szablonów Helm, Kustomize lub procesorów YAML jak
ytt). - Brak obliczanych wartości. Aliasy rozwiązują się do dokładnych kopii wartości kotwicy. Nie ma interpolacji ani logiki szablonu — to zadanie dla narzędzi takich jak Helm lub Jinja2.
- Klucz scalania to rozszerzenie YAML. Klucz scalania
<<jest zdefiniowany w rozszerzeniu YAML 1.1, a nie w głównej specyfikacji. Większość parserów go obsługuje, ale ściśle zgodne parsery YAML 1.2 mogą nie. - Odwołania cykliczne psują parsery. Nie próbuj kotwicować węzła i odwoływać się do niego z jego wnętrza. Jest to technicznie nieprawidłowe.
- Aliasy kopiują, nie referencjonują. Modyfikowanie wartości rozwiązanego aliasu w kodzie nie zmienia kotwicy ani innych aliasów. Po parsowaniu są to niezależne wartości.
Podsumowanie
Kotwice i aliasy YAML to jedna z najbardziej niedocenianych funkcji tego formatu. W każdym pliku YAML dłuższym niż około 50 linii, gdzie ten sam blok konfiguracyjny pojawia się więcej niż dwa razy, kotwice prawie na pewno powinny tam być. Różnica w możliwościach utrzymania przed/po jest dramatyczna — zmiana jednego profilu limitu zasobów lub jednej współdzielonej zmiennej środowiskowej zamiast przeszukiwania całego pliku. Użyj Formatera YAML, aby pliki z kotwicami były schludne, i Walidatora YAML, aby potwierdzić, że odwołania do kotwic rozwiązują się poprawnie.