Torna al blog

Creare un'applicazione Django e Gunicorn con Docker su Ubuntu

Creare un'applicazione Django e Gunicorn con Docker su Ubuntu

Django è un framework web open-source di alto livello Python che può aiutarti a creare rapidamente la tua applicazione Python. Incoraggia uno sviluppo rapido e un design pulito e pragmatico seguendo il pattern architetturale model–template–views. Pronto all'uso, il framework include i componenti necessari per le moderne applicazioni, come l'autenticazione utente, framework di caching, object-relational mapper, URL Dispatcher, sistema di template, e un'interfaccia amministrativa personalizzabile.

Gunicorn ‘Green Unicorn’ è un server HTTP WSGI Python per sistemi UNIX. Il server Gunicorn è compatibile con vari framework web, offre ottime prestazioni ed è leggero sulle risorse del server. Docker è una piattaforma di containerizzazione open-source disponibile da tempo, che rende lo sviluppo di applicazioni rapido, efficiente e prevedibile.

In questo tutorial, acquisirai competenze nello sviluppo e nel deployment di applicazioni web Django containerizzate e scalabili. Utilizzeremo un'applicazione Django Polls creata seguendo le guide introduttive per iniziare con Django. Al momento della stesura del tutorial, ci siamo basati su Django 3.2 supportato da Python 3.6 o versioni successive. Distribuiremo l'app come container con Docker e la serviremo con il server Gunicorn. Naturalmente, prima di distribuire l'app Django in un container, dovrai apportare alcune modifiche al codice del progetto per gestire aspetti come il logging sui flussi di standard output e il lavoro con le variabili d'ambiente. I file statici come CSS e immagini JavaScript possono essere scaricati su servizi di object storage per consentire una facile gestione dei file da un'unica posizione in un ambiente multi-container.

Ti mostreremo come implementare queste modifiche basandoci sulla ben delineata twelve-factor metodologia per la creazione di applicazioni web scalabili. Una volta completate le modifiche, compilerai un'immagine Docker dell'applicazione e distribuirai l'app containerizzata con Docker. Ti consigliamo di seguire i passaggi descritti nel tutorial per comprenderlo appieno.

Prerequisiti

Trattandosi di un tutorial pratico, ti consigliamo di disporre della configurazione seguente per poterti orientare:

In base a questi prerequisiti, dovresti avere due istanze di server Ubuntu. Un'istanza eseguirà il tuo container Docker e l'altra istanza eseguirà l'istanza PostgreSQL. Cominciamo!

Step 1: Configurazione dell'istanza del database PostgreSQL

In questa sezione, modificheremo le configurazioni di Postgres sul server Ubuntu che esegue l'istanza di Postgres. Ciò consentirà le connessioni da un indirizzo IP esterno. Una volta connesso, potremo creare un database e un ruolo utente, specifici per l'app Django Polls che stiamo distribuendo.

In primo luogo, se hai configurato il tuo ambiente come indicato nei Prerequisiti, dovresti avere un ruolo nel tuo database PostgreSQL per il tuo utente sudo. Successivamente, dobbiamo impostare una password per questo ruolo. Mentre ti trovi sul server che esegue PostgreSQL, accedi al terminale Postgres con il seguente comando:

Una volta sul terminale Postgres, esegui il comando \password per modificare la password di un utente. La sintassi per il comando \password è \password <username>. Nel nostro caso, il comando:

Inserisci la password e confermala. Salva questa password in un luogo sicuro poiché la utilizzerai in seguito per autenticarti dall'altro server Ubuntu. Successivamente, digita exit e premi Invio per uscire dal terminale Postgres.

Se hai abilitato il firewall (ufw) sull'istanza del server PostgreSQL, dovrai consentire il traffico verso la porta predefinita di Postgres 5432. Puoi limitare il traffico in modo che provenga solo da uno specifico indirizzo IP dell'altro server Ubuntu che eseguirà il container Docker. Esegui il comando seguente per aggiungere la regola ufw, sostituendo il tuo indirizzo IP dove evidenziato:

Questo assicurerà che solo il tuo server possa connettersi all'istanza PostgreSQL. Sebbene ciò consenta il traffico attraverso il firewall, è anche necessario modificare i file di configurazione di PostgreSQL per consentire la connessione dall'indirizzo IP remoto. Per impostazione predefinita, la configurazione consente solo la connessione da localhost. I file di configurazione per PostgreSQL si trovano nella directory /etc/postgresql/12/main. 12, in questo caso, è la versione di PostgreSQL che abbiamo installato per questo tutorial. Potresti aver installato una versione diversa. Pertanto, puoi spostarti nella directory /etc/postgresql/ ed elencare i contenuti per trovare il numero di versione del PostgreSQL che hai installato.

Usa nano per modificare il file di configurazione:

Trova questa riga qui sotto, decommentala e impostala per consentire le connessioni da tutti gli IP:

Salva e chiudi il file. Quindi, devi modificare anche il file pg_hba.conf, si trova nella stessa directory di postgresql.conf. Il file pg_hba.conf ti consente di definire da quali computer puoi connetterti all'istanza PostgreSQL e il metodo di autenticazione. Apri il file con nano:

Leggi i commenti in questo file per comprendere le parole chiave. La sezione che stiamo cercando è questa:

Building a Django and Gunicorn Application with Docker on Ubuntu 1

La nostra attenzione si concentrerà sulla seconda riga, che dopo essere stata decommentata dovrebbe apparire come la riga seguente:

