A la lliçó 03-05 vas fer servir les xarxes de Docker com una caixa negra: creaves una xarxa, hi connectaves contenidors i els noms resolien. Funcionava, però no sabies per què. Aquesta lliçó obre la caixa: ponts de Linux, parells veth, regles d'iptables, un servidor DNS amagat a 127.0.0.11 i drivers que donen a un contenidor una IP pròpia a la teva LAN física.

Tot el que ve a continuació es demostra sobre la plataforma Aurora Libros en marxa. Les comandes del host assumeixen Linux; si fas servir Docker Desktop, s'executen dins de la màquina virtual (lliçó 05-07).

Contingut

  1. Anatomia d'una xarxa bridge: ponts de Linux
  2. Els parells veth i com aparellar-ne els extrems
  3. El camí d'un paquet, pas a pas
  4. iptables: les cadenes que crea Docker
  5. NAT de sortida i DNAT de publicació
  6. El tallafocs del host, saltat (avís de seguretat)
  7. DOCKER-USER: recuperar el control
  8. El DNS incrustat a 127.0.0.11
  9. Adreçament: subxarxes, rangs, IP fixa i MTU
  10. Els drivers host i none en profunditat
  11. macvlan: una IP pròpia a la LAN física
  12. ipvlan en mode L2 i L3
  13. Taula comparativa dels sis drivers
  14. IPv6 a Docker
  15. Diagnòstic real: tcpdump i nsenter

  1. Anatomia d'una xarxa bridge: ponts de Linux

Una xarxa bridge de Docker és un pont de programari del kernel de Linux: un commutador Ethernet virtual. La xarxa per defecte fa servir el pont docker0; cada xarxa d'usuari en crea un anomenat br-<12 primers caràcters de l'ID de xarxa>.

docker network ls --filter driver=bridge --format "{{.ID}}\t{{.Name}}"
ip -brief link show type bridge
9f3a1c7d5e2b   aurora-libros_frontal
c81be4f0a7d9   aurora-libros_posterior
docker0          UP  02:42:8e:1c:44:a1 <BROADCAST,MULTICAST,UP,LOWER_UP>
br-9f3a1c7d5e2b  UP  02:42:3b:9c:10:7e <BROADCAST,MULTICAST,UP,LOWER_UP>
br-c81be4f0a7d9  UP  02:42:af:22:d5:03 <BROADCAST,MULTICAST,UP,LOWER_UP>

La correspondència és directa i comprovable: els dotze caràcters que segueixen br- són el principi de l'ID de la xarxa. El pont té la seva pròpia adreça IP al host, que és la porta d'enllaç dels contenidors d'aquella xarxa:

ip -brief addr show br-9f3a1c7d5e2b
docker network inspect aurora-libros_frontal --format '{{(index .IPAM.Config 0).Subnet}} gw={{(index .IPAM.Config 0).Gateway}}'
br-9f3a1c7d5e2b UP 172.21.0.1/16
172.21.0.0/16 gw=172.21.0.1

I bridge link show master br-9f3a1c7d5e2b llista les interfícies endollades a aquell pont: una per cada contenidor connectat a la xarxa.

  1. Els parells veth i com aparellar-ne els extrems

Un veth (virtual Ethernet) és un parell d'interfícies connectades per un cable virtual: el que entra per un extrem surt per l'altre. Docker en crea un per cada connexió contenidor-xarxa: un extrem es queda al host i s'endolla al pont; l'altre es mou dins del contenidor i es reanomena eth0. L'aparellament no és evident, perquè els noms són aleatoris; la clau és l'índex d'interfície, ja que cada extrem apunta a l'índex de l'altre mitjançant iflink.

docker compose exec aurora-api cat /sys/class/net/eth0/iflink   # -> 7
ip link show | grep '^7:'                                       # quina interfície és la 7?
7: veth3a1f9c2@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br-9f3a1c7d5e2b

Ja tens el parell: eth0 d'aurora-apiveth3a1f9c2 al host. La notació @if6 del nom mateix confirma la simetria: l'extrem del host apunta a l'índex 6, que és l'eth0 del contenidor dins del seu propi espai de noms de xarxa.

