En esta práctica, vamos a instalar y configurar un servidor DHCP Kea utilizando Docker. Kea es una solución moderna y potente para la gestión de direcciones IP en redes.
A diferencia del antiguo ISC DHCP Server, Kea está construido con un enfoque moderno y modular, permitiendo una mayor flexibilidad y escalabilidad. Kea soporta una amplia gama de características avanzadas, incluyendo soporte para múltiples subredes, reservas de IP, y almacenamiento de datos en bases de datos SQL como MySQL o PostgreSQL.
Empezaremos creando un servidor DHCP Kea y un par de clientes que obtendrán sus direcciones IP del servidor. Posteriormente, iremos ampliando la funcionalidad con características avanzadas como la reserva de IPs por dirección MAC, la configuración de múltiples subredes y el uso de MySQL como almacenamiento de datos…
Lo primero que haremos será crear un docker-compose.yml con la configuración de los contenedores y la red. Empezaremos indicando el nombre del proyecto y creando el contenedor del servidor usando la imagen kea-dhcp. Además, crearemos una red para nuestros contenedores y los conectaremos a ella. También pondremos una ip fija a nuestro servidor y mapearemos la carpeta local config (que estará en el mismo directorio que nuestro docker-compose.yml) a la del contendor /etc/kea .
name: dhcp-kea services: dhcp-server: image: docker.cloudsmith.io/isc/docker/kea-dhcp4:2.7.0 container_name: kea-server networks: dhcp-net: ipv4_address: 172.18.2.2 volumes: - ./config:/etc/kea networks: dhcp-net: driver: bridge ipam: config: - subnet: 172.18.2.0/24
El siguiente paso será crear los dos clientes basados en Ubuntu. Sin embargo, antes de proceder, hay un par de consideraciones importantes. La idea es que estos clientes no tengan ninguna IP asignada al principio. Queremos que, al iniciar, los clientes estén sin IP y que posteriormente utilicemos herramientas como dhclient para solicitar una dirección IP a nuestro servidor DHCP Kea. Esto nos permitirá observar la secuencia completa de mensajes DHCP intercambiados entre el servidor y los clientes.
El problema es que Docker asigna una dirección IP automáticamente a cada contenedor a través de su propio servidor DHCP interno cuando se utiliza una red bridge. Para evitar conflictos y asegurar que los clientes comiencen sin IP, debemos eliminar esta IP asignada por Docker. Esto se logrará utilizando el comando ip addr flush dev eth0 dentro del contenedor. Este comando eliminará cualquier IP previamente asignada a la interfaz de red del contenedor, permitiendo que el cliente solicite una IP nueva al servidor DHCP Kea.
Para trabajar eficazmente con redes y realizar la configuración y prueba del cliente DHCP, instalaremos algunos complementos útiles dentro de los contenedores clientes. Estos complementos incluyen:
Para que los contenedores clientes puedan administrar la red y solicitar una dirección IP del servidor DHCP Kea, necesitamos otorgarles permisos adicionales. Estos permisos son necesarios para que el contenedor pueda manipular la configuración de la interfaz de red, como eliminar direcciones IP existentes y ejecutar herramientas de cliente DHCP.
Podemos proporcionar estos privilegios de varias maneras, entre ellas:
Además, para que nos resulte más cómodo después, especificaremos las direcciones MAC de los clientes con la opción mac_address.
De esta forma, nuestros clientes quedarían:
client1: image: ubuntu container_name: kea-client1 command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-net: mac_address: 00:11:22:33:44:55 cap_add: - NET_ADMIN client2: image: ubuntu container_name: kea-client2 command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-net: mac_address: 66:77:88:99:AA:BB privileged: true
Por último, necesitamos asegurarnos que el dhcp integrado en Docker no asigne la ip fija de nuestro servidor. Esto lo podemos hacer usando la opción ip_range al especificar la red, para indicar qué direcciones tiene que asignar a los contendores.
De esta forma, nuestro docker-compose.yml completo quedaría:
name: dhcp-kea services: dhcp-server: image: docker.cloudsmith.io/isc/docker/kea-dhcp4:2.7.0 container_name: kea-server networks: dhcp-net: ipv4_address: 172.18.2.2 volumes: - ./config:/etc/kea client1: image: ubuntu container_name: kea-client1 command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-net: mac_address: 00:11:22:33:44:55 cap_add: - NET_ADMIN client2: image: ubuntu container_name: kea-client2 command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-net: mac_address: 66:77:88:99:AA:BB cap_add: - NET_ADMIN networks: dhcp-net: driver: bridge ipam: config: - subnet: 172.18.2.0/24 ip_range: 172.18.2.96/28 # Asignar IPs entre 172.18.2.100 y 172.18.2.115
Una vez tenemos nuestros contenedores listos, es hora de configurar nuestro servidor DHCP. Para ello, necesitamos crear dos archivos de configuración: kea-ctrl-agent.conf y kea-dhcp4.conf dentro de nuestra carpeta config.
Copia el siguiente contenido en el archivo kea-ctrl-agent.conf:
{ "Control-agent": { "http-host": "0.0.0.0", "http-port": 8000, "control-sockets": { "dhcp4": { "socket-type": "unix", "socket-name": "/run/kea/control_socket_4" } }, "loggers": [ { "name": "kea-ctrl-agent", "output_options": [ { "output": "stdout" } ], "severity": "INFO" } ] } }
El contenido del archivo kea-dhcp4.conf será:
{ "Dhcp4": { # First we set up global values "valid-lifetime": 4000, "renew-timer": 1000, "rebind-timer": 2000, # Next we set up the interfaces to be used by the server. "interfaces-config": { "interfaces": ["eth0"] }, # And we specify the type of lease database "lease-database": { "type": "memfile", "persist": true, "name": "/var/lib/kea/dhcp4.leases" }, # Finally, we list the subnets from which we will be leasing addresses. "subnet4": [ { "id": 1, "subnet": "172.18.2.0/24", "pools": [ { "pool": "172.18.2.70 - 172.18.2.90" } ], "interface": "eth0" } ] } }
Vamos a explicar brevemente cada concepto:
Ya tenemos listo nuestro servidor DHCP. Volvemos a la carpeta donde está nuestro docker-compose.yml y lo ejectuamos con:
docker compose up -d
Si todo ha ido bien, veremos como se crean y ejecutar los tres contendores, además de la red.
[+] Running 4/4 ✔ Network dhcp-kea_dhcp-net Created ✔ Container server Started ✔ Container client1 Started ✔ Container client2 Started
Una vez creados, nos metemos en uno de los clientes:
docker exec -it client1 bash
Primero vamos a comprobar que eth0 no tiene ninguna IP asignada:
ip a
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 31: eth0@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0
A continuación, ejecutamos la siguiente instrucción para pedir una nueva IP al servidor DHCP:
dhclient -v eth0
En este punto, deberemos ver (entre otras cosas) los mensajes intercambiados con el servidor;
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3 (xid=0x93643256) DHCPOFFER of 172.18.2.70 from 172.18.2.2 DHCPREQUEST for 172.18.2.70 on eth0 to 255.255.255.255 port 67 (xid=0x56326493) DHCPACK of 172.18.2.70 from 172.18.2.2 (xid=0x93643256) bound to 172.18.2.70 -- renewal in 913 seconds.
Si volvemos a comprobar la ip, deberíamos ver que se ha asignado correctamente:
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 31: eth0@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.18.2.70/24 brd 172.18.2.255 scope global dynamic eth0 valid_lft 3997sec preferred_lft 3997sec
Una reserva de host en un servidor DHCP permite asignar una dirección IP específica a un dispositivo basándose en su dirección MAC. Esto asegura que el dispositivo recibirá siempre la misma IP cada vez que se conecte a la red. Esta función es especialmente útil en escenarios donde se necesita una IP fija, como en servidores, impresoras de red, o cámaras de seguridad, facilitando la gestión y el acceso constante a estos dispositivos sin necesidad de configurarlos manualmente en cada uno de ellos.
Para probar esta funcionalidad, primero vamos a añadir un nuevo contenedor a nuestro docker-compose.yml que simulará ser una impresora:
printer: image: ubuntu container_name: kea-printer command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-net: mac_address: CC:DD:EE:FF:00:11 cap_add: - NET_ADMIN
Lo siguiente, será configurar Kea para indicar que reserve una IP para la máquina con esa MAC. Ésto se puede hacer mediante la opción reservations en el archivo de configuración kea-dhcp4.conf:
"subnet4": [ { "id": 1, "subnet": "172.18.2.0/24", "pools": [ { "pool": "172.18.2.70 - 172.18.2.90" } ], "interface": "eth0", "reservations": [ { "hw-address": "CC:DD:EE:FF:00:11", "ip-address": "172.18.2.84" } ] } ]
Si arrancamos los contenedores, nos metemos en printer y pedimos una IP al servidor DHCP, debería darnos siempre la 172.168.2.84:
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3 (xid=0x2f3f8d5a) DHCPOFFER of 172.18.2.84 from 172.18.2.2 DHCPREQUEST for 172.18.2.84 on eth0 to 255.255.255.255 port 67 (xid=0x5a8d3f2f) DHCPACK of 172.18.2.84 from 172.18.2.2 (xid=0x2f3f8d5a) bound to 172.18.2.84 -- renewal in 890 seconds.
La capacidad de un servidor DHCP para servir diferentes rangos de IP a diferentes subredes permite una gestión más eficiente y flexible de redes complejas. Esto es útil en entornos donde existen múltiples subredes que requieren configuraciones de red específicas, como oficinas con departamentos separados, redes de invitados y redes internas. Al administrar varios rangos de IP, el servidor DHCP puede asignar automáticamente las direcciones adecuadas a cada dispositivo según la subred a la que pertenece, lo que facilita la segmentación de la red, mejora la seguridad y optimiza el uso de los recursos de red.
Vamos a simular un entorno con diferentes subredes. Para ello, crearemos 2 subredes (con diferentes rangos de IP) en nuestro docker-compose.yml. Añadiremos el host client1 a la primera subred y client2 y la impresora a la otra. Nuestro servidor tendrá dos tarjetas de red y estará en ambas subredes:
name: dhcp-kea services: dhcp-server: image: docker.cloudsmith.io/isc/docker/kea-dhcp4:2.7.0 container_name: kea-server networks: dhcp-1floor: ipv4_address: 172.18.2.2 dhcp-2floor: ipv4_address: 172.18.3.2 volumes: - ./config:/etc/kea client1: image: ubuntu container_name: kea-client1 command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-1floor: mac_address: 00:11:22:33:44:55 cap_add: - NET_ADMIN client2: image: ubuntu container_name: kea-client2 command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-2floor: mac_address: 66:77:88:99:AA:BB cap_add: - NET_ADMIN printer: image: ubuntu container_name: kea-printer command: bash -c "apt-get update && apt-get install -y iproute2 nmap dhcpcd isc-dhcp-client iputils-ping net-tools && ip addr flush dev eth0 && sleep infinity" networks: dhcp-2floor: mac_address: CC:DD:EE:FF:00:11 cap_add: - NET_ADMIN networks: dhcp-1floor: driver: bridge ipam: config: - subnet: 172.18.2.0/24 ip_range: 172.18.2.96/28 # Asignar IPs entre 172.18.2.100 y 172.18.2.115 dhcp-2floor: driver: bridge ipam: config: - subnet: 172.18.3.0/24 ip_range: 172.18.3.96/28 # Asignar IPs entre 172.18.3.100 y 172.18.3.115
Nuestras dos subredes se llamas dhcp-1floor y dhcp-2floor. La primera tiene el rango de direcciones 172.18.2.0/24 y la segunda 172.18.3.0/24.
Un servidor Kea es capaz de servir IPs a diferentes subredes. Basta con definir en el archivo de configuración (kea-dhcp4.conf) las 2 subredes creadas:
{ "Dhcp4": { # First we set up global values "valid-lifetime": 4000, "renew-timer": 1000, "rebind-timer": 2000, # Next we set up the interfaces to be used by the server. "interfaces-config": { "interfaces": ["eth0", "eth1"] }, # And we specify the type of lease database "lease-database": { "type": "memfile", "persist": true, "name": "/var/lib/kea/dhcp4.leases" }, # Finally, we list the subnets from which we will be leasing addresses. "subnet4": [ { "id": 1, "subnet": "172.18.2.0/24", "pools": [ { "pool": "172.18.2.70 - 172.18.2.90" } ], "interface": "eth0" }, { "id": 2, "subnet": "172.18.3.0/24", "pools": [ { "pool": "172.18.3.70 - 172.18.3.90" } ], "interface": "eth1", "reservations": [ { "hw-address": "CC:DD:EE:FF:00:11", "ip-address": "172.18.3.84" } ] } ] } }
En nuestra configuración de Kea, indicamos al principio que la base de datos utilizada para almacenar la información de los arrendamientos de IP era en memoria mediante la opción lease-database:
"lease-database": { "type": "memfile", "persist": true, "name": "/var/lib/kea/dhcp4.leases" }
De hecho, si arrancamos los contenedores Docker y hacemos varias peticiones al servidor para que sirva IPs, podemos ver en el contenido de ese archivo (/var/lib/kea/dhcp4.leases) las IPs que ha ido sirviendo el servidor:
address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id 172.18.2.70,00:11:22:33:44:55,,4000,1724234915,1,0,0,2d1a2137a76e,0,,0 172.18.3.70,66:77:88:99:aa:bb,,4000,1724234927,2,0,0,7366e6e03c6d,0,,0 172.18.3.84,cc:dd:ee:ff:00:11,,4000,1724234943,2,0,0,d3b9c1306036,0,,0 172.18.3.84,cc:dd:ee:ff:00:11,,4000,1724235702,2,0,0,d3b9c1306036,0,,0 172.18.3.70,66:77:88:99:aa:bb,,4000,1724235760,2,0,0,7366e6e03c6d,0,,0 172.18.2.70,00:11:22:33:44:55,,4000,1724235778,1,0,0,2d1a2137a76e,0,,0
Podemos variar este comportamiento para hacer que toda esa información se almacene en una bbdd, lo que ofrece varios beneficios:
Para implementar este cambio, realizaremos los siguientes ajustes:
Uno de los primeros problemas que debemos enfrentar es garantizar que, al iniciar el servidor Kea, la base de datos ya esté lista. Esto es importante porque, si Kea intenta conectarse a la base de datos antes de que esta esté completamente operativa, generará errores de conexión. Para solucionar esto, normalmente podríamos usar una herramienta como kea-admin para crear la base de datos y las tablas necesarias antes de que el servidor Kea comience a funcionar.
Sin embargo, utilizar kea-admin complica el proceso de creación de contenedores. Esto nos obliga a buscar una solución que sea más sencilla y automática dentro del entorno de Docker.
En lugar de utilizar kea-admin, optamos por una solución más directa: crear la base de datos y las tablas necesarias a través de un dhcpdb_create.zip. Este script contendrá todas las instrucciones necesarias para crear la base de datos y las tablas que Kea necesita para funcionar correctamente.
Guardaremos este script en una carpeta llamada script dentro de nuestro proyecto. Luego, aprovechamos una funcionalidad de MySQL en Docker: cuando iniciamos un contenedor de MySQL, el propio contenedor ejecuta automáticamente cualquier script que esté en la carpeta docker-entrypoint-initdb.d.
Al mapear la carpeta script a docker-entrypoint-initdb.d en nuestro archivo docker-compose.yml, logramos que MySQL ejecute el script automáticamente al iniciarse el contenedor. De esta forma, la base de datos y las tablas estarán listas antes de que Kea intente conectarse, resolviendo el problema de dependencia entre los dos servicios.
Este enfoque no solo simplifica el proceso de creación de la base de datos, sino que también asegura que el contenedor MySQL siempre tenga la estructura correcta sin necesidad de ejecutar comandos adicionales o complicar el flujo de creación de contenedores.
En nuestro entorno de Docker, es crucial asegurarnos de que el contenedor kea-server no intente conectarse a la base de datos MySQL hasta que esta esté completamente lista y aceptando conexiones. Para lograr esto, utilizamos la funcionalidad de healthcheck en Docker, que nos permite verificar si un servicio está listo antes de que otro servicio dependiente intente conectarse:
dhcp-mysql: image: mysql:8.0 container_name: kea-mysql networks: dhcp-net: ipv4_address: 172.18.4.3 ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: dhcp_kea volumes: - ./mysql:/var/lib/mysql - ./script:/docker-entrypoint-initdb.d healthcheck: test: mysqladmin ping -h 127.0.0.1 -u root --password=root interval: 10s retries: 5 start_period: 30s timeout: 10s
Luego, añadimos la clave depends_on en el contenedor kea-server. Esto garantiza que kea-server no se cargue hasta que el contenedor kea-mysql pase las comprobaciones de salud.
dhcp-server: build: . depends_on: dhcp-mysql: condition: service_healthy container_name: kea-server networks: dhcp-1floor: ipv4_address: 172.18.2.2 dhcp-2floor: ipv4_address: 172.18.3.2 dhcp-net: ipv4_address: 172.18.4.2 volumes: - ./config:/etc/kea
Fíjate que en nuestra configuración de Docker, utilizamos la opción build para crear una imagen personalizada del contenedor kea-server. Esta opción nos permite definir instrucciones adicionales en un Dockerfile que ajusta la imagen base según nuestras necesidades. En este caso, estamos utilizando un Dockerfile que extiende la imagen base de Kea para añadir algunas herramientas adicionales que requerimos en nuestro entorno:
FROM docker.cloudsmith.io/isc/docker/kea-dhcp4:2.7.0 RUN apk update && apk add --no-cache bash curl mysql-client
La opción build es necesaria porque estamos personalizando la imagen base de Kea. La imagen oficial no incluye todas las herramientas que necesitamos, como el cliente MySQL o Bash. En lugar de crear un contenedor desde una imagen que no está completamente adaptada a nuestras necesidades, utilizamos esta opción para construir una imagen que ya contiene todas las herramientas necesarias, lo que hace más eficiente el desarrollo y la ejecución de nuestra aplicación.
Al agregar estas utilidades directamente en la imagen, no tenemos que instalarlas cada vez que el contenedor se inicie, lo que ahorra tiempo y evita problemas de configuración en el futuro.
Por último, sólo nos queda cambiar la configuración de kea en el archivo kea-dhcp4.conf para indicar que vamos a utilizar MySql como almacenamiento:
"lease-database": { "type": "mysql", "host": "kea-mysql", "user": "root", "password": "root", "name": "dhcp_kea" },
Una vez que hemos configurado Kea para que utilice MySQL como almacenamiento, verificamos que las asignaciones de direcciones IP (leases) se están guardando correctamente en la base de datos. Para hacer esta comprobación, podemos realizar una consulta SQL sencilla sobre la tabla lease4, que es donde se almacenan las direcciones IPv4 asignadas.
Para ello, accedemos a cualquier cliente para pedir una IP al servidor DHCP. A continuación, entramos al contenedor mysql y ejecutamos:
mysql -u root -p
Una vez estamos conectados a MySql:
use dhcp_kea;
SELECT * FROM lease4;
Esto mostrará todas las entradas de direcciones IPv4 asignadas, junto con información relevante como el tiempo de expiración de la lease, la dirección MAC del cliente, entre otros detalles.
Configura un servidor Kea DHCP para una oficina de tres pisos. Cada piso debe tener la siguiente configuración:
Piso 1:
Subred: 192.168.1.0/24 Dispositivos: 3 PCs y una impresora Reserva de IP para una impresora: 192.168.1.20
Piso 2:
Subred: 192.168.2.0/24 Dispositivos: 2 PCs Reserva de IP para una impresora: 192.168.2.15
Piso 3:
Subred: 192.168.3.0/24 Dispositivos: 3 PCs
El almacenamiento de las asignaciones debe hacerse en una bbdd MySql.
Asegúrate de que los PCs y las impresoras obtengan las configuraciones de IP adecuadas.