Sostituisci la parte evidenziata con l'indirizzo IP del tuo server Ubuntu per consentirgli di connettersi all'istanza PostgreSQL. Salva il file una volta pronto. Riavvia il database PostgreSQL affinché le modifiche abbiano effetto:

L'altro nostro server Ubuntu con l'indirizzo IP specificato dovrebbe essere in grado di connettersi all'istanza Postgres.

Passaggio 2: Connessione all'istanza del server PostgreSQL e creazione di un database e di un utente

In questo passaggio, cercheremo di assicurarci che l' istanza Ubuntu che serve il nostro container Docker possa connettersi all'altro server che esegue l' istanza PostgreSQL. Accedi all'istanza Ubuntu che ha Docker e installa il pacchetto postgresql-client all'interno della macchina host Ubuntu (non ancora all'interno del container).

Come di norma, prima aggiorna il pacchetto apt e poi installa il pacchetto con i seguenti comandi:

Il pacchetto installato sopra ti aiuterà a creare un database e un utente per la tua applicazione. Successivamente, dobbiamo connetterci all'istanza PostgreSQL fornendo i parametri di connessione al client PostgreSQL.

I parametri di connessione seguono questa sintassi:

In questo comando, il username è l'utente/ruolo che hai aggiunto al tuo database PostgreSQL. host è l'indirizzo IP dell'istanza Ubuntu che esegue il tuo database PostgreSQL. port è la porta predefinita su cui Postgres ascolta le connessioni in entrata, ovvero 5432. Al posto del database, useremo il database predefinito chiamato postgres fornito con l'installazione di PostgreSQL. Sostituisci i tuoi valori nelle parti evidenziate in modo appropriato e premi Invio. Quando richiesto, inserisci la password che hai impostato. Questo ti consentirà di accedere al prompt di Postgres da cui potrai gestire il database.

Ti sei connesso con successo all'istanza PostgreSQL. Ora puoi creare un database per l'app dei sondaggi di Django. Chiamiamolo django_polls:

Assicurati che la tua istruzione termini con un punto e virgola per evitare di incorrere in errori. Quindi, passa al database django_polls con il comando:

Successivamente, crea un utente del database specifico per questo progetto. Chiamiamo l'utente django_user:

Scegli una password sicura per il tuo utente. Una volta fatto, dobbiamo modificare i parametri di connessione per l'utente appena creato. Questo aiuta a velocizzare le operazioni del database assicurando che i valori corretti non vengano interrogati e impostati ogni volta che viene stabilita una connessione.

Imposta la codifica predefinita prevista da Django come UTF-8:

Successivamente, imposta lo schema di isolamento delle transazioni predefinito su “ read committed”, che blocca le letture da transazioni non confermate:

Imposta il tuo fuso orario. Per mantenere il tutorial universale, useremo il UTC:

Infine, concedi i privilegi amministrativi del database al nuovo utente:

Esci dal prompt di PostgreSQL quando sei pronto:

Questo è tutto per questo passaggio. Una volta configurata correttamente la tua app Django, dovrebbe essere in grado di gestire il tuo database.

Passaggio 3: Recuperare l'app da un repository Git e definire le dipendenze

In questo passaggio, cloneremo il repository dell'app Django-polls. Questo repository contiene il codice per Django’s il tutorial "Scrivere la tua prima app Django".

Accedi al server Ubuntu che esegue Docker, crea una directory chiamata django_project e navigaci all'interno:

Quindi, clona il repository nella directory con il seguente comando:

Naviga nella directory ed elenca i contenuti:

Elenca il contenuto della directory:

Building a Django and Gunicorn Application with Docker on Ubuntu 2

Nota i seguenti elementi:

  • manage.py: questo file è l'ingresso all'utilità da riga di comando fornita da Django per gestire la tua app.

  • mysite: una directory con l'ambito del progetto Django e le impostazioni del codice.

  • polls: una directory contenente il polls codice dell'applicazione.

  • templates: contiene file di template personalizzati per le pagine di amministrazione.

Per saperne di più su come abbiamo effettivamente creato il progetto, dai un'occhiata a Writing your first Django app della documentazione ufficiale. All'interno di django-polls directory, vogliamo che le nostre dipendenze Python siano definite in un file di testo. Lo chiameremo requirements.txt. Apri il file con il tuo editor preferito:

Incolla le seguenti righe all'interno del file per dichiarare le dipendenze:

In questo file abbiamo definito le dipendenze Python con le loro versioni esatte che dovrebbero essere installate quando crei l'app. Alcune di esse includono Django, django-storages per interagire con i bucket di archiviazione degli oggetti, l'adattatore psycopg2 per PostgreSQL, il server WSGI gunicorn e altre dipendenze aggiuntive. Salva e chiudi il file quando hai finito.

Passo 4: Configurazione delle variabili d'ambiente per un'app Django

La metodologia twelve-factor app raccomanda di estrarre le configurazioni hard-coded dal codice sorgente dell'applicazione. In questo modo, si ottiene la libertà di modificare il comportamento dell'applicazione a runtime modificando le variabili d'ambiente senza toccare il codice sorgente. Docker funziona con questa configurazione, quindi modificheremo il file delle impostazioni per funzionare con le variabili d'ambiente. Kubernetes funziona anche con questa configurazione. Condivideremo un altro tutorial sulla distribuzione con Kubernetes sul blog di CloudSigma.

