Comunicación entre contenedores en hosts separados
Entorno del experimento
El entorno consta de dos servidores con Docker instalado y configurado con diferentes subredes para los contenedores.
usuario@servidor-a:~# docker --version
Docker version 24.0.4, build 3713ee1
usuario@servidor-a:~# ip a show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 10.0.1.100/24 brd 10.0.1.255 scope global eth0
usuario@servidor-b:~# docker --version
Docker version 24.0.4, build 3713ee1
usuario@servidor-b:~# ip a show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 10.0.1.101/24 brd 10.0.1.255 scope global eth0
Proceso experimental
Para establecer la comunicación, se modificaron las redes Docker y se añadieron rutas estáticas.
# Modificar el rango de direcciones Docker en cada host
## En servidor-a, usar subred 192.168.10.0/24
usuario@servidor-a:~# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=192.168.10.1/24
usuario@servidor-a:~# systemctl daemon-reload
usuario@servidor-a:~# systemctl restart docker
## En servidor-b, usar subred 192.168.20.0/24
usuario@servidor-b:~# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=192.168.20.1/24
usuario@servidor-b:~# systemctl daemon-reload
usuario@servidor-b:~# systemctl restart docker
# Añadir rutas estáticas para el tráfico entre contenedores
## En servidor-a
usuario@servidor-a:~# route add -net 192.168.20.0/24 gw 10.0.1.101
usuario@servidor-a:~# iptables -A FORWARD -s 10.0.1.0/24 -j ACCEPT
## En servidor-b
usuario@servidor-b:~# route add -net 192.168.10.0/24 gw 10.0.1.100
usuario@servidor-b:~# iptables -A FORWARD -s 10.0.1.0/24 -j ACCEPT
# Verificar direcciones IP dentro de los contenedores
## Contenedor en servidor-a
root@a1b2c3d4:~# ip a
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
inet 192.168.10.2/24 brd 192.168.10.255 scope global eth0
## Contenedor en servidor-b
root@e5f6g7h8:~# ip a
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
inet 192.168.20.2/24 brd 192.168.20.255 scope global eth0
# Prueba de conectividad
root@a1b2c3d4:~# ping -c 1 192.168.20.2
PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data.
64 bytes from 192.168.20.2: icmp_seq=1 ttl=62 time=0.336 ms
root@e5f6g7h8:~# ping -c 1 192.168.10.2
PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data.
64 bytes from 192.168.10.2: icmp_seq=1 ttl=62 time=0.469 ms
Principios de comunicación entre hosts
El flujo de paquetes implica que un contenedor envía tráfico a su gateway, el host lo enruta al otro servidor y finalmante el contenedor destino recibe el paquete.
# Captura de tráfico para análisis
usuario@servidor-a:~# tcpdump -nn -i veth86b6808 -w trafico_veth_a.pcap
usuario@servidor-a:~# tcpdump -nn -i eth0 -w trafico_eth_a.pcap
usuario@servidor-b:~# tcpdump -nn -i eth0 -w trafico_eth_b.pcap
usuario@servidor-b:~# tcpdump -nn -i veth15e7d49 -w trafico_veth_b.pcap
- El contenedor origen (192.168.10.2) envía un paquete a un destino en otra red, lo que lo dirige al gateway configurado.
- El host origen reenvía el paquete al otro host basándose en la tabla de rutas.
- El host destino entrega el paquete al contenedor objetivo (192.168.20.2) a través de su interfaz Docker.
Comandos comunes en Dockerfiles
Un Dockerfile es un archivo de instrucciones para construir imágenes Docker, ejecutado secuencialmente.
# FROM: Especifica la imagen base
FROM ubuntu:22.04
# LABEL: Añade metadatos
LABEL maintainer="desarrollador@ejemplo.com"
LABEL version="2.0"
# ADD: Copia archivos y descomprime automáticamente
ADD [--chown=usuario:grupo] origen destino
ADD --chown=appuser:appgroup app.tar.gz /opt/app/
# COPY: Copia archivos sin descomprimir
COPY [--chown=usuario:grupo] origen destino
# ENV: Establece variables de entorno
ENV APP_ENV="produccion"
# USER: Define el usuario de ejecución
USER appuser:appgroup
# RUN: Ejecuta comandos durante la construcción
RUN apt-get update && apt-get install -y nginx
# VOLUME: Crea puntos de montaje
VOLUME ["/datos/logs"]
# WORKDIR: Cambia el directorio de trabajo
WORKDIR /app/src
# EXPOSE: Documenta puertos
EXPOSE 8080/tcp
# CMD: Especifica el comando por defecto
CMD ["nginx", "-g", "daemon off;"]
# ENTRYPOINT: Configura ejecutable principal, combina con CMD
Construcción y verificcaión de una imagen Nginx
Se creó una imagen personalizada de Nginx usando un Dockerfile.
# Estructura de archivos
usuario@servidor-a:/ruta/proyecto/nginx# ls
Dockerfile contenido.html nginx-1.18.0.tar.gz config_nginx.conf
# Contenido del Dockerfile
usuario@servidor-a:/ruta/proyecto/nginx# cat Dockerfile
FROM ubuntu:22.04
LABEL autor="ingeniero@dominio.com"
RUN apt-get update && apt-get install -y iproute2 ntpdate tcpdump telnet traceroute nfs-kernel-server lrzsz tree openssl libssl-dev libpcre3-dev zlib1g-dev gcc openssh-server iotop unzip zip make
RUN mkdir -p /servicio/nginx && useradd -r -s /sbin/nologin nginx
ADD nginx-1.18.0.tar.gz /tmp/src/
RUN cd /tmp/src/nginx-1.18.0 && ./configure --prefix=/servicio/nginx/ --user=nginx --group=nginx && make && make install && ln -s /servicio/nginx/sbin/nginx /usr/sbin/nginx && chown -R nginx:nginx /servicio/nginx/
RUN mkdir -p /servicio/nginx/ejecucion/
ADD config_nginx.conf /servicio/nginx/conf/
ADD contenido.html /servicio/nginx/html/
CMD ["nginx", "-g", "daemon off;"]
# Construir la imagen
docker build -t registry.local/mi-nginx:v1 .
# Ejecutar el contenedor
usuario@servidor-a:/ruta/proyecto/nginx# docker run -d -p 8080:80 registry.local/mi-nginx:v1
# Verificación: acceder vía curl o navegador
curl http://localhost:8080
Despliegue de Harbor y gestión de imágenes
Instalación de Harbor
# Requisitos previos: Docker y Docker Compose instalados
usuario@servidor-registro:~# docker --version
Docker version 24.0.4, build 3713ee1
# Descargar y descomprimir Harbor
usuario@servidor-registro:~# tar xzf harbor-offline-installer-v2.8.2.tgz -C /opt/
usuario@servidor-registro:~# cd /opt/harbor/
usuario@servidor-registro:/opt/harbor# cp harbor.yml.tmpl harbor.yml
# Editar configuración
usuario@servidor-registro:/opt/harbor# vim harbor.yml
hostname: registro.local
harbor_admin_password: contrasegura123
data_volume: /var/harbor
# Ejecutar instalación con análisis de seguridad
usuario@servidor-registro:/opt/harbor# ./install.sh --with-trivy
# Verificar servicios
docker ps
Subida de imágenes
# Etiquetar una imagen existente
docker tag alpine:latest registro.local/mi-proyecto/alpine:v1
# Configurar Docker para confiar en registros HTTP
usuario@servidor-a:~# vim /etc/docker/daemon.json
{
"insecure-registries": ["registro.local"]
}
systemctl restart docker
# Autenticarse y subir imagen
docker login registro.local
docker push registro.local/mi-proyecto/alpine:v1
Descarga de imágenes
# En otro host, configurar confianza
usuario@servidor-b:~# vim /etc/docker/daemon.json
{
"insecure-registries": ["registro.local"]
}
systemctl restart docker
# Descargar imagen
docker pull registro.local/mi-proyecto/alpine:v1
Limitación de recursos con systemd
Para evitar que los contenedores consuman recursos excesivos, se usan mecanismos de cgroups.
# Verificar el driver de cgroups
docker info | grep "Cgroup Driver"
# Si no es systemd, configurarlo
vim /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
# Usar imagen de pruebas de estrés
docker pull lorel/docker-stress-ng
# Ejemplo sin limitación
docker run -it --rm lorel/docker-stress-ng --vm 2 --vm-bytes 256M
docker stats
# Limitar memoria a 512M
docker run -it --rm -m 512M lorel/docker-stress-ng --vm 2 --vm-bytes 256M
docker stats
# Limitar CPU y memoria
docker run -it --rm -m 512M --cpus 2 lorel/docker-stress-ng --vm 2 --vm-bytes 256M --cpu 4
docker stats
Proceso de construcción de imágenes en capas
- Obtener una imagen base oficial, como una distribución Linux mínima.
- Instalar dependencias comunes para crear una capa base genérica.
- Definir el entorno de la aplicación y sus dependencias específicas.
- Configurar la imagen de negocio con archivos de configuración y código.
- Probar la imagen para asegurar que cumple con los requisitos.
- Distribuir la imagen a un registro para su uso.
Limitación precisa con lxcfs
lxcfs proporciona vistas de /proc dentro del contenedor que reflejan los recursos asignados, mejorando la precisión de las herramientas de monitoreo.
# Instalar lxcfs
usuario@servidor-a:~# apt-get install -y lxcfs
# Preparar sistema de archivos con soporte para cuotas
mkfs.xfs /dev/sdb
vim /etc/fstab
/dev/sdb /var/lib/docker xfs defaults,pquota 0 0
mount -a
systemctl restart docker
# Ejecutar contenedor con limitaciones y lxcfs
docker run -it -m 200M --cpus 1 \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
centos:7 /bin/bash
# Verificación dentro del contenedor
free -h # Debería mostrar aproximadamente 200M de memoria
top # Debería indicar 1 CPU disponible