Migración de contenedor independiente a contenedores web y de datos separados

Hola Jay @pfaffman, gracias por esta publicación y por otras sobre este tema de “dos contenedores”, incluyendo los escritos de Sam al respecto.

Pregunta:

Hemos estado intentando configurar dos contenedores como mencionas, uno para data y otro para web-only, y hemos encontrado varios obstáculos al intentar hacerlo funcionar en macOS.

Pero antes de preocuparnos por depurar esta “configuración de dos contenedores” en macOS o Ubuntu, nos gustaría asegurarnos de que estamos haciéndolo por la razón correcta.

La razón por la que queremos realizar este “baile de dos contenedores” es para que el sitio no se caiga cuando reconstruimos la aplicación web, por ejemplo, al instalar un plugin. Además, cuando ajustamos un plugin propio; hemos notado que a veces la única forma de asegurar que nuestros cambios funcionen es reconstruir (esa es una historia para otro día). También he estado luchando por configurar un entorno de desarrollo web “rápido y amigable” que me satisfaga; pero ese es otro tema para otro día.

Entonces, mi pregunta es: ¿la configuración de “dos contenedores” minimiza significativamente el tiempo de inactividad cuando se reconstruye la parte “solo web” de la aplicación?

¿Esa es la forma correcta de pensar en esto, verdad?

Cuando instalamos un plugin o lo ajustamos, ¿necesitamos reconstruir solo el archivo yml “solo web” y no el archivo yml de datos?

Venimos de un entorno LAMP donde los cambios en los plugins se realizan y se pueden hacer principalmente en tiempo de ejecución en el sitio en vivo (sin tiempo de inactividad, a menos que cometamos un error). También venimos de algunas aplicaciones web VueJS donde construimos en el escritorio y luego simplemente cargamos y colocamos la nueva aplicación, y prácticamente no hay tiempo de inactividad al actualizar o mejorar una parte de VueJS del sitio. Sin embargo, con Discourse obtenemos tiempo de inactividad, lo cual no queremos (ni siquiera unos pocos segundos).

¿La solución de “dos contenedores” muestra mejoras significativas en el tiempo de inactividad cuando (1) reconstruimos la aplicación (para plugins, ajustes de código, etc.) o (2) restauramos desde una copia de seguridad completa?

Siento que voy a recibir “golpes” (de nuevo) por hacer esta pregunta porque estamos buscando una forma de ejecutar Discourse en producción y realizar cambios con un tiempo de inactividad casi nulo, y aún no hemos encontrado una manera de hacer cosas que son tan fáciles de hacer con una aplicación LAMP o VueJS (por ejemplo).

De ahí la lucha / interés en el método de “dos contenedores”, que aún no hemos logrado poner en marcha.

¡Gracias!

Sí. El contenedor web existente sigue ejecutándose mientras se construye el nuevo contenedor. El tiempo de inactividad, entonces, es solo el tiempo que tarda en iniciarse el nuevo servidor web, lo cual suele ser menos de un minuto, aunque de ninguna manera es una propuesta de tiempo de inactividad cero. Si deseas tiempo de inactividad cero, necesitas un proxy inverso delante que permita que el nuevo contenedor se inicie y comience a funcionar antes de que apagues el antiguo. (Y si las migraciones de base de datos para el nuevo contenedor rompen algo en el antiguo, entonces obtendrás tiempo de inactividad allí a menos que realices otras maniobras).

No hay diferencia en la restauración desde una copia de seguridad.

4 Me gusta

Gracias, Jay @pfaffman,

¡Sin duda, eres un recurso de primer nivel aquí!

¿Qué te parece esta idea quizás loca (basada en mi comprensión aún limitada)?

Configurar nginx como proxy inverso en el frontend; según este tutorial:

Luego, tener dos directorios / instancias con discourse_docker (standalone) configurados, por ejemplo:

  1. /var/discourse1
  2. /var/discourse2

En ambas instancias, configurar discourse_docker (standalone) para que escuche en un socket diferente, modificando esta plantilla en cada instancia:

 - "templates/web.socketed.template.yml"

Así, en resumen, simplemente hemos reconstruido la producción (en un momento tranquilo) para que se ejecute en un contenedor diferente escuchando en un socket distinto (nginx.https.sock2), de modo que no haya conflicto de sockets; lo cual también podemos construir en modo standalone (con el objetivo de eliminar la necesidad de dos contenedores: data y web-only).

