Docker, Docker Compose y Docker Swarm

Buenas a todos. En esta ocasión os presento una guía bastante completa y elaborada de una tecnología que lleva siendo mi preferida desde que la conocí por su versatilidad y sus increíbles ventajas. Cualquiera que quiera conocerla e iniciarse en utilizarla, encontrará en este artículo una gran ayuda. Espero que os guste. ¡Vamos a ello!

Docker, ¿qué es?

La palabra «docker» se refiere a varias cosas: un proyecto de la comunidad open source, las herramientas del proyecto open source, Docker Inc. (la empresa principal promotora de ese proyecto) y las herramientas que la empresa admite formalmente. El hecho de que las tecnologías y la empresa compartan el mismo nombre puede ser algo confuso.

  • «Docker», el software de TI, es una tecnología de creación de contenedores que permite la creación y el uso de contenedores de Linux.
  • La comunidad open source de Docker trabaja para mejorar estas tecnologías a fin de beneficiar a todos los usuarios de forma gratuita.
  • La empresa, Docker Inc., desarrolla el trabajo de la comunidad Docker, lo hace más seguro y comparte estos avances con el resto de la comunidad. También respalda las tecnologías mejoradas y reforzadas para los clientes empresariales.

Con docker, se pueden usar contenedores como máquinas virtuales extremadamente livianas y modulares. Además, se obtiene una gran flexibilidad con estos contenedores: se pueden crear, implementar, copiar y mover de un entorno a otro, lo cual le permite también optimizar las aplicaciones para entornos cloud.

¿Cómo funciona Docker?

La tecnología Docker usa el kernel de Linux y las funciones de este, como Cgroups y namespaces, para segregar los procesos, de modo que puedan ejecutarse de manera independiente. El propósito de los contenedores es esta independencia: la capacidad de ejecutar varios procesos y aplicaciones por separado para hacer un mejor uso de su infraestructura y, al mismo tiempo, conservar la seguridad que tendría con sistemas separados.

Las herramientas del contenedor, como Docker, ofrecen un modelo de implementación basado en imágenes. Esto permite compartir una aplicación, o un conjunto de servicios, con todas sus dependencias en varios entornos. Docker también automatiza la implementación de la aplicación (o conjuntos combinados de procesos que constituyen una aplicación) en este entorno de contenedores.

Estas herramientas desarrolladas a partir de los contenedores de Linux, lo que hace a Docker fácil de usar y único, otorgan a los usuarios un acceso sin precedentes a las aplicaciones, la capacidad de implementar rápidamente y control sobre las versiones y su distribución.

¿La tecnología de Docker es la misma que la de los contenedores de Linux tradicionales?

No. Al principio, la tecnología Docker se desarrolló a partir de la tecnología LXC, lo que la mayoría de las personas asocia con contenedores de Linux «tradicionales», aunque desde entonces se ha alejado de esa dependencia. LXC era útil como virtualización ligera, pero no ofrecía una buena experiencia al desarrollador ni al usuario. La tecnología Docker no solo aporta la capacidad de ejecutar contenedores; también facilita el proceso de creación y diseño de contenedores, de envío de imágenes y de creación de versiones de imágenes (entre otras cosas).

LXC vs Docker

Ventajas de los contenedores Docker

Modularidad

El enfoque Docker para la creación de contenedores se centra en la capacidad de tomar una parte de una aplicación, para actualizarla o repararla, sin necesidad de tomar la aplicación completa. Además de este enfoque basado en los microservicios, puede compartir procesos entre varias aplicaciones de la misma forma que funciona la arquitectura orientada al servicio (SOA).

Control de versiones

Cada archivo de imagen de Docker se compone de una serie de capas. Estas capas se combinan en una sola imagen. Una capa se crea cuando la imagen cambia. Cada vez que un usuario especifica un comando, como ejecutar o copiar, se crea una nueva capa.

Docker reutiliza estas capas para construir nuevos contenedores, lo cual hace mucho más rápido el proceso de construcción. Los cambios intermedios se comparten entre imágenes, mejorando aún más la velocidad, el tamaño y la eficiencia. El control de versiones es inherente a la creación de capas. Cada vez que se produce un cambio nuevo, básicamente, usted tiene un registro de cambios incorporado: control completo de sus imágenes de contenedor.