Il file settings.py è il file di impostazioni principale per un progetto Django. Si tratta di un modulo Python che utilizza strutture dati native per configurare l'applicazione. Per la nostra applicazione, il file si trova nella posizione django-polls/mysite/settings.py. La maggior parte dei suoi valori è hard-coded. Questo richiederà di modificare il file di configurazione nel codice sorgente se si cambia il comportamento dell'applicazione. Vogliamo cambiare questo aspetto. Fortunatamente, Python offre la funzione getenv nel modulo os . Possiamo usarla per configurare Django in modo che legga i parametri di configurazione dalle variabili d'ambiente locali.

Continuiamo modificando il file django-polls/mysite/settings.py per sostituire i valori hard-coded delle variabili. Potremmo voler aggiornare a runtime con una chiamata a os.getenv. Questa funzione legge il valore impostato nel nome della variabile d'ambiente fornito. Facoltativamente, è possibile fornire un secondo parametro che è un valore predefinito che verrà utilizzato se la variabile d'ambiente non è impostata.

Ecco un esempio:

Nella riga sopra, diciamo a Django di recuperare la chiave segreta dalla variabile d'ambiente. Non forniamo un valore di fallback poiché forniremo la chiave esternamente. Se non esiste, l'applicazione non dovrebbe avviarsi. Pur fornendo la chiave segreta esternamente, vogliamo anche assicurarci che tutte le copie containerizzate dell'app utilizzino la stessa chiave sui vari server. Questo evita potenziali problemi che sorgono quando le varie copie dell'app utilizzano chiavi diverse.

Ecco un altro esempio con un'opzione predefinita:

In questa riga, definiamo una variabile d'ambiente DEBUG che dovrebbe essere letta. Tuttavia, se non è impostata, abbiamo fornito un secondo parametro che verrà passato alla variabile di impostazione DEBUG . DEBUG è impostato su False per garantire che le informazioni sensibili non vengano passate al frontend in caso di problemi con l'applicazione. Tuttavia, se siamo in modalità di sviluppo, vogliamo che sia impostato su True per consentirci di vedere le informazioni sull'errore per rendere più facile la correzione degli errori.

Ora che conosci l'importanza delle variabili d'ambiente, apri il file django_project/django-polls/settings.py nel tuo editor. Per prima cosa, importa il modulo os aggiungendo questa riga all'inizio del file settings.py :

Quindi, trova queste variabili e aggiornale come segue:

Nell'impostazione ALLOWED_HOSTS, specifichiamo che deve ottenere il valore dalla variabile d'ambiente DJANGO_ALLOWED_HOSTS e suddividerlo in una lista Python utilizzando la virgola ( ,) come separatore. Se la variabile manca, ALLOWED_HOSTS viene impostato su 127.0.0.1.

Successivamente, scorri il file e trova la sezione DATABASES, configurala per leggere anche dalle variabili d'ambiente:

Nota che abbiamo aggiunto il modulo json.loads. Dovresti anche aggiungere un'importazione del modulo in cima al file settings.py:

La funzione json.loads deserializza un oggetto JSON passato in DATABASES['default']['OPTIONS'] dalla variabile d'ambiente DB_OPTIONS . Specificare questa opzione ci consente di passare una struttura dati arbitraria per definire la configurazione del database. Un motore di database include un insieme di opzioni valide ad esso applicabili. L'opzione JSON ci offre la flessibilità di codificare un oggetto JSON con i parametri appropriati per il motore di database che stiamo utilizzando in quel momento.

La chiave DATABASES['default']['NAME'] specifica il nome del database nel sistema di gestione di database relazionali che abbiamo configurato. Nel caso in cui si utilizzi un database SQLite, è necessario specificare il percorso del file del database.

Nota che Python offre diversi metodi per leggere le variabili d'ambiente esterne. Ne abbiamo utilizzato solo uno. Sei libero di fare ricerche e utilizzare altri metodi. In questo passaggio, hai imparato a lavorare con le variabili d'ambiente esterne. Questo ti offre la flessibilità di modificare le variabili e alterare il comportamento dell'app in esecuzione nei container. Nel prossimo passaggio, imparerai a lavorare con i servizi di archiviazione di oggetti.

Passaggio 5: Lavorare con servizi di archiviazione di oggetti esterni

Un grande vantaggio della containerizzazione dell'applicazione è renderla portabile per una facile distribuzione di diverse copie dell'app all'aumentare del traffico, consentendo così la scalabilità. Tuttavia, ciò comporta il problema di mantenere le versioni dei file statici e degli asset tra i vari container. Grazie ai miglioramenti nella tecnologia cloud, puoi delegare questi elementi statici condivisi a uno storage esterno. Successivamente, puoi rendere i file accessibili tramite rete a tutti i tuoi container in esecuzione. Invece di cercare di sincronizzare i file tra i vari container in esecuzione, avrai un unico punto centrale per gestirli.

Il concetto che stiamo cercando di spiegare sopra è l'uso dei servizi di archiviazione di oggetti cloud, o Simple Storage Services (S3). Django ha un pacchetto chiamato django-storages che consente di lavorare con backend di archiviazione remoti. Django-storages funziona con la maggior parte dei servizi di archiviazione di oggetti compatibili con S3 come FTP, SFTP, AWS S3 di Amazon, Google Cloud Storage, Dropbox e Azure Storage, tra gli altri. In questo tutorial utilizzeremo MinIO. Sentiti libero di utilizzare qualsiasi altro servizio di archiviazione di oggetti compatibile con S3. MinIO offre un'archiviazione di oggetti ad alte prestazioni e compatibile con S3. Con MinIO, puoi creare un'infrastruttura dati compatibile con S3 su qualsiasi cloud.