Amb el parell identificat, pots mesurar el trànsit d'un contenidor concret des del host, sense entrar-hi:

ethtool -S veth3a1f9c2 | head -5
NIC statistics:
     peer_ifindex: 6
     rx_packets: 18422
     tx_packets: 21095

Fixa't en peer_ifindex: és la manera més directa de confirmar l'aparellament. I compte amb la inversió de sentit: el que el contenidor envia és rx al veth del host, perquè el paquet entra al host per aquell extrem.

  1. El camí d'un paquet, pas a pas

flowchart LR
  subgraph NSAPI["Espai de noms de xarxa d'aurora-api"]
    P["procés node<br/>172.21.0.5"] --> E0["eth0 (if6)"]
  end
  E0 -->|"cable veth"| V["veth3a1f9c2 (if7)"]
  subgraph HOST["Host"]
    V --> BR["br-9f3a1c7d5e2b<br/>172.21.0.1"]
    BR --> IPT["iptables: nat + filter"]
    IPT --> ETH["eth0 del host"]
  end
  BR -->|"mateix pont:<br/>commutació directa"| V2["veth5d80a3f"]
  V2 --> DB["aurora-db 172.22.0.3"]
  ETH --> NET(("Internet"))

Aquí hi conviuen dos camins molt diferents:

Destinació Recorregut Cost
Un altre contenidor de la mateixa xarxa eth0veth → pont → vetheth0 Commutació al kernel, sense NAT
Una altra xarxa o Internet eth0veth → pont → encaminatiptables (MASQUERADE) → eth0 del host NAT i seguiment de connexions
Des de fora cap a un port publicat eth0 del host → iptables (DNAT) → pont → vetheth0 NAT de destinació

Que la comunicació aurora-apiaurora-db sigui pura commutació explica per què el seu rendiment és pràcticament el de la xarxa del host: en aquell camí no hi ha cap traducció d'adreces.

  1. iptables: les cadenes que crea Docker

En arrencar, dockerd instal·la un conjunt de cadenes pròpies. Conèixer-les pel nom t'estalvia hores de depuració:

Cadena Taula Funció
DOCKER nat i filter Regles DNAT dels ports publicats i el seu permís de reenviament
DOCKER-USER filter Buida i reservada per a tu; s'avalua abans que DOCKER
DOCKER-ISOLATION-STAGE-1 filter Detecta trànsit que surt d'un pont cap a un altre
DOCKER-ISOLATION-STAGE-2 filter El descarta: és el que aïlla les xarxes entre elles
DOCKER-INGRESS nat i filter Només amb Swarm (lliçó 06-03)
sudo iptables -t filter -L DOCKER-ISOLATION-STAGE-2 -n --line-numbers
num  target     prot opt source               destination
1    DROP       all  --  0.0.0.0/0            0.0.0.0/0
2    RETURN     all  --  0.0.0.0/0            0.0.0.0/0

Aquí el tens, en dues línies: l'aïllament que vas comprovar a la lliçó 04-04 quan aurora-web no podia arribar a aurora-db. Un paquet que surt d'un pont i intenta entrar en un altre cau al DROP.

  1. NAT de sortida i DNAT de publicació

sudo iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
DOCKER      all  --  0.0.0.0/0        0.0.0.0/0     ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
MASQUERADE  all  --  172.21.0.0/16    0.0.0.0/0
MASQUERADE  all  --  172.22.0.0/16    0.0.0.0/0
MASQUERADE  tcp  --  172.21.0.2       172.21.0.2    tcp dpt:80
Chain DOCKER (2 references)
DNAT        tcp  --  0.0.0.0/0        0.0.0.0/0     tcp dpt:8080 to:172.21.0.2:80

Tres regles, tres mecanismes:

  • MASQUERADE de subxarxa: tot el que surt de 172.21.0.0/16 cap a fora es reescriu amb la IP del host. És la raó que un contenidor pugui descarregar d'Internet sense tenir IP pública.
  • DNAT de publicació: el -p 8080:80 d'aurora-web es tradueix en aquesta regla. Qualsevol paquet que arribi al port 8080 de qualsevol adreça local es redirigeix a 172.21.0.2:80.
  • MASQUERADE amb origen i destinació iguals: l'anomenat hairpin NAT, que permet que un contenidor s'arribi a si mateix a través del port publicat del host.

