Introducción a Docker
Docker
Docker es un motor open source de contenerización. Es una plataforma que permite construir, enviar y ejecutar cualquier aplicación en cualquier lugar. Resuelve uno de los problemas más costosos del software: el despliegue.
La figura Despliegues sin y con Docker
muestra la diferencia en los despliegues antes de usar Docker y con Docker.
Un contenedor Docker es un recipiente que contiene todo lo necesario para ejecutar el software de forma independiente. Puede haber múltiples contenedores Docker en una única máquina y los contenedores están completamente aislados unos de otros, así como de la máquina anfitrión.
Es decir, un contenedor Docker incluye un componente software, además de todas sus dependencias (binarios, librerías, ficheros de configuración, scripts, jars, etc.). El contenedor Docker tiene sus propios espacio de procesos e interfaz de red.
Características
Docker es útil para:
- Reemplazar máquinas virtuales
- Prototipado de software
- Empaquetado de software
- Habilitar una estructura de microservicios
- Modelado de redes
- Mejorar la productividad del desarrollo full-stack incluso en modo
offline - Facilitar la depuración
- Documentar las dependencias software
- Entrega continua (Continuous Delivery, CD)
Conceptos fundamentales
Los contenedores son sistemas en ejecución definidos por imágenes, estas imágenes están compuestas por una o más capas, más algunos metadatos de Docker.
Comandos principales
docker build
construye una imagen Dockerdocker run
ejecuta una imagen Docker como un contenedordocker commit
genera una imagen a partir de un contenedor Dockerdocker tag
etiqueta una imagen Docker
Imágenes y contenedores
Una imagen Docker es una colección de todos los ficheros que configuran una aplicación software. Cada cambio que se hace a la imagen original se almacena en una capa separada.
Podemos ver las imágenes como programas y los contenedores como procesos. Un contenedor es una instanciación de una imagen, de la misma forma que un objeto lo es de una clase.
Construcción de una aplicación Docker
Formas de crear una nueva imagen Docker
- De forma manual, mediante comandos Docker.
- Con Dockerfile.
- Con Dockerfile y una herramienta de gestión de la configuración.
- A partir de una imagen nula e importando un conjunto de ficheros.
Dockerfile
Un Dockerfile es un fichero de texto, al que se da el nombre Dockerfile
, con una serie de comandos en su interior.
Por ejemplo:
FROM node
MAINTAINER ian.miell@gmail.com
RUN git clone -q https://github.com/docker-in-practice/todo.git
WORKDIR todo
RUN npm install > /dev/null
EXPOSE 8000
CMD ["npm","start"]
Para construir una imagen Docker a partir del Dockerfile se ejecuta:
docker build .
Este comando devolverá un identificador de la imagen creado (por ejemplo, 66b76cea045f3). Se puede etiquetar la imagen, para que sea más fácil identificarla de esta forma:
docker tag 66b76cea045f3 todoapp
Ahora se puede ejecutar la imagen como un contenedor:
docker run -p 8000:8000 --name ejemplo1 todoapp
El contenedor se pararía con Ctrl-C
.
Se pueden ver todos los contenedores que se han lanzado y parado con:
docker ps -a
Y se puede reiniciar el contenedor (en background) con:
docker start ejemplo1
Con la siguiente instrucción vemos qué ficheros se han modificado desde que la imagen ha sido instanciada como un contenedor:
docker diff ejemplo1
Estratificación
La estratificación (Docker layering) ayuda a escalar contenedores. Si tuviéramos muchos contenedores en ejecución de la misma imagen y cada uno tuviera una copia de los ficheros que necesita el espacio en disco necesario sería muy grande.
Para solucionarlo, Docker por defecto utiliza un mecanismo copy-on-write, de forma que cuando un contenedor necesita escribir en un disco, graba el cambio copiándolo a una nueva área del disco. Cuando se hace un commit
esta nueva área del disco se congela y se graba como una nueva capa con su propio identificador.
La figura Copy-on-startup vs copy-on-write muestra la diferencia entre la copia de ficheros sin y con estratificación.
Arquitectura
La figura Arquitectura Docker muestra una visión general de la arquitectura de Docker
Docker se compone de dos partes en la máquina anfitrión:
- Un demonio con una API RESTful, y
- un cliente que habla con el demonio.
Invocamos al cliente para obtener información del demonio o bien para dar instrucciones al demonio. El demonio es un servidor que recibe peticiones y devuelve respuestas utilizando el protocolo HTTP.
El servidor recibe peticiones del cliente en línea de comandos o de cualquier otro autorizado a conectarse. El demonio también es responsable de ocuparse de las imágenes y de los contenedores en segundo plano, mientras que el cliente actúa como intermediario entre nosotros y la API RESTful.
El registro privado es un servicio que almacena imágenes Docker. Estas imágenes pueden solicitarse por cualquier demonio Docker que tenga acceso. Este registro está en una red interna y no es públicamente accesible.
El demonio Docker también puede solicitar imágenes de internet, si se le pide. Estas imágenes se pueden solicitar a Docker Hub o a otros registros públicos de imágenes.
El demonio Docker
El demonio docker se para y se arranca mediante:
service docker [stop|start]
o en sistemas basados en arranque systemctl
:
systemctl [stop|start] docker
Registros Docker
Los registros Docker se utilizan cuando queremos compartir nuestras imágenes con otros.
Un registro Docker permite hacer push
y pull
de imágenes desde un almacenamiento común utilizando una API RESTful.
<!--chapter:end:02-arq.Rmd-->
Docker para el desarrollador
Docker como máquina virtual
Una máquina virtual (VM - Virtual Machine) es una aplicación que emula un ordenador, habitualmente para ejecutar un sistema operativo y aplicaciones. Para el usuario final la experiencia es que está en una máquina física.
Docker no es una máquina virtual, no simula el hardware de la máquina y no incluye un sistema operativo. Un contenedor Docker por defecto no está restringido a un hardware específico. Si Docker virtualiza algo, virtualiza el entorno en el que los servicios corren, no la máquina. Docker no puede ejecutar de forma sencilla software Windows (o cualquier software no escrito para sistemas operativos Unix).
Sin embargo, Docker se puede utilizar desde cierto punto de vista como una máquina virtual, principalmente para tareas de desarrollo y pruebas.
Convertir una máquina virtual en contenedor
Para hacerlo se construye un fichero TAR
del sistema de ficheros de la máquina virtual utilizando, por ejemplo, qemu-nbd
sobre ssh
y se utiliza el comando ADD
en un Dockerfile para añadir el TAR
al /
de una imagen vacía.
FROM scratch
ADD img.tar /
Diferencias entre Máquinas virtuales y contenedores Docker
- Docker está orientado a las aplicaciones, mientras que las máquinas
virtuales están orientadas a sistemas operativos. - Los contenedores Docker comparten sistema operativo con otros
contenedores Docker. Las Máquinas virtuales tienen su propio sistema
operativo gestionado por un hipervisor. - Los contenedores Docker están diseñados para ejecuta un único
proceso principal, no gestionar múltiples conjuntos de procesos.
Guardando y recuperando el trabajo
commit
Para guardar los cambios de un contenedor se utiliza docker commit
, que convierte el contenedor en una imagen.
Cuidado
No se guarda todo el estado del contenedor. Al hacer commit se guarda el estado del sistema de ficheros, no de los procesos.
Tampoco se guarda ninguna referencia externa al contenedor, como bases de datos o volúmenes Docker.
tag
Cuando se hace commit
de un contenedor, Docker nos devuelve un identificador aleatorio de la imagen que debemos recordar. Con
commit tag
se puede poner un nombre a dicha imagen.
Persistencia
Volúmenes
Los volúmenes son el mecanismo de Docker para gestionar ficheros fuera del ciclo de la vida del contenedor. Esto es útil cuando se tienen ficheros de gran tamaño -por ejemplo, los de una base de datos- a los que se quiere acceder desde varios contenedores distintos.
Al ejecutar un contenedor se le puede indicar que use volúmenes de la siguiente forma:
docker run -v <ruta_ficheros_contenedor>:<ruta_externa> ...
por ejemplo:
docker run -v /var/db/tables:/var/data1 -it debian bash
Notas
- Tanto la ruta interna como la externa se creará si no existe.
- Hay que tener mucho cuidado con mapear una ruta ya existente.
- Los volúmenes no persisten en los Dockerfiles. Si se añade un
volumen y se hacen cambios a ese directorio dentro del Dockerfile,
esos cambios no serán persistentes en la imagen resultante.
Contenedores de datos
Una forma de gestionar los volúmenes en el caso de que solo queramos que estén accesibles por Docker es utilizando el patrón de diseño contenedor de solo datos data-only container1.
La solución consiste en configurar un contenedor de datos y usar el parámetro --volumes-from
cuando se ejecuten otros contenedores y se quieran ver esos datos.
El contenedor de solo datos no es necesario que esté en ejecución, es suficiente con que exista, que se haya ejecutado alguna vez en el host y que no se borre.
Inspeccionando contenedores e imágenes
Con docker inspect
se tiene acceso a los metadatos del contenedor Docker en formato JSON. Se pueden inspeccionar tanto contenedores como imágenes.
Construir imágenes
Añadir ficheros
La forma más sencilla es con el comando ADD
en un Dockerfile.
Por ejemplo:
FROM debian
RUN mkdir -p /opt/libeatmydata
ADD my.tar.gz /opt/libeatmydata/
RUN ls -lRt /opt/libeatmydata
Este Dockerfile se ejecutaría con docker build --no-cache
y construiría una imagen que partiría de la imagen de debian añadiéndole
los ficheros de my.tar.gz
.
Configuración
Cómo recuperar un Dockerfile de una imagen
Si tenemos una imagen Docker, pero no disponemos del Dockerfile asociado, se puede recuperar información sobre los pasos de construcción de la imagen mediante docker history
.
Reduciendo el esfuerzo de configuración en Dockerfile
Para reducir el esfuerzo que supone la gestión de la configuración con Dockerfiles se puede utilizar Chef Solo
o Puppet
.
Reducir el tamaño de las imágenes
El tamaño de las imágenes se puede reducir de la siguiente forma:
- Tomar una imagen base menor. Cuando se utiliza un sistema operativo
como imagen base, en lugar de utilizar el sistema operativo completo
buscar una imagen reducida, por ejemplo BusyBox o Alpine (y, si
fuera necesario, incluir las utilidades necesarias). - Limpiar la imagen resultante, borrando paquetes e información (logs
y documentación, principalmente) no útil de la imagen.
<!--chapter:end:03-desarrollo.Rmd-->
Docker y DevOps
Integración continua
Docker Hub incluye la característica de construcción automática. Si se enlaza a un repositorio Git que contenga un Dockerfile, el Docker Hub gestionará el proceso de construir una nueva imagen con cada cambio en el repositorio y hacerla disponible para descargar.
También se pueden utilizar Selenium y Jenkins como contenedores para ayudar a la integración continua.
Entrega continua
En la figura Entrega continua
se muestra el flujo habitual de la entrega continua con imágenes Docker.
Simulación de red
Cada vez que Docker crea un contenedor también crea un interfaz virtual de red, de esta forma todos los contenedores tendrán diferente IP y podrán hacer ping
unos a otros.
Para simular una red y gestionar de forma sencilla varios contenedores se utiliza la Orquestación de contenedores. Una forma muy sencilla de lograr la orquestación es con el uso de Docker Compose2.
Los distintos contenedores se pueden descubrir a través de la red vía DNS con Resorvable.
Para simular todo tipo de errores en la red se pueden utilizar las utilidades Comcast y Blockade
Docker y redes virtuales
Docker consigue el aislamiento de sus contenedores en la red de dos formas:
- Sandbox individual. Cada contenedor tiene su propia IP y su
conjunto de puertos - Sandbox en grupo. Todos los contenedores se agrupan conjuntamente
en una red privada, lo que permite hacer pruebas de red sin
interferir con la red de la máquina host.
<!--chapter:end:04-devops.Rmd-->
Docker en producción
Orquestación de contenedores
La figura Herramientas de orquestación
muestra las diferentes herramientas disponibles en el ecosistema Docker para orquestación.
Herramientas de orquestación
Seguridad
Los requisitos de seguridad cuando se ejecutan contenedores Docker son los mismos a cuando se instalan paquetes. Además el usuario root del contenedor es el mismo usuario root del host.
Debug
Para hacer debug de los contenedores Docker se pueden utilizar los comandos:
docker ps
docker top
docker stats
docker pause
ydocker unpause
docker logs
Referencias
- Docker in Practice (2016). Miell, I. and Sayers, A. H. Manning.
- Learning Docker (2015). Raj, P., Chelladhurai, J. S., and Singh, V. . Packt Publishing.
1. Parece ser que los contenedores de solo datos están obsoletos y
hoy en día se consideran un antipatrón.
2. Actualmente está deprecado, usándose en su lugar orquestadores de
contenedores como Kubernetes.