Hola Fronters, Gabriel aquí!!! Espero que estén genial. En este artículo vamos a hablar sobre redes macvlan y y como asignar direcciones IP a nuestros contenedores… Antes de continuar, de verdad quiero agradecer a todas las personas que han colaborado conmigo de una forma u otra para poder materializar y darle forma a este proyecto. De verdad estoy muy contento con lo realizado y espero poder llegar cada vez más a personas entusiastas y expertos en tecnologías libres a que juntos podamos seguir aprendiendo y logrando cosas importantes…

Del mismo modo quiero agradecer por la paciencia en la espera de este sexto artículo. Procuro preparar el material lo mejor posible y afinar mi capacidad didáctica y reforzar mis propios conocimientos, porque aquí, no solo aprenden ustedes, yo también refuerzo y aprendo.

Ahora vamos a recargar nuestras baterías, con un buen cafecito y sigamos aprendiendo!!!! 🙂

Redes macvlan

Las redes macvlan pudimos abarcarlas en el post anterior, pero creo que ya era un poco largo y no quería que nos saturásemos de información. Las redes macvlan son aquellas en las cuales creamos un dispositivo de red virtual con características de uno físico, es decir: al crearlas, tendremos la capacidad de asignarles una dirección física de red, como si se tratase de una NIC verdadera.

Las redes con driver macvlan conectarnos a puentes (bridges), vinculados con la tarjeta de red del Docker Host. Una de las formas más comunes de trabajar con dichas redes es crearlas y poder tener la capacidad de individualizar el tráfico

docker network create -d macvlan --subnet=192.168.16.0/24 --gateway=192.168.16.1 -o parent=eth0 macvln0
Creando red con driver macvlan en Docker
Imagen 1: Creando red con driver macvlan en Docker

NOTA: Una acotación importante es que si creamos este tipo de red con driver ipvlan o macvlan y estamos utilizando el mismo segmento de red de nuestra red física, NO declaremos el gateway, pues esto les causará problemas en su red, tanto en el Docker Host como en los contenedores, Puesto que al declarar la puerta de enlace, lo que hara Docker es crearla en el Docker Host con la misma dirección IP de nuestra puerta de enlace física y entonces colisionarán los paquetes y… Será terrible!!!. En ese caso, lo que haremos es crear la red macvlan de este modo:

docker network create -d macvlan --subnet=192.168.16.0/24 -o parent=eth0 macvln0

Bien, aquí ya hemos creado nuestra red y con sólo ejecutar un docker network ls podemos verla creada y el tipo de red que es:

Listando las redes de Docker
Imagen 2: Listando las redes de Docker

Si tenemos una red, bien sea de tipo ipvlan o macvlan, y la creamos con la misma subred con la que creamos la red anterior, nos dará un error, por lo que deberemos eliminar la red anterior.

Error al crear una red macvlan en Docker
Imagen 3: Error al crear una red macvlan en Docker

Bien, crearemos un contenedor con la red macvlan le asignaremos una MAC address :

docker run -itd --name macvlcontainer --net=macvln0 --ip=192.168.16.20 --mac-address=00:60:2F:8B:57:90 ubuntu:latest
Creando un contenedor con red macvlan e dirección IP fija
Imagen 4: Creando un contenedor con red macvlan e dirección IP fija

Ejecutamos docker ps -a para ver nuestros contenedores existentes. Recuerden que aun no lo hemos iniciado:

Imagen 5: Listando contenedores

Ahora inspeccionaremos el contenedor que recién acabamos de crear y veremos la direcciones MAC e IP que le asignamos:

Inspeccionando el contenedor macvlcontainer
Imagen 6: Inspeccionando el contenedor macvlcontainer

Iniciamos el contenedor, accederemos a él y chequearemos si tiene conexión y podemos actualizar paquetes:

Actualizando índices de repositorios en contenedor Docker
Imagen 7: Actualizando índices de repositorios en contenedor Docker

Y efectivamente si podemos!!!… genial, no?!…

Asignando una dirección IP estática podremos tener conectividad; pero, tendremos un problema: mi contenedor no podía obtener su dirección IP vía DHCP.

