- Docker para ciberseguridad – Parte 1: Instalación e imágenes
- Docker para ciberseguridad – Parte 2: Crear imágenes. Dockerfile
- Docker para ciberseguridad – Parte 3: Gestión de contenedores
- Docker para ciberseguridad – Parte 4: docker volume y docker network
- Docker para ciberseguridad – Parte 5: Docker Compose (1)
Ya vimos en la entrada 2, en el cheatsheet que os dejé de Dockerfile, que hay una instrucción para crear volúmenes. Además, en la entrada 3 vimos cómo podemos levantar estos volúmenes cuando ejecutamos mediante «docker run«. En esta entrada vamos a ampliar algo más de detalle sobre los volúmenes. Además, vamos a ver «docker network«, la utilidad que nos va a poder definir redes para interconectar contenedores y como salen estos hacia Internet.
docker volume
Docker Volume es una forma de almacenar datos fuera del ciclo de vida de un contenedor. Todo el sistema de archivos del contenedor se borra cuando el contenedor se destruye. En cambio, los volúmenes persisten en el sistema de archivos del host Docker. Esto permite que los datos sobrevivan a los reinicios y actualizaciones del contenedor.
Creación básica de volúmenes
Los volúmenes se pueden definir en el Dockerfile. En este caso solo se define qué carpeta dentro del contenedor debe persistir. No se indica en qué parte del host se va a almacenar, ya que las imágenes son portables y no podemos prefijar una carpeta en su construcción.
VOLUME /var/www/html
También podemos definir volúmenes cuando se lanza el contenedor con «docker run«:
$> docker run -v /tmp/dockerApacheCustomVol/:/tmp/mounted/ apachecustom
Definir el volumen en el Dockerfile nos aporta consistencia, sabiendo que siempre se va a crear el volumen de la misma carpeta cuando se lance el contenedor. Y definirlo en su ejecución nos permite dar flexibilidad al usuario de qué guardar y dónde.
Tres tipos de volúmenes
Aunque hemos visto como montar volúmenes básicos, ya hemos visto una diferencia. Cuando lo hemos montado en Dockerfile, solo hemos indicado la ruta del sistema de ficheros del contenedor. En cambio, cuando lo hemos definido en «docker run» hemos indicado la ruta del host y del contenedor. Esto es porque existen diferentes tipos de volúmenes. En concreto existen tres tipos.
- Bind mounts. Este es el ejemplo que vimos en la entrada 3 y es el que acabamos de ver cuando definimos el volumen en el «docker run». Se va a montar una ruta del host en el contenedor, y así todos los cambios que se hagan en la misma persistirán en el sistema de ficheros del host. Por tanto, se indica tanto la ruta del host como la del contenedor donde se va a montar.
- Anonymous volumes. Es el caso que hemos visto en el Dockerfile. Son volúmenes que se crean y que no se les asigna un nombre. Por este motivo no se suelen reutilizar y sirven solo para el contenedor para el que se crean. Los datos igualmente persisten en el sistema de ficheros del host, pero en una ruta con un nombre aleatorio.
- Named volumes. En contraposición a los volúmenes anónimos, estos se crean con un nombre y se pueden compartir entre varios contenedores. La ruta donde se guarda dentro del host está definida por el nombre del volumen. Su objetivo principal es poder reutilizarlo entre varios contenedores.
Veámoslo con ejemplos.
Anonymous volumes
Lo podemos definir en la ejecución del contenedor indicando la ruta del contenedor que debe persistir. Dónde se guarda en el host lo va a gestionar Docker.
# Anonymous volumen
$> docker run -d -v /dataTemp apachecustom
Si hacemos «docker inspect» para ver los volúmenes del contenedor, vemos que Docker ha creado una carpeta para guardar todo lo que se mueva a este volumen y generar esa persistencia en los datos. Como veis, la ruta incluye una cadena de caracteres aleatorios. Estos volúmenes están creados para este contenedor y no se reutilizan.
Named volumes
Como es un volumen con nombre y pensado para usarse en más de un contenedor, primero lo creamos y luego lo vamos asignando.
# Crear Named volume y listamos
$> docker volume create mi_volumen
$> docker volume ls
# Lo asignamos en docker run
$> docker run -d -v mi_volumen:/data apachecustom
Bind mounts
Este es el ejemplo que hemos visto en la entrada 3. Se monta una ruta del host en el contenedor:
# Bind mount
$> docker run -d -v /ruta/host:/ruta/contenedor apachecustom
docker network
No sé si en todo este proceso os habéis dado cuenta. Pero cuando instalamos Docker se crea una interfaz de red en el equipo:
Tenemos una IP para esta interfaz que es 172.17.0.1 y que está en una red «/16». Os dejo un enlace por si queréis entender qué son las máscaras de red. Pero básicamente definen que una subred se conforma por un rango de direcciones. En este caso las direcciones IP que están dentro de este rango van de la «172.17.0.1» a la «172.17.255.254». Os dejo esta calculadora online por si queréis darle una vuelta a este tema, que no es el foco del artículo.
Si levantamos un contenedor partiendo de la imagen de Ubuntu, podemos ver que tiene una red asignada. Lo lanzamos y usamos el comando «docker inspect» para ver el detalle.
Si bajamos por el resultado del «docker inspect» del contendor hasta una sección que se llama «NetworkSettings» vemos:
El contenedor tiene una IP que es la «172.17.0.2» (en el rango de la interfaz Docker de mi máquina), y la IP de gateway, a través de la cual sale a Internet, es la IP de mi host, la «172.27.0.1«. Vemos que el tipo de red se llama «bridge«. Y así es. Por defecto, los contenedores se levantan con una red bridge, en la cual se sale a navegar hacia internet usando como salto la IP de la interfaz docker que se ha creado en el host.
Pero este no es el único tipo de red. Hay tres tipos principales de redes. Vamos a verlo en detalle.
Red bridge
Es la red por defecto para los contenedores que se crean en un host Docker. Los contenedores en la misma red bridge pueden comunicarse entre sí usando sus nombres de host o direcciones IP. Es decir, que todos los contenedores que levantamos sin configurar la red, tienen visibilidad entre sí.
Si levantamos dos contenedores a partir de la imagen de Ubuntu, y no le especificamos ninguna configuración de red, los va a levantar conectados a la red bridge por defecto. En el ejemplo de la captura de abajo, el primer contenedor, con nombre «agitated_curran» tiene la IP 172.17.02. Y el segundo contenedor con nombre «modest_bohr» tiene la IP 172.17.0.3.
Si ingresamos a una terminal del segundo contenedor mediante «docker exec» podemos comprobarlo. Primero deberemos actualizar los repositorios e instalar el paquete que contiene la utilidad ping.
Una vez ya tenemos ping podemos lanzarlo contra la IP del primero contenedor, la 172.17.0.2 y vemos que tenemos conectividad.
Crear una red bridge
Podemos tener varias redes bridge. Todos los contenedores que estén en una de las redes bridge tendrán conectividad entre sí, pero no con los contenedores que estén en otra red bridge. Para crear otra red bridge:
# Creamos una red bridge con nombre mi_red_bridge
$> docker network create --driver bridge mi_red_bridge
# Especificando subred y gateway
$> docker network create --driver bridge --subnet 172.124.100.0/24 --gateway 172.124.100.1 mi_red_bridge
# Lanzamos un contenedor ubuntu conectado a esta red
$> docker run -dit --network mi_red_bridge ubuntu
Un pequeño gran detalle. En la gestión de redes por Docker hay una especie de DNS que nos va a traducir los nombre de los contenedores en su dirección IP. Por este motivo, cuando lancemos el ping lo podemos lanzar contra la IP del contenedor o directamente cintra el nombre del contenedor. Esto solo funciona en las redes que creemos, no en la red por defecto.
Creamos una red y conectamos dos contenedores a partir de la imagen de centos.
Ahora vamos a hacer un ping desde contenedorA a conetendor B. Y lo vamos a hacer indicando el nombre del contenedor en vez de su dirección IP.
docker network connect: Conectar distintas redes
En este ejemplo tenemos contenedorA conectado a redA y contenedorB conectado a redB.
Como podemos ver, no hay conectividad entre ellas:
Para poder interconectar dos redes disponemos del comando «docker network connect«. Este comando nos permite conectar un contendor a otra red. En este ejemplo vamos a conectar al contenedorB a la redA.
# Conectar red
$> docker network connect redA contenedorB
# Para volver a desconectar
$> docker network disconnect redA contenedorB
Red Host
Los contenedores que se una a una red Host comparten la configuración de red del host. Es decir, salen hacia la red como si fueran el propio host. Estas redes se suelen utilizar en máquinas donde se levantan servicios que requiere un alto rendimiento de red. En las redes bridge el host, que hace de gateway, debe encaminar el tráfico del contenedor hacia Internet y de vuelta al contenedor. En el caso de las redes host es más eficiente, ya que el contenedor sale directamente a la red como si fuera el host.
Para conectar un contenedor a la red con el modo host, basta con definirlo en el «docker run» de la siguiente forma:
$> docker run --network host ubuntu
La red host ya existe en Docker por defecto.
Hagamos un ejemplo. Si lanzamos el contenedor de Ubuntu con la red host, actualizamos apt e instalamos el paquete necesario para usar el comando «ip«:
Podemos ver la configuración de red:
Si salimos del contenedor y vemos la configuración de red del host vemos que es exactamente la misma. Este es el funcionamiento de la red host.
Red None
Esto es una red no red. Me explico. Para aquellos contendores que queremos que estén asilados y que no tengan ninguna conexión de red, se les asigna una red None. A este contenedor no se podrá llegar desde la red ni desde otros contenedores, estará aislado. Solo se podrá interactuar con él desde el host usando el propio docker.
$> docker run --network none ubuntu
Al igual, la red none ya existe.
Conclusiones
Dando un paso más en el uso de Docker, hemos visto como crear persistencia en los datos del contenedor con «docker volume» y como podemos definir como se conectan los contenedores entre sí con «docker network«.
Nos vemos en la próxima