Comprova la correspondència amb la publicació real:

sudo iptables -t nat -S DOCKER | grep 8080
-A DOCKER ! -i br-9f3a1c7d5e2b -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.21.0.2:80

Si publiques restringint la interfície —"127.0.0.1:8080:80"—, la regla canvia i hi afegeix -d 127.0.0.1. Aquesta diferència d'una paraula és la que separa "accessible des del meu portàtil" d'"accessible des de tota la xarxa de l'oficina".

  1. El tallafocs del host, saltat (avís de seguretat)

Aquest és el punt més important de la lliçó i el que més sorpreses desagradables provoca en producció.

Quan publiques un port, Docker insereix el seu DNAT a PREROUTING, que s'avalua abans que les regles de la cadena INPUT on ufw o firewalld posen les seves. El trànsit redirigit passa per FORWARD, no per INPUT. Resultat: el teu tallafocs no veu aquell trànsit i no el bloqueja.

sudo ufw status | head -4
docker run -d --name exposat -p 5000:80 nginx:alpine
curl -s -o /dev/null -w '%{http_code}\n' http://<IP-del-host>:5000/
Status: active
22/tcp                     ALLOW       Anywhere
200

El tallafocs diu que només el 22 està obert. El port 5000 respon igualment des de qualsevol màquina de la xarxa. Servidors sencers de PostgreSQL i Redis han acabat a Internet exactament així.

Les tres defenses, de més simple a més completa:

Defensa Com Abast
Publicar només en local "127.0.0.1:5432:5432" Impedeix l'accés remot d'arrel. La primera opció sempre
No publicar Fer servir xarxes internes i docker compose exec El que fa aurora-db des de la lliçó 04-04
Regles a DOCKER-USER Filtrar el trànsit reenviat Control fi per origen

Advertència de seguretat. L'exposició de ports i les regles de tallafocs d'un servidor s'han de revisar amb el responsable de seguretat o de compliance de la teva organització. El que aquí es mostra explica el mecanisme; la política concreta —què s'exposa, a qui i amb quins controls compensatoris— no la decideix qui escriu el compose.yaml tot sol.

  1. DOCKER-USER: recuperar el control

Docker mai no toca DOCKER-USER i l'avalua abans que les seves pròpies regles. És el lloc previst per a les teves polítiques:

# Permetre només des de la xarxa corporativa; descartar la resta
sudo iptables -I DOCKER-USER -i eth0 -j DROP
sudo iptables -I DOCKER-USER -i eth0 -s 10.0.0.0/8 -j RETURN
sudo iptables -L DOCKER-USER -n --line-numbers
num  target     prot opt source               destination
1    RETURN     all  --  10.0.0.0/8           0.0.0.0/0
2    DROP       all  --  0.0.0.0/0            0.0.0.0/0

Dues advertències imprescindibles. La primera: l'ordre importa, -I insereix al principi, així que la regla permissiva s'ha d'inserir en darrer lloc per acabar a dalt. La segona: aquestes regles no sobreviuen a un reinici; cal persistir-les amb iptables-persistent, nftables o l'eina de la teva distribució. I si penses desactivar la manipulació d'iptables del tot ("iptables": false a /etc/docker/daemon.json), assumeix que perds la sortida a Internet dels contenidors i la publicació de ports fins que escriguis tu totes les regles: poques vegades compensa.

  1. El DNS incrustat a 127.0.0.11

En una xarxa d'usuari, els noms de servei resolen perquè Docker executa un servidor DNS per contenidor escoltant a 127.0.0.11:53.

docker compose exec aurora-api cat /etc/resolv.conf
docker compose exec aurora-api getent hosts aurora-db
nameserver 127.0.0.11
options ndots:0
172.22.0.3   aurora-db

Aquella adreça de loopback no existeix en cap interfície visible. El truc: dins de l'espai de noms del contenidor hi ha una regla DNAT (-A DOCKER_OUTPUT -d 127.0.0.11/32 -p tcp --dport 53 -j DNAT --to-destination 127.0.0.11:38765) que redirigeix el trànsit a un port alt on escolta un procés del daemon.

