# Así Funciona la Red de Docker por Dentro (veth y Namespaces)

> Qué son los network namespaces y los veth pairs, de dónde salieron y cómo montarlos a mano con ip netns para entender la red de Docker.

- Autor: Alexander Zelada, Cloud Architect
- URL: https://alexanderzelada.com/veth-network-namespaces/
- Ruta: Sistema operativo por dentro (lección 3 de 4) — https://alexanderzelada.com/sistema-operativo/
- Publicado: 2026-10-16
- Actualizado: 2026-10-16
- Idioma: español

Cuando un contenedor de Docker arranca, recibe su propia IP, su propia tabla de rutas y su propio stack de red, en milisegundos y sobre el mismo kernel que el resto del sistema. Eso se consigue con dos piezas del kernel: los network namespaces, que aíslan, y los veth pairs, que conectan.

Primero verás de dónde salieron, de OpenVZ a Docker y Kubernetes. Después los montarás a mano en la terminal y verás cómo encaja el bridge.

[Vídeo en YouTube](https://www.youtube.com/watch?v=J9KDjTeZfH8)

## La pregunta de partida

Como sabes, cuando un contenedor de Docker arranca, Docker le da su propia IP, su propia tabla de rutas y su stack de red completo. Y lo hace en milisegundos, sobre el mismo kernel que el resto del sistema.

¿Cómo lo consigue? Gracias a dos primitivas del kernel que nacieron en un proyecto de contenedores llamado OpenVZ, años antes de que Docker existiera. La lección tiene dos partes: primero un poco de historia para tener contexto, y después una demo para ver cómo funcionan veth y los network namespaces.

## 2001: una sola red para todos los procesos

Estamos en 2001. Linux está creciendo, pero tiene un problema importante: el kernel tiene una sola tabla de red para todo. Todos los procesos comparten las mismas interfaces, la misma tabla de rutas y el mismo stack de red.

O sea, si dos aplicaciones quieren escuchar en el mismo puerto, hay conflicto. Si dos procesos quieren la misma IP `10.0.0.1`, hay conflicto. Solo hay una red para todos.

Si querías correr aplicaciones aisladas, por ejemplo en un hosting compartido, tenías que levantar una máquina virtual completa, con un kernel entero por cada entorno. Era costoso y no escalaba.

## SWsoft y OpenVZ: aislar dentro del kernel que ya tienes

Hay una empresa que lo ve de otra forma: **SWsoft**, una empresa de virtualización fundada en 1997. Y se hacen una pregunta clave: ¿y si, en lugar de levantar un kernel nuevo, aíslas los recursos dentro del kernel que ya tienes?

En 2005, SWsoft libera como open source su tecnología de contenedores: **OpenVZ**. Docker y Kubernetes todavía no existen, pero OpenVZ ya está corriendo contenedores en producción, en servidores de hosting de todo el mundo.

El problema es que, para hacerlo bien, necesitan que el kernel oficial soporte aislamiento de red real.

## Eric W. Biederman y los network namespaces

Uno de los que empuja esto es **Eric W. Biederman**, un desarrollador independiente del kernel que lleva años dándole vueltas a cómo aislar recursos dentro del kernel.

En 2006 manda una propuesta grande al kernel: namespaces para aislar la red, los procesos, los usuarios y los nombres de host. La comunidad del kernel la recibe con escepticismo, y Biederman se pasa dos años mandando revisiones y ajustando el diseño.

En enero de 2008 sale Linux 2.6.24 con los **network namespaces**  incluidos. A partir de ahí, un proceso puede tener su propio stack de red privado, separado del resto del sistema.

Pero un namespace aislado no puede hablar con nadie. Necesitas algo que lo conecte con el exterior.

## Pavel Emelyanov y el driver veth

En ese mismo release, **Pavel Emelyanov**, ingeniero de SWsoft en el equipo de OpenVZ, mete un driver nuevo en el kernel: el driver **veth**.

Un veth son dos interfaces virtuales conectadas entre sí, como los dos extremos de un cable Ethernet. Lo que entra por un lado sale por el otro.

```
commit e314dbdc — Author: Pavel Emelyanov <xemul@openvz.org>
Date:   Thu Oct 11 2007
[NET]: Virtual ethernet device driver.
```

Los namespaces y el veth son la base de la red de contenedores. Es lo que corre hoy dentro de Docker y dentro de Kubernetes.

## De LXC a Docker y Kubernetes

Fuera del mundo del kernel, todo esto pasa desapercibido. OpenVZ usa estas primitivas en sus propios servidores, pero casi nadie fuera de ese entorno las conoce.

Ese mismo año, 2008, aparece **LXC**  (Linux Containers). LXC coge los namespaces y el veth y los mete en una CLI para crear contenedores. Por primera vez puedes levantar un contenedor sin tocar el código del kernel.

Pero la CLI es compleja, tienes que configurar muchas cosas a mano y la curva de aprendizaje es alta. Sigue siendo cosa de especialistas.

Eso cambia en marzo de 2013. Solomon Hykes está en el escenario de **PyCon**, la conferencia anual de Python, en Santa Clara, California. Hace una demo de cinco minutos de una herramienta interna que estaban desarrollando en dotCloud, y la llaman **Docker**.

En esa demo, Docker arranca un contenedor, le asigna un namespace de red, crea un veth pair y conecta el contenedor al host, todo en milisegundos. Por debajo es lo mismo de antes, namespaces y veth pairs, pero ahora cualquiera puede usarlo con un solo comando. En menos de un año, Docker tiene miles de estrellas en GitHub.

Un año después, en 2014, Beda, Burns y McLuckie publican Kubernetes, y de repente todo el mundo necesita orquestar contenedores.

```
2002 — Primer namespace en Linux (mount namespace, Al Viro)
2006 — Biederman propone network namespaces completos
2007 — Commit del driver veth de Emelyanov (incluido en Linux 2.6.24)
2008 — Linux 2.6.24 publicado: namespaces + veth en el kernel oficial
2008 — LXC: primera herramienta usable sobre estas primitivas
2013 — Docker: democratización de los contenedores
2014 — Kubernetes: orquestación a escala
2016 — Cilium: eBPF en el otro extremo del veth
```

## Qué es un network namespace

Ya sabemos de dónde viene esto. Ahora vamos a ver qué es un network namespace por dentro.

Un **network namespace**  es una copia aislada del stack de red del kernel. Cuando creas uno, dentro tiene de todo: tabla de rutas, interfaces, tabla ARP, reglas de iptables y hasta su propio loopback.

Los procesos de dentro solo ven lo suyo y no saben nada del host. Pero un namespace aislado no puede hablar con nadie, y para eso existe el veth pair.

## Qué es un veth pair

Un **veth pair**  son dos interfaces de red que el kernel conecta entre sí. Lo que entra por una sale por la otra. El kernel las vincula al crearlas y el paquete pasa directamente de una a la otra, y por eso la latencia es de centésimas de milisegundo.

Ahora viene la parte que lo conecta con los namespaces. Una interfaz de red solo puede pertenecer a un namespace, y puedes mover un extremo del veth a otro namespace.

Los dos extremos siguen vinculados aunque estén en namespaces distintos, porque el kernel mantiene esa conexión. El resultado es que el namespace tiene su stack completamente aislado, con una sola salida al exterior, que es el veth. Solo pasa lo que tú configuras.

```
    Host namespace          Container namespace
  ┌────────────────┐      ┌────────────────────┐
  │                │      │                    │
  │    veth-A  ◄───┼──────┼───►  veth-B        │
  │                │      │                    │
  └────────────────┘      └────────────────────┘
```

En resumen, el namespace aísla y el veth conecta. Vamos a construirlo en la terminal antes de añadir nada más.

## Demo: dos namespaces conectados con un veth

### Estado inicial

Empezamos mirando qué tiene el host. Primero listo todas las interfaces de red que existen ahora mismo:

```
ip link show
```

Tenemos `lo` y `eth0`, nada más. Hay un solo stack de red y todo está compartido.

Ahora listo los namespaces de red que hay en el sistema:

```
ip netns list
```

La lista sale vacía. Solo existe el namespace raíz del host, que es implícito y no aparece aquí.

### Crear los namespaces

Creo dos namespaces, que voy a llamar `ns-rojo` y `ns-azul`:

```
sudo ip netns add ns-rojo
sudo ip netns add ns-azul
```

Los listo para confirmar que el kernel los ha registrado:

```
ip netns list
```

Ahí están. Ahora miro dentro de `ns-rojo` para ver en qué estado está:

```
sudo ip netns exec ns-rojo ip link show
```

Solo hay un loopback, y está en `DOWN`. El namespace existe y tiene su propio stack de red, pero no tiene ninguna interfaz real y no puede comunicarse con nadie.

### Crear el veth pair

Ahora creo el cable. Los dos extremos se crean en una sola llamada y se llaman `veth-rojo` y `veth-azul`:

```
sudo ip link add veth-rojo type veth peer name veth-azul
```

Listo las interfaces de tipo veth para ver que los dos extremos están ahí:

```
ip link show type veth
```

Los dos extremos existen y, en este momento, están los dos en el host. Fíjate en la `@`: `veth-azul@veth-rojo` significa «veth-azul, cuyo par es veth-rojo». El kernel ya los tiene vinculados.

### Mover cada extremo a su namespace

Ahora muevo cada extremo al namespace que le corresponde. Cuando ejecuto esto, la interfaz desaparece del host, porque no se copia, se mueve:

```
sudo ip link set veth-rojo netns ns-rojo
sudo ip link set veth-azul netns ns-azul
```

Compruebo que el host ya no los ve:

```
ip link show type veth
```

La lista sale vacía, así que han desaparecido del host. Ahora miro dentro de `ns-rojo` para confirmar que el extremo está ahí:

```
sudo ip netns exec ns-rojo ip link show
```

Ahí está `veth-rojo`. Fíjate en el `@if4`: es el kernel diciéndote que el par de esta interfaz vive en el índice 4 de otro namespace. El vínculo entre los dos extremos sigue intacto aunque estén en stacks de red distintos.

### Asignar IPs y levantar las interfaces

Configuro cada namespace en tres pasos: asigno la IP, levanto la interfaz y levanto el loopback.

```
sudo ip netns exec ns-rojo ip addr add 10.10.0.1/24 dev veth-rojo
sudo ip netns exec ns-rojo ip link set veth-rojo up
sudo ip netns exec ns-rojo ip link set lo up
```

Hago lo mismo en `ns-azul`, con la IP `.2`:

```
sudo ip netns exec ns-azul ip addr add 10.10.0.2/24 dev veth-azul
sudo ip netns exec ns-azul ip link set veth-azul up
sudo ip netns exec ns-azul ip link set lo up
```

Verifico el estado de `ns-rojo`:

```
sudo ip netns exec ns-rojo ip addr show
```

Solo ve `lo` y `veth-rojo` con su IP. No aparece el `eth0` del host ni `veth-azul`. El aislamiento es real.

### Comprobar la conexión con ping

Hago ping desde `ns-rojo` hacia la IP de `veth-azul`:

```
sudo ip netns exec ns-rojo ping -c 3 10.10.0.2
```

Salen 3 paquetes, con 0 pérdidas y 0.04 milisegundos. Son dos stacks de red aislados, conectados por un cable virtual.

### Ver el tráfico con tcpdump

Para ver el tráfico en tiempo real desde dentro de `ns-azul`, abro una segunda terminal y arranco el sniffer dentro de ese namespace, filtrando solo ICMP:

```
sudo ip netns exec ns-azul tcpdump -i veth-azul icmp
```

Mientras escucha, vuelvo a la primera terminal y lanzo el ping:

```
sudo ip netns exec ns-rojo ping -c 3 10.10.0.2
```

En la segunda terminal los paquetes aparecen en tiempo real. El echo request sale de `ns-rojo`, cruza el cable y llega a `veth-azul` en `ns-azul`. El echo reply vuelve por el mismo camino.

Si pusieras este sniffer en el host, no verías nada, porque los extremos ya no están ahí.

### La tabla de rutas del namespace

Antes de pasar al bridge, miro la tabla de rutas de `ns-rojo`:

```
sudo ip netns exec ns-rojo ip route show
```

Tiene una sola entrada: la subred `10.10.0.0/24` por `veth-rojo`. El namespace solo sabe llegar a su subred local, y no tiene ruta al host ni a internet. Para salir fuera haría falta una ruta por defecto, que es justo lo que añade Docker cuando levanta un contenedor.

Cuando termines, puedes borrar los dos namespaces:

```
sudo ip netns del ns-rojo
sudo ip netns del ns-azul
```

## Cuando hay más de dos contenedores: el bridge

Lo que hemos construido funciona para dos namespaces que hablan directamente. Pero si tienes diez contenedores que necesitan comunicarse entre sí, esta configuración no escala. Necesitas un punto central que los conecte a todos, y eso es el bridge.

```
  ns-rojo                              ns-azul
┌─────────────┐                   ┌─────────────┐
│  veth-rojo  │                   │  veth-azul  │
│ 10.20.0.1   │                   │ 10.20.0.2   │
└──────┬──────┘                   └──────┬──────┘
       │                                 │
  veth-rojo-h                       veth-azul-h
  (host)                            (host)
       │                                 │
       └────────────┬────────────────────┘
                 br-demo
               10.20.0.254
                    │
                  eth0
```

Un **Linux bridge**  es un switch Ethernet en software. Mantiene una tabla de MACs, es decir, aprende qué dirección MAC está detrás de cada uno de sus puertos. Cuando le llega un frame, lo manda directamente por el puerto correcto, sin hacer broadcast a todos.

Sus puertos son simplemente interfaces de red. En este caso, los extremos de los veth pairs que se quedan en el host.

La diferencia con la demo está en dónde queda cada extremo. Antes, los dos extremos del cable estaban dentro de los namespaces y el host no tenía ninguno. Aquí, un extremo entra al namespace y el otro se queda en el host como puerto del bridge.

Es lo que hace Docker. `docker0` es una interfaz del host como cualquier otra, con su propia IP: `172.17.0.1`. El host forma parte de esa subred, así que si desde tu terminal haces ping a la IP de un contenedor, por ejemplo `172.17.0.2`, el paquete sale por `docker0`, cruza el veth y llega al contenedor.

El contenedor tiene una ruta por defecto que apunta al bridge. Todo lo que no sabe entregar lo manda a ese default gateway, y el host decide si lo reenvía a otro contenedor o si lo saca a internet con NAT a través de `eth0`.

## Lo que hemos visto

- Hasta 2008, Linux tenía una sola red para todos los procesos, y aislar aplicaciones obligaba a levantar una máquina virtual completa.
- Los network namespaces (Biederman) y el driver veth (Emelyanov, de OpenVZ) entraron en Linux 2.6.24, y LXC, Docker y Kubernetes se construyeron encima.
- Un network namespace es una copia aislada del stack de red, y un veth pair son dos interfaces unidas por el kernel cuyos extremos puedes mover a namespaces distintos.
- Con `ip netns` e `ip link` puedes crear dos namespaces, conectarlos con un veth y hacer ping entre ellos.
- Para conectar muchos contenedores, Docker deja un extremo de cada veth en el host como puerto del bridge `docker0`, que reenvía el tráfico o lo saca a internet con NAT.
