Aggiungi una pagina offline da mostrare quando Discourse si sta ricostruendo o avviando

C’è un motivo evidente per cui non riesco a caricare un .png \u003e500kb sul mio forum dopo aver seguito questa guida?

Vedo alcune discussioni su questa riga client_max_body_size 0; ma non dovrebbe essere questo il problema, vero?

EDIT: L’ho capito rapidamente dopo aver postato qui. Ho dovuto controllare :white_check_mark: force https nelle impostazioni. Lascerò questo post qui nel caso in cui altre persone in futuro abbiano un problema

3 Mi Piace

Noto che qui:

nginx consiglia di disabilitare l’header Connection: close e di impostare proxy_http_version 1.1 — quindi qualcosa del genere:

    proxy_http_version 1.1;
    # Disabilita il \"Connection: close\" predefinito
    proxy_set_header \"Connection\" \"\";

Non riesco a trovare alcuna documentazione sull’impatto di Connection: close sui socket di dominio Unix, ma poiché questa documentazione è utile anche per eseguire un proxy esterno su un sistema separato e mi aspetterei che la rimozione dell’header non danneggi, potrebbe avere senso raccomandare di rimuoverlo qui?

1 Mi Piace

Se hai distribuito questo su un sistema con SELinux (enforcing), non puoi usare il socket di dominio Unix per la comunicazione tra host e container, perché anche se rietichetti il socket di dominio Unix, questo verrà ricreato senza l’etichetta ogni volta che riavvii il container. Dovrai invece apportare due modifiche.

Dovrai consentire a nginx di accedere alle pagine di errore e passare dal proxying tramite socket di dominio Unix a una porta. Ciò comporterà un paio di µs di latenza per richiesta, dato che i costi di esecuzione di nginx con SELinux come un livello di sicurezza, ma questo non sarà percepibile dai tuoi utenti.

Innanzitutto, esegui questi comandi per consentire a nginx di effettuare connessioni di rete e accedere alle pagine di errore:

setsebool -P httpd_can_network_connect 1
semanage fcontext -a -t httpd_sys_content_t /var/www
restorecon -R -v /var/www

Quindi, nel tuo app.yaml, commenta o rimuovi - "templates/web.socketed.template.yml", esponi la porta 80 come una porta diversa sulla macchina locale e ricompila il container.

expose:
  - "8008:80"   # http

Non usare https qui: hai terminato SSL in nginx esterno e l’header X-Forwarded-Proto informa Discourse che la richiesta è arrivata tramite https. Assicurati che la porta 8008 (o qualsiasi altra porta tu abbia scelto) non sia esposta pubblicamente dalle impostazioni del tuo firewall.

Quindi modifica la configurazione del tuo nginx esterno dal proxying tramite nginx.http.sock a http://127.0.0.1:8008 (o la porta scelta) e cancella l’header predefinito Connection: close, in modo che nginx esterno non debba stabilire una nuova connessione IP per ogni richiesta.

