Passaggio da container standalone a container web e dati separati

Ciao Jay @pfaffman, grazie per questo post e per gli altri su questo argomento dei “due contenitori”, inclusi anche gli scritti di Sam a riguardo.

Domanda:

Abbiamo cercato di configurare due contenitori come hai menzionato: uno per data e uno per web-only, ma abbiamo incontrato diversi ostacoli nel farlo funzionare su macOS.

Ma prima di preoccuparci di correggere questa configurazione a “due contenitori” su macOS o Ubuntu, vorremmo assicurarci di farlo per le ragioni giuste.

Il motivo per cui vogliamo eseguire la “danza dei due contenitori” è affinché il sito non vada offline quando ricostruiamo l’app web, ad esempio durante l’installazione di un plugin. Inoltre, quando modifichiamo un plugin sviluppato internamente, abbiamo notato che a volte l’unico modo per garantire che le nostre modifiche funzionino è ricostruire (questa è un’altra storia per un altro giorno). Ho anche faticato a impostare una configurazione di sviluppo web “veloce e amichevole” che mi soddisfi, ma anche questo è un argomento per un altro giorno.

Quindi, la mia domanda è: la configurazione a “due contenitori” riduce significativamente i tempi di inattività quando viene ricostruita solo la parte “web-only” dell’app?

È questo il modo corretto di vedere la cosa, giusto?

Quando installiamo un plugin o lo modifichiamo, dobbiamo ricostruire solo il file yml “web-only” e non quello dei dati?

Proviamo da un ambiente LAMP, dove le modifiche ai plugin possono essere apportate principalmente in tempo reale sul sito live (senza tempi di inattività, a meno che non commettiamo errori). Inoltre, proveniamo da alcune applicazioni web VueJS in cui costruiamo sul desktop e poi carichiamo semplicemente la nuova applicazione, sostituendo quella vecchia, con aggiornamenti praticamente senza tempi di inattività per la parte VueJS del sito. Tuttavia, con Discourse otteniamo tempi di inattività, che non vogliamo (nemmeno di pochi secondi).

La soluzione a “due contenitori” mostra miglioramenti significativi nei tempi di inattività quando (1) ricostruiamo l’app (per plugin, modifiche al codice, ecc.) o (2) ripristiniamo da un backup completo?

Ho la sensazione che verrò “sgridato” (di nuovo) per aver fatto questa domanda, perché stiamo cercando un modo per eseguire Discourse in produzione e apportare modifiche con tempi di inattività quasi nulli, e non abbiamo ancora trovato un metodo per fare cose che sono così semplici da realizzare con un’app LAMP o VueJS (ad esempio).

Da qui la difficoltà e l’interesse per il metodo a “due contenitori”, che non siamo ancora riusciti a far funzionare correttamente.

Grazie!

Sì. Il container web esistente continua a funzionare mentre viene costruito il nuovo container. Il tempo di inattività, quindi, è solo quello necessario per avviare il nuovo server web, che tipicamente è inferiore a un minuto, anche se non è assolutamente una soluzione a tempo di inattività zero. Se desideri un tempo di inattività zero, hai bisogno di un reverse proxy davanti che permetta al nuovo container di avviarsi e iniziare a funzionare prima di spegnere quello vecchio. (E se le migrazioni del database per il nuovo container causano problemi a quello vecchio, allora avrai tempi di inattività lì, a meno che non ricorra ad altre procedure).

Nessuna differenza nel ripristino da backup.

4 Mi Piace

Grazie Jay @pfaffman,

Sei davvero una risorsa di altissimo livello qui, senza dubbio!

Cosa ne pensi di questa idea, forse un po’ pazza (basata sulla mia ancora limitata comprensione):

Configurare nginx come reverse proxy sul front-end; seguendo questa guida:

Poi impostare due directory / istanze con discourse_docker (standalone), ad esempio:

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

In entrambe le istanze, configurare discourse_docker (standalone) per ascoltare su socket diversi, modificando questo template in ciascuna istanza:

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