Por ejemplo (para discusión/ilustración), en web.socketed.template.yml en discourse1:

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from unix:;

y en discourse2:

 - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock2;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock2 ssl http2;
       set_real_ip_from unix:;

Sin embargo, en lugar de que la plantilla de Discourse haga la magia, simplemente cambiamos manualmente los sockets en /etc/nginx/conf.d/discourse.conf y reiniciamos nginx, por lo que eliminaríamos la directiva replace: en la plantilla web.socketed.template.yml.

En esta configuración propuesta (quizás una idea loca), podemos tener dos contenedores standalone escuchando en dos sockets diferentes (sin conflicto) y simplemente configurar nginx para que se conecte al socket que deseemos y reiniciar nginx.

Esto parece claro, sencillo y quizás útil (durante un periodo lento de cero nuevas publicaciones en la instancia en vivo) para quienes no quieran (o necesiten) la complejidad de dos contenedores (data y web-only) por una sola instancia de Discourse (aplicación).

Por supuesto, la configuración más robusta (desde una perspectiva de datos), sin embargo, para la perfección en sitios con mucho tráfico sería la solución de “dos contenedores”, ya que querríamos que las instancias data y web-only (ahora escuchando en dos sockets diferentes, sock y sock2) funcionen correctamente.

En la “solución de dos contenedores” con el frontend nginx, la “configuración estándar” es que ambos contenedores web-only escuchen en el mismo socket, por lo que no pueden ejecutarse simultáneamente; pero si (por ejemplo) los hiciéramos escuchar en un socket diferente, ambos podrían ejecutarse al mismo tiempo y simplemente usaríamos el archivo de configuración de nginx (y un reinicio de nginx) para alternar entre los dos.

¿Es esta la comprensión correcta?

¿Empiezo a (lenta pero seguramente) entender esto?

¡Gracias!

Nota solo de seguimiento: Tengo la configuración de “dos contenedores” funcionando en uno de mis Mac de escritorio:

Screen Shot 2020-04-11 at 12.41.24 PM

La única salvedad en nuestra instalación fue la necesidad de crear manualmente estos directorios (y establecer la propiedad y los permisos), ya que por alguna razón los scripts no crean estos directorios:

~discourse/discourse/shared/data
~discourse/discourse/shared/web-only

y, por supuesto, al principio intenté con una contraseña vacía para la base de datos, y eso no funcionó (las instrucciones dicen establecer una contraseña, pero solo estaba experimentando).

A continuación, configuraré el frontend nginx y probaré el cambio a esa configuración con websocket para la aplicación web-only.

Eso es mucho para procesar. Pero no hay razón para tener dos directorios de Discourse. Simplemente crea múltiples archivos yml en el directorio de contenedores. Nómbralos como quieras.

2 Me gusta

Gracias por confirmar. Así es exactamente como lo tenemos configurado (un solo directorio) después de probar hoy.

Todo ha ido bien con la configuración de 2 contenedores (2CC), pero estoy teniendo problemas con la configuración del proxy inverso de nginx en macOS.

No puedo establecer una conexión funcional con el socket de dominio Unix en el directorio /shared, aunque el socket es accesible fuera del contenedor. Lo he probado con nginx, y también con python y socat (para pruebas). Siempre obtengo un error 61: conexión rechazada. Hmmmm.

¡He estado atascado con el error “conexión rechazada” todo el día!

Mañana será otro día.

Tenía una pequeña pregunta.
Si tuviéramos solo una configuración de contenedor (solo ‘app.yml’) y ejecutáramos ./launcher bootstrap app.
¿Se detendría o no nuestro sitio web/front-end?

Si es así, ¿por qué ‘bootstrap web-only’ no detiene nuestro sitio web?

Si no, ¿cuál es la ventaja de tener una configuración de dos contenedores en términos de ahorro de tiempo al reconstruir nuestro contenedor? En otras palabras, si podemos mantener nuestro sitio web en funcionamiento incluso mientras estamos ejecutando bootstrap en nuestro único contenedor, ¿por qué necesitaríamos tener dos contenedores separados?

1 me gusta

No, si creas un nuevo archivo .yml y lo llamas, por ejemplo, new_image

El proceso de bootstrap no inicia ni detiene ningún servicio cuando está configurado correctamente. Las imágenes generadas con bootstrap no están en ejecución. Por eso se llaman “bootstrapped” (generadas con bootstrap).