Restauración

Probablemente la mejor parte de la creación de capas es la capacidad de restaurar. Toda imagen tiene capas. ¿No le gusta la iteración actual de una imagen? Restáurela a la versión anterior. Esto es compatible con un enfoque de desarrollo ágil y permite hacer realidad la integración e implementación continuas (CI/CD) desde una perspectiva de las herramientas.

Rápida implementación

Solía demorar días desarrollar un nuevo hardware, ejecutarlo, proveerlo y facilitarlo. Y el nivel de esfuerzo y sobrecarga era extenuante. Los contenedores basados en Docker pueden reducir el tiempo de implementación a segundos. Al crear un contenedor para cada proceso, puede compartir rápidamente los procesos similares con nuevas aplicaciones. Y, debido a que un SO no necesita iniciarse para agregar o mover un contenedor, los tiempos de implementación son sustancialmente inferiores. Además, con la velocidad de implementación, puede crear y destruir la información creada por sus contenedores sin preocupación, de forma fácil y rentable. Por lo tanto, la tecnología Docker es un enfoque más granular y controlable, basado en microservicios, que prioriza la eficiencia.

Arquitectura de un Docker Host

Docker se basa en la arquitectura cliente-servidor, por lo que dado un Docker host (una máquina en la que instalamos Docker) tendríamos los siguientes componentes:

  • Docker Daemon: El servidor. El que gestiona los elementos de Docker *
  • Rest API: Una interfaz mediante la que se comunican el cliente y el servidor
  • Docker CLI: El cliente. Es el que utilizamos para enviar órdenes al servidor.
  • Componentes de Docker: Contenedores, imágenes, volúmenes y redes.
Arquitectura de Docker

El cliente no tiene porque estar ubicado en la misma máquina que el servidor

Instalación automatizada de Docker

Para sistemas linux basados en Debian (como Ubuntu) podemos automatizar con unas cuantas líneas la instalación de Docker y Docker Compose:

#! /bin/bash

apt-get update
apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io -y
apt-get install python-pip -y
pip install docker-compose 

En el siguiente vídeo se muestra la ejecución del script:

Imágenes

Una imagen es una especie de plantilla, una captura del estado de un contenedor. Los contenedores se lanzan a partir de una imagen. Una imagen es inmutable, no varía. Está compuesta por capas.

Las imágenes se encuentran almacenadas en los repositorios de Docker. Los cuales son accesibles vía web (hub.docker.com) o vía CLI (Interfaz de línea de comandos).

Tenemos dos tipos de imágenes:

  • Imágenes oficiales
  • Imágenes creadas por usuarios de la comunidad.
Búsqueda de imágenes oficiales en Docker Hub
Búsqueda de imágenes oficiales vía CLI

Capas

Como se ha comentado antes, una imagen es una captura del estado de un sistema, y está compuesta por capas.

Una imagen puede contener un número ilimitado de capas.

Tags

Las imágenes tienen etiquetas asociadas, y esto hace posible mantener un control de versiones de estas. Cuando se pretende obtener cierta versión de una imágen, se especifica su tag.

A modo de ejemplo, en la siguiente captura se oberva como nos referimos a distintas versiones de imágenes de Ubuntu.

Descargamos tres imágenes con tags distintas (distintas versiones) y por último visualizamos las imágenes descargadas

Si no se especifica ninguna etiqueta, por defecto se obtiene la etiqueta «latest», que hace referencia a la última versión de la imagen:

Vemos que no hay diferencia entre la tag «latest» y la tag «18.04», puesto ambas hacen referencia a la última versión existente de la imagen de Ubuntu

Vamos a ir viendo la utilidad de las etiquetas y comprendiéndolas mejor a lo largo de este artículo.

Comandos principales para la gestión de imágenes

$ sudo docker pull nombreImagen
Descargando una imagen
$ sudo docker images
Listando imágenes
$ sudo docker image inspect nombreImagen
Viendo los datos de una imagen
$ sudo docker rmi nombreImagen
Eliminando una imagen
$ sudo docker rmi $(sudo docker images -q)
Eliminando todas las imágenes descargadas

