Docker Essentials: A Developer Introduction
Contenedores
Los contenedores no son más que un proceso (o un grupo de procesos) que se ejecutan de forma
aislada, lo que se consigue con los espacios de nombres y los grupos de control de Linux. Los
espacios de nombres de Linux y los grupos de control son características integradas en el núcleo
de Linux. Aparte del propio núcleo Linux, no hay nada especial en los contenedores. Lo que hace
que los contenedores sean útiles son las herramientas que los rodean. Los laboratorios en este
curso usan Docker, que ha sido la herramienta estándar entendida para usar contenedores para
construir aplicaciones. Docker proporciona a los desarrolladores y operadores una interfaz
amigable para construir, enviar y ejecutar contenedores en cualquier entorno.
Ejemplo. Corriendo el siguiente comando $ docker container run -it ubuntu top obtenemos los
siguiente:
Los contenedores utilizan los espacios de nombres de Linux para aislar los recursos del sistema de
otros contenedores o del host. El espacio de nombres PID proporciona aislamiento para los ID de
proceso. Si ejecuta top mientras está dentro del contenedor, notará que muestra los procesos
dentro del espacio de nombres PID del contenedor, que es muy diferente de lo que puede ver si
ejecuta top en el host. Lo cual se muestra a continuación:
Aunque estamos usando la imagen de Ubuntu, es importante notar que el contenedor no tiene su
propio kernel. Utiliza el kernel del host y la imagen de Ubuntu se utiliza sólo para proporcionar el
sistema de archivos y las herramientas disponibles en un sistema Ubuntu.
El contenedor que corre es el siguientes:
PID es sólo uno de los espacios de nombres de Linux que proporciona a los contenedores
aislamiento de los recursos del sistema. Otros espacios de nombres de Linux incluyen:
MNT: Montar y desmontar directorios sin afectar a otros espacios de nombres.
NET: Los contenedores tienen su propia pila de red.
IPC: Mecanismos de comunicación entre procesos aislados, como las colas de mensajes.
User: Vista aislada de los usuarios del sistema.
UTC: Establece el nombre de host y el nombre de dominio por contenedor.
Estos espacios de nombres proporcionan el aislamiento para los contenedores que les permite
ejecutarse juntos de forma segura y sin conflictos con otros contenedores que se ejecutan en el
mismo sistema.
Tip. Los espacios de nombres son una característica del kernel de Linux. Sin embargo, Docker te
permite ejecutar contenedores en Windows y Mac. El secreto es que el producto Docker incluye
un subsistema Linux. Docker ha abierto este subsistema Linux a un nuevo proyecto: LinuxKit.
Poder ejecutar contenedores en muchas plataformas diferentes es una de las ventajas de utilizar
las herramientas de Docker con contenedores.
Dockerfile
Una de las propiedades de diseño importantes de Docker es su uso del sistema de archivos de
unión. Considera el siguiente Dockerfile:
Cada una de estas líneas es una capa. Cada capa contiene sólo el delta, o los cambios de las capas
anteriores. Para poner estas capas juntas en un único contenedor en ejecución, Docker utiliza el
sistema de archivos de unión para superponer capas de forma transparente en una sola vista.
Cada capa de la imagen es de sólo lectura excepto la capa superior, que se crea para el
contenedor. La capa de lectura/escritura del contenedor implementa "copy-on-write", lo que
significa que los archivos que se almacenan en las capas de imagen inferiores se suben a la capa
de lectura/escritura del contenedor sólo cuando se realizan ediciones en esos archivos. Los
cambios se almacenan en la capa contenedora.
La función "copy- on-write" es muy rápida y, en casi todos los casos, no tiene un efecto notable
en el rendimiento. Puedes inspeccionar qué archivos se han subido al nivel del contenedor con el
comando docker diff.
Dado que las capas de imagen son de sólo lectura, pueden ser compartidas por imágenes y por
contenedores en ejecución. Por ejemplo, la creación de una nueva aplicación Python con su propio
Dockerfile con capas base similares compartirá todas las capas que tenía en común con la primera
aplicación Python.
También puedes ver cómo se comparten las capas cuando inicias varios contenedores desde la
misma imagen. Debido a que los contenedores utilizan las mismas capas de sólo lectura, puedes
imaginar que iniciar contenedores es muy rápido y tiene una huella muy baja en el host.
Puedes notar que hay líneas duplicadas en este último Dockerfile y en el primer Dockerfile
mostrado. Aunque este es un ejemplo trivial, puedes extraer líneas comunes de ambos
Dockerfiles en un Dockerfile base, al que luego puedes apuntar con cada uno de tus Dockerfiles
hijos utilizando el comando FROM.
La estructuración de imágenes habilita el mecanismo de almacenamiento en caché de Docker
para compilaciones y envíos. Para ver más de cerca las capas, puedes utilizar el comando $ docker
image history de la imagen Python que has creado.
Cada línea representa una capa de la imagen. Notarás que las líneas superiores coinciden con el
Dockerfile que creaste, y las líneas inferiores se extraen de la imagen Python padre. No te
preocupes por las etiquetas <missing>. Éstas siguen siendo capas normales; sólo que el sistema
Docker no les ha dado un ID.
Recuerda estos puntos clave:
Utiliza el Dockerfile para crear builds reproducibles para tu aplicación e integrar tu
aplicación con Docker en el pipeline CI/CD.
Las imágenes Docker pueden estar disponibles para todos tus entornos a través de un
registro central. El Docker Hub es un ejemplo de registro, pero puedes desplegar tu
propio registro en servidores que controles.
Una imagen Docker contiene todas las dependencias que necesita para ejecutar una
aplicación dentro de la imagen. Esto es útil porque ya no tienes que lidiar con la deriva
del entorno (diferencias de versión) cuando dependes de dependencias que están
instaladas en cada entorno en el que despliegas.
Docker utiliza el sistema de archivos de unión y "copy-on-write" para reutilizar capas de
imágenes. Esto reduce la huella de almacenamiento de imágenes y aumenta
significativamente el rendimiento de los contenedores de arranque.
Las capas de imágenes son almacenadas en caché por el sistema Docker build and push.
No hay necesidad de reconstruir o empujar de nuevo las capas de imagen que ya están
presentes en un sistema.
Cada línea en un Dockerfile crea una nueva capa, y debido a la caché de capas, las líneas
que cambian con más frecuencia, por ejemplo, la adición de código fuente a una imagen,
deben aparecer cerca de la parte inferior del archivo.
Comandos usados
Listar de los contenedores en ejecución y detenidos
$ docker container ls –al
Detener un contenedor en ejecución:
$ docker container stop [CONTAINER ID]
Elimine los contenedores:
$ docker system prune
ADVERTENCIA Esto eliminará:
todos los contenedores parados
todos los volúmenes no utilizados por al menos un contenedor
todas las redes no utilizadas por al menos un contenedor
todas las imágenes colgadas
Construir una imagen Docker:
$ docker image build -t [nombre de la imagen] [ubicación de dockerfike]
Inspeccionar el contenedor:
$ docker container exec -it [CONTAINER ID] bash
Listar imágenes:
$ docker image ls
Ejecute un servidor NGINX utilizando la imagen oficial de NGINX de Docker Hub:
$ docker container run --detach --publish 8080:80 --name [nombre para contenedor] [nombre de
imagen en docker hub]
Comprobar la salida de registro del contenedor:
$ docker container logs [container id]
Inicie sesión en la cuenta de registro de Docker:
$ docker login
Etiquetar imagen con tu nombre de usuario:
$ docker tag [nombre de la imagen] [dockerhub username]/ [nombre de la imagen]
Enviar su imagen al registro de Docker Hub:
$ docker push [dockerhub username]/ [nombre de la imagen]
Orquestación de Contenedores
Docker Swarm
Docker Swarm es la herramienta de orquestación integrada en el motor Docker. Un nodo consta
de un nodo manager y nodos workers. Los managers manejan comandos y gestionan el estado
del swarm. Los workers no pueden manejar comandos y simplemente se utilizan para ejecutar
contenedores a escala. Por defecto, los gestores también se utilizan para ejecutar contenedores.
Dado un clúster Swarm de nodos inicializados, desplegarás algunos contenedores. Para ejecutar
contenedores en un Docker Swarm, necesitas crear un servicio. Un servicio es una abstracción
que representa múltiples contenedores de la misma imagen desplegados a través de un clúster
distribuido. A continuación, se muestra un ejemplo:
Esta declaración de comando es declarativa, y Docker Swarm intentará mantener el estado
declarado en este comando a menos que sea cambiado explícitamente por otro comando de
servicio docker. Este comportamiento es útil cuando los nodos se caen, por ejemplo, y los
contenedores se reprograman automáticamente en otros nodos. El flag --mount es útil para que
NGINX imprima el nombre de host del nodo en el que se está ejecutando. El comando --publish
utiliza la malla de enrutamiento integrada del swarm. En este caso, el puerto 80 está expuesto en
cada nodo del swarm. La malla de enrutamiento dirigirá una solicitud que llegue por el puerto 80
a uno de los nodos que ejecutan el contenedor.
Nota: Aunque controlas el swarm directamente desde el nodo en el que se está ejecutando,
puedes controlar un Docker swarm de forma remota conectándote al motor Docker del manager
mediante la API remota o activando un host remoto desde tu instalación local de Docker
(utilizando las variables de entorno $DOCKER_HOST y $DOCKER_CERT_PATH). Esto será útil
cuando quieras controlar remotamente aplicaciones de producción, en lugar de usar SSH para
controlar directamente los servidores de producción.
Escalar servicio
Cuando se ejecuta este comando, se producen los siguientes eventos:
El estado del servicio se actualiza a 5 réplicas, que se almacena en el almacenamiento
interno del swarm.
Docker Swarm reconoce que el número de réplicas que está programado ahora no
coincide con el estado declarado de 5.
Docker Swarm programa 5 tareas (contenedores) más en un intento de alcanzar el estado
declarado para el servicio.
Este swarm está comprobando activamente si el estado deseado es igual al estado real e
intentará reconciliarlo si es necesario.
Después de unos segundos, debería ver que el swarm hizo su trabajo e inició con éxito 5
contenedores más. Observa que los contenedores están programados en los tres nodos del
clúster. La estrategia de colocación por defecto que se utiliza para decidir dónde se van a ejecutar
los nuevos contenedores es el nodo más vacío, pero eso se puede cambiar en función de sus
necesidades.
Límites de la malla de enrutamiento: La malla de enrutamiento sólo puede publicar un servicio en
el puerto 80. Si desea varios servicios expuestos en el puerto 80, puede utilizar un equilibrador
de carga de aplicaciones externo fuera del swarm para lograrlo.
Determine cuántos nodos necesita
En el laboratorio correspondiente, el clúster Docker Swarm consta de un nodo maestro y dos
nodos trabajadores. Esta configuración no es de alta disponibilidad. El nodo administrador
contiene la información necesaria para administrar el clúster, pero si este nodo se cae, el clúster
dejará de funcionar. Para una aplicación de producción, debe aprovisionar un clúster con
múltiples nodos gestores para permitir fallos en el nodo gestor. Debería tener al menos tres
nodos gestores, pero normalmente no más de siete. Los nodos gestores implementan el
algoritmo de consenso raft, que requiere que más del 50% de los nodos estén de acuerdo en el
estado que se almacena para el clúster. Si no se consigue un acuerdo superior al 50%, el swarm
dejará de funcionar correctamente. Por este motivo, tenga en cuenta la siguiente orientación
para la tolerancia a fallos de nodos:
Tres nodos gestores toleran un fallo de nodo.
Cinco nodos gestores toleran dos fallos de nodo.
Siete nodos gestores toleran tres fallos de nodo.
Es posible tener un número par de nodos gestores, pero no añade ningún valor en cuanto al
número de fallos de nodos. Por ejemplo, cuatro nodos gestores sólo tolerarán un fallo de nodo,
que es la misma tolerancia que un clúster de tres nodos gestores. Sin embargo, cuantos más
nodos gestores haya, más difícil será alcanzar un consenso sobre el estado de un clúster. Aunque
lo normal es limitar el número de nodos gestores a no más de siete, el número de nodos
trabajadores puede ser mucho mayor. Los nodos trabajadores pueden llegar a tener miles de
nodos. Los nodos trabajadores se comunican mediante el protocolo Gossip, optimizado para
funcionar bien con mucho tráfico y un gran número de nodos.
Resumen
Docker Swarm programa servicios utilizando un lenguaje declarativo. Usted declara el
estado, y el swarm intenta mantener y reconciliar para asegurarse de que el estado real
es igual al estado deseado.
Docker Swarm se compone de nodos gestores y nodos trabajadores. Sólo los gestores
pueden mantener el estado del swarm y aceptar órdenes para modificarlo. Los
trabajadores tienen una alta escalabilidad y sólo se utilizan para ejecutar contenedores.
Por defecto, los gestores también pueden ejecutar contenedores.
La malla de enrutamiento integrada en Docker Swarm significa que cualquier puerto que
se publique a nivel de servicio estará expuesto en todos los nodos del swarm. Las
solicitudes a un puerto de servicio publicado se enrutarán automáticamente a un
contenedor del servicio que se esté ejecutando en el swarm.
Puede utilizar otras herramientas para ayudar a resolver problemas con aplicaciones
orquestadas y en contenedores en producción, incluidos Docker Swarm e IBM Cloud
Kubernetes Service.
Comandos usados
Inicializar docker swarm en un nodo:
$ docker swarm init --advertise-addr eth0
Ver los nodos:
$ docker node ls
Despliegue de un servicio mediante NGINX:
$ docker service create --detach=true --name nginx1 --publish 80:80 --mount
source=/etc/hostname,target=/usr/share/nginx/html/[Link],type=bind,ro nginx:1.12
Inspeccionar servicios:
$ docker service ls
Comprobar el contenedor en ejecución del servicio:
$ docker service ps [nombre del servicio]
Actualizar servicio con un número actualizado de réplicas:
$ docker service update --replicas=5 --detach=true [nombre del servicio]
Comprobar logs agregados del servicio:
$ docker service logs [nombre del servicio]
Aplicar actualizaciones continuas:
$ docker service update --image [última versión de la imagen] --detach=true [nombre del servicio]
Abandonar un clúster swarm, situado en el nodo que queremos excluir:
$ docker swarm leave