Sin embargo, necesitas crear un nuevo archivo yml porque debes generar una nueva imagen con un nombre diferente.

Así que, con un nuevo nombre de imagen, la nueva imagen generada con bootstrap aún no está en ejecución. Solo está “bootstrapped”.

Puedes construir la nueva imagen con un nombre diferente (no app) y la parte de construcción quedará lista. Llamémosla “new_image”.

Luego, si quisiéramos reemplazar una imagen en ejecución, llamémosla “old_image”, puedes hacer lo siguiente:

./launcher stop old_image; ./launcher start new_image

En tu caso:

./launcher bootstrap new_image          #la imagen de datos y la imagen web "app" están en ejecución
./launcher stop app; ./launcher start new_image

Dado que el contenedor de datos ya está construido, ahorras tiempo: (1) no necesitas construir la imagen de datos y (2) no hay tiempo de inactividad al reconstruir la imagen web, ya que puedes construirla (hacerle bootstrap) mientras las otras imágenes están en ejecución.

Esto es mucho más rápido; aunque no es tan rápido como usar un proxy inverso frente a los contenedores.

En tu pregunta original, tienes una imagen en ejecución llamada app. Si intentas hacer bootstrap de esa imagen llamada app nuevamente, estarás reconstruyendo la misma imagen (mismo nombre). Esto no te ahorrará tiempo, tal como intuías.

¿Queda claro ahora?

Si no, por favor pregunta. Todos pueden aprender; y aprender es emocionante. Esto es (de hecho) fácil de entender, pero requiere un poco de tiempo si eres nuevo en estos conceptos.

2 Me gusta

Para ser honesto, no entendí nada. Lo intenté, pero fracasé.

Mi pregunta era sencilla.
Si tenemos una configuración de 2 contenedores y ‘arrancamos’ (sin reconstruir) nuestro contenedor ‘solo web’, entonces nuestro contenedor web actual seguirá funcionando (porque nuestro sitio web aparece como funcionando/OK).

Y si tenemos una configuración de un solo contenedor, como ‘app.yml’, y luego arrancamos este contenedor ‘app’, ¿seguirá funcionando nuestro sitio web?

Y, si la explicación es sencilla, ¿por qué es así?

1 me gusta

¿Quizás deberías contratar a alguien para que te ayude?

1 me gusta

No hay problema.

Era solo una pequeña duda. Una parte de ella, quizás, podría responderse con un ‘sí/no’.

No hay nada grave.

1 me gusta

Mi sugerencia es que simplemente disfrutes probándolo por tu cuenta.

De hecho, lleva menos tiempo probarlo tú mismo en tu propio entorno de pruebas que hacer una pregunta y esperar una respuesta en un foro.

Obtendrás la respuesta a tu pregunta si lo intentas; por lo tanto, deberías probar estas cosas en un escenario de prueba para no romper tu aplicación de producción :slight_smile:

Discourse es de código abierto y gratuito para descargar y configurar, gracias a la generosidad de los cofundadores. Esto significa que puedes y debes aprovechar este software de código abierto para crear, destruir, recrear y volver a destruir aplicaciones de prueba tantas veces como desees.

Si no estás dispuesto a dedicar algo de esfuerzo a tareas básicas de administración de sistemas, @codinghorror tuvo una excelente sugerencia sobre contratar talento local aquí.

1 me gusta

Sí. (a menos que el inicio migre la base de datos de tal manera que el contenedor en ejecución ya no pueda usarla). Tienes un tiempo de inactividad mientras el contenedor antiguo se apaga y el nuevo se está iniciando.

No. No puedes tener dos procesos de base de datos accediendo a los mismos archivos al mismo tiempo.

3 Me gusta

¡Oh!
Gracias por aclarar esto.

1 me gusta

Así, al tener la base de datos fuera del contenedor que estás construyendo, puedes crear un nuevo contenedor web que interactúe con la base de datos mientras el otro contenedor web sigue funcionando.

1 me gusta

Duda rápida sobre este enfoque: ¿Cómo se procedería con las reconstrucciones en esta configuración?

Asumiendo que partimos de cero con la configuración de 2 contenedores que añadió @pfaffman, tendríamos dos: “app”: “data” y “web_only”.

Todas las operaciones de reconstrucción y demás están dirigidas al contenedor “web_only”, pero ¿hacemos algo con el contenedor “data” o el bootstrap de “web_only” se encarga de ello? (Solo pregunto porque estoy realizando algunas pruebas y el contenedor de datos nunca se detiene en ningún momento).

