Suponga que está ejecutando un servicio en un contenedor y hay una nueva versión del servicio disponible a través de su imagen acoplable. En tal caso, le gustaría actualizar el contenedor Docker.
Actualizar un contenedor acoplable no es un problema, pero actualizar un contenedor acoplable sin tiempo de inactividad es un desafío.
¿Confundido? Déjame mostrarte ambos caminos uno por uno.
Método 1:actualizar el contenedor docker a la última imagen (resulta en tiempo de inactividad)
Este método consta básicamente de estos pasos:
- Obtener la última imagen de la ventana acoplable
- Detenga y elimine el contenedor que ejecuta la imagen antigua de Docker
- Cree un nuevo contenedor con la imagen acoplable recién extraída
¿Quieres los comandos? Aquí tienes.
Enumere las imágenes de la ventana acoplable y obtenga la imagen de la ventana acoplable que tiene una actualización. Obtenga los últimos cambios en esta imagen usando el comando docker pull:
docker pull image_name
Ahora obtenga el ID del contenedor o el nombre del contenedor que ejecuta la imagen de la ventana acoplable más antigua. Use el comando docker ps para este propósito. Detener este contenedor:
docker stop container_ID
Y retire el contenedor:
docker rm container_id
El siguiente paso es ejecutar un nuevo contenedor con los mismos parámetros que usó para ejecutar el contenedor anterior. Creo que sabes cuáles son esos parámetros porque tú los creaste en primer lugar.
docker run --name=container_name [options] docker_image
¿Ves el problema con este enfoque? Debe detener el contenedor en ejecución y luego crear uno nuevo. Esto resultará en un tiempo de inactividad para el servicio en ejecución.
El tiempo de inactividad, aunque sea por un minuto, podría tener un gran impacto si se trata de un proyecto de misión crítica o un servicio web de alto tráfico.
¿Quiere conocer un enfoque mejor y más seguro para este problema? Lea la siguiente sección.
Método 2:actualizar el contenedor docker en una configuración de proxy inverso (con tiempo de inactividad cero o mínimo)
Si estaba buscando una solución sencilla, lamento decepcionarlo, pero no lo será porque aquí tendrá que implementar sus contenedores en una arquitectura de proxy inverso con Docker Compose.
Si busca administrar servicios críticos utilizando contenedores Docker, el método de proxy inverso lo ayudará mucho a largo plazo.
Permítanme enumerar tres ventajas principales de la configuración del proxy inverso:
- Puede implementar varios servicios públicos en el mismo servidor. Aquí no hay bloqueo de puertos.
- El servidor Let's Encrypt se encarga de la implementación de SSL para todos los servicios, todos los contenedores.
- Puede actualizar los contenedores sin afectar los servicios en ejecución (para la mayoría de los servicios web).
Si tiene curiosidad por obtener más información, puede consultar el glosario oficial de Nginx que destaca los usos comunes de un proxy inverso y cómo se compara con un balanceador de carga.
Tenemos un excelente tutorial detallado sobre cómo configurar el proxy inverso de Nginx para alojar más de una instancia de servicios web que se ejecutan en contenedores en el mismo servidor. Por lo tanto, no voy a discutirlo aquí de nuevo. Primero debe ir y configurar sus contenedores usando esta arquitectura. Confía en mí, esto vale la pena.
En este tutorial, he diseñado una metodología paso a paso que puede ser muy útil en sus actividades diarias de DevOps. Este requisito no solo puede ser muy necesario cuando actualiza sus contenedores, sino también cuando desea realizar un cambio muy necesario en cualquiera de sus aplicaciones en ejecución sin sacrificar un tiempo de actividad invaluable.
De aquí en adelante, asumimos que está ejecutando sus aplicaciones web bajo una configuración de proxy inverso que garantizaría que el redireccionamiento funcione para el nuevo contenedor actualizado como se esperaba después de los cambios de configuración que vamos a hacer.Primero mostraré los pasos de este método, seguidos de un ejemplo de la vida real.
Paso 1:actualice el archivo de composición de docker
En primer lugar, debe editar el archivo de composición de la ventana acoplable existente con el número de versión de la imagen más reciente. Se puede visualizar en Docker Hub, concretamente en el apartado de "etiquetas" de la aplicación.
Vaya al directorio de la aplicación y edite el archivo docker-compose con un editor de texto de línea de comando. Usé Nano aquí.
[email protected]:~/web-app$ nano docker-compose.yml
Dentro de
services:
, actualiceimage: web-app:x.x.x
con el número de versión más reciente y guarde el archivo.Quizás se pregunte por qué no usar la última etiqueta de todos modos en lugar de especificar el número de versión manualmente. Lo hice a propósito ya que me di cuenta de que, al actualizar los contenedores, puede haber un retraso intermitente de la última etiqueta en la selección de la última versión de la aplicación dockerizada. Cuando usa el número de versión directamente, siempre puede estar absolutamente seguro.
Paso 2:Ampliar un nuevo contenedor
Cuando usa el siguiente comando, se crea un nuevo contenedor basado en los nuevos cambios realizados en el archivo de redacción de la ventana acoplable.
[email protected]:~/web-app$ docker-compose up -d --scale web-app=2 --no-recreate
Tenga en cuenta que el contenedor anterior todavía está en funcionamiento. La
--scale
flag se utiliza para crear contenedores adicionales según lo especificado. Aquí,web-app
se ha establecido como el nombre del servicio para la aplicación web.Aunque especifique escalar hasta 2 contenedores,
--no-recreate
asegura que solo se agregue uno ya que ya tiene su antiguo contenedor ejecutándose.Para obtener más información sobre la
--scale
y--no-recreate
flag, consulte la página de documentación oficial de docker-compose up.Paso 3:Retire el contenedor antiguo
Después del paso 2, espere entre 15 y 20 segundos para que los nuevos cambios surtan efecto y luego elimine el contenedor anterior:
[email protected]:~/web-app$ docker rm -f old-web-app
En diferentes aplicaciones web, los cambios reflejados tienen un comportamiento diferente después de ejecutar el comando anterior (discutido como un consejo adicional al final de este tutorial).
Paso 4:Escale a la configuración de contenedor único como antes
Para el paso final, vuelva a escalar hacia abajo a la configuración de un solo contenedor:
[email protected]:~/web-app$ docker-compose up -d --scale web-app=1 --no-recreate
He probado este método con instancias de Ghost, WordPress, Rocket.Chat y Nextcloud. Excepto que Nextcloud cambia al modo de mantenimiento durante unos segundos, el procedimiento funciona muy bien para los otros tres.
El discurso, sin embargo, es otra historia y puede ser una excepción muy complicada en este caso debido a su modelo híbrido.
La conclusión es:cuanto más utilice la aplicación web la práctica estándar de la ventana acoplable al dockerizarla, más conveniente será administrar todos los contenedores de la aplicación web día a día.Ejemplo de la vida real:actualizar una instancia de Ghost en vivo sin tiempo de inactividad
Como prometí, les mostraré un ejemplo de la vida real. Le mostraré cómo actualizar Ghost que se ejecuta en el contenedor docker a una versión más nueva sin tiempo de inactividad.
Ghost es un CMS y lo usamos para el Manual de Linux. El ejemplo que se muestra aquí es lo que usamos para actualizar nuestra instancia de Ghost que ejecuta este sitio web.
Diga, tengo una configuración existente basada en una versión anterior ubicada en
/home/avimanyu/ghost
:version: '3.5' services: ghost: image: ghost:3.36 volumes: - ghost:/var/lib/ghost/content environment: - VIRTUAL_HOST=blog.domain.com - LETSENCRYPT_HOST=blog.domain.com - url=https://blog.domain.com - NODE_ENV=production restart: always networks: - net volumes: ghost: external: true networks: net: external: true
Tenga en cuenta que la configuración de composición de la ventana acoplable anterior se basa en una configuración de la ventana acoplable Nginx preexistente descrita aquí, que se ejecuta en una red llamada
net
. Su volumen docker también se había creado manualmente condocker volume create ghost-blog
.Cuando lo verifico con
docker ps
:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2df6c27c1fe3 ghost:3.36 "docker-entrypoint.s…" 9 days ago Up 7 days 2368/tcp ghost_ghost-blog_1 89a5a7fdcfa4 jrcs/letsencrypt-nginx-proxy-companion "/bin/bash /app/entr…" 9 days ago Up 7 days letsencrypt-helper 90b72e217516 jwilder/nginx-proxy "/app/docker-entrypo…" 9 days ago Up 7 days 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp reverse-proxy
En el momento de escribir este artículo, esta es una versión anterior de Ghost. ¡Es hora de actualizarlo a la última versión 3.37.1! Entonces, lo reviso en la sección de imágenes como:
version: '3.5' services: ghost-blog: image: ghost:3.37.1 volumes: - ghost-blog:/var/lib/ghost/content environment: - VIRTUAL_HOST=blog.domain.com - LETSENCRYPT_HOST=blog.domain.com - url=https://blog.domain.com - NODE_ENV=production restart: always networks: - net volumes: ghost-blog: external: true networks: net: external: true
Ahora, para darle un buen uso al método de escalado:
[email protected]:~/ghost$ docker-compose up -d --scale ghost-blog=2 --no-recreate
Con el comando anterior, el contenedor anterior no se ve afectado, pero se une uno nuevo con la misma configuración pero basado en la última versión de Ghost:
[email protected]:~/ghost$ docker-compose up -d --scale ghost-blog=2 --no-recreate Pulling ghost (ghost:3.37.1)... 3.37.1: Pulling from library/ghost bb79b6b2107f: Already exists 99ce436c3449: Already exists f7bdc31da5f5: Already exists 7a1300b9ff59: Already exists a495c68fa838: Already exists 6e362a39ec35: Already exists b68b4f3c36f7: Already exists 41f8b02d4a71: Pull complete 3ecc736ea4e5: Pull complete Digest: sha256:595c759980cd22e99037811397012908d89efb799776db222a4be6d4d892917c Status: Downloaded newer image for ghost:3.37.1 Starting ghost_ghost-blog_1 ... done Creating ghost_ghost-blog_2 ... done
Si hubiera usado el enfoque convencional con
docker-compose up -d
en cambio, no habría podido evitar que la recreación del contenedor existente se basara en la última imagen de Ghost.La recreación implica la eliminación del contenedor anterior y la creación de uno nuevo en su lugar con la misma configuración. Aquí es cuando ocurre el tiempo de inactividad y el sitio se vuelve inaccesible.
Esta es la razón por la que debe usar
--no-recreate
bandera mientras se amplía.Así que ahora tengo dos contenedores en ejecución basados en la misma configuración fantasma. Esta es la parte crucial donde evitamos el tiempo de inactividad:
[email protected]:~/ghost$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f239f677de54 ghost:3.37.1 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 2368/tcp ghost_ghost-blog_2 2df6c27c1fe3 ghost:3.36 "docker-entrypoint.s…" 9 days ago Up 7 days 2368/tcp ghost_ghost-blog_1 89a5a7fdcfa4 jrcs/letsencrypt-nginx-proxy-companion "/bin/bash /app/entr…" 9 days ago Up 7 days letsencrypt-helper 90b72e217516 jwilder/nginx-proxy "/app/docker-entrypo…" 9 days ago Up 7 days 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp reverse-proxy
Tenga en cuenta que el nombre del contenedor anterior es
ghost_ghost-blog_1
. Verifique su nombre de dominio y encontrará que todavía está accesible en blog.domain.com . Si actualiza el panel de administración de Ghost, que se encuentra en blog.domain.com/ghost después de escalar, seguirá intentando cargarse hasta que elimine el contenedor anterior:[email protected]:~/ghost$ docker rm -f ghost_ghost-blog_1
Pero para el blog de Ghost en sí, ¡no hay tiempo de inactividad en absoluto! Por lo tanto, de esta manera puede asegurarse de que no haya tiempo de inactividad al actualizar los blogs de Ghost.
Finalmente, reduzca la configuración a su configuración original:
[email protected]:~/ghost$ docker-compose up -d --scale ghost-blog=1 --no-recreate Starting ghost_ghost-blog_2 ... done
Como se mencionó anteriormente, después de eliminar los contenedores antiguos, los cambios se reflejan en las respectivas aplicaciones web, pero obviamente se comportan de manera diferente debido a los diversos diseños de aplicaciones.
Aquí hay algunas observaciones:
En WordPress :Asegúrese de agregar define( 'AUTOMATIC_UPDATER_DISABLED', true ); como línea inferior en el archivo wp-config.php ubicado en /var/www/html y monte /var/www/html/wp-content en lugar de /var/www/html como volumen. Verifique aquí para más detalles. Después del paso 3, el panel de administración de WordPress mostrará que su WordPress está actualizado y le pedirá que continúe y actualice su base de datos. La actualización ocurre rápidamente sin ningún tiempo de inactividad en el sitio de WordPress y ¡eso es todo!
En Rocket.Chat :La
Admin>Info
puede tardar entre 15 y 20 segundos. página para mostrar que está ejecutando la última versión incluso antes de realizar el paso 3. ¡No más tiempo de inactividad!En Nextcloud :Después del paso 2, Nextcloud cambiaría al modo de mantenimiento durante unos segundos y luego cargaría sus archivos nuevamente. En
Administration > Overview > Security & setup warnings
, es posible que reciba una advertencia como "Su servidor web no está configurado correctamente para resolver "./well-known/carddav". Esto se debe a que su antiguo contenedor aún se está ejecutando. Una vez que lo elimine con el paso 3, esta advertencia ya no existe. Asegúrese de darle algo de tiempo antes de acceder a su URL de Nextcloud, ya que podría mostrar un error de puerta de enlace incorrecta 502 hasta que su contenedor Nginx vea el contenedor Nextcloud recién escalado según la última versión.Consejos adicionales
Aquí hay algunos consejos y cosas a tener en cuenta al seguir este método.
Consejo 1
Para mantener el tiempo de inactividad en un mínimo de cero en diferentes aplicaciones, asegúrese de proporcionar suficiente tiempo a los contenedores recién escalados y actualizados para que los contenedores de Nginx finalmente puedan reconocerlos antes de eliminar los antiguos en el paso 2.
Antes de pasar al paso 2 descrito anteriormente, es recomendable observar el comportamiento de su aplicación en su navegador (tanto como una página actualizada después de iniciar sesión o como un nuevo acceso a la página en una ventana privada del navegador sin caché) después de realizar el paso 1.
Dado que cada aplicación está diseñada de manera diferente, esta medida resultaría muy útil antes de derribar sus contenedores antiguos.
Consejo 2
Si bien este puede ser un método muy ingenioso para actualizar sus contenedores a las últimas versiones de las aplicaciones que ejecutan, tenga en cuenta que puede usar el mismo método para realizar cambios de configuración o modificar la configuración ambiental sin enfrentar problemas de tiempo de inactividad.
Esto puede ser crucial si debe solucionar un problema o realizar un cambio que considere necesario en un contenedor en vivo, pero no desea que se derrumbe mientras lo hace. Después de realizar los cambios y estar seguro de que el problema se solucionó, puede eliminar el anterior con facilidad.
Nosotros mismos nos enfrentamos a esto cuando descubrimos que la rotación de registros no estaba habilitada en uno de nuestros contenedores activos. Hicimos los cambios necesarios para habilitarlo y, al mismo tiempo, evitamos cualquier tiempo de inactividad mientras lo hacíamos con este método.
Consejo 3
Si ha nombrado específicamente su contenedor en el archivo YML usando
container_name
, el método no funcionará como se esperaba porque implica la creación de un nuevo nombre de contenedor.Puede evitar este conflicto dejando la tarea de nombrar contenedores a Docker Compose (realizada automáticamente según su convención de nomenclatura). Docker Compose nombra sus contenedores como
directory-name_service-name_1
. El número al final aumentaría cada vez que se actualice el contenedor (hasta que use docker-compose down por alguna razón).En caso de que ya esté usando un servicio usando contenedores con nombres personalizados en su archivo de redacción de ventana acoplable, para usar este método, simplemente comente la línea (con un prefijo #) que incluye
container_name
dentro de la definición del servicio.Después de crear el nuevo contenedor para lo anterior usando el paso 1 como se describe en este tutorial, para el paso 2, el nombre del contenedor anterior (que no se detuvo para evitar el tiempo de inactividad) sería como se especificó anteriormente usando
container_name
(también se puede verificar con `docker ps
` antes de retirar el contenedor antiguo).¿Aprendiste algo nuevo?
Sé que este artículo es un poco extenso, pero espero que ayude a nuestra comunidad DevOps a administrar los problemas de tiempo de inactividad con servidores de producción que ejecutan aplicaciones web dockerizadas. El tiempo de inactividad tiene un gran impacto tanto a nivel individual como comercial y es por eso que cubrir este tema era una necesidad absoluta.
Únase a la discusión y comparta sus pensamientos, comentarios o sugerencias en la sección de comentarios a continuación.