I container rispondono sulla LAN dell'ufficio, poi smettono di rispondere nel momento in cui si connette la VPN aziendale. Nulla nel container è cambiato: nessun riavvio, nessun aggiornamento dell'immagine, nessuna modifica alla configurazione. Ciò che è cambiato è la tabella di routing dell'host, dove una rotta aggiunta dal tunnel ora copre l'indirizzo assegnato da Docker. Prima di modificare una sottorete o chiedere un'esclusione VPN, mettete i due piani di indirizzamento uno accanto all'altro, perché le riparazioni costano quantità di tempo molto diverse.

Leggete la rotta prima di cambiare la configurazione

Partite dalla rete che Docker ha davvero costruito. Il blocco IPAM è il piano di indirizzamento, e la sottorete è la riga che conta:

docker network inspect project_default --format '{{json .IPAM.Config}}'
# [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}]

Compose assegna per impostazione predefinita una /16 a ogni nuovo progetto, quindi due servizi possono stare dentro un intervallo di 65,536 indirizzi. È proprio questa ampiezza a rendere frequenti le sovrapposizioni: una /16 scelta a caso è abbastanza grande da inghiottire un sito che raggiungete attraverso il tunnel, e nessuno se ne accorge finché il tunnel non sale.

Poi confrontate la selezione della rotta per un indirizzo di container, una volta con il tunnel abbassato e una con il tunnel alzato:

ip route get 172.18.0.7
# 172.18.0.7 dev br-3f2a9c src 172.18.0.1

Due forme di risposta portano in luoghi diversi. Se l'output nomina tun0, wg0 o un altro dispositivo di tunnel, un prefisso VPN è almeno altrettanto specifico della rotta del bridge e il pacchetto non raggiunge mai il container. Se nomina ancora il bridge, l'host può raggiungere il container, e il vostro guasto è il filtraggio, il DNS o un percorso di ritorno che fallisce solo in una direzione.

Perché una collisione /16 è facile da incontrare

Docker prende le reti bridge dai pool privati, e il pool predefinito esce dal blocco 172.16.0.0/12 in fette da /16. Le reti aziendali usano spesso lo stesso spazio privato, e una VPN ad accesso remoto spinge quei prefissi verso ogni client che si connette. Quando entrambe le parti credono di possedere 172.18.0.0/16, una tabella di routing contiene due affermazioni contraddittorie.

La lunghezza del prefisso decide il vincitore, non l'ordine in cui compaiono le rotte. Un tunnel che spinge 10.0.0.0/8 non batte una rotta di bridge 172.18.0.0/16, perché /8 è meno specifico. Un tunnel che spinge 172.18.0.0/16, o 172.18.5.0/24 al suo interno, sì. Per questo un portatile funziona sulla rete dell'ufficio e fallisce sul Wi-Fi dell'albergo con la stessa VPN: il client riceve prefissi diversi.

La stessa tabella governa la direzione opposta. Un container che ha bisogno di un host interno a 10.8.4.20 invia il pacchetto al proprio gateway bridge, e l'host lo instrada poi nel tunnel. Quel percorso di solito funziona perché l'host esegue il NAT, ma fallisce quando il lato aziendale accetta solo l'indirizzo VPN del portatile, o quando il filtraggio del percorso inverso scarta la risposta asimmetrica.

Confrontate le tre riparazioni

Ci sono solo tre vie d'uscita, e differiscono per chi deve approvarle e per cosa si rompe mentre ne applicate una.

RepairWho owns itWhat it costs
Renumber the container networkYouRecreates the network, restarts services, invalidates literal addresses in configs and scripts
Exclude your range from the tunnelNetwork teamChanges what every laptop sees, needs a change request and a client profile update
Reach the service through a published portYou and the clientOne port only, no direct container-to-container routing across the tunnel

La rinumerazione è l'unica riparazione che potete completare senza un ticket, ed è per questo che la maggior parte degli articoli si ferma lì. È economica su un progetto giovane e costosa non appena uno strumento di migrazione o una stringa di connessione salvata punta a un indirizzo letterale. Contate quei riferimenti prima di modificare lo YAML, non dopo.

