Aumento de la implementación de software: un tutorial de Docker Swarm

Publicado: 2022-03-11

A menos que haya estado viviendo dentro de un contenedor de envío, probablemente haya oído hablar de los contenedores. La industria se ha alejado claramente de la infraestructura persistente a la efímera, y los contenedores están en el medio de ese movimiento. La razón es bastante simple: si bien los contenedores ciertamente ayudan a los equipos de desarrollo a ponerse en marcha rápidamente, tienen aún más potencial para cambiar por completo la cara de las operaciones.

Pero, ¿cómo se ve eso exactamente? ¿Qué sucede cuando está listo para dar el salto y ejecutar contenedores localmente o manualmente en algunos servidores? En un mundo ideal, solo desea lanzar su aplicación en un grupo de servidores y decir "¡ejecútelo!"

Bueno, afortunadamente, ahí es donde estamos hoy.

En este artículo, exploraremos qué es Docker Swarm, junto con algunas de las excelentes características que tiene para ofrecer. Luego, veremos cómo se ve realmente el uso del modo Swarm y la implementación en un enjambre, y terminaremos con algunos ejemplos de cómo son las operaciones diarias con un enjambre implementado. Definitivamente se recomienda un conocimiento básico de Docker y contenedores, pero primero puede consultar esta excelente publicación de blog si es nuevo en contenedores.

¿Qué es Docker Swarm?

Logotipo del modo Docker Swarm

Antes de sumergirnos en la creación e implementación de nuestro primer enjambre, es útil tener una idea de qué es Docker Swarm. Docker en sí ha existido durante años, y la mayoría de las personas hoy en día lo consideran un tiempo de ejecución de contenedores. Sin embargo, en realidad, Docker se compone de muchas piezas diferentes, todas trabajando juntas. Por ejemplo, esa porción del tiempo de ejecución del contenedor es manejada por dos componentes más pequeños, llamados runC y containerd. A medida que Docker evolucionó y se devolvió a la comunidad, descubrieron que crear estos componentes más pequeños es la mejor manera de crecer y agregar funciones rápidamente. Como tal, ahora tenemos SwarmKit y el modo Swarm, que está integrado directamente en Docker.

Docker Swarm es un motor de orquestación de contenedores. En un nivel alto, necesita múltiples Docker Engines ejecutándose en diferentes hosts y le permite usarlos juntos. El uso es simple: declare sus aplicaciones como pilas de servicios y deje que Docker se encargue del resto. Los servicios pueden ser cualquier cosa, desde instancias de aplicaciones hasta bases de datos o utilidades como Redis o RabbitMQ. Esto debería sonarle familiar si alguna vez ha trabajado con docker-compose en desarrollo, ya que es exactamente el mismo concepto. De hecho, una declaración de pila es literalmente solo un archivo docker-compose.yml con la sintaxis de la versión 3.1. Esto significa que puede usar una configuración de composición similar (y en muchos casos idéntica) para el desarrollo y la implementación de enjambres, pero me estoy adelantando un poco aquí. ¿Qué sucede cuando tiene instancias de Docker en modo Swarm?

El modo Docker Swarm agrupa los servicios en pilas.

No te caigas de la balsa

Tenemos dos tipos de nodos (servidores) en el mundo Swarm: administradores y trabajadores. Es importante tener en cuenta que los gerentes también son trabajadores, solo tienen la responsabilidad adicional de mantener las cosas en funcionamiento. Cada enjambre comienza con un nodo administrador designado como líder. A partir de ahí, solo es cuestión de ejecutar un comando para agregar nodos al enjambre de forma segura.

Swarm tiene una alta disponibilidad gracias a su implementación del algoritmo Raft. No entraré en demasiados detalles sobre Raft porque ya hay un excelente tutorial sobre cómo funciona, pero esta es la idea general: el nodo líder está constantemente comprobando con sus compañeros nodos administradores y sincronizando sus estados. Para que un cambio de estado sea "aceptado", los nodos administradores alcanzan un consenso, lo que sucede cuando la mayoría de los nodos reconocen el cambio de estado.