L'algorisme de resolució, per ordre:

  1. És un nom de servei o àlies d'una xarxa que compartim? → respon el daemon amb la IP del contenidor.
  2. És un contenidor de la mateixa xarxa pel seu nom o container_name? → igual.
  3. Si no, reenvia als servidors DNS del host (o als de dns: del servei).

Punts que convé tenir clars:

  • La resolució està acotada a les xarxes compartides. aurora-web no resol aurora-db perquè no comparteixen xarxa: no és una fallada de DNS, és el disseny.
  • Amb --scale, un mateix nom retorna diverses adreces en ordre rotatori: un balanceig rudimentari del costat del client. Comprova-ho amb docker compose up -d --scale aurora-api=3 seguit de getent hosts aurora-api.
  • options ndots:0 evita que s'afegeixin sufixos de cerca i estalvia consultes fallides.
  • Moltes biblioteques guarden en memòria cau la resolució. Si un contenidor es recrea i canvia d'IP, una aplicació que va resoldre una vegada pot quedar-se parlant amb una IP morta. És un motiu real per reiniciar clients després de recrear un servei.

  1. Adreçament: subxarxes, rangs, IP fixa i MTU

Per defecte Docker tria subxarxes de 172.17.0.0/16 en endavant, cosa que xoca amb moltes VPN corporatives. Les pots fixar:

docker network create --driver bridge \
  --subnet 10.80.0.0/24 --gateway 10.80.0.1 --ip-range 10.80.0.128/25 \
  --aux-address "reservada-nas=10.80.0.20" \
  --opt com.docker.network.driver.mtu=1450 \
  --opt com.docker.network.bridge.name=br-aurora \
  aurora-fixa
Opció Què fa
--subnet Rang complet de la xarxa
--gateway IP del pont al host
--ip-range Subconjunt del qual Docker assigna automàticament; la resta queda lliure per a IP fixes
--aux-address Adreces que Docker no ha d'assignar (equips reals dins del rang)
mtu Mida màxima de trama; abaixar-la evita fragmentació després de VPN o túnels
bridge.name Nom llegible del pont en lloc de br-<id>

Assignar una IP fixa exigeix que la xarxa tingui subxarxa pròpia: docker run -d --network aurora-fixa --ip 10.80.0.10 nginx:alpine. A Compose es declara amb ipv4_address dins de networks. Tot i això, la bona pràctica continua sent dependre dels noms DNS i no de les IP: les IP fixes es reserven per a casos on alguna cosa externa (una regla de tallafocs, una llicència, un equip antic) exigeix una adreça estable.

El valor per defecte de la MTU es defineix a /etc/docker/daemon.json amb "mtu": 1450. El símptoma típic d'una MTU mal ajustada és cruel: les connexions s'estableixen i les peticions petites funcionen, però les respostes grans es queden penjades.

  1. Els drivers host i none en profunditat

host elimina l'espai de noms de xarxa: el contenidor fa servir directament la pila del host.

docker run -d --name api-host --network host auroralibros/aurora-api:1.2.0
docker exec api-host ip -brief addr | head -2 && ss -tlnp | grep 3000
lo     UNKNOWN  127.0.0.1/8
eth0   UP       192.168.1.42/24
LISTEN 0  511  *:3000  users:(("node",pid=48122,fd=21))

El procés escolta directament al port 3000 del host. El que hi guanyes i el que hi perds:

Guanyes Perds
Sense NAT: menys latència i més rendiment en càrregues de molts paquets petits Aïllament de xarxa: zero
Accés a interfícies reals (útil en monitoratge, VPN, DHCP) Resolució per nom de servei: no existeix
Sense límit pràctic de ports publicats Col·lisions de ports entre contenidors
Multicast i protocols que el NAT trenca -p s'ignora amb un avís

Casos legítims: agents de monitoratge (node-exporter, lliçó 05-06), balancejadors de molt alt rendiment i eines de diagnòstic. Per a aurora-api seria un error: perdria l'aïllament sense guanyar res apreciable.