Utilizamos “tres contenedores” y un proxy inverso nginx:

  1. data
  2. socket1
  3. socket2

Digamos que actualmente estamos ejecutando socket1 (lo que llamas ‘solo web’) y queremos reconstruir y probar algo. Reconstruiremos socket2 mientras socket1 sigue funcionando. Dado que cada uno de estos contenedores utiliza un socket de dominio Unix, ambos pueden ejecutarse simultáneamente porque los sockets compartidos están en archivos (ubicaciones) diferentes.

Luego, supongamos que socket2 está construido y listo para funcionar.

Hago lo siguiente:

ln -sf /var/discourse/shared/socket2/nginx.http.sock /var/run/nginx.http.sock

Ahora estamos funcionando en vivo con socket2.

Si, por ejemplo, hubiera un problema, simplemente puedo hacer esto:

ln -sf /var/discourse/shared/socket1/nginx.http.sock /var/run/nginx.http.sock

y volvemos a estar donde estábamos antes, funcionando con socket1.

Por diversión, agregué algo de código al perfil de inicio de sesión de bash, de modo que cuando inicie sesión en el servidor, siempre me indique qué socket (contenedor) está en ejecución:

Último inicio de sesión: vie may 15 09:39:39 2020 desde 159.192.33.138
srw-rw---- 1 root docker  0 may  5 07:38 /var/run/docker.sock
lrwxrwxrwx 1 root root   44 may 15 09:16 /var/run/nginx.http.sock -> /var/discourse/shared/socket/nginx.http.sock

Espero que esto ayude.

En mi opinión, esto (en general) es la “manera correcta de proceder” (y es mi “estándar predeterminado”, en producción, no en staging ni en desarrollo), pero eso es solo mi punto de vista, cada uno puede tener el suyo. Requiere un poco más de trabajo para configurarlo, pero creo que vale la pena (en producción).


Advertencia:


Es una buena idea guardar copias de tus archivos de plantilla originales yml en algún lugar seguro, ya que las plantillas pueden sobrescribirse y esto podría ser un problema. Por lo tanto, tendemos a “guardar todos los yml”, “hacer pull primero” y luego “verificar los yml” antes de iniciar el proceso (por una abundancia de precaución).

2 Me gusta

No es necesario reconstruir el contenedor de datos a menos que haya una actualización de PostgreSQL (como acaba de ocurrir) o de Redis (que ocurrió hace unos 6 meses).

En la mayoría de los casos, solo debes reemplazar web_only por app en las instrucciones que ves. Tengo notas en Managing a Two-Container Installation - Documentation - Literate Computing Support

5 Me gusta

Entonces, en este enfoque, tienes dos instancias web y usas la inactiva para probar cosas con los mismos datos, ¿verdad? Es interesante, definitivamente lo probaré una vez que me decida por toda esta estrategia de “intentar ahorrar espacio” :stuck_out_tongue:

Muchas gracias. Leí tu artículo de principio a fin, solo una pequeña pregunta: aquí te referías a data, ¿verdad? (es decir, reconstruir data en lugar de web_only) como dice en el artículo? O quizás hay un truco diferente que me estoy perdiendo (es decir, cuando hay una actualización grande, necesitas ponerlos juntos y luego separarlos de nuevo).

Sí, probamos, añadimos y reconstruimos en un contenedor basado en sockets mientras el otro está en ejecución.

Sí, ambos utilizan el mismo contenedor de datos. El contenedor de datos no “se preocupa” por a qué aplicación web “se conecta”.

En realidad, es muy sencillo una vez que entiendes la idea básica de cómo ejecutar los contenedores en sockets de dominio Unix compartidos y no en puertos TCP/IP expuestos externamente.

No encontramos que ocupe mucho espacio, pero entonces, tampoco ejecutamos (en producción) con espacio limitado, ya que el espacio en disco no es caro.

1 me gusta

Gracias por los detalles. Acabo de probar con dos “contenedores de aplicaciones” apuntando a un Data One en un entorno de pruebas y funciona de maravilla.

Sin embargo, no puedo moverlo a PRD, ya que no puedo reconstruir el contenedor de datos en PRD y no quiero cambiar nada sin tener eso resuelto. Simplemente detiene las operaciones con un discourse@discourse FATAL: terminando la conexión debido a un comando del administrador