¿Cómo podemos resolver un problema de ese tipo?, Te lo diré después del cafécito… Nos vemos al rato!

Problemas asignando DHCP en los contenedores (y como lo solucionamos)

Docker y sus controladores de red no son muy amigables y a pesar de poder hacer las mismas cosas de mil formas diferentes, en este apartado, es un poco «complejo». ¿Por qué es complejo? Pues porque no cuenta con una solución «endógena per se» para realizar esta tarea, sino a través de «plugins» o sub-programas agregados que permiten llevar a cabo dicha tarea, de forma rápida y con pocas trabas, lo cual es frustrante (lo digo a criterio personal).

Para solucionar este inconveniente haremos uso de varias herramientas como bridge-utils y pipework. Para ello haremos más uso de nuestras habilidades en la interface de comandos de Linux.

Creando la nueva configuración de red

Para crear la nueva configuración de red modificaremos el archivo o fichero /etc/network/interfaces con la siguiente información, sólo ajustando los parámetros que corresponden a su nombre de interface de red y a sus parámetros de red (subnet, máscara de red, etc)

# Backup del fichero original
sudo cp /etc/network/interfaces /etc/network/interfaces-bak

# Editamos el fichero /etc/network/interfaces con los siguientes parámetros
sudo nano /etc/network/interfaces

Ahora agregamos el contenido en el archivo /etc/network/interfaces:

### Inicio del fichero
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet manual

auto br0
iface br0 inet static
      bridge-ports eth0
      address 192.168.16.60/24
      gateway 192.168.16.1
      bridge-stp off
      bridge-fd 0

dns-nameserver 8.8.8.8 8.8.4.4

### Fin del fichero
Editando los parámetros de red en el Docker Host para configurar el bridge br0
Imagen 8: Editando los parámetros de red en el Docker Host para configurar el bridge br0

Ahora en este punto, deberíamos modificar el archivo o fichero /etc/sysctl.conf, descomentamos y cambiamos el valor del parámetro net.ipv4.ip_forward a 1. Si no sabes como hacerlo, te lo explico aquí.

Ahora deberíamos reiniciar, pero como aun debemos crear el puente br0 que declaramos en el archivo /etc/network/interfaces, esto nos daría un error. Así que ese paso lo dejaremos para el final.

Ahora vamos por un Capucchino.

Está muy bueno, así que a tomarse diez minutillos nada más y vamos por más.

Creando el puente en el Docker Host

Los puentes o bridges, son dispositivos que permiten la conexión múltiple de otros nodos y segmentos de red, de forma independiente, así mismo también interconectar a nodos de diferentes redes. Esto se puede con una o varias interfaces de red o NIC.

Para crear puentes, existen varias formas y herramientas, para los efectos de este tutorial utilizaremos Bridge Utils, porque es fácil y rápido. Para ello, y con la finalidad de no perder la conectividad de red, lo recomendable es tener acceso físico al Docker Host, o crear en este una interface de manejo interno, cuyo acceso y dirección IP no cambie.

El primer cambio, que haremos es instalar bridge-utils:

sudo apt update && sudo apt install -y bridge-utils

Luego creamos el puente:

# Creamos el bridge
sudo brctl addbr br0

# Adjuntamos la interface de red con la cual va a trabajar el bridge
sudo brctl addif eth0 br0

Veremos una salida de este tipo al ejecutar el comando anterior:

Verificando la creación y configuración del bridge br0 en el Docker Host
Imagen 9: Verificando la creación y configuración del bridge br0 en el Docker Host

Ahora sí reiniciaremos el servicio de red:

sudo service networking restart

Revisando las interfaces de red:

Verificando las interfaces de red en el Docker Host
Imagen 10: Verificando las interfaces de red en el Docker Host

Viendo toda la tabla de enrutamiento de nuestras interfaces de red:

route -n
Tablas de enrutamiento en el Docker Host
Imagen 11: Tablas de enrutamiento en el Docker Host

Y haremos ping tanto a un nodo interno como hacia internet:

# Ping a dirección IP privada de nodo
ping -c 3 192.168.16.129

# Ping externo
ping -c 3 8.8.8.8
Haciendo ping a nodo en la LAN
Imagen 12: Haciendo ping a nodo en la LAN
Imagen 13: Haciendo ping a Google

