Introducción a Docker
Documento escrito por: Fernando Lucendo García
Puedes ver todos los documentos de este autor aquí.
¿Qué es Docker?
Docker es un software que permite la virtualización a nivel de sistema operativo, a diferencia de otros programs de virtualizaciones tiene una gran ventaja y es su función de contenedores. Es conveniente que comprendamos cuales son los Fundamentos de Orquestación y Operaciones IT que se realizan en la automatización y a la hora de lanzar servicios IT, para ello puedes visitar el enlace de un artículo anterior sobre estos conceptos aquí.
De esta manera Docker permite aislar en estos contenedores como si fuesen programas independientes dentro del mismo sistema operativo, aislando los recursos a nivel de kernel.
Usando:
- cgroups
- namespaces
Así pues, desde el propio sistema anfitrión se podrán acceder a los ficheros del sistema virtualizado pero no se podrán ver los ficheros del anfitrión desde el sistema virtualizado.
Los contenedores compartirán kernel y librerías que consumirán muy pocos recursos del sistema anfitrión.
Los sistemas operativos que se podrán lanzar deben ser Linux.
Este sistema es muy flexible y portable, ya que se podrá migrar fácilmente un sistema previamente configurado a cualquier otra máquina en pocos muy pocos minutos o incluso segundos.
Instalando Docker
- Necesitaremos un sistema operativo de 64 bits.
- Debemos leer detenidamente los pasos de instalación según nuestra distribución del sistema operativo, ya que cada uno necesitará unos comandos específicos. Puedes consultar la instalación para tu sistema operativo en este enlace.
En mi caso, mi distribución el Kali Linux por lo que debo seguir los pasos de instalación de Docker para una distribución basada en Debian. Para mi distribución los comandos a seguir serán los siguientes:
Actualizamos la lista de paquetes disponibles y sus versiones, pero no instala o actualiza ningún paquete. Esta lista la coge de los servidores con repositorios que tenemos definidos en el sources.list.
sudo apt update
Instalaremos la aplicación docker.io en el sistema, la opción
-y
sirve para confirmar la instalación de forma automática.sudo apt install -y docker.io
Habilitaremos el proceso docker con esta orden:
sudo systemctl enable docker --now
Podemos consultar el estado de docker para comprobar su correcta instalación:
sudo service docker status
Comprobamos la versión instalada de docker en nuestro sistema:
sudo docker version
Componentes Docker-Engine
Docker-engine es una aplicación basada en la arquitectura cliente-servidor y se compone de:
- El demonio de Docker, que será el que se ejecuta en 2º plano en nuestro sistema operativo, que ejecutará los contenedores.
- REST API, que permitirá la comunicación con el demonio de Docker.
- Interfaz de línea de comandos o CLI donde se ejecutarán los comandos Docker.
Docker registry
Este registro de Docker es un servicio que permitirá almacenar imágenes para poder transformar estas imágenes.
- El registro puede ser público o privado.
- El registro oficial de Docker donde se almacenan las imágenes oficiales está aquí.
Imágenes
Una imagen es una plantilla de solo lectura compuesta por un sistema de ficheros y parámetros listos para su ejecución. Estas imágenes están basadas en sistemas operativos Linux.
Una imagen sirve como un sistema operativo con una configuración preestablecida previamente que está lista para su ejecución. La plantilla no se ejecuta, si no que guardará la información del sistema operativo y su configuración, por lo tanto Docker no ejecutará la plantilla, si no que creará una instancia en una máquina virtual.
Gestión de imágenes
Mostrar las imágenes instaladas actualmente en nuestro sistema:
docker images
Mostrar el historial de cambios realizados en una imagen hasta el estado en el que se encuentra:
docker history id_imagen
Mostrar la descripción detallada de las características de la imagen:
docker inspect id_imagen
Guardar una imagen en nuestro sistema:
docker save tag_imagen
Cargar una imagen en nuestro sistema:
docker load tag_imagen
Borrar una imagen de nuestro sistema:
docker images id_imagen
Contenedores
Un contenedor es una instancia de una imagen. El sistema de ficheros del contenedor se encuentra dentro del propio sistema anfitrión, este sistema es similar a los jail root de Linux, aislando una porción del sistema operativo para la máquina virtual. Estos contenedores son máquinas virtuales que se podrán ejecutar, reiniciar, parar, etc.
Gestión de contenedores
Cuando hay un contenedor activo podemos agregarnos a ese contenedor con:
docker attach
Ejecutamos un comando en un contenedor:
docker exec
Inspeccionar las características detalladas de un contenedor:
docker inspect
Matar un contenedor:
docker kill
Ver el registro de los ficheros log de un contenedor:
docker log
Pausar un contenedor (pausamos la máquina virtual):
docker pause
Continuar un contenedor (continuamos la máquina virtual):
docker unpause
Establecemos un puerto para entrar en un contenedor:
docker port
Ver los contenedores activos en nuestro sistema, es decir, aquellos que estén corriendo:
docker ps
Renombrar un contenedor:
docker rename
Podremos gestionar la máquina virtual, iniciandola, parandola o reiniciandola:
docker start
docker stop
docker restart
Borrar un contenedor
docker rm
Para poder borrar un contenedor este debe estar parado previamente.
Ejecuta o instancia un contenedor a partir de una imagen, este comando será muy importante.
docker run
Ver el estado del contenedor:
docker stats
Ver la monitorización de un contenedor, su uso, rendimiento, etc.:
docker top
Actualizaremos un contenedor con respecto a su imagen:
docker update
Practicando la gestión de contenedores
Cuando vamos a lanzar una imagen, Docker buscará dicha imagen en nuestro sistema de forma local, al no encontrar la imagen hará un pull a un Docker registry en este caso Docker Hub y descargará la imagen de Ubuntu. Normalmente Ubuntu trabaja con un sistema de ficheros en el que la imagen principal puede considerarse una imagen base, como una primera instantanea del sistema operativo.
Conforme se vayan ejecutando comandos se agregarán capas adicionales sobre esa imagen base,
Ahora bien, habiendo descargado nuestra primera imagen podremos observar como al ejecutar el siguiente comando sudo docker images
aparecerán las imágenes disponibles en nuestro sistema. En mi caso como solo he descargado la de Ubuntu, sólo aparecerá esa.
Con esta información podremos consultar el historial de modificaciones realizadas sobre alguna imagen específica:
Podemos observar cómo sobre la imágen que hemos descargado de Ubuntu se han añadido algunas modificaciones y ficheros adicionales que quedarán refleados en el historial.
Cuando se necesita consultar cualquier dato sobre una imágen podemos hacerlo usando su IMAGE ID o su REPOSITORY, y en el caso de usar la primera opción, no es necesario escribir todo el ID completo, basta con introducir los 4 o 5 primeros dígitos de este ID, ya que serán suficientes para diferenciarlos del resto.
También podemos ejecutar el comando docker inspect
seguido del ID de la imagen que queremos consultar para poder ver la información detallada de esta imagen.
Ahora, si queremos eliminar una imagen, debemos borrar previamente los contenedores asociados a esta imagen.
Para poder verlos escribiremos sudo docker ps -a
, que nos mostrará todos los contenedores, tanto los activos como los que están parados.
Una vez tenemos el CONTAINER ID del contenedor que deseamos eliminar podemos hacerlo con el comando sudo docker rm id_contenedor
, de igual forma que en casos anteriores, con introducir los primeros 4 o 5 dígitos del ID será suficiente.
Cuando hayamos borrado el contenedor, podremos borrar también la imagen usando el comando sudo docker rmi
, en este caso en vez de usar el ID de la imagen he usado la otra opción, el repositorio.
Manejo de puertos
En Docker podemos "enlazar los puertos" de nuestra máquina física con el puerto que se especifique en para la máquina virtual de Docker".
Vamos a lanzar una imagen con el parámetro -d
para que se quede en 2º plano y en modo escucha y con el parámetro -p
o --port
, donde indicaremos el puerto donde escuchará en nuestra máquina, y seguidamente el puerto donde se redirigirá en el contenedor. Por último el comando python app.py
ejecutará la aplicación web que se abrirá dentro del contenedor de webpapp.
Una vez hecho esto, podremos comprobar que se ha creado el contenedor y su estado así como los puertos que usará con el comando docker ps -l
:
No obstante, podemos comprobar que lo que hemos hecho esté funcionando correctamente en nuestro sistema, para ello vamos a abrir un navegador web, en mi caso he usado Mozilla Firefox que viene por defecto en ya instalado en Kali Linux y pondremos en la barra de búsqueda: localhost:81
.
Si queremos ver el registro de la actividad que se ha llevado a cabo en el contenedor podemos consultarlo con el comando docker logs
seguido del ID del contenedor.
Construir imágenes
Para poder construir imágenes propias y añadir funcionalidades o modificaciones a la imágenes existentes necesitaremos realizar los siguientes pasos:
- Crear y configurar un fichero Dockerfile, donde meteremos a partir de una imagen base, algunos comandos o parámetros que modificarán esta imagen final o el comportamiento de la misma.
- Cuando ya se ha realizado el Dockerfile, el siguiente paso será construir la imagen con el comando
docker build
.
Practica sobre las imagenes
Vamos a lanzar una imagen que contiene un un servidor apache y php con una terminal asociada usando el parámetro -t
y en formato interactivo con el parámetro -i
seguido de /bin/bash
para poder usar dicha terminal.
Podemos consultar los apaquetes que trae instalados nuestra nueva imagen usando el comando dpkg -l
y podemos filtrar estos paquetes, buscando aquellos que tengan la palabra Apache:
Si vamos a construir una imagen con Docker a partir de otra imagen, cualquier comando que hayamos introducido después de arrancar el contenedor se introducirá junto con la creación de la imagen, algo que no queremos ya que hará que el proceso de ejecución de la imágen lleve más tiempo. Por lo tanto solo debemos introducir los comandos indispensables, ya que cada comando será una capa adicional.
Si por ejemplo queremos añadir algún paquete adicional a una imagen que hemos obtenido nosotros, podremos encadenar comandos para no acumular más "capas" innecesariamente.
Una vez hayamos realizado todos los cambios necesarios y añadido lo que queramos. Saldremos del contenedor con el comando exit
. A continuación podemos observar que el contenedor está en nuestro sistema ejecutando el comando sudo docker ps -l
Este comando nos devolverá un listado de los contenedores del sistema, con lo que obtendremos el ID necesario para el siguiente comando.
Con el comando sudo docker commit -m "Paquete passenger añadido" -a "Fernando Lucendo" 27fcbabfbaa1 falcon/apache-php-pack-passenger:latest
.
Confirmaremos los cambios (commit
) añadiendo un mensaje, normalmente indicando los cambios realizados, (-m "Mensaje"
) e indicaremos con el parámetro -a "autor"
el autor, seguidamente irá el ID del contenedor, un usuario registrado en Docker Hub y después de la /
se indicará el nombre que le pondremos a la imágen creada.
Si hemos confirmado los cambios en la imagen, podrmeos consultar la nueva imagen creada con el comando sudo docker images
:
Hecho todo lo anterior, ahora podremos lanzar nuestra propia imagen construida por nosotros mismos con las modificaciones realizadas sobre otra imagen. Para esto usaremos el comando de docker run
(con los parámetros que necesitemos, en mi caso pondre -t -i
) seguido de el repositorio y el tag.
Para comprobar los cambios realizados sobre una imagen en Docker podemos utilizar el comando sudo docker history id_imagen
. Como veremos en la siguiente imagen
Construir imagen con Dockerfile
Todo lo visto anteriormente es una de las formas de construir una imagen, pero normalmente, lo que se suele hacer es editar el fichero Dockerfile ya que a partir de aquí podremos añadir configuraciones y paquetes adicionales sin necesidad de estar dentro del contenedor.
Primero es recomendable crear un directorio (comando mkdir
) donde almacenar este fichero, en mi caso he decidido llamarlo docker-prueba, y después nos situamos en ese directorio (comandocd
):
Lo que buscamos hacer en esta opción es construir una imagen de Docker a través de un fichero Dockerfile, todo lo que hemos hecho en el caso anterior por comandos, pero ahora indicado en el Dockerfile, para ello crearemos un fichero nuevo con nuestro editor de texto favorito en Linux, en mi caso usaré nano:
sudo nano Dockerfile
Ahora podremos añadir las líneas que necesitamos para construir la imagen a partir de este fichero, en mi caso he añadido unos comentarios (#comentario
) en el propio fichero que explican que hace cada una de las líneas añadidas.
Es muy importante que a la hora de realizar una imagen a través de un fichero Dockerfile tengamos mucho cuidado con los comandos que metemos, ya que si son comandos interectivos, creación de la imagen se quedará colgada esperando una interacción, como por ejemplo, el uso del comando apt-get install
. Cuando usamos este comando la propia terminal nos pide una confirmación para la instalación, este paso lo podemos ahorrar introduciendo el parámetro -y
en el propio comando, quedando de la siguiente forma: apt-get install -y
.
Cuando hayamos elaborado el fichero Dockerfile lo guardaremos y podremos lanzarlo con el comando:
sudo docker build -t falcon/apache-php-passenger:latest .
El punto al final del comando anterior indica que el Dockerfile se encuentra en el directorio actual donde nosotros nos encontramos.
Finalmente si la imagen se lanzo y ejecutó correctamente, podremos visualizarla usando el comando visto en anteriores ocasiones: sudo docker images
Y de igual forma podremos ejecutar un nuevo contenedor con esta imagen como hicimos en el caso anterior, con el comando:
sudo docker run -t -i falcon/apache-php-pack-passenger:latest /bin/bash
DockerHub
Creando una cuenta en Docker Hub podremos subir nuestros propios repositorios la plataforma de Docker.
Los pasos a seguir para realizar esto son:
Tener creada una cuenta en Docker Hub
Para crear un repositorio:
- Iremos a Create repository.
- Rellenaremos nuestros datos (nombre, descripción, etc.)
- Escogeremos la visibilidad. En privada nos permitirá crear un solo repositorio con la cuenta Free.
- Le daremos a Create.
Para subir la imagen al repositorio:
Crear un tag de la imagen.
sudo docker tag id_imagen tag_imagen
Logearnos en Docker Hub a través de la terminal.
sudo docker login
Esto nos pedirá el nombre de usuario como Username y la contraseña como Password. Si el logueo se completa correctamente nos devolverá el mensaje Login Succeeded.
Subir la imagen.
sudo docker push nombre_repositorio_imagen
De esta forma habremos subido nuestras imagenes modificadas a nuestro perfil de Docker Hub y si están subidas de forma pública, cualquier persona podría ver, descargar y usar nuestras imágenes en sus contenedores.
Redes en Docker
Al instalar Docker, este creará por defecto 3 redes distintas:
Bridge. También conocida como interfaz puente, es la que se establece por defecto.
Esta red está representada en docker mediante la interfaz docker0. Podemos comprobar cómo se encuentra esta interfaz en nuestro equipo con el comando
ifconfig
Sus principales características son:
Aislan segmentos de red, por ejemplo, podemos crear una red bridge que tenga varios contenedores que se comuniquen entre sí, pero queden aislados del resto que no estén en esa red.
Se podrán añadir o eliminar contenedores de este segmento de red.
Los contenedores tienen comunicación entre sí si están en la misma red Bridge, pero no con otras redes.
Si un contenedor es añadido a varias redes Bridge se podrá comunicar con los miembros de las redes en las que esté.
Si un contenedor tiene varias redes brigde, podrá salir al exterior con la primera red no interna que encuentre en sus interfaces.
Una de las características especiales de las redes bridge es que permite hacer Link entre contenedores. De tal forma que:
No hace falta exponer los puertos de cada contenedor para que puedan contectarse entre si.
Gracias a Links los contenedores "descubren" los servicios de cada contenedor manteniendo una conexión segura.
Se necesita el uso de los nombres en los contenedores.
Se usarán unas variables de entorno para almacenar los datos recogidos.
Host. Usada para enlazar distintos contenedores en un mismo Host.
None. Usada para aislar al contenedor y dejarlo sin red.
Podemos ver las redes que tenemos en Docker usando el comando sudo docker network ls
:
Comandos para la gestión de red en Docker
Conectar un contenedor a una red:
sudo docker network connect
Desconectar un contenedor a una red:
sudo docker network disconnect
Crear una red nueva:
sudo docker network create
Inspeccionar las redes:
sudo docker network inspect
Listar las redes existentes:
sudo docker network ls
Borrar una red:
sudo docker network rm
Practicando con las redes de Docker
Vamos a crear una red de prueba de tipo bridge en nuestro equipo con el comando sudo docker create -d bridge network redprueba
siendo -d brige el tipo de red que queremos y red prueba el nombre asignado para esta red.
Ahora podremos comprobar que hemos añadido una nueva red usando el comando sudo docker network ls
Seguidamente podemos inspeccionar la red que acabamos de crear con el comando sudo docker inspect redprueba
. Veremos que se ha añadido una subred y puerta de enlace a esta red.
Es importante también ver como, de forma local en nuestro equipo se ha añadido una nueva interfaz de red que hace referencia a la nueva red añadida en Docker, que además ocupa el primer lugar en la lista de nuestras interfaces de red.
Con la nueva red creada, podemos añadirle esta nueva red junto a una de las imagenes que ya tenemos guardadas para así crear un contenedor con esta nueva red, para ello haremos lo siguiente:
Y podremos ver si el contenedor ha obtenido la red creada anteriormente inspeccionando dicho contenedor, si la dirección que ha obtenido y la puerta de enlace pertenecen a la red de redprueba siendo una red del tipo 172.18.0.0 entonces se habrá creado el contenedor con la nueva red correctamente
Primero debemos obtener el ID del contenedor con el comando sudo docker ps
, que listará todos los contenedores activos.
Y después con el comando sudo docker inspect id_contenedor
veremos la red que se le ha asignado a nuestro nuevo contenedor.
Ahora vamos a crear un 2º contenedor llamado web2 pero en este caso sin asignarlo a la misma red que web1.
Al no añadir este contenedor llamado web2 a la red creada anteriormente redprueba, no deberían tener conexión entre ellos, esto es algo que vamos a comprobar.
Si ahora ejecutamos el contenedor recién creado web2 podremos comprobar mediante un ping
que efectivamente no tendremos conectividad con el primer contenedor llamado web1 cuya dirección IP es 172.18.0.2.
Para poder tener conectividad y que ambos contenedores sean visibles entre si necesitamos que estén en la misma red, por lo que vamos a usar el comando visto al principio en el apartado de redes en Docker, sudo docker network connect redprueba web2
. Este comando añadirá al contenedor web2 la red que necesitamos (redprueba) para establecer la conexión entre ambos contenedores.
Almacenamiento en Docker
El almacenamiento en Docker se puede realizar de dos formas distintas:
Contenedor volumen de datos
Un contenedor volumen de datos es un tipo especial de volumen de datos que se crea y se gestiona dentro del contenedor en sí mismo. En lugar de persistir los datos en un host externo, los datos se almacenan en una ubicación dentro del contenedor. Esto significa que los datos solo están disponibles para el contenedor en el que se crearon y que se perderán cuando se elimine el contenedor.
Volumen de datos
Un volumen de datos es un mecanismo que permite a un contenedor acceder y almacenar datos fuera de su sistema de archivos raíz. Los volúmenes de datos se utilizan para persistir datos generados por un contenedor y compartirlos con otros contenedores, incluso si el contenedor original ya no existe.
Comandos para la gestión de Volumen de datos
Crear un nuevo volumen de datos:
sudo docker volume create
Inspeccionar un volumen de datos:
sudo docker volume inspect
Listar un volumen de datos:
sudo docker volume ls
Eliminar un volumen de datos:
sudo docker volume rm
Es importante revisar que no tengamos ningún volumen de datos ocupando espacio innecesariamente, cuando creamos contenedores se les asocia un volumen de datos que debe ser eliminado si no vamos a usar más ese contenedor.
Practicando con volumenes en Docker
En este apartado vamos a crear un nuevo contenedor usando el comando visto en las anteriores prácticas, con el parámetro -P
para exponer sus puertos, con nombre web3 y le asignaremos un volumen con el parámetro -v /volweb3
siendo /volweb3 el nombre del volumen asignado.
Una vez creado el nuevo volumen con su contenedor correspondiente podremos inspeccionar este contenedor y ver si efectivamente ha obtenido el volumen indicado.
Para ello usaremos el comando sudo docker inspect web3
También podemos comprobar que efectivamente el ID hexadecimal que nos ofrece el comando anterior es exactamente el mismo que nos devuelve la ejecución del comando sudo docker volume ls
, por lo que podemos ver que se creó el volumen de forma exitosa.
Practicando la compartición de directorios con contenedores Docker
La siguiente práctica será, compartir un directorio de nuestra máquina física (el anfitrión) con un contenedor.
¿Para qué puede ser útil?
Imaginemos que tenemos unos contenedores que usan un servidor web de NGINX y necesitamos tener una copia de los logs de estos contenedores para su posterior análisis.
En primer lugar vamos a crear un directorio en nuestro equipo anfitrión donde queremos que se copien estos ficheros de log.
sudo mkdir ~/logs_nginx
Usando el comando anterior se creará un directorio llamado logs_nginx en el directorio de inicio del usuario actual sin necesidad de especificar la ruta completa al directorio de inicio gracias al ~/
.
Entonces, ahora, podemos crear un nuevo contenedor asociandole como volumen el nuevo directorio creado llamado logs_nginx donde guardará una copia de los ficheros de log del servidor web NGINX. Aparte le hemos indicado con el parámetro -p 6000:80
que el puerto de nuestra máquina física 6000 se corresponda con el puerto 80 del contenedor.
Si se ha creado el nuevo contenedor correctamente, podremos ver cómo de primeras, ya sin hacer nada, el contenedor ha metido sus primeros ficheros logs en nuestro directorio local.
Pero no contentos con esto, queremos ver que efectivamente la máquina física guarda el contenido de los ficheros log. Para ello vamos a ejecutar el siguiente comando curl localhost:6000
Este comando hace una solicitud HTTP utilizando la herramienta de línea de comandos "curl" para hacer una solicitud GET al servidor local en el puerto 6000, que abrimos previamente para enlazarlo con el 80 del contenedor de NGINX.
El comando nos devuelve la página de inicio que tiene este servidor NGINX por defecto, y a continuación debería verse esta solicitud reflejada en el fichero log de nuestra máquina física.
Usando contenedores como almacenamiento
Crearemos un contenedor con un volumen llamado /tmp y cuyo nombre será contenedor_datos y usará la imagen de Ubuntu.
Recuerda que el comando sudo docker create
creará un contenedor a partir de una imagen, pero NO la ejecutará, mientras que el comando sudo docker run
hará lo mismo que el comando anterior pero este SI ejecutará el contenedor.
Dentro de este contenedor recién creado, (primero debemos lanzarlo) vamos a crear un archivo de prueba para comprobar que efectivamente los contenedores que se asocien a este volumen de almacenamiento tendrán los mismos ficheros.
Para esto vamos a usar el comando ya visto sudo docker run -t -i --volumes-from contenedor_datos ubuntu /bin/bash
, con este comando ejecutaremos el contenedor ya creado con el volumen indicado.
Nuestro fichero de prueba se llama ficheroprueba y contiene el texto Hola ZonaBit.
Ahora haremos un nuevo contenedor con la etiqueta --volumes-from contenedor_datos
que lo que hará será tomar el volumen que esté en otro contenedor, en este caso, el contenedor creado anteriormente llamado contenedor_datos.
Una vez hecho esto podemos comprobar que efectivamente los dos contenedores creados comparten el mismo volumen de datos.
Conclusión
En conclusión, Docker ha revolucionado la forma en que se gestionan las aplicaciones y los servicios en la nube, gracias a su capacidad de virtualizar a nivel de sistema operativo y su función de contenedores.
A través de su enfoque en la modularidad, la portabilidad y la escalabilidad, Docker ha permitido a los desarrolladores, administradores de sistemas y empresas de todos los tamaños crear y ejecutar aplicaciones y servicios de manera más rápida y eficiente. Además, la comunidad de Docker ha creado una gran cantidad de herramientas y recursos que permiten a los usuarios aprovechar al máximo esta tecnología innovadora.
En resumen, Docker ha cambiado la forma en que pensamos sobre la virtualización y la gestión de aplicaciones, y es una herramienta indispensable en el arsenal de cualquier profesional de TI que busque mejorar la eficiencia y la productividad en sus proyectos.