Una breve e veloce introduzione a Docker e tutte le funzionalità principali che offre, con alcuni esempi pratici. Insomma, una serie di motivi sul perché dovresti usarlo.
A cosa serve la virtualizzazione?
In generale le applicazioni si trovano ad interagire con il sistema operativo, l’hardware ed altri processi.
Se anche solo uno di questi elementi cambia, il software potrebbe non funzionare come previsto.
Macchine virtuali
Le Macchine virtuali (VMs) rappresentano una maniera efficace per migliorare la riproducibilità.
Usano un software estremamente complesso, l’hypervisor, per emulare fedelmente hardware e sistema operativo. Sono (quasi) completamente indipendenti dal sistema host su cui vengono eseguite.
Containers
I Containers sono una alternativa più leggera alle VMs.
Utilizzano alcune funzionalità specifiche del kernel Linux, ovvero namespaces e cgroups, per isolare completamente un processo dal resto del sistema. Proprio per questo non possono emulare un kernel o hardware diversi.
Docker
Viene offerto anche un servizio di registry dove trovare immagini pre-costruite.
Installazione
Poiché Docker è stato progettato specificamente per il kernel Linux, non è nativamente supportato su Windows e macOS. Per poter essere eseguito su queste piattaforme è necessario avviare una macchina virtuale con Linux.
Docker Desktop gestisce tutte queste complicazioni in maniera trasparente.
- Windows
- Mac
- Linux
- Docker Desktop (userà una VM)
- Docker Engine
Immagini
Un’immagine è un template di sola lettura con le istruzioni per creare un container Docker.
Sono composte da layers che vengono impilati l’uno sull’altro, come i commit di git.
Solo il layer superiore è scrivibile, mentre quelli inferiori sono immutabili e protetti da modifiche attraverso l’uso di hash.
Dockerfile
I Dockerfiles contengono le istruzioni per costruire un’immagine.
FROM python:3.9.7-slim-buster # Image di base
WORKDIR /app # Working directory (se non esiste viene creata)
COPY requirements.txt requirements.txt # Copia i file da hosta al container
RUN pip install -r requirements.txt # Esegue un comando nel container
COPY . . # Copia i file da host al container
ENTRYPOINT ["python", "main.py"] # Esegui un comando quando lanci il container
Costruire un’immagine
# Costruisce un'immagine dal Dockerfile nella directory corrente
# docker build -t <image-name>:<tag> <context>
# -t: assegna un tag all'immagine
# .: usa la cartella attuale come contesto
docker build -t my-image:latest .
Esegue un container
# Esegue un container da un'immagine
# docker run <image-name>:<tag>
# -d: esegue il container in modalità detached, ovvero in background
# -p: inoltra le porte del container all'host
# --name: nome del container
# --rm: rimuove il container non appena questo si ferma
docker run -d -p 5000:5000 --name my-container --rm my-image:latest
Memorizzazione dei dati
All’interno di un container, la memoria è effimera e viene distrutta quando il container viene rimosso.
Per fare in modo che i dati persistano si utilizzano volumi e bind mounts.
Trasferire file nel container
- Li si copia direttamente nell’immagine. I file fanno parte di uno dei suoi layer e sono indipendenti da quelli ancora presenti sull’host.
# COPY <src> <dest>
# ADD ha la stessa sintassi, ma <src> può essere un URL o un file tar
COPY requirements.txt requirements.txt
COPY . .
- Si usa una bind mount. In questo caso la connessione fra il filesystem del container e quello dell’host è bidirezionale e continua.
# docker run -v <host-path>:<container-path>
docker run -v $(pwd):/app
Porte
Le ports sono un modo per esporre all’esterno la rete e le socket del container.
Perché ciò avvenga, è necessario utilizzare la flag -p
nel momento in cui si avvia il container.
Visualizzazione delle porte
Loading diagram...
# docker run -p <host-port>:<container-port> <image-name>
docker run -p 8000:80 my-web
docker run -p 3306:3306 my-database
Docker compose
Docker compose è lo strumento per definire e lanciare della configurazioni Docker con più container.
L’architettura viene descritta in maniera dichiarativa in un file YAML, suddiviso nei servizi che compongono l’applicazione. È possibile indicare anche volumi e reti, che verranno create automaticamente.
Persino quando si ha a che fare con un container singolo, avere un file docker-compose.yml
permette di visualizzare e modificare facilmente la sua configurazione.
docker-compose.yml
version: "3.9" # Versione di docker compose
services: # Lista di servizi
db: # Nome del servizio
image: mysql # L'immagine viene presa da Docker Hub
volumes:
- db-data:/var/lib/mysql # Crea un volume e lo monta per memorizzare i dati
environment: # Imposta le variabili d'ambiente
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: example
MYSQL_USER: example
MYSQL_PASSWORD: example
web: # Nome del servizio
build: . # Costruisce l'immagine dal Dockerfile nella directory corrente
ports: # Elenco di porte da esporre
- "5000:5000"
volumes: # Lista dei volumi da montare.
- .:/app # Bind mount della directory corrente
depends_on: # Lista di servizi da avviare prima di questo
- db
volumes: # List di volumi
db-data: # Nome del volume
Esempi
Tre esempi di come usare Docker in contesti diversi:
- Distribuire uno script Haskell
- Eseguire Qiskit in un notebook Jupyter
- Architettura Web con PHP e MySQL
Distribuire uno script Haskell
Hai uno script Haskell che vuoi distribuire ad altre persone. Loro potrebbero non avere l’intera toolchain Haskell installata o potrebbero non essere familiari con essa. Docker fornisce portabilità con facilità.
# https://hub.docker.com/_/haskell
FROM haskell:slim # Immagine base
COPY main.hs . # Copia lo script dentro la cartella di lavoro
RUN ghc -o main main.hs # Compila lo script
ENTRYPOINT ["./main"] # Esegue lo script quando il container viene avviato
docker build -t my-haskell-script . # Crea l'immagine
docker run -it --rm my-haskell-script # Crea ed avvia il container
Eseguire Qiskit in un notebook Jupyter
Vuoi eseguire un notebook Jupyter con Qiskit su una macchina su cui non hai intenzione di installare Python e tutte le dipendenze necessarie. Docker fornisce un ambiente isolato e pronto all’uso senza sporcare il sistema host
# https://quay.io/repository/jupyter/datascience-notebook
FROM quay.io/jupyter/datascience-notebook # Immagine base ufficiale
USER root # Passa ad utente root
COPY requirements.txt . # Copia il file dei requisiti
RUN pip install --no-cache-dir -r requirements.txt # Installa i requisiti
docker build -t jupyter-qiskit-example . # Build the image
docker run -it --rm \ # Create and run the container.
-p 8888:8888 \ # Forward port 8888 to the host
-v $(pwd)/notebooks:/home/jovyan/work \ # Bind mount the notebooks directory
-e NB_UID=$(id -u) -e NB_GID=$(id -g) -e GRANT_SUDO=yes \ # Env vars
jupyter-qiskit-example
Architettura Web con PHP e MySQL
Vuoi eseguire un’applicazione PHP con un database MySQL.
Loading diagram...
docker compose up -d # Esegue l'applicazione in modalità detached
docker compose down # Ferma l'applicazione
docker volume prune # Rimuove i volumi non utilizzati
Comandi
Una collezione di comandi comuni che potrebbero tornare utili
Comandi per le immagini
# Crea un'immagine da un Dockerfile
docker build -t <image-name>:<tag> <context>
# Elenca tutte le immagini
docker images
# Rimuove un'immagine
docker rmi <image-name>:<tag>
# Rimuove tutte le immagini
docker rmi $(docker images -q)
# Rimuove tutte le immagini non utilizzate
docker image prune
Comandi per la creazione di container
# Crea e avvia un container da un'immagine
# docker [OPZIONI] run <image-name>:<tag>
# (alcune) OPZIONI:
# -it: esegue in container in modalità interattiva con il terminale
# -d: esegue il container in modalità detached, ovvero in background
# -p: inoltra le porte del container all'host
# --name: nome del container
# --rm: rimuove automaticamente il container non appena questo si ferma
# -v <host-path>:<container-path>: monta una directory dell'host in una del container
# -v <volume-name>:<container-path>: monta un volume in una directory del container
# --env-file <file>: carica le variabili d'ambiente da un file
# -e <key>=<value>: imposta una variabile d'ambiente
# --entrypoint <command>: sovrascrive il comando di default del container
docker run -d -p 5000:5000 -v my-volume:/app --name my-container my-image:latest
docker run -it -v ./app:/app my-image:latest
docker run -e MYSQL_PASSWORD=pass --rm my-image:latest
docker run -it --entrypoint "/bin/bash" my-image:latest
Comandi per la gestione dei container
# Elenca tutti i container in esecuzione
docker ps
# Elenca tutti i container
docker ps -a
# Interrompe un container
docker stop <container-name>
# Interrompe tutti i container
docker stop $(docker ps -q)
# Rimuove un container
docker rm <container-name>
Comandi per l’interazione con i container
# Esegue un comando in un container
docker exec <container-name> <command>
# Attacca un terminale interattivo ad un container
docker exec -it <container-name> /bin/bash
# Mostra i log di un container
docker logs <container-name>
# Mostra i log di un container in tempo reale
docker logs -f <container-name>
Comandi per i volumi
# Crea un volume
docker volume create <volume-name>
# Elenca tutti i volumi
docker volume ls
# Rimuove un volume
docker volume rm <volume-name>
# Rimuove tutti i volumi non utilizzati
docker volume prune
Comandi di Docker compose
# Avvia la configurazione in background
docker compose up -d
# Forza il rebuild di tutte le immagini
docker compose up --build
# Avvia un singolo servizio
docker compose up <service-name>
# Interrompe l'applicazione
docker compose down
# Elenca tutti i servizi dell'applicazione
docker compose ps