Un'esclusione dal tunnel è la risposta più pulita quando la sovrapposizione sta in un intervallo che la vostra azienda possiede davvero. È anche la più lenta, ed è inutile per un fornitore che non può cambiare il profilo del client. Una porta pubblicata o un piccolo proxy inverso evitano la discussione: il client parla con un indirizzo che il tunnel instrada già correttamente, e solo quella porta deve essere raggiungibile.

Fissate un intervallo di proposito

Per un singolo progetto, dichiarate la sottorete nel file compose così il progetto smette di attingere dal pool condiviso:

networks:
  default:
    ipam:
      config:
        - subnet: 10.201.7.0/24

Prendete quell'intervallo da un registro che il vostro team tiene, non da un esempio trovato cercando. Verificatelo contro i prefissi che la VPN spinge, le LAN domestiche da cui le persone lavorano e le altre reti di container sullo stesso host. Poi pianificate la ricreazione, perché una rete esistente conserva la sua vecchia sottorete: docker compose down, poi up -d. docker network rm rifiuta finché ci sono endpoint collegati, e ogni ipv4_address voce nel file deve cadere dentro il nuovo intervallo.

Per le reti che Docker crea da solo, impostate un pool del demone in /etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.201.0.0/16", "size": 24 },
    { "base": "10.202.0.0/16", "size": 24 }
  ]
}

Il pool vale per le reti create dopo il riavvio del demone, e i bridge già esistenti mantengono i loro indirizzi. È comodo e facile da dimenticare, quindi trattate un vecchio progetto come ancora sovrapposto finché qualcuno non ricrea la sua rete. La documentazione di Docker su reti del motore descrive le regole di assegnazione se dovete difendere il pool in una revisione.

Il bridge Linux e Docker Desktop falliscono in modo diverso

Su un host Linux, i container stanno su bridge nello spazio dei nomi dell'host. Una sola tabella decide tutto, quindi il confronto qui sopra è tutta la storia: leggete la rotta del bridge, leggete la rotta del tunnel e vedete quale vince per un indirizzo reale di container.

Docker Desktop mette i container dietro una VM con il proprio NAT. L'host non ha alcuna rotta verso l'indirizzo del container; raggiunge il servizio attraverso una porta pubblicata, di solito legata a 127.0.0.1. Un client VPN può quindi rompere il servizio senza alcuna sovrapposizione di sottoreti, installando una rotta per l'intervallo interno della VM o bloccando il traffico loopback mentre il tunnel è connesso. Se ip route get sull'host segnala che non esiste una rotta per l'indirizzo del container, è previsto e non è il difetto.

La conseguenza pratica è dove guardate per primo. Su Linux, partite dalla tabella di routing. Su Desktop, partite dalla porta pubblicata e dalle regole del firewall del client VPN, poi controllate se la sottorete interna della VM collide con qualcosa che il tunnel spinge.

Verificate con il tunnel alzato

Riconnettete la VPN due volte prima di dichiararla riparata, perché un profilo che spinge rotte alla connessione e le ritira alla disconnessione può sembrare corretto dopo il primo tentativo. Poi eseguite i controlli che il client esegue davvero:

  1. ip route get dall'host, usando l'indirizzo reale del container.
  2. Una richiesta da un altro container, per dimostrare che il servizio stesso è sano.
  3. curl -sv http://127.0.0.1:8080/ dal client con il tunnel alzato, poi traceroute -n verso l'indirizzo del container da una macchina sulla LAN.
  4. sysctl net.ipv4.conf.all.rp_filter se la richiesta raggiunge l'host ma la risposta sparisce.

Infine, scrivete l'intervallo scelto nello stesso registro che avete usato per sceglierlo, con la data e il progetto che lo possiede. La prossima sovrapposizione arriverà da un progetto che nessuno ha documentato, e l'unica difesa è che l'intervallo sia stato riservato di proposito invece che estratto a caso.

Articoli correlati