Milioni di utenti si collegano a Internet per accedere a informazioni per vari scopi, tra cui l'apprendimento, l'intrattenimento, le notizie e la condivisione dei progressi delle loro vite’ con gli amici. Pertanto, quando si distribuisce un'app, è nel vostro interesse implementare un'infrastruttura altamente sicura e scalabile per la vostra applicazione. Il cloud offre vari modi per proteggere e scalare una Django applicazione. La scalabilità orizzontale è un metodo che consente di eseguire diverse copie dell'app. Ciò garantisce una maggiore tolleranza ai guasti e un'elevata disponibilità. Inoltre, aumenta le prestazioni per elaborare più richieste contemporaneamente.
Scalare orizzontalmente un'applicazione Django
È possibile scalare orizzontalmente un'applicazione Django predisponendo diversi server applicativi che eseguono l'applicazione Django e il suo server HTTP WSGI (come Gunicorn o uWSGI). Sarà quindi necessario configurare un'infrastruttura per distribuire le richieste in arrivo su questi server applicativi. Un bilanciatore di carico e un reverse proxy come Nginx possono aiutare la vostra infrastruttura nella distribuzione del traffico. Nginx può distribuire certificati SSL garantendo connessioni sicure alla vostra app tramite HTTPS. Infine, Nginx può anche fornire la memorizzazione nella cache dei contenuti statici per ridurre al minimo il carico sul server.
Configurare questi vari componenti separatamente e garantire che comunichino può essere un compito arduo. Fortunatamente, l'uso di Docker semplifica il processo di configurazione e garantisce che i vari componenti si comportino allo stesso modo indipendentemente da dove vengono distribuiti.
Cosa farai in questa guida
In questa guida, imparerai come scalare orizzontalmente un'applicazione Django containerizzata, servita con un server HTTP WSGI Gunicorn. Configurerai due server applicativi, ciascuno con Docker installato, che eseguono la stessa copia di un container dell'app Django e Gunicorn.
Proteggerai anche la tua applicazione con un Let’s Encrypt certificato SSL predisponendo e configurando un terzo server proxy che eseguirà un container reverse proxy Nginx e un container client Certbot. Certbot è un pacchetto che aiuta a gestire i certificati SSL dell'autorità di certificazione Let’s Encrypt. Recupera il certificato, configura i blocchi del server Nginx con la posizione del certificato e gestisce i rinnovi automatici. Lo fa configurando un cron job per controllare periodicamente se il certificato sta per scadere e deve essere rinnovato. Mantenendo aggiornato il tuo certificato SSL, il tuo sito web avrà sempre un punteggio di sicurezza elevato su SSL Labs.
Il terzo server proxy si trova davanti alla tua architettura distribuita e riceve tutto il traffico esterno in entrata. Quindi, distribuisce il traffico ai server della tua app. I server dell'app si trovano dietro un firewall, che consente l'accesso solo al server proxy.
Questo tutorial è il secondo di una serie di tre tutorial sul lavoro con Django, Docker e Kubernetes. Dovresti prima seguire i passaggi descritti nel tutorial su Creazione di un'applicazione Django e Gunicorn con Docker su Ubuntu. In quel tutorial, abbiamo configurato il codice del progetto di base, un Dockerfile e collegato l'app a MinIo Simple Storage Service (S3) per servire i nostri file statici.
Prerequisiti
Per seguire questo tutorial, avrai bisogno di quanto segue:
- Quattro server Ubuntu 20.04:
Se hai seguito i passaggi nel tutorial dei prerequisiti, Creazione di un'applicazione Django e Gunicorn con Docker su Ubuntu, hai già due dei quattro server:
-
Il primo server eseguirà l' istanza del database PostgreSQL. Segui i passaggi 1 e 2 del tutorial: Creazione di un'applicazione Django e Gunicorn con Docker su Ubuntu per configurare il database. Le configurazioni di Postgres dovrebbero essere modificate per consentire connessioni esterne solo dagli IP dei server della tua app.
-
Il secondo e il terzo server ospiteranno i container per il codice della tua applicazione. Dovresti già avere il secondo server in esecuzione dal tutorial dei prerequisiti. Modificheremo il suo firewall per consentire solo connessioni esterne dall'IP del server proxy. Puoi seguire i passaggi da 1 a 4 di questo tutorial passo-passo per aiutarti a configurare il tuo server Ubuntu su CloudSigma.
-
Il quarto server sarà il server proxy che gestisce il bilanciamento del carico e la distribuzione del traffico ai due container del server applicativo.
-
Docker dovrebbe essere installato sui due server app e sul server proxy.
Dopo aver seguito i passaggi nel tutorial sui prerequisiti, dovresti avere Docker già installato su uno dei server. Puoi seguire i passaggi 1, 2 e 3 del nostro tutorial sull'installazione e il funzionamento di Docker. Ricorda di aggiungere l'utente sudo creato sopra al gruppo Docker.
- Acquisisci un nome di dominio registrato e configura i suoi record DNS per puntare al proxy indirizzo IP pubblico del server. A scopo dimostrativo, utilizzeremo example_domain.com.
-
Configura un servizio di archiviazione di oggetti S3. Abbiamo utilizzato MinIO come servizio di archiviazione nel tutorial sui prerequisiti. Pertanto, segui le spiegazioni nel Passo 5 del tutorial sui prerequisiti per configurare il tuo MinIO bucket di archiviazione.
Passo 1: Verificare che il primo server dell'applicazione Django funzioni
Come spiegato nei Prerequisiti, questa guida viene dopo il tutorial su Creazione di un'applicazione Django e Gunicorn con Docker su Ubuntu. Se provieni da quel tutorial e hai già implementato i passaggi, dovresti avere il primo server in esecuzione. Il codice dell'applicazione si basa sul tutorial dell'applicazione Polls. È importante leggere attentamente questi passaggi per comprendere la configurazione iniziale. Se hai implementato i passaggi del tutorial, puoi saltare questo primo passaggio.
In caso contrario, puoi semplicemente clonare il ramo Dockerizzato nel tuo server. Inizia accedendo al tuo primo server app ed esegui il seguente comando git per clonare il ramo django-polls-docker del repository django-polls:
|
1 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
Successivamente, naviga nella directory django-polls :
cd django-polls
In questa directory troverai un Dockerfile utilizzato da Docker per creare l'immagine dell'applicazione, la directory django-polls che contiene il codice dell'applicazione Python e un file env contenente un elenco di variabili d'ambiente che verranno passate al container all'avvio per modificarne il comportamento. Nel Dockerfile, definiamo le dipendenze del pacchetto Django tramite il file requirements.txt. Inoltre, dobbiamo dichiarare una porta 8000 da utilizzare per accettare il traffico in entrata e impostarla per eseguire un server gunicorn con 3 worker. Per saperne di più sulle istruzioni del Dockerfile, dai un'occhiata al Passo 7 del tutorial Creazione di un'applicazione Django e Gunicorn con Docker su Ubuntu.
Puoi creare l'immagine Docker utilizzando il comando:
docker build -t django-polls:v1 .
Dopo che Docker ha creato l'immagine, puoi elencare le immagini disponibili sul server con il seguente comando:
docker images
Ecco l'output quando abbiamo eseguito il comando:
Successivamente, dobbiamo modificare il file env utilizzato per configurare l'ambiente di runtime. Questo file viene passato al container Docker all'avvio. Apri il file env con l'editor nano:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
DJANGO_SECRET_KEY=your_secret_key DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=your_server_IP_address DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=your_database_password DB_HOST=your_database_host DB_PORT=your_database_port STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=la_tua_chiave_di_accesso_minio MINIO_SECRET_KEY=la_tua_chiave_segreta_minio MINIO_URL=il_tuo_url_minio:la_tua_porta_minio |
Il env presenta del testo segnaposto che è necessario modificare e compilare con i valori corretti:
-
DJANGO_SECRET_KEY: Genera un valore unico e imprevedibile come spiegato nella documentazione di Django. Puoi usare questo comando per generare una stringa casuale e impostarla nella variabile: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'.
-
DJANGO_ALLOWED_HOSTS: questo valore viene utilizzato per proteggere la tua app dagli attacchi HTTP Host Header. Puoi impostarlo su *, carattere jolly che corrisponde a tutti gli host se in modalità di sviluppo. Quando distribuisci la tua app in produzione, impostalo sul tuo nome di dominio registrato. Per la nostra dimostrazione è dominio_di_esempio.com.
-
DB_DATABASE: impostalo sul nome del database PostgreSQL che hai creato nella sezione Prerequisiti, nel nostro caso è polls_db.
-
DB_USERNAME: impostalo sul nome utente che hai scelto per il tuo database.
-
DB_PASSWORD: impostalo sulla password che hai scelto per il tuo database.
-
DB_HOST: impostalo sull'host che esegue l'istanza del tuo database come configurato nella sezione Prerequisiti. Questo è spiegato nei passaggi 1 e 2 del tutorial Creazione di un'applicazione Django e Gunicorn con Docker su Ubuntu per configurare il database.
-
DB_PORT: impostalo sulla porta del tuo database.
Salva e chiudi il file una volta terminata la modifica. Con le credenziali del database configurate, possiamo creare lo schema del database eseguendo il container e sovrascrivendo il comando CMD impostato nel Dockerfile. Puoi trovare maggiori informazioni sul punto di ingresso del Dockerfile nella documentazione ufficiale. Successivamente, esegui il seguente comando:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py makemigrations && python manage.py migrate" |
In questo comando, stiamo eseguendo l'immagine django-polls:v1 e passando il file env modificato in precedenza. La parte: sh -c "python manage.py makemigrations && python manage.py migrate” crea lo schema del database definito dal codice dell'app. Se stai eseguendo il comando per la prima volta, dovresti vedere un output simile che indica la creazione dello schema del database:
Una volta creato lo schema, possiamo creare il superuser di Django. Esegui il seguente comando per avviare il container con una shell interattiva:
|
1 |
docker run -i -t --env-file env django-polls:v1 sh |
Il comando avvia il container con un prompt di shell che puoi utilizzare per interagire con la shell Python. Creiamo un utente con il seguente comando:
|
1 |
python manage.py createsuperuser |
Segui le istruzioni per fornire un nome utente, un indirizzo email e una password. Digita nuovamente la password e premi invio per creare l'utente. Esci dalla shell e arresta il container premendo CTRL+D.
Successivamente, dobbiamo eseguire nuovamente il container, sovrascrivendo il comando predefinito con il comando Django collectstatic. Il comando genererà i file statici per l'app e li caricherà su MinIO Cloud Storage:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py collectstatic --noinput" |
Il comando genera e carica il file sul servizio di object storage configurato. Ecco l'output:
Ora puoi eseguire l'applicazione senza specificare alcun comando aggiuntivo per sovrascrivere il comando CMD predefinito definito nel Dockerfile:
|
1 |
docker run --env-file env -p 80:8000 django-polls:v1 |
Docker esegue il comando predefinito definito nel Dockerfile, avvia il container con il server gunicorn, espone la porta del container 8000 e la mappa sulla porta di Ubuntu 80. Ora puoi visualizzare l'interfaccia dell'applicazione nel tuo browser accedendo all'indirizzo IP del primo server nella barra degli indirizzi: http://FIRST_SERVER_IP.
Riceverai un errore 404 Pagina non trovata perché non abbiamo definito nulla per / percorso. Naviga su http://FIRST_SERVER_IP/polls per vedere l'interfaccia dei sondaggi:
Visita l'interfaccia di amministrazione per creare alcuni sondaggi: http://FIRST_SERVER_IP/admin:
Fornisci le credenziali impostate con il comando createsuperuser sopra indicato per accedere all'interfaccia amministrativa:
Se visualizzi il sorgente della pagina, noterai che i file statici vengono recuperati dal bucket di archiviazione come definito. Dopo aver confermato che il container serve l'app come previsto, puoi arrestare il container premendo CTRL+C nel terminale.
Successivamente, dobbiamo mantenere il container in esecuzione in modalità detached in modo da poter uscire dalla sessione SSH del primo server. Questo lascerà il container in esecuzione in background. Esegui il seguente comando:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Il flag -d avvia il container in modalità detached in modo che possa rimanere in esecuzione in background. Il flag --rm pulisce il filesystem del container dopo la sua chiusura. Diamo un nome al container, polls, in modo da poterlo vedere quando elenchiamo i container.
Esci dalla sessione SSH del tuo primo server e naviga su http://FIRST_SERVER_IP/polls nel tuo browser per confermare che sia in esecuzione come previsto. Se riesci a visualizzare l'interfaccia dei sondaggi, il tuo primo server applicativo è stato configurato con successo. Configureremo il secondo server applicativo nel passaggio successivo.
Passo 2: Configurazione del secondo server applicativo
Cloneremo il ramo dockerizzato dell'applicazione che abbiamo creato nel tutorial Building a Django and Gunicorn Application with Docker on Ubuntu . Puoi trovare maggiori dettagli sui comandi che useremo qui in quel tutorial, o la versione riassunta nel Passo 1.
Dovresti avere il secondo server in esecuzione, aver aggiunto un utente sudo non root e installato Docker come spiegato nella sezione Prerequisites.
Il passo successivo consiste nel configurare questo server per connettersi all'istanza del server PostgreSQL. Come spiegato nel Passo 1 del tutorial Building a Django and Gunicorn Application with Docker on Ubuntu, devi consentire l'indirizzo IP del secondo server attraverso le configurazioni di ufw e di PostgreSQL.
First, log into the PostgreSQL database server instance with your non-root sudo user. To add the ufw rule, esegui il seguente comando:
|
1 |
sudo ufw allow from SECOND_SERVER_IP to any port 5432 |
Successivamente, esegui questo comando e aggiungi l'indirizzo IP del secondo server al file di autenticazione del client PostgreSQL:
|
1 |
sudo nano /etc/postgresql/12/main/pg_hba.conf |
Leggi i commenti per comprendere meglio le configurazioni. Successivamente, aggiungi questa riga sotto la sezione hosts, specificando il tuo indirizzo IP:
|
1 |
host all all SECOND_SERVER_IP/24 md5 |
Salva e chiudi il file quando hai finito di modificarlo.
Quindi, riavvia il servizio PostgreSQL affinché le modifiche abbiano effetto:
|
1 |
sudo service postgresql restart |
Disconnettiti dall'istanza del server del database PostgreSQL e procedi con la configurazione della seconda istanza del server dell'app.
Accedi al secondo server dell'app con ssh. Quindi clona il branch django-polls-branch del repository django-polls con il seguente comando:
|
1 2 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
Spostati nella directory django-polls:
|
1 |
cd django-polls |
Dopodiché, compila l'immagine con il seguente comando:
|
1 |
docker build -t django-polls:v1 . |
Una volta completato il processo di compilazione dell'immagine, modifica il file env con i valori di configurazione come spiegato nel Passo 1. Apri il file con nano:
|
1 |
nano env |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DJANGO_SECRET_KEY=la_tua_chiave_segreta DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=indirizzo_IP_del_tuo_server DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=password_del_tuo_database DB_HOST=host_del_tuo_database DB_PORT=porta_del_tuo_database STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=la_tua_access_key_minio MINIO_SECRET_KEY=la_tua_secret_key_minio MINIO_URL=il_tuo_url_minio:la_tua_porta_minio |
Sostituisci i testi segnaposto con i valori effettivi che hai aggiunto nel Passo 1. Ricorda di modificare la DJANGO_ALLOWED_HOSTS variabile in modo appropriato. Salva e chiudi il file quando hai finito. Aggiorna le tue credenziali MinIO nel env file come hai fatto nel passaggio precedente.
Ora puoi avviare il container dell'app in modalità detached con il seguente comando:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Il comando avvia il container e lo mantiene in esecuzione in background. Esci dalla sessione ssh del secondo server dell'applicazione e naviga su http://SECOND_SERVER_IP/polls nel tuo browser per confermare che sia in esecuzione come previsto. Dovresti essere in grado di visualizzare l'interfaccia dei sondaggi se tutto è andato come previsto.
Ora hai due server applicativi che eseguono la stessa copia della tua applicazione. Nel passaggio successivo, configurerai il container Nginx affinché funga da reverse proxy.
Passo 3: Configurazione del container Docker Nginx
Nginx è uno dei software per server web open-source più popolari al mondo. È responsabile di garantire la disponibilità e la scalabilità dei siti a più alto traffico su Internet. Garantisce sicurezza ed è molto versatile. Puoi usarlo per reverse proxy, caching e bilanciamento del carico. Abbiamo configurato la nostra applicazione per utilizzare un servizio di object storage separato per gestire i suoi file statici e multimediali. Pertanto, non utilizzeremo le funzionalità di caching di Nginx. Utilizzeremo invece le funzionalità di reverse proxy e bilanciamento del carico di Nginx. Il server front-facing di Nginx riceverà il traffico in entrata e lo distribuirà ai server applicativi di backend. Successivamente, garantirà una comunicazione sicura tra client e server proteggendo il traffico tramite certificati SSL ottenuti da Let’s Encrypt.
Esistono diversi modi per implementare il reverse proxy e il bilanciamento del carico con Nginx. Uno dei modi consiste nell'impostare il reverse proxy Nginx separatamente dal server applicativo di backend, come abbiamo fatto in questo tutorial. Questa configurazione è flessibile e consente di scalare sia il livello proxy Nginx sia il livello applicativo. Puoi aggiungere più proxy Nginx o implementare un bilanciatore di carico cloud. Un altro modo per implementare il reverse proxy consiste nell'utilizzare uno dei server applicativi di backend como proxy Nginx. In questo modo, puoi reindirizzare le richieste in arrivo localmente e verso altri server applicativi. Opzionalmente, puoi configurare un container Nginx su tutti i server applicativi di backend e impostare un bilanciatore di carico cloud frontale per ricevere il traffico in entrata e distribuirlo ai server applicativi di backend.
Iniziamo a configurare il server proxy. Accedi al quarto server Ubuntu che avevi impostato per essere utilizzato come proxy Nginx e crea una directory di configurazione:
|
1 |
mkdir conf |
Apri un file di configurazione con nano all'interno della directory:
|
1 |
nano conf/nginx.conf |
Successivamente, aggiungi la seguente configurazione al file:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } server { listen 80 default_server; return 444; } server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example_domain.com; # SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_domain.com/privkey.pem; ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; client_max_body_size 4G; keepalive_timeout 5; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://django; } location ^~ /.well-known/acme-challenge/ { root /var/www/html; } } |
In questo file di configurazione, specifichiamo i blocchi server, upstream, e location per istruire Nginx a reindirizzare le richieste HTTP verso HTTPS e a distribuire le richieste tra i due server applicativi che abbiamo configurato nel Passo 1 e Passo 2. Puoi trovare informazioni generali sulla struttura del file di configurazione di Nginx nella loro documentazione ufficiale.
Abbiamo studiato i file di configurazione forniti dalla documentazione dell'immagine Nginx di Docker Hub, Certbot, e Gunicorn per arrivare a questo file di configurazione Nginx minimale. Sebbene questo sia solo a scopo dimostrativo e per avviare la nostra configurazione, sei libero di esplorare e sperimentare con altre configurazioni seguendo le guide di Nginx.
Il blocco upstream viene utilizzato per definire il gruppo di server che elaboreranno le richieste in arrivo. Un nome viene assegnato al gruppo ed è richiamato dalla direttiva proxy_pass. Abbiamo nominato il blocco come django e specificato gli indirizzi IP dei due server applicativi backend:
|
1 2 3 4 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } |
Abbiamo anche definito 3 blocchi server. Il primo blocco server cattura tutte le richieste che non corrispondono al tuo dominio e restituisce un 444 codice (chiude la connessione senza inviare una risposta al client, negando così richieste dannose o malformate). Una richiesta HTTP diretta all'indirizzo IP del tuo server viene gestita da questo blocco poiché è definito come default_server:
|
1 2 3 4 |
server { listen 80 default_server; return 444; } |
Il secondo blocco server gestisce le richieste HTTP in arrivo (porta 80) e le reindirizza a HTTPS (porta 443) utilizzando un reindirizzamento HTTP 301:
|
1 2 3 4 5 6 |
server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } |
Il terzo blocco server ora gestisce le richieste. Ha diverse direttive e ne definiremo l'importanza di seguito.
Abbiamo due direttive che definiscono i percorsi del certificato TLS e della chiave come forniti da Certbot. I certificati vengono montati nel container Nginx all'avvio:
|
1 2 3 |
# SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_domain.com/privkey.pem; |
Successivamente, abbiamo le impostazioni di sicurezza SSL predefinite consigliate da Certbot. Puoi saperne di più dalla documentazione ufficiale di Nginx sul modulo ngx_http_ssl_module. Mozilla offre anche ulteriori informazioni sulla sicurezza lato server. Il valore ssl_ciphers nel file conf è tratto dalla pagina di Mozilla:
|
1 2 3 4 5 6 |
ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; |
Nelle prossime due direttive, definiremo la dimensione massima consentita del corpo della richiesta del client e imposteremo il timeout per le connessioni keep-alive con il client. Nginx chiuderà le connessioni con il client dopo i secondi impostati nella direttiva keepalive_timeout. Puoi trovare maggiori informazioni sulle configurazioni di Nginx per il deployment di Gunicorn dalla documentazione ufficiale:
|
1 2 |
client_max_body_size 4G; keepalive_timeout 5; |
Nel file di configurazione abbiamo anche definito due blocchi location. Il primo blocco gestisce il proxying delle richieste come definito con le direttive proxy. Le richieste in arrivo vengono inoltrate tramite proxy ai server upstream django definiti in precedenza:
|
1 2 3 4 5 6 7 |
location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://django; } |
Puoi trovare maggiori informazioni sulle direttive proxy su Nginx Module ngx_http_proxy_module e sulla documentazione relativa a distribuzione di un server Gunicorn.
Nel secondo blocco location, definiamo un percorso: /well-known/acme-challenge/. Di aiuto viene utilizzato da Certbot per verificare il tuo nome di dominio con Let’s Encrypt prima di emettere o rinnovare un certificato SSL:
|
1 2 3 |
location ^~ /.well-known/acme-challenge/ { root /var/www/html; } |
Questo è tutto per il file di configurazione di Nginx. Ora puoi salvare e chiudere il file una volta terminata la modifica.
Il file di configurazione appena definito può essere utilizzato per eseguire un container Nginx. Tuttavia, non funzionerà poiché non abbiamo ancora emesso i certificati SSL da Let’s Encrypt. In questo tutorial, utilizzeremo l'immagine Docker nginx:1.20.2 versione 1.20.2 dal repository ufficiale di immagini Nginx su Docker Hub.
Puoi eseguire il comando seguente per scaricare l'immagine e verificare che tutto funzioni correttamente:
|
1 2 3 4 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Questo comando crea un container chiamato nginx e mappa le porte 80 e 443 tra il sistema host e il container. Il flag --rm rimuove tutti i container intermedi dopo una compilazione andata a buon fine. Utilizziamo il flag -v per montare il file di configurazione nel container in /etc/nginx/conf.d/nginx.conf che è la directory di configurazione predefinita di Nginx. È montato in modalità di sola lettura utilizzando il flag ro per impedire al container Nginx di modificarlo. Impostiamo la directory webroot predefinita e la montiamo come /var/www/html. Concludiamo istruendo Docker a utilizzare l'immagine nginx:1.20.2 per questa build. Acquisiamo il certificato TLS/SSL e la chiave da Let’s Encrypt nel passaggio successivo.
Passaggio 4: Provisioning del certificato SSL/TLS da Let’s Encrypt e configurazione del rinnovo automatico di Certbot
Certbot aiuta a emettere certificati TLS gratuiti da Let’s Encrypt e a gestirne il rinnovo automatico prima della scadenza. Ciò migliora la sicurezza dei tuoi siti web e garantisce che vengano distribuiti tramite HTTPS. Per mantenere la nostra architettura containerizzata, utilizzeremo l'immagine Docker di Certbot per ottenere i certificati SSL/TLS e configurare il rinnovo automatico. Assicurati di avere Docker installato sul tuo server proxy come indicato nelle istruzioni dei Prerequisiti.
Dovresti anche avere un record DNS A del tuo nome di dominio registrato che punta all'indirizzo IP del tuo server proxy. Puoi verificare eseguendo l'immagine Docker di certbot e passando il flag --staging:
|
1 2 3 4 |
docker run -it --rm -p 80:80 --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly --standalone --staging -d example_domain.com |
Il comando scaricherà l'immagine Certbot e la eseguirà in modalità interattiva. Ciò significa che verrà fornita con una shell, consentendoti di inserire alcuni dettagli. Mappa la porta 80 dell'host alla porta 80 all'interno del container. Utilizziamo il flag -v per montare due directory dell'host nel container: /etc/letsencrypt/ e /var/lib/letsencrypt/. Il flag --standalone specifica che vogliamo che l'immagine Certbot venga eseguita senza utilizzare Nginx. Infine, abbiamo il flag --staging che farà eseguire Certbot sui server di staging e convaliderà il tuo nome di dominio.
Inserisci il tuo indirizzo email e accetta i Termini di servizio quando richiesto. Di seguito è riportato l'output di una convalida andata a buon fine:
|
1 2 3 4 5 6 7 |
Account registrato. Richiesta un certificato per example_domain.com Ricevuto con successo ricevuto certificato. Certificato è salvato in: /etc/letsencrypt/live/example_domain.com/fullchain.pem Chiave è salvata in: /etc/letsencrypt/live/example_domain.com/privkey.pem Questo certificato scade il 2022-04-29. Questi file saranno be aggiornati quando il certificato si rinnova. |
PROSSIMI PASSI:
Il certificato dovrà essere rinnovato prima della scadenza. Certbot può rinnovare automaticamente il certificato in background, ma potrebbe essere necessario eseguire alcuni passaggi per abilitare tale funzionalità. Controlla questo link per le istruzioni.
Puoi visualizzare il certificato utilizzando il comando cat:
|
1 |
sudo cat /etc/letsencrypt/live/example_domain.com/fullchain.pem |
Il comando precedente dovrebbe mostrare il tuo certificato nel terminale. Una volta confermato che Certbot ha fornito il tuo certificato, ora puoi testare la configurazione di Nginx che avevi creato nel Passo 3. Esegui il comando Docker qui sotto per avviare il container Nginx:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
In questo comando, abbiamo utilizzato il flag -v per montare la posizione delle directory dei certificati SSL/TLS di Let’s Encrypt.
Quando il container è attivo e funzionante, apri la pagina web nel tuo browser: http://example_domain.com. Probabilmente vedrai un avviso che indica che il sito web non è sicuro:
Questo perché avevamo fornito solo certificati di staging/test e non certificati di produzione da Let’s Encrypt. Otteniamo i certificati di produzione eseguendo il seguente comando Certbot senza il flag --staging flag:
|
1 2 3 4 |
docker run -it --rm -p 80:80 --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly --standalone -d example_domain.com |
Nel prompt, conferma che desideri rinnovare e sostituire il certificato esistente digitando 2 e premi INVIO. Questo dovrebbe fornire un certificato pronto per la produzione. Ora puoi eseguire il container Nginx e tutto dovrebbe funzionare correttamente:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Una volta che il container è attivo e funzionante, apri nuovamente la pagina web nel tuo browser: http://example_domain.com di nuovo. Noterai che il tuo browser viene reindirizzato a HTTPS anche se inserisci HTTP. Ciò significa che sia il nostro server nella configurazione di Nginx sia i certificati SSL/TLS forniti funzionano correttamente. Naviga verso la rotta polls route http://example_domain.com/polls poiché non abbiamo una rotta definita per il percorso principale /. Dovresti vedere l'interfaccia dei sondaggi:
Finora hai configurato con successo un'architettura pronta per la produzione. Hai implementato due server backend che elaboreranno le richieste in arrivo inoltrate dal server proxy. Il server proxy gestirà il bilanciamento del carico e la sicurezza del traffico utilizzando i certificati TLS forniti.
Tuttavia, tieni presente che i certificati Let’s Encrypt scadono in 90 giorni. Pertanto, dovresti rinnovarli prima della scadenza dei 90 giorni. Poiché il container Nginx sarà in esecuzione, dovresti utilizzare la modalità webroot invece della modalità standalone quando esegui il comando certbot per il rinnovo del certificato. Ricorda che avevi specificato la directory /var/www/html/.well-known/acme-challenge/ nel file di configurazione di Nginx nel Passo 3. Certbot utilizzerà questo percorso per memorizzare i file di convalida. Inoltre, il client Let’s Encrypt chiamerà questo percorso con le richieste di convalida quando proverai a rinnovare i certificati. Una volta terminata l'esecuzione del comando di rinnovo, puoi ricaricare Nginx per applicare le modifiche.
Arresta il container premendo CTRL+C nel tuo terminale, e avviamolo di nuovo in modalità detached con il flag -d flag:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -d -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Questo lascerà il container Nginx in esecuzione in background. Testiamo la procedura di rinnovo del certificato con il flag --dry-run eseguendo il comando seguente:
|
1 2 3 4 5 |
docker run -it --rm --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ -v "/var/www/html:/var/www/html" \ certbot/certbot renew --webroot -w /var/www/html --dry-run |
In questo comando, abbiamo specificato il plugin --webroot e il percorso da utilizzare per le richieste di convalida con il flag -w. Specifichiamo anche il flag --dry-run per verificare la procedura di rinnovo automatico senza effettivamente emettere un certificato.
Dovresti vedere un output simile in caso di simulazione riuscita:
|
1 2 3 4 5 6 7 8 |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Elaborazione /etc/letsencrypt/renewal/example_domain.com.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Simulazione del rinnovo di un certificato esistente per example_domain.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulazioni, tutti i rinnovi simulati sono riusciti: /etc/letsencrypt/live/hackinroms.com/fullchain.pem (successo) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
Ogni volta che rinnovi un certificato per la tua applicazione in esecuzione, devi ricaricare Nginx affinché il container inizi a utilizzare il nuovo certificato. Il seguente comando Docker ricarica il container nginx (ricorda che abbiamo nominato il container come nginx):
|
1 |
docker kill -s HUP nginx |
Il comando invia un segnale Unix HUP al processo Nginx in esecuzione all'interno del nginx container Docker. Questo fa sì che Nginx ricarichi le sue configurazioni e inizi a utilizzare i certificati rinnovati.
Poiché abbiamo installato TLS/SSL sul nostro server proxy e il nostro sito web viene servito tramite HTTPS, ora dobbiamo proteggere i nostri server applicativi backend per consentire solo le richieste provenienti dal server proxy.
Passo 5: Proteggere i server Django di backend dall'accesso esterno
Il server proxy implementato in questo tutorial gestisce la terminazione SSL, in cui decrittografa la connessione SSL e inoltra i pacchetti non crittografati ai server applicativi di backend. Poiché proteggeremo i server di backend da qualsiasi accesso esterno, questo livello di sicurezza dovrebbe funzionare nella maggior parte dei casi. Tuttavia, se stai distribuendo applicazioni che trasmettono dati sensibili come informazioni bancarie o dati sanitari, dovresti implementare la crittografia end-to-end.
In questo tutorial, i server Gunicorn nel backend sono protetti da Nginx poiché non sono destinati a essere esposti direttamente. Il server proxy Nginx è come un gateway per i server di backend, impedendo ai client esterni di accedere direttamente ai server applicativi di backend. Dovresti assicurarti che tutte le richieste passino attraverso il server proxy. Detto questo, Docker presenta un problema per cui aggira le regole di ufw e apre le porte all'esterno, il che potrebbe lasciare la tua infrastruttura insicura. Questo è in effetti evidente poiché abbiamo configurato i nostri server applicativi nel Passo 1 e Passo 2 senza consentire la porta 80 nelle regole ufw. Tuttavia, puoi comunque accedere alle pagine web quando visiti uno dei due indirizzi IP pubblici del server nel browser. Un modo per risolvere questo problema consiste nell'utilizzare iptables direttamente senza passare per ufw. Puoi leggere i documenti ufficiali di Docker e iptables per saperne di più. Un altro modo consigliato è l'utilizzo di firewall cloud.
Modifichiamo le configurazioni di UFW per bloccare l'accesso esterno a tutte le porte che potrebbero essere state aperte da Docker. Quando abbiamo mappato la porta dell'host 80 alla porta del container Docker 8000 con il flag -p 80:8000 nel comando Docker, abbiamo anche inavvertitamente aperto la porta 80 sulla macchina host. Puoi disabilitare questo accesso modificando la configurazione di UFW come descritto nel repository ufw-docker README.
Applichiamo la modifica per il primo server dell'app Django. Accedi al server e apri il file in /etc/ufw/after.rules con nano come utente sudo:
|
1 |
sudo nano /etc/ufw/after.rules |
Il file contiene le seguenti regole ufw :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# # rules.input-after # # Regole che dovrebbero essere eseguite dopo le regole aggiunte dalla riga di comando di ufw. Le regole personalizzate # dovrebbero essere aggiunte a una di queste catene: # ufw-after-input # ufw-after-output # ufw-after-forward # # Non eliminare queste righe richieste, altrimenti si verificheranno errori *filter :ufw-after-input - [0:0] :ufw-after-output - [0:0] :ufw-after-forward - [0:0] # Fine righe richieste # non registrare i servizi rumorosi per impostazione predefinita -A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input -A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input -A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input # non registrare i broadcast rumorosi -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input # non eliminare la riga 'COMMIT' o queste regole non verranno elaborate COMMIT |
Aggiungi il seguente blocco di righe di configurazione di UFW in fondo al file:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN COMMIT # END UFW AND DOCKER |
Le regole che hai aggiunto impediscono l'accesso pubblico alle porte aperte da Docker. Inoltre, consentono l'accesso da 10.0.0.0/8, 172.16.0.0/12, e 192.168.0.0/16 intervalli di IP privati. Puoi leggere ulteriori dettagli sulle regole nel ufw-docker README. Salva e chiudi i file quando hai finito di modificarli. Questa configurazione dovrebbe funzionare se avessi configurato una rete cloud privata virtuale (VPC), con tutti e tre i server nella VPC, e avessi specificato gli IP privati dei server Django nella direttiva upstream di Nginx config file.
Tuttavia, abbiamo utilizzato IP pubblici e potremmo non avere una VPC. Pertanto, è necessario aggiungere una regola a ufw per consentire il traffico dal server proxy Nginx attraverso la porta 80 di entrambi i server dell'app Django. Puoi aggiungere una regola di autorizzazione a ufw specificando l' IP del server di origine alla porta 80 utilizzando il seguente comando:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
Una volta terminate le modifiche, riavvia il server dell'app Django affinché le modifiche abbiano effetto, poiché l'esecuzione di sudo ufw reload sembra non riuscire ad applicare le modifiche:
|
1 |
sudo reboot |
Al riavvio del server, avvia il container come hai fatto nel Passo 1 o Passo 2:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Successivamente, prova a visitare l'IP del primo server Django nel browser per vedere se presenta l'interfaccia Polls: http://FIRST_SERVER_IP/polls. Non funzionerà. Ora, disconnettiti dal primo server e ripeti i passaggi eseguiti qui per il secondo server. Apri il file /etc/ufw/after.rules con nano come utente sudo:
|
1 |
sudo nano /etc/ufw/after.rules |
Come fatto in precedenza, scorri fino in fondo e aggiungi il blocco di configurazione UFW:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN COMMIT # FINE UFW E DOCKER |
Salva e chiudi il file dopo aver aggiunto il blocco sopra.
Successivamente, aggiungi una regola di autorizzazione a ufw specificando l'IP del server di origine alla porta 80 utilizzando il seguente comando:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
Riavvia il server affinché le modifiche abbiano effetto:
|
1 |
sudo reboot |
Quando il server è di nuovo attivo, avvia nuovamente il container con il comando:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Verifica se riesci a visualizzare l'interfaccia dei sondaggi andando direttamente all'indirizzo IP del secondo server: http://SECOND_SERVER_IP/polls. Dovrebbe fallire anche questo.
Questa architettura è ora pronta per essere testata. Puoi visitare https://example_domain_here/polls per visualizzare l'interfaccia predefinita di Polls dal tuo browser. Ciò significa che il server proxy Nginx ha ancora accesso ai server backend Django.
Conclusione
In questa guida, ti abbiamo mostrato come implementare un'infrastruttura scalabile utilizzando i container Docker. L'infrastruttura include un server di database PostgreSQL separato, due server applicativi backend e un server proxy Nginx per bilanciare il carico e distribuire il traffico tra i due server. Sebbene abbiamo basato la nostra applicazione sull'applicazione Django Polls, puoi personalizzare questa architettura per varie applicazioni utilizzando diversi framework, come Node.js, Laravel, ecc.
Questa è una linea guida di base per iniziare. Alcuni miglioramenti che puoi aggiungere consistono nell'ospitare la tua immagine su un repository di immagini come Docker Hub consentendo una facile distribuzione dell'immagine su più server. Puoi anche aggiungere pipeline di integrazione e distribuzione continua per creare, testare e distribuire automaticamente le immagini sui server delle app ogni volta che si verifica un evento. Ad esempio, un evento potrebbe essere il push di nuovo codice su un ramo specificato in un repository git. Potresti anche voler automatizzare ciò che accade quando il container riscontra un errore. La documentazione ufficiale di Docker fornisce una buona linea guida su Avvio automatico dei container in caso di errori o riavvio del sistema.
Buon computing!








Commenti
Ancora nessun commento. Scrivi il primo.