Ti mostreremo come configurare un servizio di archiviazione MinIO sulla piattaforma CloudSigma. Segui questi passaggi:

  • Inizia creando un account su CloudSigma. In caso di problemi durante la creazione dell'archiviazione MinIO, contatta il supporto via chat live gratuito 24/7 di CloudSigma, e ti assisteranno.

  • Aggiungi le tue informazioni di fatturazione.

  • Successivamente, richiedi il tuo bucket accessibile pubblicamente da qui: https://blog.cloudsigma.com/xxxx. Dovrai contattare il supporto via chat live per ottenere le credenziali di accesso al tuo account.

  • Una volta creato il tuo ambiente di archiviazione di oggetti MinIO, ti verranno fornite le credenziali di accesso e altre istruzioni per accedervi. Le credenziali dovrebbero includere il tuo MINI_ACCESS_KEY, MINIO_SECRET_KEY, e MINIO_URL. Utilizzerai queste chiavi nelle istruzioni seguenti.

Apportiamo alcune altre modifiche al file mysite/settings.py che abbiamo modificato nel passaggio precedente. Nel file, aggiungi l'app storages all'elenco di Django di INSTALLED_APPS:

INSTALLED_APPS

L'app storages è installata tramite django-storages come definito nel file requirements.txt. Scorri fino in fondo al file e sostituisci la variabile STATIC_URL con il seguente frammento di codice:

Nota che alcune delle variabili di configurazione sono hard-coded:

  • STATICFILES_STORAGE: definisce il backend di archiviazione che Django utilizzerà per gestire i file statici. Nella nostra guida stiamo utilizzando l'archiviazione MinIO, ma puoi utilizzare qualsiasi backend compatibile con S3 come spiegato nella documentazione di Django Storages.

  • AWS_S3_OBJECT_PARAMETERS: definisce gli header cache-control.

  • AWS_LOCATION: lo usiamo per impostare una directory all'interno del bucket di archiviazione in cui verranno memorizzati tutti i file statici. Sei libero di scegliere un nome diverso.

  • AWS_DEFAULT_ACL: imposta l'elenco di controllo degli accessi (ACL) per i file statici. Impostando il valore su ‘ public-Read’ renderà i file accessibili a tutti gli utenti pubblici.

  • STATIC_URL: Django utilizza l'URL di base impostato in questa variabile per generare gli URL per i file statici. L'URL di base in questo caso deriva dalla combinazione dell'URL dell'endpoint e della sottodirectory dei file statici.

  • STATIC_ROOT: definisce dove raccogliere localmente i file statici prima di copiarli nell'archiviazione di oggetti remota.

Abbiamo anche alcune variabili d'ambiente definite esternamente per mantenere flessibilità e portabilità:

  • AWS_STORAGE_BUCKET_NAME: definisce il nome del bucket di archiviazione in cui Django caricherà gli asset.

  • AWS_S3_ENDPOINT_URL: definisce l'URL dell'endpoint utilizzato per accedere al servizio di archiviazione degli oggetti. Questo sarà l'URL mappato sul server che ospita il tuo servizio MinIO.

Salva e chiudi il file quando hai finito di modificarlo.

Una volta configurate queste impostazioni e installate le dipendenze Python dichiarate, puoi eseguire il comando Django manage.py collectstatic in qualsiasi momento per raccogliere i file statici del tuo progetto e caricarli nel backend di archiviazione degli oggetti remoto:

Tuttavia, non abbiamo ancora configurato il file env con le configurazioni, quindi probabilmente fallirà.

Quando esegui il comando, ci vorrà un momento per copiare i tuoi asset su MinIO Cloud Storage, a seconda delle loro dimensioni e della velocità della tua connessione internet.

Questo è tutto per questo passaggio. Vediamo come possiamo gestire l'invio dei log di Django a Docker Engine in modo da poterli visualizzare utilizzando il comando docker logs nel passaggio successivo.

Step 6: Configurazione del logging in un'app Django

In modalità Debug, quando l'opzione DEBUG è impostata su True, Django registra le informazioni su standard output e standard error. Le informazioni di log di solito appaiono nel terminale da cui hai avviato il server HTTP di sviluppo.

In produzione, probabilmente utilizzerai un server HTTP diverso e l'opzione DEBUG è impostata su False. In questo caso Django utilizzerà un metodo di logging diverso. Django invia i log con priorità ERROR o CRITICAL a un account email amministrativo definito da te. Questo funziona benissimo in molte situazioni.

Nelle configurazioni containerizzate e Kubernetes, il logging su standard output e standard error è altamente raccomandato. I messaggi di log vengono raccolti in una singola directory sul filesystem del Node e sono facilmente accessibili utilizzando i comandi kubectl e docker . Con un punto di logging centralizzato sul filesystem del Node, il team operativo può facilmente eseguire processi su ciascun nodo per monitorare e inoltrare i log. Pertanto, dobbiamo configurare la nostra applicazione per scrivere i log in questa configurazione standard.

Sarai felice di sapere che Django sfrutta il modulo logging altamente personalizzabile della libreria standard di Python. Questo ti consente di definire un dizionario che passa attraverso logging.config.dictConfig per definire gli output e la formattazione desiderati. Ecco un ottimo articolo su Django Logging, The Right Way che può aiutarti a padroneggiare le tecniche di logging in Django.