...
  location / {
    proxy_pass http://127.0.0.1:8008;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    # Disabilita il \"Connection: close\" predefinito
    proxy_set_header "Connection" "";
...
1 Mi Piace

Ciao @sam (e forse @falco). Mi è stato affidato il compito di ripulire alcuni di questi documenti #documentation:sysadmin. Questo ha un numero molto elevato di letture e penso che sia uno dei meno utili.

Pensi che abbia senso scrivere una sostituzione che avvii haproxy - Official Image | Docker Hub e nginx - Official Image | Docker Hub, magari con docker compose, facendo montare al container nginx i certificati dal container discourse e facendo in modo che haproxy in modalità tcp faccia qualcosa del genere (sono sicuro che questo non funzionerà, ma presumo di poter capire cosa funzionerà):

backend my_app_be
  balance roundrobin
  option httpchk HEAD /srv/status
        server discourse app:443 check
  server fallback nginx:80 check backup

Penso che questa potrebbe essere una soluzione praticabile più facile da seguire rispetto a questo argomento. Lascerei quindi questo per scopi storici (e forse lo chiuderei?) ma rimanderei a quello descritto sopra.

3 Mi Piace

Si noti che questo argomento ha una significativa sovrapposizione con quello:

Quell’argomento ha ricevuto aggiornamenti significativi di recente (che hanno aumentato ulteriormente la sovrapposizione). Forse avrebbe senso unire la parte della pagina offline da qui a lì come nota (poiché è facile da aggiungere se si esegue già un’istanza Nginx separata), quindi contrassegnare questo come deprecato (collegando alle alternative)?

Il tuo argomento HAProxy suggerito avrebbe ancora senso in aggiunta, come modo predefinito per le persone che non vogliono installare un Nginx frontale per altri motivi.

2 Mi Piace

Come avrei dovuto saperlo?

Oh.

Ma seriamente, la tua soluzione mi piace più della mia!

E quell’argomento dice a grandi lettere che è un argomento avanzato.

Ma forse non è nemmeno necessario, dato che la maggior parte delle persone capisce comunque meglio nginx. Ho iniziato a pensarci ora, quindi la parte difficile sarà farmi smettere. :slight_smile:

3 Mi Piace

È sempre un bene se ci sono molte alternative. Ma questa è una delle più facili (ce ne sono parecchie…), e familiare per molti.

Quindi per favore, non toccarla.

E lasciarla così ha un altro punto: i risultati di ricerca. A causa dell’alto traffico (e dell’uso molto limitato dei tag…) cercare qualcosa di specifico qui è diventato piuttosto difficile. Ma questa è abbastanza facile da trovare e ha uno scopo molto mirato. Se questo argomento si sposterà su un altro, trovarlo sarà più difficile.

C’è un motivo per cui è così popolare… non molti sono così ispirati a usare docker o haproxy.

2 Mi Piace

Sospira. Beh, immagino che sia vero anche questo, ma è obsoleto di almeno 4 anni. Non l’ho fatto di recente, ma non è più necessario modificare i file manualmente poiché acme (o qualcosa di simile?) lo farà per te.

Quello che penso davvero è che abbia molto più senso usare un’installazione a due container, che ha tempi di inattività ridotti, piuttosto che saltare attraverso questi ostacoli per mettere online una pagina mentre ricostruisci, ma non riesco a convincere le persone nemmeno di questo.

Quindi forse la cosa da fare è riscrivere questo per come funzionano le cose oggi.

1 Mi Piace

E invece è molto più difficile. Penso che sia più facile correggere le istruzioni su come installare e usare certbot piuttosto che insegnare come, quando e dove aggiornare i container sql-side, ecc.

Inoltre, c’è un altro punto a sfavore di docker (anche Discourse funziona in questo modo…): possiamo trovare molte domande di livello base come come usare docker in primo luogo. O come evitare errori di battitura nei file yml :wink:

Eppure funziona (tranne la sezione SSL che è un po’ confusionaria, ma lo era anche 4 anni fa :wink: )

No. Non sono contrario ad altre soluzioni. Sono molto contrario quando vecchi link e testi devono essere spostati in nuove posizioni senza motivi molto validi.

2 Mi Piace

Dovremo essere d’accordo sul fatto di non essere d’accordo. Ma credo che ci siano probabilmente molte persone che sono d’accordo con te. (Forse questa soluzione è una soluzione “imposta e dimentica”, e la soluzione a due contenitori richiede attenzione quando c’è un aggiornamento di Postgres, che avviene circa ogni 2 anni.)

OK. Su questo siamo d’accordo! Quindi penso che la strada da percorrere sia vedere cosa posso fare per ripulire quella parte e mettere in attesa la soluzione haproxy.

1 Mi Piace

Mi piacerebbe vedere questo fatto come parte di Discourse, ma grazie @fefrei per questo! Lavoro fantastico! Lo userò con Apache ma almeno i passaggi di base dovrebbero essere gli stessi.

1 Mi Piace

OK, ci ho messo solo 2 ore per farlo come volevo!

Pagina di manutenzione di Discourse con Apache2

Come root

cd /var/discourse
nano containers/app.yml

Commenta queste righe:

  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

expose:
  #- "80:80"   # http
  #- "443:443" # https

Aggiungi alla FINE della sezione templates (deve essere l’ultima):

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

Nota: Questo farà in modo che Discourse ascolti solo sull’IP interno e apache2 si occuperà delle porte 80/443 e della terminazione SSL.

Nota: Discourse deve essere ricostruito affinché questo abbia effetto:

cd /var/discourse
./launcher rebuild app

Installa apache2 e certbot

apt install -y apache2 certbot python3-certbot-apache

Crea una directory per la pagina html:

mkdir /var/www/discourse_maintenance

Pagina HTML:
/var/www/discourse_maintenance/discourse_maintenance.html

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="refresh" content="5">
        <title>Discourse Maintenance</title>
        <style>
            .center {
                display: flex;
                justify-content: center;
            }
            .container {
                max-width: 500px;
                padding: 50px 50px 30px 50px;
            }
            .title {
                padding-top: 20px;
            }
            h1, p {
                font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
            }
        </style>
    </head>
    <body>
        <div class="center">
            <div class="container">
                <h1 class="title">Discourse Maintenance&hellip;</h1>
                <p>We are currently upgrading the site, or performing scheduled maintenance.</p>
                <p>You'll automatically be redirected to the site once it's available.</p>
            </div>
        </div>
    </body>
</html>

Abilita il modulo Proxy:

a2enmod proxy
a2enmod proxy_http
a2enmod headers

File vhost di Apache:

<IfModule mod_ssl.c>
<VirtualHost *:443>
  ServerName your.discourse.domain
  ServerAdmin your@email.com
  DocumentRoot /var/www/discourse_maintenance

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

  # Maintenance Mode
  RewriteEngine On
  RewriteCond /var/www/under_maintenance -f
  # safety check to prevent redirect loops 
  RewriteCond %{REQUEST_URI} !/discourse_maintenance.html$
  # redirect internally all requests to maintenance.html 
  RewriteRule ^.*$ /var/www/discourse_maintenance/discourse_maintenance.html

  ProxyPass / unix:///var/discourse/shared/standalone/nginx.http.sock|http://127.0.0.1/
  ProxyPassReverse / unix:///var/discourse/shared/standalone/nginx.http.sock|http://127.0.0.1/

  SSLCertificateFile /etc/letsencrypt/live/your.discourse.domain/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/your.discourse.domain/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf

</VirtualHost>
</IfModule>

Per abilitare la manutenzione esegui touch /var/www/under_maintenance

Per disabilitare la manutenzione esegui touch /var/www/under_maintenance

Crediti: Add an offline page to display when Discourse is rebuilding or starting up per l’idea iniziale, la pagina html (tagliata/modificata a mio piacimento) e la configurazione nginx da cui ho basato la configurazione Apache.

Modifica: Suggerimenti benvenuti per renderlo automatico quando si risponde 502/503. Ci ho provato ma non sono riuscito a farlo funzionare come volevo, quindi sono andato con un metodo noto che uso su altri server web quando l’applicazione backend è in manutenzione, ecc.

2 Mi Piace

Al riavvio del sistema, questo ritarderà la pagina di errore/manutenzione fino a quando docker non sarà avviato, il che richiede molto più tempo dell’avvio del sistema. Inoltre, non offre l’opzione delle protezioni SELinux fornite dal sistema per il sistema nginx. L’utilizzo del sistema nginx può, almeno su un sistema gestito da systemd con avvio rapido, mostrarti la pagina di errore entro pochi secondi dall’avvio. Per me, questo significa che i miei sistemi rispondono con la pagina di manutenzione molto rapidamente durante gli aggiornamenti di sistema che richiedono il riavvio. (Sto eseguendo AlmaLinux 9 sull’host e si avvia molto velocemente su nginx.)

Potrebbe avere senso documentare un’alternativa haproxy e confrontare le esperienze, ma haproxy-in-docker non è una sostituzione diretta per nginx esterno, e chiudere questo argomento sarebbe un errore.

Non si tratta solo di disponibilità.

L’utilizzo di docker per il traffico esterno tramite IPv4 nasconde gli indirizzi IPv6 esterni all’nginx e a Discourse interni. Avrai lo stesso problema con haproxy. Guarda i tuoi log per indirizzi IP locali solo 127.0.0.1 o 172.* RFC1918. Non utilizzare un proxy esterno significa che tutto il traffico IPv6 appare come lo stesso IP, il che interrompe il rate limiting della zona nginx interna, considerando tutto il traffico IPv6 come una singola zona.

IPv6 è sempre più importante.

2 Mi Piace

Ho scoperto per caso stamattina che questo passaggio non solo evita l’applicazione del socket Unix, ma rimuove anche l’uso del modulo real_ip, in modo che il rate limiting venga applicato in base a tutte le connessioni insieme, piuttosto che a tutte le connessioni per IP. Probabilmente dovrei contribuire con un nuovo template con variabili, ma al momento ho appena aggiunto questo al mio file YAML del container dell’app:

run:
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from 172.0.0.0/24;
  - 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 172.0.0.0/24;

Non so se abbia senso avere, ad esempio, un file templates/web.httpratelimit.yml con qualcosa del genere con una variabile per l’indirizzo ma senza usare socket di dominio Unix. Cosa ne pensi?

2 Mi Piace
server {
  listen 80; listen [::]:80; listen 443 ssl http2; listen [::]:443 ssl http2;
  server_name DOMAIN;
  ssl_certificate      /etc/letsencrypt/live/DOMAIN/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/DOMAIN/privkey.pem;

ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
  ssl_protocols TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  add_header Strict-Transport-Security "max-age=63072000;";
  ssl_stapling on;
  ssl_stapling_verify on;

  client_max_body_size 0;

  location / {
    error_page 502 =502 /errorpages/offline.html;
    proxy_intercept_errors on;

    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
  }

  location /errorpages/ {
    alias /var/www/errorpages/;
  }
}

! # cambia il tuo dominio e il percorso del file di errore

ho ottenuto questo script con ssl e funziona

2 Mi Piace

Ho appena provato a usare questa guida e non sono riuscito a farla funzionare.

Ho iniziato con un proxy Nginx per eseguire due siti da un singolo container Discourse. Volevo solo aggiungere la parte della pagina di errore, quindi ho saltato le parti che sembravano sovrapporsi a Run other websites on the same machine as Discourse. Devo aver saltato un passaggio fondamentale, però. Alla fine ho ottenuto quello che mi serviva da questa tutorial di DigitalOcean. Non è difficile impostarlo manualmente, ma sembra che debba esserci un modo migliore.

Dato che Docker è il modo standard per eseguire Discourse, questo sembra meglio. Presumo che sarebbe il tipo di cosa che imposti e dimentichi.

3 Mi Piace

L’idea all’interno di questo thread è ottima anche per chi di noi utilizza caddy come reverse proxy, sia come applicazione standalone che utilizzando Cloudflare Tunnels.

discourse.example.org {
        reverse_proxy <host | ip>:port

        handle_errors 5xx {
                root * /path/to/error-pages
                rewrite * /error.html
                file_server {
                        status 404
                }
        }
}

La sezione status 404 è importante solo se si utilizzano Cloudflare Tunnels. Se caddy restituisce 5xx a Cloudflare, Cloudflare Tunnel visualizzerà il proprio errore di disconnessione. Modificare lo stato indica a Cloudflare che esiste una connessione live valida che servirà una pagina di errore.

2 Mi Piace

Forse non capisco come funziona, ma un aggiornamento non aggiorna solo la pagina su cui ti trovi già? Come ti riporta a un URL diverso?

Non c’è bisogno di andare a un URL diverso: il trucco è che la pagina di errore viene servita direttamente sull’URL a cui l’utente ha tentato di accedere (ad esempio, https://meta.discourse.org/t/add-an-offline-page-to-display-when-discourse-is-rebuilding-or-starting-up/45238/158), ed è esattamente quell’URL che si aggiornerà, restituendo o lo stesso errore di nuovo, o la pagina che l’utente desiderava :slight_smile:

Oh, capisco. Non avevo letto attentamente tutta la configurazione perché sto usando un altro server per la pagina offline, nel caso in cui il mio intero computer si spegnesse per qualche motivo.
Ha senso comunque. Ora sto solo cercando di far funzionare il mio js che dovrebbe reindirizzare di nuovo all’URL originale…