Docker - parte 2

Pubblicato il mar 21 maggio 2019 in tutorial • 3 min read

Nella prima parte abbiamo parlato dei concetti base e dell'installazione di Docker Community Edition (CE).

Adesso creeremo una docker image che dovrebbe far girare una semplice web application basata su Flask, un micro-framework in Python.

Prerequisiti

Prima di addentrarci nella creazione della Docker image vera e propria dobbiamo parlare brevemente della web application in Flask.

Per risolvere le dipendenze della web application, creeremo un file di testo che chiameremo requirements.txt; esso conterrà i moduli necessari per eseguire l'applicazione. In questo caso i moduli necessari sono il web framework Flask stesso e il server web WSGI HTTP Gunicorn (Green Unicorn) basato anche'esso su Python.

Ecco il contenuto del file requirements.txt:

Flask==1.0.2
gunicorn==19.9.0

A questo punto possiamo creare la web application. Esse consisterà di un solo file myapp.py. Questa web application stamperà a video Hello World quando visiteremo col browser la directory radice ( / ) del web server che gira all'interno del container. Ecco il file myapp.py:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

Servirà inoltre un altro file (wsgi.py) col seguente contenuto:

from myapp import app

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

Il file wsgi.py è quello che, in seguito, verrà eseguito dal server WSGI gunicorn.

Creare il Dockerfile

Sebbene esistano delle immagini Docker predefinite, che includono già un ambiente Python pronto all'uso, nel nostro caso provederemo a creare una nuova immagine Docker da zero basata su Alpine Linux. Questa è una distribuzione Linux estremamente leggera ed ottimizzata per essere utilizzata come immagine base per i container Docker.

Questa scelta ci permetterà, da un lato, di creare un immagine Docker totalmente personalizzabile; dall'altro, ci consentirà di ridurre le dimensioni dell'immagine finale rispetto ad altre immagini Docker basate su altre distribuzioni Linux. Ecco il dockerfile:

FROM alpine:3.9
MAINTAINER Your Name <your.name@example.com>

ENV APP_PATH /myapp

WORKDIR ${APP_PATH}
COPY . ${APP_PATH}

RUN apk update \
  && apk add --no-cache python3 \
  && pip3 install --no-cache-dir --upgrade pip \
  && pip3 install --no-cache-dir -r requirements.txt

EXPOSE 8000

CMD ["gunicorn", "-b 0.0.0.0:8000", "-w 4", "wsgi:app"]

Analizziamo brevemente il dockerfile.

  • FROM -- definisce l'immagine Docker padre che serve come base per la nostra immagine. Nel caso in cui sarà necessario eseguire immagini multiple dello stesso container è bene assicurarsi che l'immagine base sia leggera in termini di spazio utilizzato.
  • MANTAINER -- Definisce il mantainer dell'immagine. E' semplicemente un modo per inserire le informazioni necessarie a contattare il creatore del Dockerfile.
  • ENV -- Definisce le variabili di ambiente che saranno richiamate in seguito dai comandi presenti nel Dockerfile. Tali variabili potranno, eventualmente, essere sovrascritte in seguito al momento dell'esecuzione del container passandole come argomenti al comando docker. Ad esempio: -e env_var_name=another_value. Questo aumenta la flessibilità dell'immagine Docker.
  • WORKDIR -- imposta la directory di lavoro per tutti i comandi, inclusi RUN, CMD, ENTRYPOINT, COPY e ADD. La directory di lavoro verrà creata automaticamente se non esiste.
  • COPY -- copia files o directories del sistema host nel container; in questo caso copia tutto il contenuto della directory corrente nel container.
  • RUN -- Specifica quali comandi verranno eseguiti nell'immagine. Ciascun RUN creerà un nuovo layer nell'immagine. A causa di ciò è consigliabile ragguppare il più possibile i comandi in modo da ridurre il numero di layer creati e quindi la dimensione finale dell'immagine.
  • EXPOSE -- Rende una porta del container disponibile all'esterno. Questo permette all'host di raggiungere via rete il container.
  • CMD -- Definisce un comando che è eseguito nella WORKDIR. Nel nostro caso la web application è avviata utilizzando gunicorn.

Adesso possiamo creare l'immagine Docker con il comando:

$ docker build -t myapp .

Il dockerfile presente nella directory specificata (nel nostro caso: " . ") verrà utilizzato per creare l'immagine. Bisogna assicurarsi di utilizzare un tag, tramite il parametro -t per indicare un'etichetta o label in modo da assegnare all'immagine un nome univoco.

Il risultato di questo comando sarà una immagine Docker molto leggera, circa 60 MB.

Dopo la creazione del container, possiamo eseguire l'immagine in questo modo:

docker run -p 8000:8080 myapp

La porta 8000 esposta dal container verrà resa disponibile sull'host alla porta 8080.

Per testare che il container stia funzionando correttamente basta, a questo punto, visitare col browser l'indirizzo http://127.0.0.0:8080/.

A presto.