La belleza de esto es que los nodos administradores pueden caerse esporádicamente sin comprometer el consenso del enjambre. Si un cambio de estado llega a un consenso, sabemos que está garantizado que existirá en la mayoría de los nodos del administrador y persistirá incluso si el líder actual falla.

Digamos que tenemos tres nodos administradores llamados A, B y C. Por supuesto, A es nuestro líder intrépido. Bueno, un día, un error de red transitorio deja a A fuera de línea, dejando a B y C solos. Al no tener noticias de A en mucho tiempo (unos pocos cientos de milisegundos), B y C esperan un período de tiempo generado aleatoriamente antes de presentarse a la elección y notificar al otro. Por supuesto, el primero que se presente a la elección, en este caso, será electo. En este ejemplo, B se convierte en el nuevo líder y se restablece el quórum. Pero luego, un giro en la trama: ¿Qué sucede cuando A vuelve a estar en línea? Creerá que sigue siendo el líder, ¿verdad? Cada elección tiene un período asociado, por lo que A fue elegido en el período 1. Tan pronto como A vuelva a conectarse y comience a ordenar a B y C, amablemente le informarán que B es el líder del período 2, y Un testamento renunciará.

Este mismo proceso funciona a una escala mucho mayor, por supuesto. Puede tener muchos más de tres nodos de administrador. Sin embargo, agregaré otra nota rápida. Cada Swarm solo puede soportar un número específico de pérdidas de administrador. Un enjambre de n nodos de administrador puede perder (n-1)/2 administradores sin perder quórum. Eso significa que para un enjambre de tres gerentes, puede perder uno, para cinco, puede perder dos, etc. La razón subyacente de esto se remonta a la idea del consenso de la mayoría, y definitivamente es algo a tener en cuenta al pasar a la producción.

Programación y conciliación de tareas

Hasta ahora, hemos establecido que nuestros gerentes son realmente buenos para mantenerse sincronizados. ¡Genial! Pero, ¿qué están haciendo en realidad? ¿Recuerdas que dije que implementas una pila de servicios en Swarm? Cuando declara sus servicios, proporciona a Swarm información importante sobre cómo desea que se ejecuten sus servicios. Esto incluye parámetros como cuántas réplicas desea de cada servicio, cómo deben distribuirse las réplicas, si solo deben ejecutarse en ciertos nodos y más.

Una vez que se implementa un servicio, es trabajo de los administradores asegurarse de que se sigan cumpliendo los requisitos de implementación que establezca. Supongamos que implementa un servicio Nginx y especifica que debe haber tres réplicas. Los administradores verán que no se está ejecutando ningún contenedor y distribuirán uniformemente los tres contenedores entre los nodos disponibles.

Sin embargo, lo que es aún más genial es que si un contenedor falla (o un nodo completo se desconecta), Swarm creará automáticamente contenedores en los nodos restantes para compensar la diferencia. Si dice que desea que se ejecuten tres contenedores, tendrá tres contenedores en ejecución, mientras que Swarm se encarga de todos los detalles esenciales. Además, y esto es una gran ventaja, escalar hacia arriba o hacia abajo es tan fácil como darle a Swarm una nueva configuración de replicación.

Descubrimiento de servicios y equilibrio de carga

Quiero señalar un detalle importante pero sutil de ese último ejemplo: si Swarm está iniciando contenedores de manera inteligente en los nodos de su elección, no necesariamente sabemos dónde se ejecutarán esos contenedores. Eso puede sonar aterrador al principio, pero en realidad es una de las características más poderosas de Swarm.

Continuando con el mismo ejemplo de Nginx, imagine que le dijimos a Docker que esos contenedores deberían exponer el puerto 80. Si dirige su navegador a un nodo que ejecuta ese contenedor en el puerto 80, verá el contenido de ese contenedor. No hay sorpresa allí. Sin embargo, lo que puede ser sorprendente es que si envía su solicitud a un nodo que no está ejecutando ese contenedor, ¡aún verá el mismo contenido! ¿Que esta pasando aqui?

