Gestión de redes Docker entre hosts y limitación de recursos en contenedores

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

  1. El contenedor origen (192.168.10.2) envía un paquete a un destino en otra red, lo que lo dirige al gateway configurado.
  2. El host origen reenvía el paquete al otro host basándose en la tabla de rutas.
  3. 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

  1. Obtener una imagen base oficial, como una distribución Linux mínima.
  2. Instalar dependencias comunes para crear una capa base genérica.
  3. Definir el entorno de la aplicación y sus dependencias específicas.
  4. Configurar la imagen de negocio con archivos de configuración y código.
  5. Probar la imagen para asegurar que cumple con los requisitos.
  6. 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

Etiquetas: Docker dockerfile Nginx Harbor systemd

Publicado el 6-9 16:07