Apri il file django-polls/mysite/settings.py  nel tuo editor. Aggiungi un'importazione per la libreria Python logging.config all'inizio del file:

Finora, con tutte le importazioni che abbiamo aggiunto, la tua sezione delle importazioni in settings.py dovrebbe apparire così:

Building a Django and Gunicorn Application with Docker on Ubuntu 3

La libreria logging.config accetta un dizionario con la nuova configurazione di logging tramite la funzione dictConfig per sovrascrivere il comportamento di logging predefinito di Django.

Scorri fino in fondo al file e aggiungi il seguente frammento di codice di configurazione del logging:

LOGGING_CONFIG è impostato su None per disabilitare/cancellare le configurazioni di logging predefinite definite da Django. LOGLEVEL è impostato dalla variabile d'ambiente DJANGO_LOGLEVEL. Tuttavia, se non esiste, vogliamo che sia impostato su ‘ info’.

Il modulo logging.config che abbiamo importato all'inizio fornisce una funzione dictConfig che viene utilizzata per impostare un nuovo dizionario di configurazione. Il dizionario definisce la formattazione del testo utilizzando la chiave formatters. L'output viene impostato con la chiave handlers, e infine la chiave loggers definisce quale messaggio deve andare a quale handler.

Una volta definite queste impostazioni, Docker esporrà i log tramite il comando docker logs. Allo stesso modo, in un altro tutorial che faremo per Kubernetes, puoi visualizzare i log con il comando kubectl logs command. Let’s now begin the containerization process in the next step.

Passo 7: Definizione del Dockerfile dell'applicazione

In questo passaggio, definiamo la configurazione per avviare l'immagine del container che eseguirà l'app Django servita dal server WSGI Gunicorn. Definiremo l'ambiente di runtime per la creazione di un'immagine del container, installeremo l'applicazione e le sue dipendenze ed eseguiremo alcune configurazioni finali.

  • L'immagine genitore per un'app Django

Decidere l'immagine di base su cui fondare il proprio container è la primissima decisione da prendere quando si affrontano distribuzioni containerizzate. Naturalmente, si ha la possibilità di creare le proprie immagini di container da SCRATCH, ovvero un file system vuoto, o di basarle su un'immagine di container esistente. Poiché non vogliamo reinventare la ruota, costruiremo la nostra immagine a partire da un'immagine di base. Sono disponibili molte immagini di container open-source dal repository ufficiale delle immagini dei container di Docker. A meno che non stiate creando la vostra immagine da zero, è fortemente consigliato utilizzare un'immagine dall'hub ufficiale di Docker. Questo perché Docker verifica che le immagini seguano le migliori pratiche e garantisce la presenza di aggiornamenti regolari e patch di sicurezza.

Poiché Django è un framework Python, sfrutteremo un'immagine con un ambiente Python standard che ha già installati gli strumenti e le librerie di cui abbiamo bisogno. Dalla pagina ufficiale delle immagini Python su Docker Hub, è possibile trovare un'immagine basata su Python per varie versioni di Python.

Dai nostri vari tutorial basati su Docker, noterai che utilizziamo immagini basate su Alpine Linux. Alpine Linux offre un ambiente di sistema operativo robusto ma leggero per l'esecuzione di applicazioni containerizzate. Sebbene il suo file system sia piccolo, è estensibile e viene fornito con un sistema di gestione dei pacchetti completo con la possibilità di aggiungere funzionalità.

Quando si sceglie un'immagine di base su Docker Hub, si potrebbe notare la disponibilità di più tag per ciascuna immagine. Per quanto riguarda Python, abbiamo 3-alpine, che punta all'immagine dell'ultima versione di Python 3 dell'ultima versione di Alpine. Ciò significa che nel caso in cui il tuo progetto funzioni con una versione dell'immagine precedente, potrebbe rompersi quando i manutentori dell'immagine Docker effettuano un aggiornamento. Per evitare scenari simili in futuro, si consiglia sempre di scegliere i tag più specifici per l'immagine che si desidera utilizzare.

In questo tutorial, utilizzeremo 3.8.12-alpine3.15 come immagine di base per la nostra applicazione Django. Questo tag specifico verrà specificato nel Dockerfile utilizzando l'istruzione FROM. Il Dockerfile si troverà nella directory principale del progetto: django_project.

Inizia uscendo dalla directory Django-polls per tornare alla directory django_project :

Una volta all'interno della directory, usa il tuo editor preferito per aprire un file chiamato Dockerfile :

Successivamente, incolla la riga seguente per impostare la base della tua immagine:

La parola chiave FROM  definisce il punto di partenza di un'immagine Docker personalizzata. Una volta definito questo, possiamo continuare ad aggiungere istruzioni per configurare le applicazioni. Queste istruzioni installeranno le dipendenze necessarie, copieranno i file dell'applicazione e configureranno l'ambiente di runtime.

Aggiungi il seguente frammento di codice all'interno del Dockerfile:

In questo frammento di codice, diciamo a Docker di copiare il file requirements.txt  in /app/requirements.txt per garantire che le dipendenze dell'applicazione siano disponibili nel filesystem dell'immagine. I requisiti includono tutti i pacchetti Python necessari per eseguire l'applicazione. Le dipendenze vengono copiate per prime in modo che Docker possa memorizzare nella cache il livello dell'immagine. Questo perché Docker memorizza nella cache ogni passaggio del Dockerfile. La prima compilazione dell'immagine è solitamente più lunga. Docker scaricherà le dipendenze e poi le memorizzerà nella cache. Se il file requirements.txt non cambia, Docker eseguirà la compilazione dalla cache, rendendo così le compilazioni successive più veloci.