En realidad, Swarm está utilizando una red de entrada para enviar su solicitud a un nodo disponible que ejecuta ese contenedor y, al mismo tiempo, lo equilibra. Entonces, si realiza tres solicitudes al mismo nodo, es probable que acceda a los tres contenedores diferentes. Siempre que conozca la IP de un solo nodo en el enjambre, puede acceder a cualquier cosa que se ejecute en él. Por el contrario, esto le permite apuntar un balanceador de carga (como un ELB) a todos los nodos del enjambre sin tener que preocuparse por qué se está ejecutando y dónde.

No se detiene en las conexiones externas. Los servicios que se ejecutan en la misma pila tienen una red superpuesta que les permite comunicarse entre sí. En lugar de codificar direcciones IP en su código, simplemente puede usar el nombre del servicio como el nombre de host al que desea conectarse. Por ejemplo, si su aplicación necesita comunicarse con un servicio Redis llamado "redis", simplemente puede usar "redis" como nombre de host y Swarm enrutará su solicitud al contenedor apropiado. Y debido a que esto funciona a la perfección en el desarrollo con docker-compose y en producción con Docker Swarm, es una cosa menos de la que preocuparse al implementar su aplicación.

La malla de enrutamiento del modo Docker Swarm enruta las solicitudes a los contenedores adecuados, incluso cuando se ejecutan en diferentes nodos.

Actualizaciones continuas

Si está en operaciones, probablemente haya experimentado un ataque de pánico cuando una actualización de producción sale terriblemente mal. Podría ser una mala actualización del código, o incluso solo un error de configuración, ¡pero de repente la producción se cae! Lo más probable es que al jefe no le importe de ninguna manera. Solo van a saber que es tu culpa. Bueno, no te preocupes, Swarm también te respalda.

Al actualizar un servicio, puede definir cuántos contenedores deben actualizarse a la vez y qué debe suceder si los nuevos contenedores comienzan a fallar. Después de cierto umbral, Swarm puede detener la actualización o (a partir de Docker 17.04) revertir los contenedores a la imagen y la configuración anteriores. No te preocupes por tener que llevarle un café a tu jefe mañana por la mañana.

Seguridad

Por último, pero no menos importante, Docker Swarm viene con excelentes funciones de seguridad listas para usar. Cuando un nodo se une al enjambre, utiliza un token que no solo se verifica a sí mismo sino que también verifica que se está uniendo al enjambre que crees que es. A partir de ese momento, toda la comunicación entre nodos se realiza mediante encriptación TLS mutua. Este cifrado es aprovisionado y administrado automáticamente por Swarm, por lo que nunca tendrá que preocuparse por la renovación de certificados y otros problemas típicos de seguridad. Y, por supuesto, si quiere forzar la rotación de una clave, hay un comando para eso.

Docker Swarm es seguro por defecto.

La última versión de Docker Swarm también viene con gestión de secretos integrada. Esto le permite implementar de forma segura secretos, como claves y contraseñas, en los servicios que los necesitan, y solo en los servicios que los necesitan. Cuando proporciona un servicio con un secreto, los contenedores para ese servicio tendrán un archivo especial montado en su sistema de archivos que incluye el valor del secreto. No hace falta decirlo, pero esto es mucho más seguro que usar variables de entorno, que era el enfoque tradicional.

Buceo en el enjambre

Si eres como yo, ¡estás ansioso por saltar y probar todas estas funciones! Entonces, sin más preámbulos, ¡vamos a sumergirnos!

Aplicación de ejemplo Docker Swarm

Creé una aplicación Flask muy rudimentaria para demostrar el poder y la facilidad de usar Docker Swarm. La aplicación web simplemente muestra una página que le dice qué contenedor atendió su solicitud, cuántas solicitudes en total se han atendido y cuál es la contraseña de la base de datos "secreta".

Se divide en tres servicios: la aplicación Flask real, un proxy inverso Nginx y un almacén de claves Redis. En cada solicitud, la aplicación incrementa la clave num_requests en Redis, por lo que independientemente de la instancia de Flask que esté accediendo, verá reflejada la cantidad correcta de solicitudes.

Todo el código fuente está disponible en GitHub si desea "verificar" lo que está sucediendo.

¡Juega con Docker!

