Fa quatre lliçons que arrossegues la mateixa avaria i ja saps exactament què la causa: nslookup aurora-db retorna NXDOMAIN perquè la xarxa bridge per defecte no té DNS intern entre contenidors. La connectivitat hi és —nc -zv 172.17.0.2 5432 respon succeeded—, però l'API no sap com es diu la seva base de dades.
Aquesta lliçó és la que ho arregla, i amb dues comandes. Però abans d'escriure-les entendràs de debò el model de xarxa de Docker: per què cada contenidor té el seu propi localhost, què fa cadascun dels drivers integrats, en què es diferencia la bridge per defecte d'una xarxa creada per tu, i quina és la diferència —que es confon constantment— entre publicar un port i que dos contenidors es parlin. Després crearàs aurora-net, mouràs els tres serveis a dins, i executaràs el curl que fa tot el curs que esperes. I per rematar-ho, posaràs davant aurora-web amb Nginx com a proxy invers, i obriràs la llibreria completa al navegador.
Contingut
- Per què els contenidors no es veuen entre ells
- Els drivers de xarxa integrats
- La bridge per defecte enfront de les xarxes definides per l'usuari
- Les comandes de
docker network - Publicació de ports, revisitada
- Pràctica:
aurora-neti la resolució de noms - El moment de la veritat:
/llibresretorna els vuit llibres aurora-web: Nginx com a proxy invers- Connectar i desconnectar xarxes en calent
- Bones pràctiques de xarxa
- Per què els contenidors no es veuen entre ells
Un contenidor no és només un procés aïllat: té el seu propi namespace de xarxa. Això significa que posseeix, en exclusiva, la seva llista d'interfícies, la seva taula de rutes, les seves regles de tallafocs, els seus ports i —això és el que confon tothom— el seu propi localhost.
flowchart TB
subgraph HOST["Host (la teva màquina)"]
direction TB
HL["localhost del host<br/>127.0.0.1"]
subgraph C1["Contenidor aurora-api"]
L1["EL SEU localhost<br/>127.0.0.1"]
E1["eth0: 172.17.0.4"]
P1["node escoltant a :3000"]
end
subgraph C2["Contenidor aurora-db"]
L2["EL SEU localhost<br/>127.0.0.1"]
E2["eth0: 172.17.0.2"]
P2["postgres escoltant a :5432"]
end
BR["docker0<br/>bridge 172.17.0.1"]
E1 --- BR
E2 --- BR
end
L1 -. "NO és el mateix" .- L2
L1 -. "NO és el mateix" .- HL
Tres localhost diferents a la mateixa màquina. Quan server.js intentava connectar-se a localhost:5432 al mòdul 2, estava preguntant per PostgreSQL dins del seu propi contenidor, on només viu node. D'aquí l'ECONNREFUSED: algú va contestar (el mateix contenidor) dient que allà no hi ha ningú escoltant.
Comprova-ho tu mateix:
docker exec aurora-api hostname -i
docker exec aurora-db hostname -i
docker exec aurora-api sh -c 'nc -z localhost 5432; echo "PostgreSQL al meu localhost: $?"'Cadascun amb la seva IP, i al localhost de l'API no hi ha cap base de dades. La regla, en una línia:
Dins d'un contenidor,
localhostsempre significa "jo mateix". Per parlar amb un altre contenidor cal fer servir la seva adreça: la seva IP o, molt millor, el seu nom.
- Els drivers de xarxa integrats
Docker implementa les xarxes mitjançant drivers intercanviables:
| Driver | Què fa | Aïllament | Quan fer-lo servir |
|---|---|---|---|
bridge |
Xarxa virtual privada al host; cada contenidor rep una IP interna | Alt | Per defecte i per a gairebé tot: aplicacions de diversos contenidors en una màquina |
host |
El contenidor comparteix la pila de xarxa del host: sense IP pròpia, sense NAT | Cap | Rendiment extrem, o serveis que necessiten veure tots els ports del host |
none |
Només interfície de loopback: sense xarxa | Total | Treballs per lots que no necessiten xarxa; màxima seguretat |
overlay |
Xarxa que abasta diversos hosts d'un clúster | Alt | Docker Swarm. Es veu a la lliçó 06-03 |
macvlan |
El contenidor rep una MAC i una IP de la teva xarxa física | Mitjà | Integrar contenidors en una LAN existent. Detall a la lliçó 05-01 |
ipvlan |
Similar, compartint la MAC del host | Mitjà | Entorns amb restriccions de MAC. Detall a la lliçó 05-01 |
Les tres primeres vénen preconfigurades i les veus amb:
NETWORK ID NAME DRIVER SCOPE
6b2f8a1c9e73 bridge bridge local
f19d4c7e2a85 host host local
3a8e1b5f9c24 none null localUna ullada ràpida a host i none, que es fan servir poc però convé reconèixer:
docker run --rm --network host alpine:3.20 hostname -i
docker run --rm --network none alpine:3.20 sh -c 'ip -o addr show | awk "{print \$2, \$4}"'Amb --network host, el contenidor té la mateixa IP que la teva màquina: no hi ha -p que valgui perquè els ports són directament els del host, i no hi ha cap aïllament de xarxa. Amb --network none, només existeix lo: ni tan sols pot fer ping a res.
bridge |
host |
none |
|
|---|---|---|---|
| IP pròpia | Sí | No, la del host | No |
Cal -p |
Sí | No, i no funciona | Irrellevant |
| Aïllament de xarxa | Sí | No | Total |
| Rendiment | Molt bo (hi ha NAT) | El màxim | — |
| Conflictes de ports | Només al host | Directes entre contenidors | No |
| Disponible a Docker Desktop (macOS/Windows) | Sí | Amb limitacions | Sí |
- La bridge per defecte enfront de les xarxes definides per l'usuari
Aquí hi ha el cor de la lliçó. Existeixen dos tipus de xarxa bridge i es comporten de manera molt diferent:
Bridge per defecte (bridge, docker0) |
Bridge definida per l'usuari | |
|---|---|---|
| Com es fa servir | Automàtica: tot contenidor sense --network hi cau |
docker network create la-meva-xarxa + --network la-meva-xarxa |
| DNS intern entre contenidors | NO | SÍ: cada contenidor es resol pel seu nom |
| Aïllament | Tots els contenidors junts, inclosos els d'altres projectes | Només els que connectis a aquesta xarxa |
| Connectar/desconnectar en calent | No | Sí, amb network connect/disconnect |
| Àlies de xarxa | No | Sí, amb --network-alias |
| Variables d'entorn d'enllaç | Sistema --link, obsolet |
No cal |
| Recomanació | Evitar-la | Fer-la servir sempre |
Només la primera fila importa avui, però és determinant: en una xarxa pròpia, Docker aixeca un servidor DNS intern a 127.0.0.11 que resol els noms dels contenidors connectats a aquesta xarxa. A la bridge per defecte, aquest DNS existeix però només reenvia consultes als servidors del host: no coneix cap nom de contenidor. Aquest és, literalment, tot el misteri que arrossegues des de la lliçó 02-06.
Abans d'arreglar-ho, deixa constància del punt de partida:
- Les comandes de
docker network
docker network| Comanda | Per a què |
|---|---|
docker network ls |
Llistar xarxes |
docker network create |
Crear una xarxa |
docker network inspect |
Veure'n la configuració i els contenidors |
docker network connect |
Connectar un contenidor en marxa a una xarxa |
docker network disconnect |
Desconnectar-lo |
docker network rm |
Esborrar una xarxa |
docker network prune |
Esborrar totes les xarxes sense contenidors |
Crear una xarxa
Amb opcions, quan necessitis control:
docker network create \
--driver bridge \
--subnet 172.28.0.0/16 \
--gateway 172.28.0.1 \
--label projecte=aurora-libros \
aurora-net-personalitzada| Opció | Per a què |
|---|---|
--driver |
El driver; bridge és el valor per defecte |
--subnet |
Fixar el rang d'IPs (útil si xoca amb la teva VPN) |
--gateway |
La passarel·la d'enllaç d'aquesta subxarxa |
--ip-range |
Restringir el rang d'assignació automàtica |
--internal |
Xarxa sense sortida a Internet: aïllament total cap a fora |
--label |
Etiquetes, com als contenidors |
--attachable |
Permetre connectar contenidors solts a una xarxa overlay |
--internal mereix atenció: una xarxa interna permet que els contenidors es parlin entre ells però els talla la sortida a l'exterior. És una defensa excel·lent per a una base de dades que no té per què sortir a Internet, i es reprèn a la lliçó 05-03.
Inspeccionar
docker network inspect aurora-net --format '{{.Name}} | driver={{.Driver}} | subxarxa={{range .IPAM.Config}}{{.Subnet}}{{end}}'I la consulta més útil de totes: qui hi està connectat.
docker network inspect aurora-net --format '{{range .Containers}}{{.Name}} → {{.IPv4Address}}{{println}}{{end}}'De moment no imprimeix res: la xarxa és buida.
Esborrar i netejar
Una xarxa amb contenidors connectats no es pot esborrar (network ... has active endpoints). I les tres xarxes predefinides —bridge, host, none— no es poden eliminar mai.
- Publicació de ports, revisitada
Ara que entens el model, la taula de la lliçó 03-01 cobra sentit ple:
| Sintaxi | Efecte |
|---|---|
-p 3000:3000 |
Port 3000 de totes les interfícies del host → 3000 del contenidor |
-p 127.0.0.1:5432:5432 |
Només accessible des del mateix host |
-p 8080-8090:8080-8090 |
Un rang de ports |
-p 5514:514/udp |
Publicació UDP |
-p 3000 |
Port aleatori del host → 3000 del contenidor |
-P |
Publica tots els EXPOSE en ports aleatoris |
| (res) | El contenidor no és accessible des del host, però sí des de la seva xarxa |
I aquesta és la distinció que cal tenir absolutament clara:
Publicar un port (-p) |
Comunicació entre contenidors | |
|---|---|---|
| Per a què serveix | Que el host i el món exterior arribin al contenidor | Que un contenidor arribi a un altre |
| Com s'adreça | localhost:PORT_HOST des del host |
nom-contenidor:PORT_INTERN |
Requereix -p? |
Sí, és la seva definició | No, en absolut |
| Port que es fa servir | El del host (el de l'esquerra) | L'intern del contenidor, sempre |
| Requisit | Cap llevat que el port estigui lliure | Ser a la mateixa xarxa definida per l'usuari |
D'aquí surten dos errors clàssics que ara pots evitar:
- Publicar ports "perquè els contenidors es vegin". No cal i a sobre exposa serveis interns.
aurora-dbno necessita cap-pperquè l'API la faci servir; només el necessites tu, si vols connectar-t'hi amb un client des del host. - Fer servir el port del host a la cadena de connexió d'un altre contenidor. Si publiques
-p 5433:5432, un altre contenidor s'ha de continuar connectant aaurora-db:5432, el port intern. El 5433 només existeix per al host.
- Pràctica:
aurora-net i la resolució de noms
aurora-net i la resolució de nomsMans a l'obra. Primer, la xarxa:
docker network create --label projecte=aurora-libros aurora-net
docker network ls --filter name=aurora-netc7f2e9a13b8d46052fa8c1e7b3d9a4f6c2e0b8d5a3f1c9e7b5d3a1f9c7e5b3d1
NETWORK ID NAME DRIVER SCOPE
c7f2e9a13b8d aurora-net bridge localAra recrea els tres serveis a dins seu. Com que la xarxa és un ajust de namespace, es fixa en crear el contenidor (lliçó 03-01), així que cal esborrar i tornar a crear:
docker run -d \
--name aurora-db \
--network aurora-net \
--label projecte=aurora-libros --label component=base-de-dades \
-e POSTGRES_USER=aurora \
-e POSTGRES_PASSWORD=aurora_secreta \
-e POSTGRES_DB=aurora_llibres \
-p 127.0.0.1:5432:5432 \
postgres:16-alpinedocker run -d \
--name aurora-cache \
--network aurora-net \
--label projecte=aurora-libros --label component=cache \
redis:7-alpineFixa't en un canvi important a aurora-cache: ja no porta -p. Ningú des del host no necessita parlar amb Redis; només l'API, i per a això n'hi ha prou de ser a la mateixa xarxa. És el principi d'exposar el mínim imprescindible.
Abans d'arrencar l'API, torna a carregar el catàleg, perquè el contenidor de la base de dades és nou:
sleep 5
docker cp ~/aurora-libros/db/init.sql aurora-db:/tmp/init.sql
docker exec aurora-db psql -U aurora -d aurora_llibres -f /tmp/init.sql
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"Apunta't aquesta molèstia, perquè és la segona vegada que la pateixes: cada vegada que recrees aurora-db perds les dades i les has de tornar a carregar a mà. Aquesta és la mancança que resol la lliçó 03-06.
I ara l'API:
docker run -d \
--name aurora-api \
--network aurora-net \
--label projecte=aurora-libros --label component=api \
--env-file ~/aurora-libros/aurora.env \
-p 3000:3000 \
auroralibros/aurora-api:1.2.0Verifica la resolució de noms
docker exec aurora-api getent hosts aurora-db
docker exec aurora-api getent hosts aurora-cache
docker exec aurora-api getent hosts aurora-apiJa hi som. La mateixa comanda que fa deu minuts retornava codi 2 i cap línia, ara resol els tres noms. No has canviat ni una línia de server.js, ni el Dockerfile, ni el fitxer d'entorn: només has posat els contenidors en una xarxa pròpia.
Comprova també la connectivitat real, port a port:
docker run --rm --network aurora-net nicolaka/netshoot \
sh -c 'nc -zv aurora-db 5432; nc -zv aurora-cache 6379; nc -zv aurora-api 3000'Connection to aurora-db (172.18.0.2) 5432 port [tcp/postgresql] succeeded!
Connection to aurora-cache (172.18.0.3) 6379 port [tcp/redis] succeeded!
Connection to aurora-api (172.18.0.4) 3000 port [tcp/*] succeeded!Tots tres, per nom. I fixa't en el detall: aurora-cache respon al 6379 encara que no vas publicar cap port. La publicació és per al host; dins de la xarxa, tots els ports del contenidor estan disponibles per als seus veïns.
I l'estat de la flota:
docker ps --filter label=projecte=aurora-libros --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"NAMES STATUS PORTS
aurora-api Up 30 seconds (healthy) 0.0.0.0:3000->3000/tcp
aurora-cache Up 1 minute 6379/tcp
aurora-db Up 2 minutes 127.0.0.1:5432->5432/tcp(healthy). Aquest parèntesi fa quatre lliçons que diu unhealthy. El HEALTHCHECK que vas escriure a la lliçó 02-04 fa la seva comprovació contra /salut, /salut consulta PostgreSQL i Redis, tots dos responen, i per primera vegada retorna 200. I fixa't en la línia d'aurora-cache: 6379/tcp sense més, sense fletxa. Exposat però no publicat.
- El moment de la veritat:
/llibres retorna els vuit llibres
/llibres retorna els vuit llibresdb: ok. cache: ok. I ara, la comanda que fa tot el curs que esperes:
{
"origen": "db",
"llibres": [
{ "id": 3, "titol": "Cien años de soledad", "autor": "Gabriel García Márquez", "isbn": "978-84-397-2071-7", "preu": "17.95" },
{ "id": 1, "titol": "El jardín de senderos que se bifurcan", "autor": "Jorge Luis Borges", "isbn": "978-84-206-3312-1", "preu": "14.50" },
{ "id": 8, "titol": "El tiempo entre costuras", "autor": "María Dueñas", "isbn": "978-84-8365-351-1", "preu": "20.15" },
{ "id": 6, "titol": "La casa de los espíritus", "autor": "Isabel Allende", "isbn": "978-84-9838-618-3", "preu": "18.40" },
{ "id": 4, "titol": "La sombra del viento", "autor": "Carlos Ruiz Zafón", "isbn": "978-84-08-04364-5", "preu": "21.00" },
{ "id": 7, "titol": "Los detectives salvajes", "autor": "Roberto Bolaño", "isbn": "978-84-339-6835-7", "preu": "23.60" },
{ "id": 5, "titol": "Nada", "autor": "Carmen Laforet", "isbn": "978-84-233-4361-2", "preu": "12.75" },
{ "id": 2, "titol": "Rayuela", "autor": "Julio Cortázar", "isbn": "978-84-376-0494-7", "preu": "19.90" }
]
}Els vuit llibres. El jardín de senderos que se bifurcan, Rayuela, Cien años de soledad, La sombra del viento, Nada, La casa de los espíritus, Los detectives salvajes i El tiempo entre costuras, ordenats per títol, servits per una API en un contenidor, llegits d'una base de dades PostgreSQL en un altre contenidor, a través d'una xarxa virtual que has creat tu. Atura't un segon en això: la plataforma d'Aurora Libros està viva. És la primera vegada des de la lliçó 01-07, on l'executaves a mà en quinze passos i fallava, que el sistema complet funciona d'extrem a extrem.
I hi ha una segona comprovació, més subtil, que demostra que la memòria cau també està fent la seva feina:
curl -s http://localhost:3000/llibres | jq -r '.origen'
curl -s http://localhost:3000/llibres | jq -r '.origen'
docker exec aurora-cache redis-cli KEYS '*'
docker exec aurora-cache redis-cli TTL llibres:totsLa primera crida va anar a PostgreSQL i va desar el resultat a Redis amb un TTL de 60 segons; la segona ja el va servir des de la memòria cau. Els tres contenidors estan cooperant: l'API parla amb la base de dades pel seu nom i amb la memòria cau pel seu. Comprova-ho també per l'altre extrem:
{
"id": 2,
"titol": "Rayuela",
"autor": "Julio Cortázar",
"isbn": "978-84-376-0494-7",
"preu": "19.90"
}Què ha canviat exactament
| Abans (bridge per defecte) | Ara (aurora-net) |
|
|---|---|---|
getent hosts aurora-db |
Res, codi 2 | 172.18.0.2 aurora-db |
/salut |
503, db: ko, cache: ko |
200, db: ok, cache: ok |
| Estat del contenidor | Up (unhealthy) |
Up (healthy) |
/llibres |
{"error": "No s'ha pogut obtenir el catàleg"} |
Els vuit llibres |
| Canvis al codi | — | Cap |
Aquesta última fila és la lliçó que cal endur-se: el problema mai no va ser a l'aplicació ni a la imatge. Era a com es connectaven els contenidors entre ells.
aurora-web: Nginx com a proxy invers
aurora-web: Nginx com a proxy inversFalta el quart servei. El teu web/index.html de la lliçó 01-07 crida /api/llibres, una ruta relativa, no http://localhost:3000/llibres. Aquell disseny era deliberat: el navegador ha de parlar amb un únic origen, i qui reparteix per dins és Nginx.
Crea ~/aurora-libros/web/nginx.conf:
server {
listen 80;
server_name _;
# Docker resol els noms de contenidor a 127.0.0.11.
# Amb "valid=10s" Nginx reconsulta el DNS i no es queda amb una IP vella
# si aurora-api es recrea i canvia d'adreça.
resolver 127.0.0.11 valid=10s;
# 1) La web estàtica
location / {
root /usr/share/nginx/html;
index index.html;
}
# 2) Tot el que comenci per /api/ es reenvia a aurora-api
location /api/ {
set $api_upstream http://aurora-api:3000;
proxy_pass $api_upstream/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
}Línia a línia, el que importa:
| Directiva | Què fa |
|---|---|
listen 80 |
Nginx escolta al 80 dins del contenidor |
resolver 127.0.0.11 valid=10s |
Fa servir el DNS intern de Docker i refresca la resolució cada 10 s |
set $api_upstream ... + proxy_pass $api_upstream/ |
En fer servir una variable, Nginx resol el nom a cada petició en comptes de només en arrencar. Sense aquest truc, si recrees aurora-api i li canvia la IP, el proxy continua apuntant a la vella i retorna 502 |
La / final de proxy_pass ...$api_upstream/ |
Retalla el prefix: /api/llibres es reenvia com a /llibres, que és la ruta que exposa l'API |
X-Forwarded-For i companyia |
Capçaleres estàndard perquè l'API sàpiga qui és el client original |
proxy_connect_timeout 5s |
No deixar peticions penjades eternament si l'API no respon |
Arrenca el contenidor:
docker run -d \
--name aurora-web \
--network aurora-net \
--label projecte=aurora-libros --label component=web \
-p 8080:80 \
-v ~/aurora-libros/web/index.html:/usr/share/nginx/html/index.html:ro \
-v ~/aurora-libros/web/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
--stop-signal SIGQUIT \
nginx:alpineDos detalls de la comanda:
- Els dos
-vmunten fitxers del host en només lectura (:ro). És la sintaxi mínima de la lliçó 03-01; la lliçó 03-06 explica què són exactament aquests muntatges i per què:roés important en fitxers de configuració. --stop-signal SIGQUITaplica el que vas aprendre a la lliçó 02-04: Nginx fa una aturada ordenada amb SIGQUIT i una d'abrupta amb SIGTERM. Amb aquesta opció,docker stop aurora-webno talla peticions a mig servir.
Comprova la cadena completa:
curl -s -o /dev/null -w "web estàtica: HTTP %{http_code}\n" http://localhost:8080/
curl -s http://localhost:8080/api/llibres | jq -r '.llibres[] | .titol' | head -4
curl -s http://localhost:8080/api/salut | jq -cweb estàtica: HTTP 200
Cien años de soledad
El jardín de senderos que se bifurcan
El tiempo entre costuras
La casa de los espíritus
{"servei":"aurora-api","version":"1.0.0","db":"ok","cache":"ok"}Obre http://localhost:8080 al navegador. La pàgina d'Aurora Libros carrega, crida /api/llibres, Nginx la reenvia a aurora-api, l'API consulta PostgreSQL o Redis, i la taula s'omple amb els vuit títols i els seus preus en euros. L'arquitectura completa, funcionant:
flowchart LR
N["Navegador<br/>localhost:8080"] --> W
subgraph NET["Xarxa aurora-net (172.18.0.0/16)"]
W["aurora-web<br/>nginx:alpine<br/>:80"]
A["aurora-api<br/>node 22<br/>:3000"]
D["aurora-db<br/>postgres 16<br/>:5432"]
C["aurora-cache<br/>redis 7<br/>:6379"]
W -- "/api/ → http://aurora-api:3000/" --> A
A -- "aurora-db:5432" --> D
A -- "aurora-cache:6379" --> C
end
Ni una sola IP escrita a mà enlloc: aurora-web busca aurora-api, i aurora-api busca aurora-db i aurora-cache, tots per nom.
- Connectar i desconnectar xarxes en calent
Amb xarxes definides per l'usuari, i només amb elles, pots canviar la topologia sense recrear res:
docker network create aurora-admin
docker network connect aurora-admin aurora-db
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}={{$v.IPAddress}} {{end}}' aurora-dbaurora-db té ara dues interfícies i dues IPs, una a cada xarxa. És el patró habitual per donar accés a una eina d'administració sense obrir la xarxa de l'aplicació:
docker run -d --name aurora-adminer --network aurora-admin -p 8081:8080 adminer:latest
docker exec aurora-adminer getent hosts aurora-dbAdminer veu la base de dades, però no veu aurora-api ni aurora-cache, perquè no és a aurora-net. Aïllament per disseny.
docker network disconnect aurora-admin aurora-db
docker rm -f aurora-adminer
docker network rm aurora-adminI un apunt sobre àlies de xarxa, que seran importants al mòdul 4:
docker run -d --name aurora-db-nou --network aurora-net \
--network-alias base-de-dades --network-alias postgres \
-e POSTGRES_PASSWORD=aurora_secreta postgres:16-alpine
docker exec aurora-api getent hosts base-de-dades
docker rm -f aurora-db-nouUn contenidor pot respondre a diversos noms a la mateixa xarxa. És el que permet, per exemple, canviar d'implementació sense tocar la configuració de qui la consumeix.
- Bones pràctiques de xarxa
| Pràctica | Per què |
|---|---|
| Una xarxa per aplicació | aurora-net conté només els quatre serveis d'Aurora Libros. Un altre projecte, una altra xarxa. Sense contactes accidentals |
| No facis servir mai la bridge per defecte | Sense DNS, sense aïllament i amb tots els contenidors de la màquina junts |
| Publica només el que necessiti l'exterior | aurora-web al 8080 i prou. aurora-cache no porta -p, i aurora-db només a 127.0.0.1 |
Lliga les bases de dades a 127.0.0.1 |
-p 127.0.0.1:5432:5432 et deixa fer servir un client local sense exposar PostgreSQL a la wifi |
| Refereix-te als serveis pel nom, mai per IP | Les IPs canvien a cada arrencada; els noms no |
| Fes servir el port intern a les cadenes de connexió | Si publiques -p 5433:5432, un altre contenidor continua fent servir aurora-db:5432 |
Considera --internal per a les xarxes de dades |
Talla la sortida a Internet de serveis que no la necessiten |
| Etiqueta les xarxes | --label projecte=aurora-libros permet netejar sense destrossar el que és d'altri |
Com queda la superfície exposada d'Aurora Libros:
NAMES PORTS
aurora-web 0.0.0.0:8080->80/tcp
aurora-api 0.0.0.0:3000->3000/tcp
aurora-cache 6379/tcp
aurora-db 127.0.0.1:5432->5432/tcpEn un desplegament real, fins i tot el -p 3000:3000 de l'API sobraria: si el navegador només parla amb Nginx, l'API no necessita estar publicada. Ho pots provar recreant-la sense -p i veuràs que http://localhost:8080/api/llibres continua funcionant perfectament, perquè el proxy hi arriba per la xarxa interna.
El que aquesta lliçó no cobreix, i on es veu: com funciona el bridge per dins (parelles veth, iptables, nat), les xarxes overlay entre diversos hosts, macvlan i les polítiques de xarxa s'estudien a la lliçó 05-01 i a la 06-03.
Errors Habituals i Consells
- Fer servir
localhostper referir-se a un altre contenidor. Dins d'un contenidor,localhostés ell mateix. Fes servir el nom de l'altre contenidor. - Deixar els contenidors a la bridge per defecte i esperar que es resolguin per nom. No passarà mai. Crea una xarxa amb
docker network create. - Escriure IPs a mà. Funciona fins al següent reinici. Docker assigna les IPs per ordre d'arrencada.
- Fer servir el port publicat del host a la connexió entre contenidors. Amb
-p 5433:5432, un altre contenidor ha de continuar fent servir el 5432. - Publicar ports "perquè es vegin entre ells". No cal, i exposa serveis interns a tota la xarxa.
- Intentar canviar de xarxa amb
docker start. La xarxa es fixa en crear. Es canvia ambnetwork connect/disconnect, o recreant. - 502 a Nginx després de recrear l'API. Nginx va desar a la memòria cau la IP antiga. Es resol amb
resolver 127.0.0.11 valid=10siproxy_passsobre una variable, com a la configuració d'aquesta lliçó. - Oblidar la
/final deproxy_pass. Sense ella,/api/llibreses reenvia com a/api/llibresi l'API respon 404. Amb ella, hi arriba com a/llibres. - Consell:
docker network inspectés la manera més ràpida de respondre a "qui hi ha en aquesta xarxa i amb quina IP?". - Consell: per diagnosticar, llança
netshootconnectat a la xarxa (--network aurora-net) i provanc,digicurldes de dins seu.
Exercicis
Exercici 1: demostra l'aïllament entre xarxes
Crea dues xarxes, xarxa-a i xarxa-b. Aixeca un contenidor servidor-a amb nginx:alpine a xarxa-a, i dos clients de nicolaka/netshoot: client-a a xarxa-a i client-b a xarxa-b. Demostra amb comandes que:
client-aresolservidor-apel nom i obté un HTTP 200.client-bno el resol ni hi arriba, ni per nom ni per IP.- Després, sense recrear res, fes que
client-bhi pugui arribar, i verifica quantes IPs té llavors.
Exercici 2: publicar enfront de comunicar
Aixeca una pila mínima en una xarxa xarxa-botiga: un mini-db amb redis:7-alpine sense cap -p, i un mini-api amb nicolaka/netshoot que executi sleep 3600, publicant -p 9999:80. Respon amb comandes i explicacions:
- Pot
mini-apiparlar ambmini-db? Demostra-ho. - Pots tu, des del host, parlar amb
mini-dbambredis-clionc? Per què? - Què respon
curl localhost:9999i per què, simini-apino té cap servidor web? - Recrea
mini-dbamb-p 127.0.0.1:6380:6379i repeteix el punt 2. Ha canviat alguna cosa per amini-api?
Exercici 3: trenca i arregla el proxy invers
Amb la plataforma d'Aurora Libros funcionant, provoca i diagnostica dues avaries reals del proxy:
- Canvia a
nginx.confla destinació ahttp://aurora-api-inexistent:3000, recarrega Nginx ambdocker kill -s SIGHUP aurora-webi observa què retornacurl localhost:8080/api/llibres. Diagnostica ambdocker logs aurora-web. - Restaura la destinació correcta però treu la barra final de
proxy_pass. Recarrega i observa el nou error. Explica exactament quina ruta li està arribant aaurora-api(comprova-ho ambdocker logs aurora-api). - Deixa-ho tot correcte i demostra, recreant
aurora-apisense-p, que la web continua funcionant alocalhost:8080.
Solucions
Solució a l'exercici 1
docker network create xarxa-a
docker network create xarxa-b
docker run -d --name servidor-a --network xarxa-a nginx:alpine
docker run -d --name client-a --network xarxa-a nicolaka/netshoot sleep 3600
docker run -d --name client-b --network xarxa-b nicolaka/netshoot sleep 36001. Des de client-a:
docker exec client-a getent hosts servidor-a
docker exec client-a curl -s -o /dev/null -w "HTTP %{http_code}\n" http://servidor-a2. Des de client-b:
docker exec client-b nslookup servidor-a 2>&1 | tail -2
docker exec client-b sh -c 'nc -zv -w 3 172.20.0.2 80; echo "codi: $?"'** server can't find servidor-a: NXDOMAIN
nc: connect to 172.20.0.2 port 80 (tcp) failed: Connection timed out
codi: 1Aquí hi ha dues fallades diferents i totes dues importen: el nom no es resol (no comparteixen xarxa, així que el DNS de xarxa-b no coneix servidor-a) i tampoc no funciona la IP, perquè són dues bridges separades i el trànsit entre elles no està permès. Compara-ho amb el diagnòstic de la lliçó 03-04: allà el nom fallava però la IP funcionava, cosa que demostrava que sí que compartien xarxa. La combinació de les dues proves és el que distingeix "mateixa xarxa sense DNS" de "xarxes diferents".
3. Connectar en calent:
docker network connect xarxa-a client-b
docker exec client-b curl -s -o /dev/null -w "HTTP %{http_code}\n" http://servidor-a
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}={{$v.IPAddress}} {{end}}' client-bDues IPs, una per xarxa, sense haver aturat ni recreat el contenidor. Això només és possible amb xarxes definides per l'usuari.
Solució a l'exercici 2
docker network create xarxa-botiga
docker run -d --name mini-db --network xarxa-botiga redis:7-alpine
docker run -d --name mini-api --network xarxa-botiga -p 9999:80 nicolaka/netshoot sleep 36001.
Sí, perfectament, i mini-db no publica cap port. Dins d'una xarxa compartida, tots els ports del contenidor estan disponibles per als seus veïns: -p no hi té res a veure.
2.
No. Com que mini-db no va publicar cap port, no existeix cap regla que reenviï trànsit del host cap a ell. El host i la xarxa de Docker són dos àmbits diferents: el host només arriba al que s'ha publicat explícitament.
3.
La publicació existeix —docker ps mostra 0.0.0.0:9999->80/tcp—, però dins del contenidor no hi ha res escoltant al 80: mini-api només executa sleep. És la demostració que -p no comprova res: crea el reenviament encara que la destinació estigui buida. És el mateix error que es comet en invertir l'ordre a -p 80:8080.
4.
docker rm -f mini-db
docker run -d --name mini-db --network xarxa-botiga -p 127.0.0.1:6380:6379 redis:7-alpine
redis-cli -p 6380 PING 2>/dev/null || docker run --rm --network host redis:7-alpine redis-cli -p 6380 PING
docker exec mini-api redis-cli -h mini-db PINGAra tu et pots connectar des del host pel port 6380, i per a mini-api no ha canviat absolutament res: continua fent servir mini-db:6379, el port intern. Els dos camins són independents.
Solució a l'exercici 3
1. Destinació inexistent:
sed -i 's|http://aurora-api:3000|http://aurora-api-inexistent:3000|' ~/aurora-libros/web/nginx.conf
docker kill -s SIGHUP aurora-web
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080/api/llibres
docker logs --tail 3 aurora-webHTTP 502
2026/08/04 21:47:12 [error] 31#31: *5 aurora-api-inexistent could not be resolved (3: Host not found),
client: 172.18.0.1, server: _, request: "GET /api/llibres HTTP/1.1", host: "localhost:8080"502 Bad Gateway és l'error de "el proxy no ha pogut parlar amb la destinació", i el registre en dona el motiu exacte: could not be resolved. És el mateix NXDOMAIN de la lliçó 03-04, vist des de Nginx. I val la pena notar que la web estàtica es continua servint sense problema a http://localhost:8080/: només falla la ruta /api/.
2. Sense la barra final:
sed -i 's|http://aurora-api-inexistent:3000|http://aurora-api:3000|' ~/aurora-libros/web/nginx.conf
sed -i 's|proxy_pass $api_upstream/;|proxy_pass $api_upstream;|' ~/aurora-libros/web/nginx.conf
docker kill -s SIGHUP aurora-web
curl -s -w "\nHTTP %{http_code}\n" http://localhost:8080/api/llibres
docker logs --tail 2 aurora-apiAra sí que arriba a l'API, però amb la ruta equivocada. Amb la barra final, proxy_pass http://aurora-api:3000/ substitueix el prefix /api/ per /, així que /api/llibres hi arriba com a /llibres. Sense ella, Nginx concatena la ruta completa i l'API rep /api/llibres, una ruta que server.js no defineix, d'aquí el 404 d'Express. Un sol caràcter, i la meitat dels proxies mal configurats del món s'expliquen per ell.
3. Restaurar i treure la publicació de l'API:
sed -i 's|proxy_pass $api_upstream;|proxy_pass $api_upstream/;|' ~/aurora-libros/web/nginx.conf
docker kill -s SIGHUP aurora-web
docker rm -f aurora-api
docker run -d --name aurora-api --network aurora-net \
--label projecte=aurora-libros --label component=api \
--env-file ~/aurora-libros/aurora.env \
auroralibros/aurora-api:1.2.0
sleep 5
curl -s -o /dev/null -w "API directa (3000): HTTP %{http_code}\n" http://localhost:3000/llibres
curl -s http://localhost:8080/api/llibres | jq -r '.llibres | length'Just el que es buscava: des del host, el port 3000 ja no existeix; des de la web, els vuit llibres continuen arribant. L'API ha deixat d'estar exposada i el servei no se n'ha ressentit, perquè Nginx hi arriba per la xarxa interna. És exactament l'arquitectura que voldries en producció: una sola porta d'entrada.
Com que a les lliçons següents convé poder cridar l'API directament per fer proves, torna a deixar-la publicada:
docker rm -f aurora-api
docker run -d --name aurora-api --network aurora-net \
--label projecte=aurora-libros --label component=api \
--env-file ~/aurora-libros/aurora.env -p 3000:3000 \
auroralibros/aurora-api:1.2.0Conclusió
El misteri està resolt i la plataforma està viva. Saps que cada contenidor té la seva pròpia pila de xarxa i el seu propi localhost, i que per això localhost:5432 dins de l'API preguntava per una base de dades que mai no va ser allà. Coneixes els drivers integrats —bridge per a gairebé tot, host quan no vols aïllament ni NAT, none per a aïllament total— i saps que overlay, macvlan i ipvlan existeixen per a escenaris de diversos hosts i d'integració amb la xarxa física que veuràs a les lliçons 05-01 i 06-03.
I tens clavada la diferència que ho explicava tot: la bridge per defecte no té DNS intern; una xarxa definida per l'usuari sí. Un docker network create aurora-net i un --network aurora-net a cada contenidor, i getent hosts aurora-db va passar de retornar codi 2 a retornar 172.18.0.2 aurora-db. Sense tocar una línia de server.js, ni el Dockerfile, ni el fitxer d'entorn. També saps connectar i desconnectar xarxes en calent per donar accés puntual a una eina d'administració, i posar àlies de xarxa a un contenidor.
Distingeixes publicar un port de comunicar dos contenidors: -p és perquè el host i l'exterior hi entrin; dins de la xarxa, tots els ports estan disponibles per nom sense publicar res. Per això aurora-cache ja no porta -p, aurora-db està lligada a 127.0.0.1, i has demostrat que fins i tot l'API pot viure sense publicació si Nginx és l'única porta d'entrada.
El resultat, en una línia: curl http://localhost:3000/llibres retorna els vuit llibres d'Aurora Libros, servits per Node des de PostgreSQL, desats a la memòria cau de Redis amb el seu TTL de 60 segons, i http://localhost:8080 mostra la llibreria completa al navegador a través d'un proxy invers que reenvia /api/ sense exposar l'API. El healthcheck marca healthy per primera vegada en tot el curs. Quatre contenidors, una xarxa, zero IPs escrites a mà.
Queda un problema, i ja l'has patit dues vegades en aquesta mateixa lliçó: cada vegada que recrees aurora-db, els vuit llibres desapareixen i has de tornar a carregar init.sql amb docker cp. Les dades viuen en un volum anònim que ningú no controla, o directament a la capa d'escriptura que mor amb el contenidor. A la lliçó següent, Persistència de Dades amb Volums, atacaràs aquest problema d'arrel: veuràs els tres tipus de muntatge —volums gestionats, bind mounts i tmpfs—, la diferència entre -v i --mount, on viuen realment els volums i per què no els has de tocar a mà. Donaràs a PostgreSQL el volum aurora-dades, muntaràs init.sql perquè s'executi tot sol, i repetiràs la prova definitiva: inserir un llibre nou, destruir el contenidor de la base de dades, recrear-lo, i comprovar que el llibre continua allà.
Docker: De Principiant a Avançat
Mòdul 1: Introducció a Docker
- Què és Docker?
- Instal·lant Docker
- Arquitectura de Docker
- Comandes Bàsiques de Docker
- Entenent les Imatges de Docker
- Creant el teu Primer Contenidor Docker
- El Projecte del Curs: la Plataforma Aurora Libros
Mòdul 2: Treballant amb Imatges Docker
- Docker Hub i Repositoris
- Construint Imatges Docker
- Conceptes Bàsics de Dockerfile
- Instruccions Avançades del Dockerfile
- Gestionant Imatges Docker
- Etiquetatge i Publicació d'Imatges
Mòdul 3: Contenidors Docker
- Executant Contenidors
- Cicle de Vida del Contenidor
- Gestionant Contenidors
- Inspecció i Depuració de Contenidors
- Xarxes a Docker
- Persistència de Dades amb Volums
- Límits de Recursos i Polítiques de Reinici
Mòdul 4: Docker Compose
- Introducció a Docker Compose
- Definint Serveis a Docker Compose
- Comandes de Docker Compose
- Aplicacions Multi-Contenidor
- Variables d'Entorn a Docker Compose
- Perfils, Overrides i Múltiples Entorns
- Desenvolupament Local amb Docker Compose
Mòdul 5: Conceptes Avançats de Docker
- Aprofundiment en Xarxes Docker
- Opcions d'Emmagatzematge Docker
- Millors Pràctiques de Seguretat a Docker
- Optimitzant Imatges Docker
- Builds Avançades amb BuildKit i Buildx
- Registre i Monitoratge a Docker
- El Runtime per Dins: Namespaces, Cgroups i Capes
Mòdul 6: Docker en Producció
- Preparar una Imatge per a Producció
- CI/CD amb Docker
- Orquestrant Contenidors amb Docker Swarm
- Introducció a Kubernetes
- Desplegant Contenidors Docker a Kubernetes
- Escalat i Balanceig de Càrrega
- Estratègies de Desplegament i Rollback