Instalando Pipework y crear contenedores con IP vía DHCP

Ahora que creamos nuestro puente en el Docker Host, vamos a instalar Pipework, siendo esta una herramienta super útil para lo que deseamos hacer.

Pipework es un proyecto creado por parte de uno de los ingenieros de Docker, llamado Jérôme Petazzoni, y que tiene por finalidad facilitar la conexión de contenedores a un servidor DHCP. Utiliza por defecto redes macvlan y lo que hace de algún modo es enlazar la interface del Docker Host con la interface del contenedor. Es muy interesante y al final de este artículo encontraremos el GitHub oficial del proyecto. Está bien documentado, algo que se agradece mucho. Démosle un poco de amor a ese héroe sin capa.

Para comenzar a trabar con Pipework, debemos instalarlo en nuestro sistema, clonando el proyecto desde su github y colocándolo en la ruta correspondiente:

# Clonamos Pipework en nuestro sistema
git clone https://github.com/jpetazzo/pipework.git

# Copiamos o movemos Pipework en el directorio que debe estar
sudo cp ~/pipework/pipework /usr/local/bin/
Instalando Pipework en Docker Host
Imagen 14: Instalando Pipework en Docker Host

Listo!, Ya hemos instalado Pipework en nuestro Docker Host. Vamos a crear nuestro primer contenedor de prueba, a modo de prueba. Esta vez con Alpine:

# Creando un contenedor con acceso a DHCP con Pipework 
sudo pipework br0 $(docker run --name alpinista2 --rm -d --privileged -ti alpine ash) dhclient

Y sí, debemos crearlo con sudo pues, con esa instrucción es que le daremos permiso a Pipework de enlazar las interfaces y de no ser así nos dará un error como este y no creará la interface con DHCP:

ln: failed to create symbolic link '/var/run/netns/42699': Permission denied

Y veremos que nuestro contenedor estará corriendo con una dirección IP suministrada por nuestro servidor DHCP:

Creando contenedor Alpine con Docker con Pipework para heredar IP vía DHCP
Imagen 15: Creando contenedor Alpine con Docker con Pipework para heredar IP vía DHCP

Y haremos las pruebas de conectividad de rigor a nuestro contenedor alpinista2, constatando que nuestro contenedor recibió la dirección IP 192.168.16.124 en la interface eth1 y que efectivamente se puede comunicar con otros hosts como si fuese un equipo más:

Pruebas de conectividad con contenedor alpinista2
Imagen 16: Pruebas de conectividad con contenedor alpinista2

El «problema» de los contenedores Debian y Ubuntu (un paso más)

El problema al crear contenedores Debian y Ubuntu es que para poder ser convertidos en imágenes de fácil despliegue, carecen de muchas herramientas básicas que vienen regularmente con los instaladores de las distribuciones regulares. Para poder solventar esa situación, debemos crear el contenedor en modo bridge, instalar los paquetes básicos necesarios para poder ejecutar la solicitud de asignación de IP desde el contenedor hacia el servidor DHCP y por último, enlazar las interfaces del Docker Host y el contenedor para que se puedan ver.

Para ello debemos ejecutar los siguientes pasos:

docker run --name ubcont1 -d --privileged -ti ubuntu bash
Creando contenedor Ubuntu en Docker con driver bridge
Imagen 17: Creando de un contenedor Ubuntu en Docker con driver bridge

Ahora debemos instalar la paquetería de software mínima necesaria para poder hacer obtener una IP de nuestro DHCP, visualizar IPs y hacer test de ping (y editar cualquier cosa que pueda hacer falta dentro de nuestro contenedor) y limpiaremos la caché de instaladores de paquetes:

docker exec -u 0 ubcont1 bash -c "apt update && apt upgrade -y && apt install ifupdown
net-tools nano iputils-ping -y && apt clean"
Instalando software básico para conectividad de red con pipework
Imagen 18: Instalando software básico para conectividad de red con Pipework en contenedor ubcont1

Y por último, ejecutamos Pipework, vinculando la interface bridge br0 del Docker Host con la interface eth1 del contenedor. Esta interface no existe en el Docker Host, por lo tanto Pipework se encargará de crearla y vincularla:

sudo pipework br0 -i eth1 ubcont1 dhclient
Enlazando contenedores con pipework para poder heredar DHCP.
Imagen 19: Enlazando contenedores con pipework para poder heredar IP vía DHCP.

Chequeamos las interfaces de red para ver si todo trabaja correctamente:

docker exec -it ubcont1 ip a

Y hacemos las respectivas pruebas de conectividad tanto internas como externas:

# ICMP a nodo en LAN
docker exec -it ubcont1 ping -c 3 192.168.16.129


# ICMP a WAN (Google)
docker exec -it ubcont1 ping -c 3 8.8.8.8

Aquí veremos el resultado del test completo:

Test de conectividad completo en contenedor ubcont1 con Pipework
Imagen 20: Test de conectividad completo en contenedor ubcont1 con Pipework

Excelente!!! ¿Qué tal si vamos por otro café?

IP reservada vía DHCP en contenedores Docker

Cuando trabajamos con redes de tipo macvlan, es porque generalmente tenemos el control total de la gestión de red de nuestra organización, así que podría prescindir de asignar una IP fija y que sea el servidor DHCP quien asigne las direcciones IP por su cuenta. Para hacer la prueba, le indicaremos al servidor DHCP que la mac address 00:60:2F:8B:57:91, del nuevo contenedor que crearemos a continuación, que llamaremos ubcont2 y la dirección IP será la 192.168.16.130. Para ello reservaremos la IP de dicho contenedor en el DHCP para que la dirección IP sea asignada dinámicamente, pero que se mantenga fija:

Reserva de IP en servidor DHCP
Imagen 21: Reserva de IP en servidor DHCP

Y ahora creamos el contenedor, indicando únicamente la MAC address creada previamente:

docker run --name ubcont2 -d --privileged -ti ubuntu bash

Instalamos paquetes necesarios, tal como en el apartado anterior:

docker exec -u 0 ubcont2 bash -c "apt update && apt upgrade -y && apt install ifupdown
net-tools nano iputils-ping -y && apt clean"
Creación de contenedor ubcont2 e instalación de software necesario para poder heredar IP vía DHCP
Imagen 22: Creación de contenedor ubcont2 e instalación de software necesario para poder heredar IP vía DHCP

Y ahora creamos nuestra vinculación con Pipework, tomando en cuenta que ahora asignaremos una dirección física de red a la interface que crearemos y vincularemos, en este caso será eth1:

sudo pipework br0 -i eth1 ubcont2 dhclient 00:60:2f:8b:57:91
Enlazando contenedores con pipework para poder heredar la dirección IP reservada en el servidor DHCP
Imagen 23: Enlazando contenedores con Pipework para poder heredar la dirección IP reservada en el servidor DHCP

Ahora chequeamos red:

docker exec -it ubcont2 ip a
Revisando interfaces de red en contenedor ubcont2
Imagen 24: Revisando interfaces de red en contenedor ubcont2

Y las pruebas de ping respectivas para verificar que alcanza tanto a internet como a otros nodos en la LAN:

Realizando las pruebas de conectividad pertinentes en el contenedor ubcont2
Imagen 25: Realizando las pruebas de conectividad pertinentes en el contenedor ubcont2

Y veremos nuestra dirección de red y la dirección IP que reservamos. Lo hemos logrado!!!

Cierre y despedida

En el artículo de hoy hemos aprendido a crear redes macvlan y a solventar el problema de conectividad con la red LAN en términos de obtener una dirección IP de forma dinámica por medio de nuestro router o servidor DHCP. Espero que les haya gustado. Estoy terminando de escribir este tutorial el 24 de diciembre de 2021, hora 01:40, así que aprovecho de desearles a todos una Feliz Navidad.

Hasta luego Fronters!!!!

Referencias

Docker: Documentación Oficial

Linuxhint.com: How to use bridge utils in Ubuntu Muy útil este artículo.

Karatos.org: Docker network detailed explanation and pipework source code interpretation and practice Un buen artículo sobre Pipework y su funcionamiento (desgraciadamente este artículo no está activo)

Pipework – Github Jpetazzo: El Padre de Pipeworks