Network Namespaces
Los network namespaces son utilizados por los contenedores para implementar el aislamiento de red.
Este es un estudio muy importante para entender cómo funciona la red en contenedores. Es la base para entender muchas cosas en Docker y Kubernetes.
Hablando de procesos, los contenedores están separados del host a través de namespaces.
Si pensamos en una casa, cada hijo tiene su habitación. Esto garantiza que cada uno tenga privacidad en su propia habitación y solo puede desordenar su espacio. Sería una casa dentro de la casa, pero los padres pueden revisar las habitaciones.
Cuando creamos un contenedor estamos garantizando que los procesos de ese contenedor no son capaces de ver los procesos del host y de otros contenedores. El contenedor piensa que está solo. El host logra ver los procesos de todos los contenedores.

Si observó en la imagen anterior, el contenedor ve el proceso que mantiene el contenedor vivo como PID 1, pero el host ve este proceso con PID 3816.
Los namespaces son un aislamiento creado por Linux para "engañar" al contenedor.
Cuando un contenedor es creado, un namespace de red es creado en el host para aislar las informaciones de red del host y del contenedor. ¿Quién hace ese trabajo? El container runtime. Vamos a intentar hacerlo nosotros mismos y ponerlo en práctica.

Creando Networks Namespaces
En una máquina virtual ubuntu, vamos a practicar. Instale el paquete net-tools. Vamos a crear 4 namespaces para seguir el ejemplo del estudio.
# Creando el namespace de redes
ubuntu@ubuntu:~$ sudo ip netns add red
ubuntu@ubuntu:~$ sudo ip netns add blue
ubuntu@ubuntu:~$ sudo ip netns add orange
ubuntu@ubuntu:~$ sudo ip netns add gray
ubuntu@ubuntu:~$ sudo ip netns
gray
orange
blue
red
# Probando las interfaces que aparecen en cada namespace
# HOST
ubuntu@ubuntu:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff
# NAMESPACES
# En el namespace red tenemos la interfaz loopback sin tener acceso a las interfaces del host
ubuntu@ubuntu:~$ sudo ip -n red link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
ubuntu@ubuntu:~$ sudo ip -n blue link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
ubuntu@ubuntu:~$ sudo ip -n orange link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
ubuntu@ubuntu:~$ sudo ip -n gray link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# Lo mismo vale para la tabla arp
# HOST
ubuntu@ubuntu:~$ arp
Address HWtype HWaddress Flags Mask Iface
_gateway ether 52:55:0a:00:02:02 C enp1s0
10.0.2.3 ether 52:55:0a:00:02:03 C enp1s0
# NAMESPACES
# Ninguna salida para los namespaces
ubuntu@ubuntu:~$ sudo ip netns exec red arp
ubuntu@ubuntu:~$ sudo ip netns exec blue arp
ubuntu@ubuntu:~$ sudo ip netns exec orange arp
ubuntu@ubuntu:~$ sudo ip netns exec gray arp
# Lo mismo para tablas de ruta
# HOST
ubuntu@ubuntu:~$ route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default _gateway 0.0.0.0 UG 100 0 0 enp1s0
10.0.2.0 0.0.0.0 255.255.255.0 U 100 0 0 enp1s0
link-local 0.0.0.0 255.255.0.0 U 1000 0 0 enp1s0
# NAMESPACES
ubuntu@ubuntu:~$ sudo ip netns exec red route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
ubuntu@ubuntu:~$ sudo ip netns exec blue route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
ubuntu@ubuntu:~$ sudo ip netns exec orange route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
ubuntu@ubuntu:~$ sudo ip netns exec gray route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
Entonces ya vimos un poco del uso de namespaces y cuál es la idea detrás de esto, tanto en procesos como en network. Ahora vamos a poner manos a la obra.
Conectando dos namespaces
Con 4 namespaces podemos entonces intentar conectar dos namespaces red y blue para probar. Ahora vamos a entrar en el mundo virtual.
La propuesta será esta.
Del mismo modo que conseguimos conectar dos máquinas con cables, podemos crear nuestros cables virtuales entre los namespaces. Un cable está conectado en una interfaz, entonces necesitamos crear las interfaces.
Vamos a crear las interfaces virtuales en el host y luego ponerlas disponibles en los namespaces.
# Creando nuestro cable virtual entre una interfaz y otra
ubuntu@ubuntu:~$ sudo ip link add veth-red type veth peer name veth-blue
ubuntu@ubuntu:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff
5: veth-blue@veth-red: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether be:5c:26:df:91:c2 brd ff:ff:ff:ff:ff:ff
6: veth-red@veth-blue: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f6:2e:b7:78:83:d0 brd ff:ff:ff:ff:ff:ff
# Ahora vamos a colocar esas interfaces en cada ns, pues en el momento aún está en el host
ubuntu@ubuntu:~$ sudo ip link set veth-red netns red
ubuntu@ubuntu:~$ sudo ip link set veth-blue netns blue
# VERIFICANDO... Observe que las interfaces están en los namespaces
sudo ip -n red link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: veth-red@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
link/ether f6:2e:b7:78:83:d0 brd ff:ff:ff:ff:ff:ff link-netns blue
ubuntu@ubuntu:~$ sudo ip -n blue link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: veth-blue@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
link/ether be:5c:26:df:91:c2 brd ff:ff:ff:ff:ff:ff link-netns red
# VERIFICANDO...¿Podemos verlas en el host? No.
ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff
# Esas interfaces no tienen ips, entonces vamos a asignar ips de modo que estén en la misma red
ubuntu@ubuntu:~$ sudo ip -n red addr add 192.168.15.1/24 dev veth-red
ubuntu@ubuntu:~$ sudo ip -n blue addr add 192.168.15.2/24 dev veth-blue
# VERIFICANDO
sudo ip netns exec red ip addr show dev veth-red
4: veth-red@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether f6:2e:b7:78:83:d0 brd ff:ff:ff:ff:ff:ff link-netns blue
inet 192.168.15.1/24 scope global veth-red
valid_lft forever preferred_lft forever
inet6 fe80::f42e:b7ff:fe78:83d0/64 scope link
valid_lft forever preferred_lft forever
sudo ip netns exec blue ip addr show dev veth-blue
3: veth-blue@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether be:5c:26:df:91:c2 brd ff:ff:ff:ff:ff:ff link-netns red
inet 192.168.15.2/24 scope global veth-blue
valid_lft forever preferred_lft forever
inet6 fe80::bc5c:26ff:fedf:91c2/64 scope link
valid_lft forever preferred_lft forever
# Vamos a levantar esas interfaces pues estaban down.
ubuntu@ubuntu:~$ sudo ip -n red link set veth-red up
ubuntu@ubuntu:~$ sudo ip -n blue link set veth-blue up
# Y hacer ping para ver si funciona
ubuntu@ubuntu:~$ sudo ip netns exec red ping 192.168.15.2
PING 192.168.15.2 (192.168.15.2) 56(84) bytes of data.
64 bytes from 192.168.15.2: icmp_seq=1 ttl=64 time=0.065 ms
...
ubuntu@ubuntu:~$ sudo ip netns exec blue ping 192.168.15.1
64 bytes from 192.168.15.1: icmp_seq=1 ttl=64 time=0.027 ms
...
Ahora vamos a verificar las tablas arp y las rutas
ubuntu@ubuntu:~$ sudo ip netns exec red arp
Address HWtype HWaddress Flags Mask Iface
192.168.15.2 ether be:5c:26:df:91:c2 C veth-red
ubuntu@ubuntu:~$ sudo ip netns exec blue arp
Address HWtype HWaddress Flags Mask Iface
192.168.15.1 ether f6:2e:b7:78:83:d0 C veth-blue
# EL HOST NO CONOCE, ES DECIR, COMPLETAMENTE AISLADO
ubuntu@ubuntu:~$ arp
Address HWtype HWaddress Flags Mask Iface
_gateway ether 52:55:0a:00:02:02 C enp1s0
10.0.2.3 ether 52:55:0a:00:02:03 C enp1s0
# LO MISMO VALE PARA LAS RUTAS
ubuntu@ubuntu:~$ sudo ip netns exec red route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.15.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-red
ubuntu@ubuntu:~$ sudo ip netns exec blue route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.15.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-blue
Cuando eliminas un namespace eliminas todos los recursos que están en él.
Solo para dar un reinicio para el próximo ejemplo.
ubuntu@ubuntu:~$ sudo ip netns delete red
ubuntu@ubuntu:~$ sudo ip netns delete blue
ubuntu@ubuntu:~$ sudo ip netns add red
ubuntu@ubuntu:~$ sudo ip netns add blue
Conectando varios namespaces
Para dos namespaces es fácil, pero ¿qué pasa cuando queremos tener varios namespaces como haremos en Kubernetes?
Así como creamos los cables virtuales, podemos crear switches virtuales y conectar el namespace completo en ese switch. Crear un switch es lo mismo que crear una red virtual dentro del host ya que los switches conectan dispositivos en la misma red.
Existen varias soluciones disponibles para crear esa network dentro del host. Tenemos por ejemplo el Linux Bridge y el OvS (Open vSwitch). Vamos a usar el Linux Bridge.
Lo primero que debemos hacer es crear una interfaz de red en el host, como cualquier otra, pero siendo del tipo bridge.
ubuntu@ubuntu:~$ sudo ip link add v-net-0 type bridge
ubuntu@ubuntu:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff
7: v-net-0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6e:33:35:a2:97:5c brd ff:ff:ff:ff:ff:ff
sudo ip link set dev v-net-0 up
Vamos ahora a crear un par de cables virtuales para cada namespace siendo que una punta quedará dentro del namespace y su otra punta dentro del host que actuará como un switch.
# Creando los pares
ubuntu@ubuntu:~$ sudo ip link add veth-red type veth peer name veth-red-br
ubuntu@ubuntu:~$ sudo ip link add veth-blue type veth peer name veth-blue-br
ubuntu@ubuntu:~$ sudo ip link add veth-orange type veth peer name veth-orange-br
ubuntu@ubuntu:~$ sudo ip link add veth-gray type veth peer name veth-gray-br
# Moviendo la punta de un par hacia dentro de cada namespace
ubuntu@ubuntu:~$ sudo ip link set veth-red netns red
ubuntu@ubuntu:~$ sudo ip link set veth-blue netns blue
ubuntu@ubuntu:~$ sudo ip link set veth-orange netns orange
ubuntu@ubuntu:~$ sudo ip link set veth-gray netns gray
# Activando todas las interfaces que fueron para los namespaces y asignando las ips y las máscaras
# Las que fueron para los namespaces y asignando los ips
ubuntu@ubuntu:~$ sudo ip -n red link set veth-red up
ubuntu@ubuntu:~$ sudo ip -n blue link set veth-blue up
ubuntu@ubuntu:~$ sudo ip -n orange link set veth-orange up
ubuntu@ubuntu:~$ sudo ip -n gray link set veth-gray up
# Asignando los ips de las interfaces dentro de los namespaces
ubuntu@ubuntu:~$ sudo ip -n red addr add 192.168.15.1/24 dev veth-red
ubuntu@ubuntu:~$ sudo ip -n blue addr add 192.168.15.2/24 dev veth-blue
ubuntu@ubuntu:~$ sudo ip -n orange addr add 192.168.15.3/24 dev veth-orange
ubuntu@ubuntu:~$ sudo ip -n gray addr add 192.168.15.4/24 dev veth-gray
# Las que permanecen en el host
ubuntu@ubuntu:~$ sudo ip link set veth-red-br up
ubuntu@ubuntu:~$ sudo ip link set veth-blue-br up
ubuntu@ubuntu:~$ sudo ip link set veth-orange-br up
ubuntu@ubuntu:~$ sudo ip link set veth-gray-br up
# Es aquí que ocurre la magia, la unión de todo el mundo.
ubuntu@ubuntu:~$ sudo ip link set veth-red-br master v-net-0
ubuntu@ubuntu:~$ sudo ip link set veth-blue-br master v-net-0
ubuntu@ubuntu:~$ sudo ip link set veth-orange-br master v-net-0
ubuntu@ubuntu:~$ sudo ip link set veth-gray-br master v-net-0
# Y por último necesitamos que la v-net-0 tenga un ip de la red en el propio host
ubuntu@ubuntu:~$ sudo ip addr add 192.168.15.5/24 dev v-net-0
Esta red aún continúa privada, no tiene acceso fuera del host. El namespace red nunca conseguirá conectarse con otras interfaces fuera de su red. Para eso es necesario definir un gateway.
Nuestro host es quien tiene acceso a ambas redes entonces serviría como nuestro router.
Conectando namespaces con el host
A pesar de que las redes del namespace se comunican, no se comunican con la red del host si no se define un gateway
ubuntu@ubuntu:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:bd:d2:80 brd ff:ff:ff:ff:ff:ff`
#IP DEL HOST
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp1s0
valid_lft 71671sec preferred_lft 71671sec
inet6 fec0::4b95:b6e6:2660:9d70/64 scope site temporary dynamic
valid_lft 86341sec preferred_lft 14341sec
inet6 fec0::5054:ff:febd:d280/64 scope site dynamic mngtmpaddr
valid_lft 86341sec preferred_lft 14341sec
inet6 fe80::5054:ff:febd:d280/64 scope link
valid_lft forever preferred_lft forever
7: v-net-0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 6e:33:35:a2:97:5c brd ff:ff:ff:ff:ff:ff
inet 192.168.15.5/24 scope global v-net-0
valid_lft forever preferred_lft forever
inet6 fe80::6c33:35ff:fea2:975c/64 scope link
valid_lft forever preferred_lft forever
8: veth-red-br@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether da:96:b7:3f:34:43 brd ff:ff:ff:ff:ff:ff link-netns red
inet6 fe80::d896:b7ff:fe3f:3443/64 scope link
valid_lft forever preferred_lft forever
10: veth-blue-br@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether 4a:cb:ef:ec:9b:26 brd ff:ff:ff:ff:ff:ff link-netns blue
inet6 fe80::48cb:efff:feec:9b26/64 scope link
valid_lft forever preferred_lft forever
12: veth-orange-br@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether e6:d6:7a:e9:14:82 brd ff:ff:ff:ff:ff:ff link-netns orange
inet6 fe80::e4d6:7aff:fee9:1482/64 scope link
valid_lft forever preferred_lft forever
14: veth-gray-br@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master v-net-0 state UP group default qlen 1000
link/ether b6:e8:a4:01:63:d7 brd ff:ff:ff:ff:ff:ff link-netns gray
inet6 fe80::b4e8:a4ff:fe01:63d7/64 scope link
valid_lft forever preferred_lft forever
#INTENTO DE HACER PING AL HOST
ubuntu@ubuntu:~$ sudo ip netns exec red ping 10.0.2.15
ping: connect: Network is unreachable
Vamos entonces a definir rutas para que el host sea alcanzable.
# Definimos que si queremos ir a la red 10.0.2.0/24 tendremos que ir por el ip de v-net-0
ubuntu@ubuntu:~$ sudo ip netns exec red ip route add 10.0.2.0/24 via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec red ping 10.0.2.15
PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.
64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.130 ms
64 bytes from 10.0.2.15: icmp_seq=2 ttl=64 time=0.035 ms
#Entonces vamos a continuar y hacer esto para todos
ubuntu@ubuntu:~$ sudo ip netns exec blue ip route add 10.0.2.0/24 via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec orange ip route add 10.0.2.0/24 via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec gray ip route add 10.0.2.0/24 via 192.168.15.5
# Vamos a agregar también las rutas para salir a internet
ubuntu@ubuntu:~$ sudo ip netns exec red ip route add default via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec blue ip route add default via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec orange ip route add default via 192.168.15.5
ubuntu@ubuntu:~$ sudo ip netns exec gray ip route add default via 192.168.15.5
Usando el host como NAT
Pero ¿sería posible salir a Internet y hacer ping a google ya que el host tiene acceso a Internet? No, todavía no sería posible. Tampoco sería posible acceder a otras máquinas en la misma red del host.
Los paquetes van a salir del namespace con un encabezado con el ip de origen y necesitamos transformarlos como si hubieran venido del host y no de una red dentro del host. Para eso necesitamos enmascarar esos paquetes usando iptables.
iptables -t nat -A POSTROUTING -s 192.168.15.0*24 -j MASQUERADE
Conectando desde fuera hacia dentro de un namespace
Si tuviéramos algún servicio ejecutándose en un namespace ¿cómo podría ser alcanzable? Vamos a imaginar un servicio que esté usando el puerto 80 del contenedor. En realidad es alcanzable a través del host, pero mapeado hacia dentro del namespace.
iptables -t nat -A PREROUNTING --dport 80 --to-destination 192.168.15.1:80 -j DNAT
Si observó bien, es exactamente como funciona el cluster ip en kubernetes.
Si quisiéramos mapear el puerto 8080 del host para enviar al puerto 80 del contenedor sería.
iptables -t nat -A PREROUNTING --dport 80 --to-destination 192.168.15.1:80 -j DNAT