Il passaggio successivo contiene l'istruzione RUN che esegue un elenco di comandi Linux, concatenati con l'operatore Linux &&. I comandi eseguono le seguenti operazioni:

  • Utilizzare il gestore di pacchetti apk di Alpine per installare i file di sviluppo di PostgreSQL e le dipendenze di compilazione di base.

  • Creare un ambiente virtuale Python.

  • Installare le dipendenze Python come definito nel file requirements.txt utilizzando pip.

  • Compilare i pacchetti di runtime necessari analizzando i requisiti dei pacchetti Python installati.

  • Rimuovere eventuali dipendenze di compilazione non più necessarie.

Il motivo per cui si concatenano i comandi nel passaggio RUN è ridurre i livelli dell'immagine. Docker crea un nuovo livello di immagine sopra il filesystem esistente ogni volta che incontra ADD, COPY, o RUN istruzione nel Dockerfile. Comprimere i comandi ove applicabile ridurrà al minimo il numero di layer di immagine creati.

Gli elementi aggiunti ai layer dell'immagine non possono essere rimossi in un layer successivo. È necessario dichiarare le istruzioni per eliminare gli elementi indesiderati prima di passare all'istruzione successiva. Questo è necessario per ridurre le dimensioni dell'immagine. Dovresti notare che abbiamo aggiunto il apk del comando alla fine del RUN comando. Questo è stato fatto per rimuovere le dipendenze di compilazione dopo averle utilizzate per compilare i pacchetti dell'app.

Successivamente, abbiamo un'altra ADD istruzione che usiamo per copiare il codice dell'applicazione nella /app directory. Successivamente, useremo l' WORKDIR istruzione per impostare la directory di lavoro dell'immagine sulla /app directory, che ora contiene il codice dell'applicazione.

Successivamente, abbiamo le ENV istruzioni che usiamo per impostare due variabili d'ambiente che l'immagine renderà disponibili ai container in esecuzione. Per prima cosa, impostiamo la VIRTUAL_ENV variabile su /env. In secondo luogo, impostiamo la PATH variabile per includere la /env/bin directory. In queste due righe, stiamo eseguendo il source dello script /env/bin/activate script, che è il modo in cui attiviamo un ambiente virtuale in un ambiente Linux. Puoi leggere di più su come lavorare con ambienti virtuali in Python su altri sistemi operativi. L'ultima istruzione è il EXPOSE comando che imposta la porta 8000 sulla quale il container rimarrà in ascolto a runtime.

A questo punto, il tuo Dockerfile è quasi completo, a parte il comando predefinito che verrà eseguito all'avvio dei container. Definiamolo nella prossima sezione.

  • Comprendere il comando predefinito dell'immagine Docker

All'avvio di un container Docker, è possibile fornire un comando da eseguire. Tuttavia, se non si fornisce un comando, il comando predefinito dell'immagine Docker determinerà cosa accadrà all'avvio del container. Usiamo le istruzioni ENTRYPOINT o CMD singolarmente o insieme per definire un comando predefinito all'interno del Dockerfile.

Se si sceglie di definire sia ENTRYPOINT che CMD, nell'istruzione ENTRYPOINT , si definisce l'eseguibile che verrà eseguito dal container. Nell'istruzione CMD , si definisce l'elenco di argomenti predefinito per il comando eseguibile. È possibile sovrascrivere l'elenco di argomenti predefinito aggiungendo argomenti alternativi sulla riga di comando quando si avvia il container nel formato:

Questo formato impedisce agli sviluppatori di sovrascrivere facilmente il comando ENTRYPOINT . Il comando ENTRYPOINT è definito per chiamare uno script che configurerà l'ambiente ed eseguirà diverse azioni in base all'elenco di argomenti fornito.

È possibile utilizzare l'istruzione ENTRYPOINT da sola per configurare l'eseguibile del container. Tuttavia, questo formato non consente di definire un elenco di argomenti predefinito. È possibile fornire argomenti quando si esegue il container con il comando docker run comando.

Se si sceglie di procedere solo con CMD  da solo, Docker lo interpreta come il comando e l'elenco di argomenti predefiniti, che è possibile sovrascrivere a runtime. Puoi trovare ulteriori informazioni sulla documentazione ufficiale di riferimento del Dockerfile.

Vediamo come applicare le informazioni apprese sui comandi predefiniti al nostro esempio di container. Vogliamo servire l'applicazione per impostazione predefinita utilizzando il server gunicorn . Sebbene l'elenco di argomenti passato al server gunicorn non debba essere configurabile a runtime, vogliamo la flessibilità di eseguire altri comandi per scopi quali il debug o la gestione delle configurazioni (inizializzazione del database, raccolta di asset statici, ecc.). Come puoi vedere, è nel nostro interesse utilizzare CMD per definire un comando predefinito che ci consentirà di sovrascriverlo ogni volta che sarà necessario.

Ecco alcune sintassi che puoi utilizzare per definire il comando CMD :

  • CMD ["command", "argument 1", "argument 2", . . . ,"argument n"]: Il formato exec (formato consigliato), accetta un comando e un elenco di argomenti. Esegue il comando direttamente senza elaborazione della shell.
  • CMD command "argument 1" "argument 2" . . . "argomento n": Il formato shell definisce un comando e un elenco di argomenti. Passa l'elenco dei comandi alla shell per l'elaborazione. Potrebbe essere utile se si desidera sostituire le variabili di ambiente in un comando, tuttavia non è del tutto prevedibile.
  • CMD ["argomento 1", "argomento 2", . . . ,"argomento n"]: Il formato dell'elenco degli argomenti definisce solo l'elenco degli argomenti predefinito e viene utilizzato insieme a un ENTRYPOINT istruzione.

Utilizzeremo il exec formato per definire la nostra istruzione finale nel Dockerfile. Aggiungi la seguente riga alla fine del tuo Dockerfile:

Ora puoi salvare e chiudere il Dockerfile.

Quando avvii i container utilizzando questa immagine, eseguiranno gunicorn associato alla porta localhost 8000 con 3 worker, e chiameranno la funzione application nel file wsgi.py trovato nella directory mysite . Puoi scegliere di fornire un comando diverso per sovrascrivere il comando predefinito in fase di runtime ed eseguire un processo diverso invece di gunicorn. Potresti voler saperne di più su worker di Gunicorn.

Il tuo Dockerfile è ora pronto e puoi usare docker build per compilare l'immagine dell'app. Puoi usare docker run per avviare il container sulla tua macchina di sviluppo locale.

  • Creazione dell'immagine Docker

Il comando docker build cercherà un Dockerfile nella directory corrente per impostazione predefinita per trovare le sue istruzioni di compilazione. Invia anche il “contesto” di compilazione al demone Docker. Un contesto di compilazione è un insieme di file che dovrebbero essere disponibili durante il processo di compilazione. Per impostazione predefinita, la directory corrente in cui si sta eseguendo il comando docker build è impostata come contesto di compilazione.

Mentre ti trovi nella stessa directory che contiene il tuo Dockerfile, esegui il comando docker build. Fornisci un'immagine e un tag con il flag -t e imposta la directory corrente come contesto di compilazione utilizzando il punto ( .) alla fine del comando:

In questo comando, abbiamo chiamato l'immagine django-polls e il tag v1. Nota il punto alla fine del comando, lo usiamo per indicare la directory corrente come contesto di compilazione.

Al termine di docker build , dovresti vedere un output simile al seguente:

Building a Django and Gunicorn Application with Docker on Ubuntu 4

La tua immagine Docker è ora pronta. Se non avessimo spostato alcune configurazioni in variabili d'ambiente esterne, potresti facilmente eseguire il tuo container con il comando docker run . Tuttavia, poiché non abbiamo configurato le variabili d'ambiente esterne che abbiamo impostato nel file settings.py , l'esecuzione fallirà. Concludiamo questo aspetto nel passaggio successivo.

Passaggio 8: Configurazione dell'ambiente di runtime e test dell'app

Siamo quasi alla fine di questo tutorial. In questo passaggio, configureremo le variabili d'ambiente nel file env . Con le variabili del file env configurate, possiamo creare lo schema del database, generare e caricare i file statici sul servizio di archiviazione di oggetti esterno e infine testare l'app.

Docker include diversi metodi che puoi utilizzare per fornire variabili d'ambiente al container. Nel nostro caso, vogliamo fornire un elenco di variabili d'ambiente tramite un file. Pertanto, utilizzeremo il metodo --env-file .

Utilizzando il tuo editor preferito, crea un file chiamato env nella directory django_project:

Incolla il seguente elenco di variabili:

Le variabili nell'elenco sono quelle definite nei passaggi precedenti:

  • DJANGO_SECRET_KEY: Genera un valore unico e imprevedibile come spiegato nella documentazione di Django. Puoi utilizzare questo comando per generare una stringa casuale e impostarla nella variabile:

  • DEBUG: Abbiamo impostato questo valore su True, ma per una distribuzione in produzione, ricordati di impostarlo su False lasciandolo vuoto.

  • DJANGO_LOGLEVEL: lo abbiamo impostato su info, sentiti libero di regolarlo al livello desiderato.

  • DJANGO_ALLOWED_HOSTS: imposta questo valore sull'indirizzo IP del server Ubuntu che esegue i tuoi container Docker. Facoltativamente, impostalo su *, carattere jolly che corrisponde a tutti gli host se in modalità di sviluppo.

  • DB_DATABASE: se hai utilizzato un nome di database diverso, impostalo qui in modo appropriato.

  • 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 nel Passo Uno.

  • DB_PORT: impostalo sulla porta del tuo database.

  • STATIC_MINIO_BUCKET_NAME: impostalo sul nome del bucket che hai creato nel tuo account MinIO Cloud Storage.

Salva e chiudi il file quando hai finito di modificarlo.

Le configurazioni dell'ambiente sono ora pronte. Dobbiamo eseguire il container passando argomenti per sovrascrivere il comando CMD predefinito e creare lo schema del database utilizzando i comandi manage.py makemigrations e manage.py migrate.

Ecco il comando:

In questo comando, stiamo eseguendo l'immagine del container django-polls:v1, utilizzando il flag env-file per passare il file delle variabili d'ambiente. Sovrascriviamo anche il comando CMD predefinito con sh -c "python manage.py makemigrations && python manage.py migrate" Quando questo comando viene eseguito per avviare il container, creerà lo schema del database come definito nel codice dell'applicazione.

In caso di successo, dovresti vedere un output simile a quello qui sotto:

Building a Django and Gunicorn Application with Docker on Ubuntu 4

L'output indica che lo schema del database è stato creato con successo.

Il passaggio successivo consiste nel creare un utente amministratore per l'app Django. Avvieremo il container e apriremo una shell interattiva al suo interno con il seguente comando:

Il comando avvia il container con un prompt di shell che puoi utilizzare per interagire con la shell Python. Creiamo un utente:

Segui le istruzioni per fornire un nome utente, un indirizzo email, una password, reinserisci la password e premi invio per creare l'utente. Esci dalla shell e termina il container premendo CTRL+D.

Successivamente, dobbiamo eseguire nuovamente il container, sovrascrivendo il comando predefinito con il comando Django collectstatic per generare i file statici per l'app e caricarli sul tuo servizio di cloud storage MinIO:

Al termine, dovresti vedere un output simile a quello qui sotto, che indica che il tuo container si è connesso correttamente al servizio di storage MinIO e ha caricato i file statici:

static files

Il nostro bucket di archiviazione ora si presenta così, con le directory create da Django:

Building a Django and Gunicorn Application with Docker on Ubuntu 5

Infine, ora possiamo eseguire l'app con il comando:

Ecco l'output:

output

Quando si esegue il comando precedente, questo esegue il comando CMD predefinito nell'immagine ed espone la porta 8000 come definito. Ora, Ubuntu sulla porta 80 viene mappato sulla 8000 porta del django-polls:v1 container.

Ora possiamo testare l'applicazione nel browser. Vai all'indirizzo IP pubblico del tuo server nel browser: http://your_server_public_ip.

Aspettati di trovare un errore 404 Page Not Found, poiché come da Tutorial di Django, non abbiamo definito una rotta per / percorso:

page not found

Abbiamo la variabile DEBUG impostata su True, ecco perché vediamo questa pagina di errore con molte informazioni cruciali. Rimuoviamo l'impostazione della variabile DEBUG . Per prima cosa, dovrai arrestare il container in esecuzione con CTRL+C. Quindi, apri il file env :

Successivamente, trova la variabile DEBUG e rimuovila, oppure lasciala vuota. La lasciamo vuota perché la funzione getenv interpreta False come una stringa, restituendo quindi true:

Salva il file ed esegui nuovamente il container con il comando:

Se visiti questo http://your_server_public_ip nel tuo browser, dovresti vedere la pagina 404 predefinita:

not found

Hai visto come puoi manipolare il comportamento a runtime della tua app Django utilizzando le variabili d'ambiente, senza modificare il codice sorgente.

Naviga su http://your_server_public_ip/polls per vedere la home page di Polls:

Building a Django and Gunicorn Application with Docker on Ubuntu 6

Non ci sono sondaggi poiché abbiamo appena distribuito l'app.

Naviga sull'interfaccia di amministrazione: http://your_server_public_ip/admin per visualizzare la finestra di autenticazione dell'amministratore:

polls administration

Fornisci le credenziali che avevi impostato con il comando createsuperuser per accedere. Ora dovresti trovarti sull'interfaccia della pagina amministrativa:

site administration

Nota che tutti i file statici sono serviti dal servizio di archiviazione esterno che abbiamo configurato. Puoi fare clic con il tasto destro nella finestra del browser e selezionare Visualizza sorgente pagina:

external storage

Puoi aggiungere alcune domande e opzioni e testare le prestazioni generali dell'app:

questions and choices

Torna all'indice di Polls http://your_server_public_ip/polls e prova a votare per la domanda:

django framework

Dopo aver testato e confermato che tutto funziona come previsto, puoi terminare il container.

Conclusione

Hai configurato con successo un'app web Django per funzionare correttamente in un ambiente basato su container. Ciò ha comportato l'adattamento dell'app per funzionare con variabili d'ambiente esterne, l'impostazione dell'app per l'utilizzo di un servizio di cloud storage per i file statici e la creazione di un Dockerfile per l'immagine del container. Puoi visualizzare le modifiche che abbiamo apportato per dockerizzare l'app sul django-polls-docker branch del repository django-polls su GitHub.

Da qui, le possibilità sono limitate solo dalla tua immaginazione. Puoi configurare un reverse proxy Nginx da interporre tra i client e il server Gunicorn. Puoi anche aggiungere Certbot per ottenere certificati TLS per proteggere il tuo server Nginx. Consigliamo di aggiungere un proxy HTTP per fare da buffer ai client lenti e proteggere il tuo server Gunicorn da attacchi denial of service.

Anche se abbiamo definito 3 worker nel comando di avvio del Dockerfile, puoi impostare il numero che preferisci a seconda delle risorse disponibili sul tuo server. Puoi trovare maggiori informazioni sui documenti di progettazione ufficiali di Gunicorn. Se lo desideri, puoi caricare l'immagine Docker che hai creato su Docker Hub e provare a distribuirla su diversi ambienti in cui è installato Docker. Se desideri saperne di più, continua a seguire il nostro blog dei tutorial poiché realizzeremo un tutorial successivo per rendere sicura l'app Django con Nginx e Let's Encrypt.

Enfin, ecco altre risorse che ti aiuteranno a utilizzare Docker:

Buona programmazione!

author

Shreyas Patil

Autore · CloudSigma

Preslav Dobrev è un designer creativo presso CloudSigma, con un focus su un'identità aziendale coerente attraverso l'uso di canali di marketing tradizionali e innovativi. È abile nel fondere la visione artistica con il marketing strategico per creare narrazioni di brand di grande impatto.

Commenti

Ancora nessun commento. Scrivi il primo.