Creando imágenes personalizadas: Dockerfiles

Una de las cosas más interesantes de esta tecnología es la posibilidad de crear imágenes personalizadas, lo que da un sin fin de posibilidades. Más adelante dedicaré un artículo exclusivamente a este apartado, en el que crearé distintas imágenes para propósitos diferentes

Contenedores

¿Qué son?

Como se ha comentado en la introducción del artículo, podemos considerar los contenedores como máquinas virtuales extremadamente livianas y modulares, con una gran flexibilidad y que contienen solo un sistema de ficheros, una aplicación, y las librerías indispensables para su ejecución.

Comandos principales para la gestión de contenedores

Para la gestión de contenedores, existen multitud de comandos y opciones, de las cuales vamos a ver las más comunes.

Empezamos por el más obvio: «docker run». Este comando nos permite lanzar contenedores, y es uno de los que más opciones y parámetros puede recibir. Sin embargo, el único obligatorio es la imagen a partir de la cuál queremos lanzar el contenedor.

Este comando tiene la siguiente sintáxis:

$ sudo docker run [opciones] imagen[:tag] [comandos] [argumentos]

Las opciones de este comando son:

Opciones del comando «docker run»

Para ver la documentación detallada de este y todos los demás comandos podemos ejecutar:

$ man comando

A continuación un ejemplo:

man docker images

A continuación detallamos las opciones del comando docker run más comunes:

  • -i Modo interactivo. Permite interactuar con el contenedor. Debe ir acompañado de la opción «-t», la cual asocia un tty al contenedor.
  • -d Modo detached. Lanza el contenedor en segundo plano. Esto es útil para lanzar contenedores en los que van a corres servicios con los que no necesitamos interactuar directamente (tales como servidores web).
  • –name Para asignar un nombre al contenedor. Si no se especifica se asigna un nombre aleatorio.
  • -p ptoAnfitrión:ptoContenedor Para exponer un puerto. Se redirigirá el tráfico entre el puerto del host anfitrión y el puerto del contenedor especificados.
  • –ip Para asignar una dirección ip concreta al contenedor
  • –cpu-count Para establecer un número máximo de cpus a utilizar por el contenedor
  • -m Para establecer un máximo de memoria a consumir por el contenedor.
  • -e Para enviar variables de entorno al contenedor.
  • -h Para asignar un hostname al contenedor
$ sudo docker -it nombreContenedor
Lanzando un contenedor en modo interactivo
$ sudo docker run -dt nombreContenedor
Lanzando un contenedor en segundo plano
$ sudo docker run -dt --name nombreContenedor imagen
Asignando un nombre a un contenedor
$ sudo docker ps
Listando contenedores corriendo
$ sudo docker ps -a
Listando todos los contenedores (en cualquier estado)
$ sudo docker exec nombreContenedor comando
Ejecutando un comando en el contenedor desde el host anfitrión
$ sudo docker inspect nombreContenedor
Visualizando los metadatos de un contenedor
$ sudo docker run [opciones] -p puertoAnfitrión:puertoContenedor imagen
Exponiendo el puerto 80 de un contenedor
$ sudo docker logs nombreContenedor
Visualizando los logs de un contenedor
$ sudo docker stop nombreContenedor
Parando la ejecución de un contenedor
$ sudo docker start nombreContenedor
Iniciando la ejecución de un contenedor
$ sudo docker kill nombreContenedor
Matando un contenedor
$ sudo docker rm -f nombreContenedor
Eliminando un contenedor
$ sudo docker rm -f $(sudo docker ps -a -q)
Eliminando todos los contenedores existentes

Gestión del almacenamiento

Por defecto, todos los ficheros creados dentro de un contenedor se almacenan en una capa de contenedor «grabable». Esto significa que:

  • Los datos no persisten cuando ese contenedor deja de existir, y puede ser difícil obtener estos datos si otro proceso los necesita.
  • La capa de escritura de un contenedor está estrechamente acoplada a la máquina anfitriona donde se está ejecutando el contenedor. No es fácil mover los datos a otro lugar.
  • La escritura en la capa de escritura de un contenedor requiere un controlador de almacenamiento para gestionar el sistema de ficheros. Este driver supone una capa de abstracción adicional que reduce el rendimiento en comparación con el uso de volúmenes de datos, que se escriben directamente en el sistema de archivos del host anfitrión.

