L’altruistico obiettivo che mi ha motivato a realizzare questo tutorial è insegnarti ad eseguire il deploy di una web app realizzata con Flask su di una macchina virtuale AWS EC2, senza chiederti nulla in cambio.

No, non è vero, il vero motivo è che mi scordo continuamente i passaggi, quindi ho bisogno di un riferimento, e dato che lo devo scrivere, perché non renderlo disponibile anche per te?

Vabbè dai cominciamo.

0: Vediamo il progetto

Il progetto che andremo a deployare è disponibile su questa repository Github.

L’app non fa altro che stampare due righe, queste qui:

Sì lo so, è proprio una cosa stupida, ma il processo di deploy è lo stesso indipendentemente da dimensione e complessità del progetto.

Se hai un minimo di conoscenza di Flask non avrai nessun problema a seguirne il codice, fai solo attenzione al file wsgi.py:

from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run()

Questo è il file principale, quello da eseguire per avviare la nostra applicazione, che tipicamente si chiama app.py, il nome wsgi.py ha un significato ben preciso che scopriremo più avanti.

1: Creiamo l’istanza EC2

Come ti ho già detto, per questo tutorial utilizzerò un’istanza EC2 (Elastic Cloud Computing), il servizio di AWS per creare macchine virtuali in cloud, se preferisci puoi utilizzare qualsiasi altro servizio che già conosci e passare direttamente alla prossima parte.

Vai su aws.amazon.com e crea un nuovo account se non ne hai già uno (penso che sia possibile usare anche il proprio account amazon).

Se hai la dashboard in inglese, puoi scegliere di cambiare la lingua in Italiano dal menu a tendina in basso a destra (basta che non pensi di poter fare il programmatore senza conoscere l’inglese eh).

Dalla barra di ricerca in alto cerca EC2 e clicca sul risultato.

Ora ti dovresti trovare nella dashboard di EC2, andiamo a creare una nuova macchina, clicca sul tasto arancione Avvia Istanza.

Andiamo a configurare la nostra nuova macchina. Prima cosa da scegliere, il sistema operativo, questo tutorial è stato testato solo su Ubuntu Server, selezioniamo l’ultima versione disponibile (al momento attuale la 20.04).

Ora andiamo a scegliere il tipo di macchina, inteso come configurazione di hardware. Il progetto che andremo a deployare è così semplice che anche una t2.nano dovrebbe andar bene, ma se il tuo account AWS è nuovo di zecca, allora hai a disposizione 750 ore di utilizzo gratuito di una t2.micro. Andiamo di micro dai.

Possiamo lasciare tutto di default per quanto riguarda il “Configura l’istanza” e passare allo storage, anche in questo caso, trattandosi di un progetto babbo babbo gli 8gb di default dovrebbero bastare.

Avanti, i tag, mai capito a che servono, andiamo avanti.

Questo è importante, il gruppo di sicurezza, dobbiamo abilitare il traffico in entrata sulle porte 80 (HTTP) e 443 (HTTPS), farlo è semplice, basta cliccare su aggiungi regola e selezionare il tipo del servizio.

Bomba, abbiamo finito, vai avanti e premi su lancio.

Ci viene chiesto di creare una nuova coppia di chiavi o di selezionarne una esistente, fai estremamente attenzione, le chiavi servono per poter accedere alla macchina tramite connessione SSH, se le perdi sono cavoli amari.

Creiamone una nuova, nel mio caso la ho chiamata “helloflaskkey”, e clicca su “Scarica la coppia di chiavi”, tienila in un posto sicuro e fai attenzione a non perderla.

Per terminare clicca su “Avvia le istanze”.

Ci siamo, clicchiamo su Visualizza le istanze e refreshiamo compulsivamente fino a quando lo stato non sarà divento “In esecuzione”. Diamo un nome alla macchina, per farlo basta cliccare alla destra del – sotto la colonna Name.

2: Creare un’IP statico

Okay, ora prima di collegarci alla macchina dobbiamo fare una cosa importante, le istanze EC2 hanno IP dinamici, questo vuol dire che cambiano ad ogni riavvio, e come ben saprai se abbiamo domini associati alla nostra macchina, questo spacca tutto.

Fortunatamente possiamo crea un’IP statico e assegnarlo all’istanza in maniera molto semplice.

Dalla barra di sinistra scendi fino a Reti e sicurezza e da qui seleziona IP elastici.

Clicca su Assegna indirizzo IP elastico.

Dalla nuova pagina, lascia pure tutto così com’è e clicca su Assegna. Okay, abbiamo creato l’IP, ora associamolo alla nostra istanza, clicca sull’IP (colonna indirizzo IPv4 allocato).

Dalla nuova finestra clicca su Associa indirizzo IP elastico.

Selezioniamo l’istanza a cui assegnare l’IP, se ne abbiamo più di una possiamo riconoscere la nostra dal nome che gli abbiamo dato prima.

Clicchiamo sul tasto Associa in basso a destra ed il gioco è fatto, ora la nostra macchina ha un IP Statico.

3: Colleghiamoci all’istanza tramite SSH

Ci siamo quasi, ora dobbiamo collegarci alla macchina, torniamo all’elenco delle istanze, selezioniamo la nostra e clicchiamo sul tasto Connetti in alto.

AWS ci mostra diverse opzioni per collegarci all’istanza, utilizzando EC2 Instance Connect puoi aprire un terminale direttamente nel browser con un semplice click, però a me funziona una volta no e l’altra manco a parlarne, quindi preferisco usare un mio client SSH.

Scegli tu come collegarti, inutile che mi metto a scrivere roba qui, Amazon spiega tutto passo passo.

5: Prerequisiti e dipendenze

Se hai fatto tutto correttamente, ora dovresti essere connesso alla macchina virtuale tramite connessione SSH.

Prima di fare qualsiasi cosa, aggiorniamo liste di pacchetti e repository:

sudo apt-get update

Bene, ora installiamo tutti i software necessari:

sudo apt-get install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools python3-venv

Ora cloniamo la web app babba che andremo a deployare

git clone https://github.com/gfgullo/HelloFlask.git

e spostiamoci dentro la cartella appena scaricata.

cd HelloFlask

Qui dentro, creiamo l’ambiente virtuale con virtualenv e attiviamolo

python3 -m venv venv
source venv/bin/activate

Ora possiamo installare i vari moduli Python che ci serviranno (che in realtà sono solo Flask e flask-bootstrap giusto perché l’ho voluto mettere). Dato che a noi piace fare le cose per bene, installiamo le dipendenze dal file requirements.txt che si trova dentro HelloFlask.

pip install -r requirements.txt

Per verificare che funzioni tutto correttamente, possiamo provare ad eseguire il file wsgi.py.

python wsgi.py

Dovresti vedere questo:

6: Andiamo su internet

Sembra funzionare tutto correttamente, Flask è avviato su un server di sviluppo locale, ora tocca metterlo su internet.

Per comunicare con un web server Python utilizza un protocollo chiamato WSGI (questo spiega il nome del file principale), quindi ci tocca utilizzare un web server che supporti questo protocollo, io, come tanti, utilizzo gunicorn. Installiamolo.

pip install gunicorn

Non possiamo usare la porta 80 in maniera rapida con gunicorn, perché porte <1024 richiedono il superuser.

Quindi per questo test veloce apriamo la 8080.

Torniamo sulla lista delle istanze EC2, selezioniamo la nostra, dal menu in basso andiamo in Sicurezza -> Dettagli di Sicurezza e clicchiamo sul valore di Gruppi di sicurezza.

Clicchiamo su Modifica le regole in entrata.

Ora aggiungiamo una nuova regola, inserendo in intervallo porte il valore 8080 e in origine 0.0.0.0/0, questa consentirà il traffico in entrata sulla porta 8080 da tutti gli indirizzi IP.

Clicchiamo su Salva regole e torniamo al terminale SSH.

Per avviare gunicorn sulla porta 8080 e fargli girare le richieste alla nostra app flask basta digitare questo comando:

gunicorn --bind 0.0.0.0:8080 wsgi:app

Fai attenzione ad una cosa, wsgi è il nome del file principale (wsgi.py), quello in cui l’app viene avviata con il metodo .run(), mentre app è il nome dell’oggetto Flask dentro wsgi.py.

In caso di successo vedrai questo output.

Questo vuol dire che la tua app è già su internet, e puoi navigarci da INDIRIZZO_IP_STATICO:PORTA, nel mio caso 52.32.237.47:8080

Tutto bello, ma non è questo il modo di deployare un’app Flask, infatti gunicorn è estremamente limitato come web server, non ha neanche il supporto ai file statici o al protocollo HTTPS.

Ora vedremo una soluzione migliore, ma prima ricordati di chiudere la porta 8080, non lasciamo porte aperte se non le utilizziamo, non siamo mica al Colosseo !

7: Andiamo giù di NGINX

Una soluzione migliore per servire la nostra app potrebbe essere il mio amato NGINX, che è decisamente più completo, robusto e production-friendly, però non supporta il protocollo WSGI, quindi non ha modo di comunicare con Python.

Che facciamo allora ?

Usiamo entrambi !

  • Usiamio NGINX come reverse-proxy: accetterà le richieste in entrata, in caso di file statici (CSS, Javascript, file multimediali ecc…) li servirà direttamente, altrimenti girerà la richiesta a Gunicorn tramite un socket locale.
  • Usiamo Gunicorn come application server: starà nel mezzo tra NGINX e Flask, ricevendo le richieste dal primo e inoltrandole al secondo tramite protocollo WSGI.

Complicato eh ? In realtà è più difficile a dirsi che a farsi.

Installiamo NGINX:

sudo apt-get install nginx

8: Creiamo un servizio per Gunicorn

Creiamo un servizio che avvii Gunicorn al boot del sistema, puoi trovare il file del servizio in deploy/helloflask.service.

[Unit]
Description=Gunicorn instance to serve a flask app
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/HelloFlask
Environment="PATH=/home/ubuntu/HelloFlask/venv/bin"
ExecStart=/home/ubuntu/HelloFlask/venv/bin/gunicorn --workers 1 --reload --bind unix:helloflask.sock -m 007 wsgi:app

[Install]
WantedBy=multi-user.target

ExecStart è il comando che il servizio esegue, questo è molto simile a quello che abbiamo già lanciato, ci sono alcune differenze:

  • unix:helloflask.sock: leghiamo gunicorn ad un socket chiamato helloflask.sock
  • -m 007 specifichiamo i permessi sul socket, in modo tale che solo l’autore ne abbia l’accesso.
  • –reload: diciamo a gunicorn di riavviarsi nel caso in cui il codice dell’app subisca delle modifiche.
  • –workers 1: indichiamo un solo worker, e quindi fissiamo ad 1 il numero di richieste gestibili in parallelo.

Perfetto, ora per attivare il servizio prendiamolo e copiamolo in /etc/system/systemd/

sudo cp deploy/helloflask.service /etc/systemd/system/helloflask.service

Ora abilitiamo il servizio e avviamolo utilizzando systemctl

sudo systemctl enable helloflask
sudo systemctl start helloflask

Se hai fatto tutto correttamente dovresti ora avere un file chiamato helloflask.sock dentro la cartella del progetto.

Inoltre, con systemctl status helloflask possiamo monitorare lo stato del servizio e leggere eventuali log di flask, questo è l’output corretto:

Il servizio c’è ! Passiamo a NGINX.

9: Creiamo un server block per NGINX

Se hai familiarità con Apache, i server block di NGINX sono l’equivalente dei suoi virtual host, possiamo utilizzarli per configurare ed utilizzare più domini sotto uno stesso server.

Trovi il file di configurazione del server block in deploy/helloflask, eccolo qui:

server {
    listen 80;
    server_name helloflask.profession.ai www.helloflask.profession.ai;

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/ubuntu/HelloFlask/helloflask.sock;
    }
}

studiamolo un po’:

  • listen è la porta in cui mettersi in ascolto, che ovviamente è l’80.
  • server_name sono uno o più domini associati a questo server block, nota che per configurare un tuo dominio (o un sottodominio come nel mio caso) devi utilizzare il tuo gestore di DNS per aggiungere un record A che punti all’IP della macchina virtuale. Nel mio caso uso Route53 sempre di AWS.

Se non hai un dominio, puoi inserire come server_name l’indirizzo IP della macchina, oppure il DNS IP che ti ha assegnato AWS.

  • in proxy_pass specifichiamo l’indirizzo del socket a cui passare le richieste, che è quello creato in precedenza dal servizio di gunicorn.

Dai, ci siamo quasi.

Copiamo il file di configurazione del server block in /etc/nginx/sites-available

sudo cp deploy/helloflask /etc/nginx/sites-available/helloflask

Per abilitarlo creiamo un link simbolico in /etc/nginx/sites-enabled/

sudo ln -s /etc/nginx/sites-available/helloflask /etc/nginx/sites-enabled/

Ora l’IP della macchina punta alla root di NGINX, quindi, collegandoci direttamente all’IP dovremo vedere la pagina di default di NGINX, questa qui:

Invece, se proviamo a collegarci ai domini che abbiamo configurato nel server block, dovremo vedere la nostra applicazione.

10: Generiamo e installiamo un certificato SSL

Dove vai se un certificato SSL non ce l’hai ?

Da nessuna parte, specialmente nel 2021.

Recentemente ho scoperto un tool fighissimo che si occupa di generare ed installare certificati SSL in maniera totalmente automatizzata. Il tool si chiama certbot, installiamo la versione per NGINX:

sudo apt-get install python3-certbot-nginx

Una volta installato, possiamo avviare la procedura di generazione del certificato con:

sudo certbot --nginx -d METTI_QUA_IL_TUO_DOMINIO -d www.METTI_QUA_IL_TUO_DOMINIO

Se va tutto per il verso giusto, dovresti vedere questo messaggio.

Proviamo a tornare sul sito web

Certbot non solo ha generato il certificato, ma lo ha anche installato per noi, ed ora la connessione risulta sicura.

TOP !

About Giuseppe Gullo

Cresciuto a pane e bit, ho cominciato a programmare a 13 anni, durante un periodo di convalescenza forzata dovuta ad un brutto incidente.

Durante la mia adolescenza ho utilizzato un mio approccio hacker all'apprendimento per passare da un argomento all'altro senza sosta, sviluppo web, programmazione software, sviluppo mobile per android ed iOS, sviluppo di videogame 2d e 3d con Unity.

Poco più che ventenne mi sono avvicinato all'intelligenza artificiale, ed è stato amore a prima vista.

Ho lavorato come sviluppatore indipendente e freelancer, creando diverse dozzine di servizi che hanno raggiunto centinaia di migliaia di persone in tutto il mondo.

Il mio life goal è riuscire a sfruttare le enormi potenzialità dell'AI per migliorare le condizioni di vita degli esseri umani.