none deixa el contenidor únicament amb lo: docker run --rm --network none alpine:3 ping -c1 -W1 8.8.8.8 respon Network is unreachable. És l'opció correcta per a processos de càlcul pur o per tractar fitxers d'origen dubtós: si no hi ha xarxa, no hi ha exfiltració.

  1. macvlan: una IP pròpia a la LAN física

macvlan dona al contenidor la seva pròpia adreça MAC a la xarxa física. Per a la resta de l'oficina, el contenidor és un equip més: obté una IP del rang real i no necessita NAT ni publicació de ports.

# 1) Esbrina la interfície física i la subxarxa reals
ip -brief addr show eth0

# 2) Crea la xarxa macvlan sobre aquella interfície
docker network create -d macvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  --ip-range 192.168.1.240/28 \
  -o parent=eth0 \
  lan-oficina

# 3) Un contenidor amb IP de la LAN
docker run -d --name web-lan --network lan-oficina --ip 192.168.1.241 nginx:alpine
docker exec web-lan ip -brief addr show eth0
eth0   UP   192.168.1.241/24

Des d'un altre equip de l'oficina, http://192.168.1.241/ respon sense haver publicat cap port.

Els tres paranys, per ordre de freqüència:

  1. El host no pot parlar amb els seus propis contenidors macvlan. No és una fallada: el kernel impedeix el trànsit entre la interfície pare i les seves interfícies macvlan filles. La solució és crear una interfície macvlan addicional al host i encaminar-hi:
sudo ip link add macvlan-host link eth0 type macvlan mode bridge
sudo ip addr add 192.168.1.250/32 dev macvlan-host && sudo ip link set macvlan-host up
sudo ip route add 192.168.1.240/28 dev macvlan-host
  1. Mode promiscu. El commutador o l'hipervisor han d'acceptar diverses MAC per port. A la majoria de serveis de núvol (i a moltes xarxes Wi-Fi) està prohibit, i macvlan senzillament no funciona.
  2. Col·lisió amb el DHCP. Docker no parla DHCP: reserva un --ip-range fora de l'àmbit del servidor DHCP o acabaràs amb dos equips compartint IP.

  1. ipvlan en mode L2 i L3

ipvlan resol el problema del mode promiscu: tots els contenidors comparteixen la MAC de la interfície pare i es distingeixen per IP.

# L2: mateix domini de difusió que el host, sense MAC pròpia
docker network create -d ipvlan --subnet 192.168.1.0/24 --gateway 192.168.1.1 \
  -o parent=eth0 -o ipvlan_mode=l2 lan-l2
# L3: el host encamina; sense difusió, subxarxa independent
docker network create -d ipvlan --subnet 10.90.1.0/24 \
  -o parent=eth0 -o ipvlan_mode=l3 lan-l3
Aspecte macvlan ipvlan L2 ipvlan L3
MAC Una per contenidor Compartida amb el pare Compartida
Mode promiscu Necessari No No
Difusió i DHCP No
Encaminament Via gateway de la LAN Via gateway de la LAN El fa el host
Rutes externes Automàtiques Automàtiques Cal afegir-les al router

El mode L3 és el més net en centres de dades amb encaminament controlat, però exigeix que algú ensenyi al router com arribar a 10.90.1.0/24. Si no ho fas, els contenidors surten i no torna res.

  1. Taula comparativa dels sis drivers

Driver Aïllament Rendiment DNS intern Publicació de ports Cas d'ús típic
bridge (usuari) Alt Bo (NAT a la sortida) -p amb DNAT El 95 % dels casos, inclosa Aurora Libros
bridge per defecte Mitjà Igual No (només --link, obsolet) -p Compatibilitat; evita-la
host Cap El millor No No aplica Agents de monitoratge, alt rendiment
none Total No aplica No No Processos sense xarxa
macvlan Mitjà (LAN plana) Molt bo, sense NAT Sí, a la xarxa Docker Innecessària Integrar amb equips de la LAN o sistemes antics
ipvlan Mitjà Molt bo Innecessària Com macvlan on el mode promiscu està prohibit

El driver overlay, per comunicar contenidors de diversos hosts, es tracta a la lliçó 06-03 juntament amb la malla d'encaminament de Swarm.

  1. IPv6 a Docker

Des de la versió 27, IPv6 s'habilita per xarxa sense tocar el daemon:

docker network create --ipv6 --subnet 2001:db8:aur::/64 aurora-v6
docker run --rm --network aurora-v6 alpine:3 ip -6 -brief addr show eth0
# eth0  UP  2001:db8:aur::2/64  fe80::42:acff:fe14:2/64

A Compose n'hi ha prou amb enable_ipv6: true a la xarxa. Tres avisos: fes servir adreces ULA (fd00::/8) si no tens un prefix públic delegat; activa "ip6tables": true a /etc/docker/daemon.json perquè funcionin el NAT i la publicació; i comprova que les teves regles de tallafocs existeixen també en IPv6, perquè una política només IPv4 deixa una porta oberta que ningú no mira.

  1. Diagnòstic real: tcpdump i nsenter

La imatge nicolaka/netshoot amb --network container: (lliçó 03-04) et posa dins de l'espai de noms de xarxa del contenidor amb totes les eines:

docker run --rm -it --network container:aurora-libros-aurora-api-1 nicolaka/netshoot \
  sh -c 'ip -brief addr; ip route; ss -tlnp'
eth0   UP   172.21.0.5/16
eth1   UP   172.22.0.4/16
default via 172.21.0.1 dev eth0
LISTEN 0 511 *:3000

Dues interfícies, perquè aurora-api és a frontal i a posterior; la ruta per defecte surt per frontal, ja que posterior és internal i no té porta d'enllaç.

Capturar el trànsit real entre l'API i la base de dades:

docker run --rm --net container:aurora-libros-aurora-api-1 nicolaka/netshoot \
  timeout 10 tcpdump -n -i any 'host 172.22.0.3 and port 5432' -c 6
IP 172.22.0.4.51442 > 172.22.0.3.5432: Flags [S], seq 2451
IP 172.22.0.3.5432 > 172.22.0.4.51442: Flags [S.], seq 8891, ack 2452
IP 172.22.0.4.51442 > 172.22.0.3.5432: Flags [P.], length 96
IP 172.22.0.3.5432 > 172.22.0.4.51442: Flags [P.], length 1284

Les adreces són les de la xarxa posterior i no hi ha cap traducció: comunicació directa a través del pont, tal com anticipava l'apartat 3. Des del host, sense entrar en cap contenidor, la captura equivalent és sudo tcpdump -n -i veth3a1f9c2 -c 5 'tcp port 5432'.

nsenter és la via de més baix nivell: entra a l'espai de noms de xarxa d'un procés fent servir les eines del host.

pid=$(docker inspect --format '{{.State.Pid}}' aurora-libros-aurora-api-1)
sudo nsenter -t "$pid" -n ip -brief addr
sudo nsenter -t "$pid" -n ss -tnp state established
eth0   UP   172.21.0.5/16
eth1   UP   172.22.0.4/16
ESTAB  0  0  172.22.0.4:51442  172.22.0.3:5432

És la tècnica que salva el dia quan un contenidor és distroless i no té ni sh (lliçó 05-03): tu no necessites res a dins, perquè fas servir els binaris del host sobre el seu namespace. El perquè exacte que això funcioni és a la lliçó 05-07.

Errors Habituals i Consells

Creure que el tallafocs del host protegeix els ports publicats. No ho fa: el DNAT de Docker s'avalua abans. Publica a 127.0.0.1 o filtra a DOCKER-USER.

Afegir regles a INPUT per bloquejar un contenidor. El trànsit va per FORWARD. La cadena correcta és DOCKER-USER.

Escriure regles dins de la cadena DOCKER. Docker la reescriu en arrencar o en publicar un port, i les teves regles desapareixen.

Dependre de les IP dels contenidors. Canvien en recrear-los. Fes servir noms de servei i reserva les IP fixes per a requisits externs reals.

Fer servir --network host "perquè vagi més ràpid". Perds l'aïllament i la resolució per nom, i en una API HTTP la diferència sol ser negligible. Mesura abans.

Muntar macvlan sense comprovar el mode promiscu, o oblidar-se d'abaixar la MTU després d'una VPN: en el primer cas tot sembla ben configurat i no respon res; en el segon, les connexions s'obren però es pengen en transferir dades grans.