Quindi, in sintesi, abbiamo semplicemente ricostruito la produzione (in un momento tranquillo) per eseguire un contenitore diverso in ascolto su un socket diverso (nginx.https.sock2), in modo da non esserci conflitti di socket; che possiamo costruire anche in modalità standalone (con l’obiettivo di eliminare la necessità di due contenitori, data e web-only).

Ad esempio (per discussione / illustrazione), in web.socketed.template.yml in 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:;

e in 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:;

Tuttavia, invece di far fare la magia al template di Discourse, semplicemente commutiamo manualmente i socket in /etc/nginx/conf.d/discourse.conf e riavviamo nginx, quindi rimuoveremmo la direttiva replace: nel template web.socketed.template.yml.

In questa configurazione proposta (forse un’idea pazza), possiamo avere due contenitori standalone in ascolto su due socket diversi (non in conflitto) e semplicemente configurare nginx per connettersi al socket desiderato e riavviare nginx.

Sembra chiaro, semplice e forse utile (durante un periodo di calma con zero nuovi post nell’istanza live) per coloro che potrebbero non voler (o aver bisogno) della complessità di due contenitori (data e web-only) per una singola istanza di Discourse (app).

Ovviamente, la configurazione più robusta (dal punto di vista dei dati), tuttavia, per la perfezione sui siti molto trafficati sarebbe la soluzione a “due contenitori” perché vorremmo avere l’istanza data e quella web-only (ora in ascolto su due socket diversi, sock e sock2).

Nella “soluzione a due contenitori” con il front-end nginx, la “configurazione standard” prevede che entrambi i contenitori web-only ascoltino sullo stesso socket, quindi non possono essere eseguiti contemporaneamente; ma se (ad esempio) li facessimo ascoltare su socket diversi, potrebbero essere eseguiti entrambi contemporaneamente e potremmo semplicemente utilizzare il file di configurazione nginx (e un riavvio di nginx) per passare da uno all’altro.

È questa la comprensione corretta?

Sto iniziando a (lentamente ma spero sicuramente) capire?

Grazie!

Nota di seguito solo: Ho la configurazione a “due contenitori” funzionante su uno dei miei Mac desktop:

Screen Shot 2020-04-11 at 12.41.24 PM

L’unica accortezza nella nostra installazione è stata la necessità di creare manualmente queste directory (e impostare proprietà e permessi) poiché queste directory non vengono create per qualche motivo dagli script:

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

e naturalmente, all’inizio ho provato con una password vuota per il database, ma non ha funzionato (le istruzioni dicono di impostare una password, ma stavo solo sperimentando).

Prossimo passo: configurerò il front-end nginx e proverò il passaggio a quella configurazione con websocket per l’app web-only.

È un bel po’ di informazioni da assimilare. Ma non c’è motivo di avere due directory Discourse. Crea semplicemente più file yml nella directory dei container. Dai loro il nome che preferisci.

2 Mi Piace

Grazie per la conferma. È esattamente come abbiamo configurato il sistema (directory singola) dopo aver sperimentato oggi.

Tutto è andato bene con la configurazione a 2 container (2CC), ma sto riscontrando difficoltà con la configurazione del reverse proxy nginx su macOS.

Non riesco a stabilire una connessione funzionante al socket di dominio Unix nella directory /shared, anche se il socket è accessibile dall’esterno del container. Ho provato con nginx, Python e socat (per i test). Ricevo sempre l’errore 61 “connection refused”. Hmmmm

Sono bloccato su “connection refused” per tutto il giorno!!

Domani è un altro giorno.

Avevo una piccola domanda.
Se avessimo una configurazione con un solo contenitore (solo ‘app.yml’) e avessimo eseguito il comando ./launcher bootstrap app,
il nostro sito web/front-end si fermerebbe o no?

Se sì, perché ‘bootstrap web-only’ non ferma il nostro sito?

Se no, qual è il vantaggio di una configurazione a due contenitori, in termini di risparmio di tempo durante la ricostruzione del contenitore? In altre parole, se possiamo mantenere il nostro sito in esecuzione anche mentre stiamo eseguendo il bootstrap del nostro unico contenitore, perché dovremmo avere due contenitori separati?

1 Mi Piace

No, se crei un nuovo file .yml, chiamalo, ad esempio, new_image

