Se hai mai copiato un blocco YAML da una parte all'altra di un file, hai già incontrato il problema che anchors e aliases risolvono. I grandi config Kubernetes, i file Docker Compose multi-ambiente e le pipeline CI complesse tendono a ripetere gli stessi blocchi — limiti di risorse, variabili d'ambiente, mount dei volumi — più e più volte. YAML ha una risposta built-in per questo, e la maggior parte degli sviluppatori non sa che esiste.

Anchors (&) e aliases (*) sono il meccanismo di YAML per definire un valore una volta e referenziarlo ovunque nello stesso file. Pensa a un anchor come alla dichiarazione di una variabile e a un alias come al suo utilizzo. La merge key (<<) estende questo al merging di oggetti — simile all'operatore spread di JavaScript. Entrambe le funzionalità sono definite nella sezione sugli ancore di nodo della specifica YAML 1.2.

Sintassi di base per anchor e alias

La sintassi è minimale. Anteponi &nome a un valore per creare un anchor, poi usa *nome ovunque nel documento per referenziarlo:

yaml
# Define the anchor once
default_timeout: &timeout 30

# Reference it multiple times
services:
  api:
    timeout: *timeout      # → 30
  worker:
    timeout: *timeout      # → 30
  scheduler:
    timeout: *timeout      # → 30

Quando un parser YAML legge questo, ogni alias viene risolto con lo stesso valore dell'anchor. Cambia l'anchor e tutti gli alias si aggiornano. Niente ricerca-e-sostituzione.

Merging di oggetti con la chiave <<

Il riutilizzo scalare è utile, ma il vero potere viene dal merging di interi blocchi mapping. La merge key << assorbe tutte le coppie chiave-valore dall'anchor referenziato nella mapping corrente:

yaml
# 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"]

Le chiavi definite direttamente nella mapping sovrascrivono le chiavi dell'anchor mergiato. Quindi se base imposta restart: unless-stopped e il servizio imposta anche restart: no, vince il valore a livello di servizio.

Prima e dopo: Docker Compose nel mondo reale

Ecco come appare un tipico file Docker Compose multi-servizio senza anchors — pieno di ripetizioni:

yaml
# 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:
      - postgres

Ora con anchors e merge key — ogni blocco ripetuto è definito esattamente una volta:

yaml
# 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"]
Suggerimento pro: Il prefisso x- sui campi di estensione (come x-common) è una convenzione Docker Compose che impedisce a Compose di tentare di analizzare il blocco come definizione di servizio. Viene ignorato dal motore Compose ma è pienamente utilizzabile come anchor YAML.

GitHub Actions: matrix strategy con anchors

Anche la configurazione CI è un posto dove gli anchors rendono. Ecco un workflow GitHub Actions che riutilizza la configurazione comune degli step su più job:

yaml
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 build

Nota: il parser YAML di GitHub Actions supporta anchors e aliases. Tuttavia, alcuni sistemi CI (in particolare il config.yml di CircleCI prima della versione 2.1) avevano una propria sintassi simile agli anchors. Controlla sempre la documentazione dello strumento specifico che utilizzi.

Kubernetes: riutilizzo dei limiti di risorse

Nei manifest Kubernetes, i limiti di risorse e le variabili d'ambiente sono candidati ideali per gli anchors. Ecco un pattern che uso regolarmente:

yaml
# 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 profile

Merge multipli e priorità di sovrascrittura

Puoi mergere da più anchors in una singola mapping. Forniscili come lista sotto <<. Le voci precedenti nella lista hanno priorità su quelle successive per le chiavi duplicate:

yaml
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=widget

Limitazioni da conoscere

  • Solo nello stesso file. Anchors e aliases funzionano solo all'interno di un singolo stream di documenti YAML. Non puoi referenziare un anchor da un file diverso. Per il riutilizzo tra file, hai bisogno del meccanismo del tuo strumento (es. template Helm, Kustomize, o processori YAML come ytt).
  • Nessun valore calcolato. Gli aliases si risolvono in copie esatte del valore dell'anchor. Non c'è interpolazione o logica template — per quello servono strumenti come Helm o Jinja2.
  • La merge key è un'estensione YAML. La merge key << è definita in una estensione YAML 1.1, non nella specifica core. La maggior parte dei parser la supporta, ma i parser YAML 1.2 strettamente conformi alle specifiche potrebbero non farlo.
  • I riferimenti circolari rompono i parser. Non tentare di ancorare un nodo e poi referenziarlo dall'interno di se stesso. È tecnicamente non valido.
  • Gli aliases copiano, non referenziano. Modificare il valore di un alias risolto nel codice non cambia l'anchor o gli altri alias. Sono valori indipendenti dopo il parsing.

Conclusione

Gli anchors e gli aliases YAML sono una delle funzionalità più sottoutilizzate del formato. In qualsiasi file YAML più lungo di circa 50 righe in cui lo stesso blocco di configurazione appare più di due volte, gli anchors appartengono quasi certamente lì. La differenza nella manutenibilità prima/dopo è notevole — cambiare un profilo di limite di risorse o una variabile d'ambiente condivisa invece di cercare in tutto il file. Usa il YAML Formatter per mantenere ordinati i file con anchors, e il YAML Validator per confermare che i tuoi riferimenti anchor si risolvano correttamente.