Consell: davant d'un problema de xarxa, segueix sempre el mateix ordre: resol el nom? (getent hosts), hi ha ruta? (ip route), escolta algú? (ss -tlnp dins de la destinació), arriba el paquet? (tcpdump). Cada pas descarta una capa sencera.

Exercicis

Exercici 1. Amb la plataforma aixecada, identifica el parell veth d'aurora-db, demostra que el seu extrem del host està endollat al pont de la xarxa posterior i mesura el trànsit que hi ha passat. Explica per què rx al host correspon al que envia el contenidor.

Exercici 2. Demostra el salt del tallafocs: publica un contenidor en un port, comprova que ufw no el declara obert i que tot i així respon des d'una altra màquina; després bloqueja'l correctament amb DOCKER-USER i verifica que continua funcionant des de localhost.

Exercici 3. Captura amb tcpdump una consulta real entre aurora-api i aurora-db provocada per curl /llibres, i demostra amb docker exec sobre Redis per què la segona petició no genera trànsit cap a la base de dades.

Solucions

Solució 1.

idx=$(docker compose exec -T aurora-db cat /sys/class/net/eth0/iflink | tr -d '\r')
veth=$(ip -o link | awk -v i="$idx:" '$1==i {sub(/@.*/,"",$2); print $2}')
xarxa=$(docker network inspect aurora-libros_posterior --format '{{.Id}}' | cut -c1-12)
echo "veth=$veth  pont esperat=br-$xarxa"
ip -o link show "$veth" | grep -o 'master [^ ]*'
ethtool -S "$veth" | grep -E 'peer_ifindex|rx_packets|tx_packets'
veth=veth5d80a3f  pont esperat=br-c81be4f0a7d9
master br-c81be4f0a7d9
     peer_ifindex: 10
     rx_packets: 3921
     tx_packets: 4877

El master coincideix exactament amb el pont derivat de l'ID de la xarxa posterior: aquell veth és el cable d'aurora-db cap a aquella xarxa i no cap a una altra. I peer_ifindex: 10 apunta a l'índex de l'eth0 de dins del contenidor.

Sobre la inversió de sentit: les estadístiques es llegeixen des del punt de vista del host. Quan aurora-db respon a una consulta, el paquet surt pel seu eth0, viatja pel cable virtual i entra al host per veth5d80a3f; per al host això és recepció, per tant rx. Confondre-ho porta a diagnòstics invertits: si veus el rx disparat al veth d'una base de dades, no és que li estiguin enviant molt, és que està retornant molt, i probablement falti un LIMIT en alguna consulta.

Solució 2.

docker run -d --name exposat -p 5000:80 nginx:alpine
sudo ufw status | grep -c 5000            # 0: ufw no sap res d'aquell port
curl -s -o /dev/null -w 'host: %{http_code}\n' http://127.0.0.1:5000/
# des d'una altra màquina: curl -s -o /dev/null -w 'remot: %{http_code}\n' http://192.168.1.42:5000/
sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 5000 -j DROP
curl -s -o /dev/null -w 'host després de la regla: %{http_code}\n' http://127.0.0.1:5000/
# des d'una altra màquina: curl -m 3 http://192.168.1.42:5000/
sudo iptables -D DOCKER-USER -i eth0 -p tcp --dport 5000 -j DROP && docker rm -f exposat
0
host: 200
remot: 200
host després de la regla: 200
curl: (28) Connection timed out after 3001 milliseconds

La regla filtra per interfície d'entrada (-i eth0), així que el trànsit que arriba per la interfície física es descarta mentre que el que neix al host mateix —i que per tant no entra per eth0— continua passant. Aquest matís és el que la fa utilitzable: bloqueja l'exterior sense trencar la depuració local.

La lliçó de fons: ufw informava d'un sistema amb un sol port obert i era fals. Publicar un port en un servidor amb IP pública equival a obrir-lo a Internet, tret que lliguis la publicació a 127.0.0.1 o filtris explícitament. És exactament el motiu pel qual aurora-db no publica res des de la lliçó 04-04.