Il bootstrap non avvia né ferma alcun servizio, se configurato correttamente. Le immagini bootstrap non sono in esecuzione. Ecco perché sono chiamate “bootstrap”.

Tuttavia, devi creare un nuovo file yml perché devi creare una nuova immagine con un nuovo nome immagine.

Quindi, con un nuovo nome immagine, la nuova immagine bootstrap non è ancora in esecuzione. È solo “bootstrap”.

Puoi costruire la nuova immagine con un nuovo nome (non app) e la parte di costruzione è completata. Chiamiamola “new_image”.

Poi, se volessimo sostituire un’immagine in esecuzione, chiamiamola “old_image”, puoi fare questo:

./launcher stop old_image; ./launcher start new_image

Nel tuo caso:

./launcher bootstrap new_image          #immagine dati e immagine web "app" in esecuzione
./launcher stop app; ./launcher start new_image

Poiché il contenitore dati è già costruito, risparmi tempo (1) non costruendo l’immagine dati e (2) senza tempi di inattività durante la ricostruzione dell’immagine web, perché puoi costruire questa (fare il bootstrap dell’immagine) mentre le altre immagini sono in esecuzione.

Questo è molto più veloce; ma non è veloce quanto l’uso di un proxy inverso davanti ai contenitori.

Nella tua domanda originale, hai un’immagine in esecuzione chiamata app. Se provi a fare il bootstrap di quell’immagine chiamata app di nuovo, stai ricostruendo la stessa immagine (nome). Questo non ti farà risparmiare tempo, come i tuoi istinti ti hanno detto.

È chiaro ora?

Se no, chiedi pure. Tutti possono imparare; e imparare è entusiasmante. Questo è (in realtà) facile da capire, ma richiede un po’ di tempo se sei nuovo ai concetti.

2 Mi Piace

Per essere onesti, non ho capito nulla. Ho provato, ma ho fallito.

La mia domanda era semplice.
Se abbiamo una configurazione con 2 container e eseguiamo il ‘bootstrap’ (non il rebuild) del nostro container ‘solo-web’, il nostro container web attuale continua a funzionare (perché il nostro sito web risulta funzionante/ok).

E se abbiamo una configurazione con un singolo container, come ‘app.yml’, e poi eseguiamo il bootstrap di questo container ‘app’, il nostro sito web continuerà a funzionare?

E, se la spiegazione è semplice, perché è così?

1 Mi Piace

Forse dovresti assumere qualcuno per aiutarti?

1 Mi Piace

Nessun problema.

Era solo un piccolo dubbio. Una parte di esso, forse, potrebbe essere risolta con un “sì/no”.

Non c’è nulla di importante.

1 Mi Piace

Il mio consiglio è di divertirti provandolo da solo.

In realtà, provare da solo nel proprio ambiente di test richiede meno tempo rispetto a fare una domanda e aspettare una risposta in un forum.

Otterrai la risposta alla tua domanda se provi; quindi dovresti provare queste cose in uno scenario di test in modo da non danneggiare la tua applicazione di produzione :slight_smile:

Discourse è open source e gratuito da scaricare e configurare, grazie alla generosità dei co-fondatori. Questo significa che puoi e dovresti sfruttare questo open source per creare, distruggere, ricreare e distruggere nuovamente app di test (quante volte vuoi).

Se non sei disposto a impegnarti in compiti base di sysadmin, @codinghorror ha avuto un’ottima idea: assumere talenti locali qui.

1 Mi Piace

Sì. (a meno che il bootstrap non migri il database in modo tale che il container in esecuzione non possa più utilizzarlo). Ci sarà un periodo di inattività mentre il vecchio container viene spento e il nuovo viene avviato.

No. Non è possibile avere due processi di database che accedono agli stessi file contemporaneamente.

3 Mi Piace

Oh!!
Grazie per aver chiarito la questione.

1 Mi Piace

Quindi, avendo il database al di fuori del contenitore che stai costruendo, puoi creare un nuovo contenitore web che interagisce con il database mentre l’altro contenitore web continua a funzionare.

1 Mi Piace

Un rapido dubbio riguardo a questo approccio: come si procederebbe con i Rebuild su questa configurazione?