Siéntete libre de usar tus propios servidores a medida que avanzas en este tutorial, pero te recomiendo usar play-with-docker.com si solo quieres participar. Es un sitio administrado por algunos desarrolladores de Docker que te permite activar varios servidores en red nodos que tienen Docker preinstalado. Se cerrarán después de cuatro horas, ¡pero eso es suficiente para este ejemplo!

Crear un enjambre

¡Muy bien, aquí vamos! Continúe y cree tres instancias en PWD (play-with-docker) o active tres servidores en su servicio VPS (servidor privado virtual) favorito e instale el motor Docker en todos ellos. Tenga en cuenta que siempre puede crear una imagen y reutilizarla cuando agregue nodos en el futuro. No hay diferencia en cuanto al software entre un nodo administrador y un nodo trabajador, por lo que no necesita mantener dos imágenes diferentes.

¿Sigues girando? No te preocupes, esperaré. Bien, ahora vamos a crear nuestro primer administrador y nodo líder. En su primera instancia, inicialice un enjambre:

 docker swarm init --advertise-addr <node ip here>

Reemplace <node_ip_here> con la dirección IP de su nodo. En PWD, la dirección IP se muestra en la parte superior y, si está utilizando su propio VPS, siéntase libre de usar la dirección IP privada de su servidor siempre que sea accesible desde los otros nodos de su red.

¡Ahora tienes un enjambre! Sin embargo, es un enjambre bastante aburrido, ya que solo tiene un nodo. Avancemos y agreguemos los otros nodos. Notará que cuando ejecutó init , mostró un mensaje largo que explica cómo usar el token de unión. No vamos a usar ese porque haría que los otros nodos fueran trabajadores, y queremos que sean administradores. Obtengamos el token de unión para un administrador ejecutando esto en el primer nodo:

 docker swarm join-token manager

Copie el comando resultante y ejecútelo en su segundo y tercer nodo. ¡He aquí, un enjambre de tres nodos! Verifiquemos que todos nuestros nodos realmente existen. El comando docker node ls todos los nodos en nuestro enjambre. Debería ver algo como esto:

 $ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS su1bgh1uxqsikf1129tjhg5r8 * node1 Ready Active Leader t1tgnq38wb0cehsry2pdku10h node3 Ready Active Reachable wxie5wf65akdug7sfr9uuleui node2 Ready Active Reachable

Observe cómo nuestro primer nodo tiene un asterisco junto a la ID. Eso simplemente nos dice que ese es el nodo al que estamos conectados actualmente. También podemos ver que este nodo es actualmente el líder, y los otros nodos son accesibles si algo le sucediera.

¡Tómese un momento para apreciar lo fácil que fue e implementemos nuestra primera aplicación!

¡Envíalo!

¡Recién llegado, el equipo de desarrollo comercial le prometió a un cliente que su nueva aplicación se implementaría y estaría lista en una hora! Típico, lo sé. Pero no temas, no necesitaremos tanto tiempo, ¡ya que fue construido usando Docker! Los desarrolladores tuvieron la amabilidad de prestarnos su archivo docker-compose :

 version: '3.1' services: web: image: lsapan/docker-swarm-demo-web command: gunicorn --bind 0.0.0.0:5000 wsgi:app deploy: replicas: 2 secrets: - db_password nginx: image: lsapan/docker-swarm-demo-nginx ports: - 8000:80 deploy: mode: global redis: image: redis deploy: replicas: 1 secrets: db_password: external: true

Lo desglosaremos en un momento, pero todavía no hay tiempo para eso. ¡Vamos a desplegarlo! Continúe y cree un archivo en su primer nodo llamado docker-compose.yml y rellénelo con la configuración anterior. Puede hacerlo fácilmente con echo "<pasted contents here>" > docker-compose.yml .

Normalmente, podríamos simplemente implementar esto, pero nuestra configuración menciona que usamos un secreto llamado db_password , así que vamos a crear rápidamente ese secreto:

 echo "supersecretpassword" | docker secret create db_password -

¡Genial! Ahora todo lo que tenemos que hacer es decirle a Docker que use nuestra configuración:

 docker stack deploy -c docker-compose.yml demo