Solució 3.

docker compose exec aurora-cache redis-cli FLUSHALL
docker run --rm --net container:aurora-libros-aurora-api-1 nicolaka/netshoot \
  timeout 12 tcpdump -n -i any 'host 172.22.0.3 and port 5432' &
sleep 1
curl -s http://localhost:8080/api/llibres | head -c 58; echo    # primera: buida la memòria cau
curl -s http://localhost:8080/api/llibres | head -c 58; echo    # segona: memòria cau calenta
docker compose exec aurora-cache redis-cli KEYS 'llibres:*'
wait
{"origen":"db","total":9,"llibres":[{"id":1,"titol":"El
{"origen":"cache","total":9,"llibres":[{"id":1,"titol":
1) "llibres:tots"
IP 172.22.0.4.51488 > 172.22.0.3.5432: Flags [P.], length 112
IP 172.22.0.3.5432 > 172.22.0.4.51488: Flags [P.], length 1284
2 packets captured

La primera petició retorna origen: db i a la captura hi apareix el diàleg amb PostgreSQL al 5432, amb adreces de la xarxa posterior sense traduir. La segona retorna origen: cache i no afegeix ni un paquet a la captura: el patró cache-aside troba la clau llibres:tots a Redis i ni tan sols obre una consulta.

Dues conclusions útils. La primera, d'arquitectura: tcpdump acaba de convertir en observable una cosa que fins ara era un camp JSON en el qual calia confiar. La segona, de diagnòstic: si un dia /llibres respongués origen: db sempre, aquesta mateixa captura et diria si el problema és a l'escriptura de la clau a Redis o a la seva lectura, sense tocar ni una línia de codi.

Conclusió

Les xarxes de Docker han deixat de ser màgia. Saps que una xarxa bridge és un pont de Linuxdocker0 o br-<id>— amb un extrem veth per contenidor endollat a sobre, i saps aparellar els extrems amb iflink i ethtool -S per mesurar el trànsit d'un contenidor concret des del host. Coneixes els dos camins d'un paquet: commutació pura entre contenidors de la mateixa xarxa, i MASQUERADE quan surt a l'exterior. I has llegit les regles reals: la cadena DOCKER amb el seu DNAT per cada -p, i DOCKER-ISOLATION-STAGE-2 amb el DROP de dues línies que explica tot l'aïllament entre xarxes que fas servir des del mòdul 3.

T'endús també un avís que val més que moltes configuracions: publicar un port salta el tallafocs del host, perquè el DNAT s'avalua abans que INPUT; la defensa és publicar a 127.0.0.1, no publicar en absolut o escriure regles a DOCKER-USER, l'única cadena que Docker respecta. Saps com resol realment un nom el DNS incrustat de 127.0.0.11 i per què la resolució està acotada a les xarxes compartides; controles l'adreçament (--subnet, --ip-range, --aux-address, mtu) per conviure amb VPN i equips reals; coneixes en profunditat els sis drivers, inclosos macvlan amb el seu parany del mode promiscu i ipvlan en L2 i L3; i diagnostiques amb netshoot, ss, tcpdump sobre el trànsit real entre aurora-api i aurora-db, i nsenter quan dins del contenidor no hi ha ni un sh.

La capa següent que cal obrir és la de les dades. A la lliçó 05-02 veuràs l'emmagatzematge a fons: què és un storage driver i com funciona overlay2 per dins amb els seus lowerdir, upperdir i merged; quant costa realment el copy-on-write —mesurat— i per què això obliga que una base de dades faci servir volums; els volume drivers i les seves opcions, inclosos NFS i els volums compartits entre piles; i una estratègia de còpies de seguretat d'Aurora Libros amb restauració provada, perquè una còpia que no s'ha restaurat mai no és una còpia.

Docker: De Principiant a Avançat

Mòdul 1: Introducció a Docker

Mòdul 2: Treballant amb Imatges Docker

Mòdul 3: Contenidors Docker

Mòdul 4: Docker Compose

Mòdul 5: Conceptes Avançats de Docker

Mòdul 6: Docker en Producció

Mòdul 7: Ecosistema i Eines de Docker

© Copyright 2026. Tots els drets reservats