Supponendo di partire da zero con l’impostazione a 2 container aggiunta da @pfaffman, avremmo due container: “app”: “data” e “web_only”.

Tutte le operazioni di Rebuild e simili sono indirizzate a quello “web_only”, ma dobbiamo fare qualcosa con quello “data” o il bootstrap di “web_only” se ne occupa? (Chiedo solo perché sto facendo alcuni test e il container dei dati non si spegne mai in alcun momento).

Utilizziamo “tre container” e un reverse proxy nginx:

  1. data
  2. socket1
  3. socket2

Supponiamo di essere attualmente in esecuzione con socket1 (quello che tu chiami ‘solo web’) e di voler ricostruire e testare qualcosa. Ricostruiremo socket2 mentre socket1 è attivo. Poiché ciascuno di questi container utilizza un socket di dominio Unix, possono entrambi essere eseguiti contemporaneamente perché i socket condivisi risiedono in file diversi (posizioni diverse).

Poi, supponiamo che socket2 sia stato ricostruito e pronto per partire.

Faccio questo:

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

Ora siamo in produzione su socket2.

Se, ad esempio, ci fosse un problema, posso semplicemente fare questo:

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

e torniamo alla situazione precedente, in esecuzione su socket1.

Per divertimento, ho aggiunto del codice al profilo di login bash, così quando accedo al server mi viene sempre indicato quale socket (container) è attivo:

Last login: Fri May 15 09:39:39 2020 from 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

Spero che questo sia d’aiuto.

IMHO, questo (in generale) è il “modo da seguire” (ed è il mio “standard predefinito”, in produzione e non in staging o sviluppo), ma questo vale solo per me, YMMV. Richiede un po’ più di lavoro per la configurazione, ma penso che ne valga la pena (in produzione).


Caveat:


È una buona idea conservare copie dei tuoi file originali yml in un luogo sicuro, poiché i template possono essere sovrascritti e ciò potrebbe creare problemi. Quindi tendiamo a “salvare tutti i file yml”, “eseguire prima un pull” e poi “controllare i file yml” prima di avviare il bootstrap (per eccesso di cautela).

2 Mi Piace

Non è necessario ricreare il contenitore dei dati a meno che non ci sia un aggiornamento di PostgreSQL (come appena accaduto) o di Redis (avvenuto negli ultimi 6 mesi circa).

Nella maggior parte dei casi, basta sostituire web_only con app nelle istruzioni che vedi. Ho delle note su Managing a Two-Container Installation - Documentation - Literate Computing Support

5 Mi Piace

Quindi, in questo approccio, hai due istanze web e utilizzi quella inattiva per testare le cose con gli stessi dati? È interessante, lo proverò sicuramente una volta che avrò definito questa strategia di “tentativo di risparmiare spazio” :stuck_out_tongue:

Grazie mille. Ho letto il tuo articolo dall’inizio alla fine, ho solo una piccola domanda: qui intendevi “data”, giusto? (cioè, ricreare i dati invece di web_only) come dice nell’articolo? O forse c’è un trucco diverso che mi sto perdendo (cioè: quando c’è un grande aggiornamento devi metterli insieme e poi separarli di nuovo).

Sì, testiamo, aggiungiamo e ricostruiamo su un contenitore basato su socket mentre l’altro è in esecuzione.

Sì, entrambi utilizzano lo stesso contenitore dati. Il contenitore dati non “si cura” di quale applicazione web “dialoga”.

È davvero molto semplice, una volta compresa l’idea di base su come eseguire i contenitori su socket di dominio Unix condivisi e non su porte TCP/IP esposte esternamente.

Non troviamo che occupi molto spazio, ma poi, non eseguiamo (in produzione) con spazio limitato perché lo spazio su disco non è costoso.

1 Mi Piace

Grazie per i dettagli. Ho appena provato a far puntare due “app container” a un Data One in un ambiente di test e funziona alla perfezione.

Non posso però spostarlo in PRD, poiché non posso ricreare il Data Container in PRD e non voglio apportare modifiche senza aver prima risolto questa situazione. Si limita a interrompere le operazioni con discourse@discourse FATAL: terminating connection due to administrator command