Docker tiene dos opciones para que los contenedores almacenen archivos en el equipo host, de modo que los archivos persistan incluso después de que el contenedor se detenga: volúmenes y bind mounts. Si ejecutamos Docker en Linux, también podemos usar un montaje tmpfs (método en el que los datos se escriben en la memoria del host anfitrión, por tanto no son persistentes, pero esto es útil en algunos casos).

TIpos de montaje

  • Volúmenes: se almacenan en una parte del sistema de ficheros del host anfitrión que administra Docker (/var/lib/docker/volumes/ en Linux). Los procesos que no son de Docker no deben modificar esta parte del sistema de ficheros. Los volúmenes son la mejor manera de mantener los datos en Docker.
  • Bind mounts: pueden almacenarse en cualquier parte del sistema de ficheros del host anfitrión, incluso pueden ser ficheros de sistema o directorios importantes. Los demás procesos del host anfitrión pueden modificarlos en cualquier momento.
  • Tmpfs mounts: sistemas de ficheros temporales que se almacenan en la memoria del host anfitrión. Son efímeros y no perduran tras la muerte de un contenedor.
Tipos de montaje

En el siguiente vídeo se muestra cómo persisten los datos tras la muerte de un contenedor usando volúmenes:

En el siguiente vídeo se muestra como varios contenedores pueden compartir un mismo volumen:

En el siguiente vídeo se muestra cómo podemos montar un directorio de nuestro sistema de ficheros en el sistema de ficheros del contenedor:

Redes

A la hora de crear aplicaciones con contenedores conectados entre si debemos usar las redes de Docker para poder comunicar los contenedores entre ellos. Imaginad por ejemplo que quiero crear un blog y para ello necesito una base de datos y un servidor de aplicaciones o algo parecido como mínimo. Podría crear dos contenedores, pero para que mi servidor/aplicación se conectase con la base de datos deberían poder verse. ¿Cómo podemos hacer eso? pues a través de las redes de Docker.

Comandos principales para la gestión de redes

$ docker network ls
Listando redes

Estas tres redes de la imagen anterior son las redes por defecto que crea docker, pudiendo nosotros crear las que queramos.

Docker nos crea una red host, esta es la red en la que se encuentra el anfitrión, es decir la propia máquina que corre Docker. Con esta red se gana velocidad a cambio de perder mucho en seguridad, por lo cual no recomiendo usarla para los contenedores. Personalmente pienso que si usamos contenedores para tener «contenida» la ejecución, hacerlo en la red del anfitrión no es una buena idea.

$ docker network inspect nombreRed
Viendo los datos de una red concreta
$ docker network create [parámetros] nombreRed
Creando una red personalizada
$ docker run [opciones] --network=nombreRed nombreContenedor
Conectando un contenedor a una red

Docker Compose

Docker Compose es una herramienta que permite simplificar el uso de Docker, generando scripts que facilitan el diseño y la construcción de servicios.  A continuación vemos un ejemplo de como levantar un wordpress utilizando Docker Compose:

version: '3.3'

services: # Servicio 1
   db:
     image: mysql:5.7 # Imagen del servicio 1
     volumes:
       - db_data:/var/lib/mysql # Persistencia de la base de datos
     restart: always
     environment: # Variables de entorno para la bd
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress: # Servicio 2
     depends_on:
       - db
     image: wordpress:latest # Imagen del servicio 2
     ports:
       - "8000:80" # Se expone el servicio por el pto 8000
     restart: always
     environment: # Variables de entorno para wordpress
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}

A continuación vemos la receta ejecutándose:

Docker Swarm

Docker Swarm es la aplicación nativa de Docker para la orquestación de contenedores. A continuación vemos un ejemplo para crear un servicio web utilizado esta herramienta:

Nodos que conformarán el clúster

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *