GNU/Linux >> Tutoriales Linux >  >> Linux

Actualización de contenedores Docker con cero o mínimo tiempo de inactividad

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: , actualice image: 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 con docker 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.


Linux
  1. Cómo instalar WordPress con Docker en Ubuntu

  2. Instale ModSecurity con Apache en un contenedor Docker

  3. 7 funciones divertidas de transporte de imágenes/contenedores de Linux

  4. Cómo implementar un contenedor nginx con Docker en Linode

  5. Cómo actualizar el contenedor Docker sin tiempo de inactividad

Cómo editar código en contenedores Docker con Visual Studio Code

Cómo ejecutar contenedores Docker

Trabajar con Docker Containers desde la línea de comandos

Cómo nombrar o renombrar contenedores Docker

Cómo:Introducción a los contenedores de Windows y Docker

Cómo gestionar contenedores Docker