Cuando ejecute este comando, verá que Docker crea los tres servicios que definimos: web , nginx y redis . Sin embargo, debido a que llamamos demostración a nuestra pila, nuestros servicios en realidad se llaman demo_web , demo_nginx y demo_redis . Podemos ver nuestros servicios en ejecución ejecutando el comando docker service ls , que debería mostrar algo como esto:

 $ docker service ls ID NAME MODE REPLICAS IMAGE PORTS cih6u1t88vx7 demo_web replicated 2/2 lsapan/docker-swarm-demo-web:latest u0p1gd6tykvu demo_nginx global 3/3 lsapan/docker-swarm-demo-nginx:latest *:8000->80/ tcp wa1gz80ker2g demo_redis replicated 1/1 redis:latest

¡Voila! Docker descargó nuestras imágenes en los nodos apropiados y creó contenedores para nuestros servicios. Si sus réplicas aún no están a plena capacidad, espere un momento y verifique nuevamente. Es probable que Docker todavía esté descargando las imágenes.

Ver es creer

Sin embargo, no confíe en mi palabra (o la palabra de Docker). Intentemos conectarnos a nuestra aplicación. Nuestra configuración de servicio le dice a Docker que exponga NGINX en el puerto 8000. Si está en PWD, debería haber un enlace azul en la parte superior de la página que dice "8000". ¡PWD ha detectado automáticamente que tenemos un servicio ejecutándose en ese puerto! Haga clic en eso y lo enrutará al nodo seleccionado en el puerto 8000. Si implementó sus propios servidores, simplemente navegue a una de las direcciones IP de sus servidores en el puerto 8000.

Será recibido con una pantalla bellamente diseñada que le brindará información básica:

Contenido servido por la pila creada

Tome nota de qué contenedor atendió su solicitud y luego actualice la página. Lo más probable es que haya cambiado. ¿Pero por qué? Bueno, le dijimos a Docker que creara dos réplicas de nuestra aplicación Flask y está distribuyendo solicitudes a ambas instancias. Acabas de golpear el otro contenedor la segunda vez. También notará que la cantidad de solicitudes aumentó porque ambos contenedores Flask se comunican con la única instancia de Redis que especificamos.

Siéntase libre de intentar acceder al puerto 8000 desde cualquier nodo. Seguirás siendo redirigido correctamente a la aplicación.

Desmitificando la Magia

En este punto, todo funciona y, con suerte, ¡encontró que el proceso fue indoloro! Echemos un vistazo más de cerca a ese archivo docker-compose.yml y veamos qué le dijimos realmente a Docker. En un nivel alto, vemos que hemos definido tres servicios: web , nginx y redis . Al igual que un archivo de redacción normal, proporcionamos a Docker una imagen para usar en cada servicio, así como un comando para ejecutar. En el caso de nginx , también especificamos que el puerto 8000 en el host debe asignarse al puerto 80 en el contenedor. Todo esto es sintaxis de composición estándar hasta ahora.

Lo nuevo aquí son las claves de despliegue y secretos. Estas claves son ignoradas por docker-compose , por lo que no afectarán su entorno de desarrollo, pero son utilizadas por docker stack . Veamos el servicio web. Bastante simple, le estamos diciendo a Docker que nos gustaría ejecutar dos réplicas de nuestra aplicación Flask. También informamos a Docker que el servicio web requiere el secreto db_password . Esto es lo que asegura que el contenedor tendrá un archivo llamado /run/secrets/db_password que contiene el valor del secreto.

Bajando a Nginx, podemos ver que el modo de implementación está configurado en global . El valor predeterminado (que se usa implícitamente en la web) es replicated , lo que significa que especificaremos cuántas réplicas queremos. Cuando especificamos global , le dice a Docker que cada nodo en el enjambre debe ejecutar exactamente una instancia del servicio. Ejecute docker service ls nuevamente, notará que nginx tiene tres réplicas, una para cada nodo en nuestro enjambre.

Finalmente, le indicamos a Docker que ejecute una sola instancia de Redis en algún lugar del enjambre. No importa dónde, ya que nuestros contenedores web se enrutan automáticamente cuando solicitan el host de Redis.

Uso de Swarm día a día

¡Felicitaciones por implementar su primera aplicación en Docker Swarm! Tomemos un momento para revisar algunos comandos comunes que usará.

Inspeccionando tu enjambre

¿Necesita verificar sus servicios? Pruebe docker service ls y docker service ps <service name> . El primero le muestra una descripción general de alto nivel de cada servicio, y el segundo le brinda información sobre cada contenedor que se ejecuta para el servicio especificado. Ese es particularmente útil cuando desea ver qué nodos están ejecutando un servicio.

Actualizaciones continuas

¿Qué pasa cuando estás listo para actualizar una aplicación? Bueno, lo bueno de la docker stack deploy es que en realidad también aplicará actualizaciones a una pila existente. Supongamos que ha enviado una nueva imagen de Docker a su repositorio. En realidad, puede ejecutar el mismo comando de implementación que usó la primera vez y su enjambre descargará e implementará la nueva imagen.

Por supuesto, es posible que no siempre desee actualizar todos los servicios de su pila. También podemos realizar actualizaciones a nivel de servicio. Supongamos que recientemente actualicé la imagen de mi servicio web. Puedo emitir este comando para actualizar todos mis contenedores web:

 docker service update \ --image lsapan/docker-swarm-demo-web:latest \ demo_web

Un beneficio adicional de ese comando es que aplicará una actualización continua si especificó que debería hacerlo en su configuración original. E incluso si no lo hizo, puede pasar indicadores para actualizar que le indicarán que realice una actualización continua de esta manera:

 docker service update \ --image lsapan/docker-swarm-demo-web:latest \ --update-parallelism 1 --update-delay 30s \ demo_web

Eso actualizará un contenedor a la vez, esperando 30 segundos entre actualizaciones.

Ampliación o reducción de los servicios

Tener dos contenedores web es genial, pero ¿sabes qué es mejor? Tener diez! Escalar los servicios hacia arriba y hacia abajo en un enjambre es tan fácil como:

 docker service scale demo_web=10

Ejecute ese comando y verifique la salida del docker service ps demo_web . Verá que ahora tenemos diez contenedores, y ocho de ellos se iniciaron hace un momento. Si está interesado, también puede volver a la aplicación web y actualizar la página varias veces para ver que ahora obtiene más de los dos ID de contenedor originales.

Eliminación de pilas y servicios

Sus pilas y servicios están implementados y escalados, ¡increíble! Pero ahora quieres desconectarlos. Esto se puede hacer con el comando rm respectivo. Para eliminar nuestra pila de demostración, ejecute el comando:

 docker stack rm demo

O, si prefiere eliminar un solo servicio, simplemente use:

 docker service rm demo_web

Nodos de drenaje

¿Recuerdas cuando docker node ls anteriormente para verificar los nodos en nuestro enjambre? Proporcionó un montón de información sobre cada nodo, incluida su disponibilidad . De forma predeterminada, los nodos están activos , lo que significa que son un juego justo para ejecutar contenedores. Sin embargo, hay ocasiones en las que es posible que deba desconectar temporalmente un nodo para realizar el mantenimiento. Claro, podría simplemente apagarlo y el enjambre se recuperaría, pero es bueno avisarle a Moby (la ballena Docker).

Aquí es donde entra en juego el drenaje de nodos. Cuando marca un nodo como Drain , Docker Swarm delegará los contenedores que se ejecutan en él a otros nodos y no iniciará ningún contenedor en el nodo hasta que cambie su disponibilidad de nuevo a Active .

Digamos que queremos drenar el node1 . Podemos ejecutar:

 docker node update --availability drain node1

¡Fácil! Cuando esté listo para que vuelva a funcionar:

 docker node update --availability active node1

Terminando

Como hemos visto, Docker junto con el modo Swarm nos permite implementar aplicaciones de manera más eficiente y confiable que nunca. Vale la pena mencionar que Docker Swarm no es de ninguna manera el único motor de orquestación de contenedores que existe. De hecho, es uno de los más jóvenes. Kubernetes ha existido por más tiempo y definitivamente se usa en más aplicaciones de producción. Dicho esto, Swarm es el desarrollado oficialmente por Docker, y están trabajando para agregar aún más funciones todos los días. Independientemente de cuál elija usar, ¡siga en contenedores!