In questa sezione ci faremo un’idea di base del funzionamento di un container, in modo da avere gli strumenti minimi per affrontare il prossimo capitolo.

Faccio una premessa, io sono solito utilizzare il client per mac. I comandi sono gli stessi per tutti i sistemi operativi, tuttavia può capitare che qualche immagine non sia compatibile con alcuni sistemi operativi o architetture. Questo risulterà più chiaro nel prossimo capitolo ed in particolare quando parleremo di immagini ad architettura multipla e di manifest.

Lanciare il primo container

Iniziamo con l’istruzione di base, quella che permette di creare un container a partire da un immagine, ovvero:

docker container run

L’istruzione “docker container run”, istanzia un container a partire da una specifica immagine.

Coma da tradizione, quando si inizia a prendere confidenza con una nuova tecnologia, si comincia sempre con un “Hello world”. Guarda caso esiste proprio un’immagine che si chiama “hello-world”.

Eseguiamo quindi il comando

docker container run hello-world

Nel mio caso, l’output generato è il seguente:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:8e3114318a995a1ee497790535e7b88365222a21771ae7e53687ad76563e8e76
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Interpretazione dell’output

Le prime righe dell’output prodotto dal run sono particolarmente interessanti perché ci permettono di intuire cosa sta succedendo under-the-hood.

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:8e3114318a995a1ee497790535e7b88365222a21771ae7e53687ad76563e8e76
Status: Downloaded newer image for hello-world:latest


Come prima cosa vediamo che Docker cerca di recuperare l’immagine richiesta da un repository locale all’host, ma senza avere successo.
Per ora tralasciamo la stringa “:latest”, avremo modo di parlarne molto presto.
Dal momento che abbiamo richiesto un’immagine che non abbiamo mai scaricato prima d’ora, essa viene recuperata dal docker daemon, come vediamo dalla seconda riga.

Subito dopo viene indicato l’esito del pull dell’immagine, l’hash a 256 bit ottenuto mediante l’algoritmo di encryption sha256 e la conferma di avvenuto download.

Tutto ciò che segue è l’output del container che il demone di Docker inoltra al client a sua volta scrive sullo standard output.

Visualizzare i container

Abbiamo creato un container, ora verifichiamo i dettagli mediante il comando che fornisce la lista dei container presenti sul nostro host:

docker container ls

Eseguendo questa istruzione, però, ci accorgiamo di qualcosa di strano. La lista non mostra nessun container relativo all’immagine “hello-world”.

Com’è possibile?
Eppure lo abbiamo creato! Abbiamo anche visto l’output!

Quando abbiamo di questi dubbi, la soluzione più efficace è quella di utilizzare l’help del comando.

docker container ls --help

Ottenendo questo output

Usage:	docker container ls [OPTIONS]

List containers

Aliases:
  ls, ps, list

Options:
  -a, --all             Show all containers (default shows just running)
  -f, --filter filter   Filter output based on conditions provided
      --format string   Pretty-print containers using a Go template
  -n, --last int        Show n last created containers (includes all states) (default -1)
  -l, --latest          Show the latest created container (includes all states)
      --no-trunc        Don't truncate output
  -q, --quiet           Only display numeric IDs
  -s, --size            Display total file sizes

Ecco svelato il problema!
Eseguendo il comando nella versione di default, ovvero senza flag aggiuntivi, vengono mostrati solo i container in esecuzione.
Questo significa che il container che abbiamo appena creato non è più in esecuzione. Nel caso specifico, hello-world termina dopo aver stampato un messaggio.

Per vedere tutti i container, compresi quelli che non sono in esecuzione, dobbiamo aggiungere il flag –all o -a.
La nuova versione del comando diventa quindi:

docker container ls --all

Dalla quale otteniamo un output di questo tipo:

CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
9f0b8fee671c        hello-world         "/hello"            About a minute ago   Exited (0) About a minute ago                       laughing_visvesvaraya

Ora il container che abbiamo generato a partire dall’immagine hello-world è visibile.

Nella colonna status si nota che il container è terminato. Vedremo dettagliatamente questi aspetti nel capitolo dedicato ai container.

Eliminare un container

Sebbene in molti casi può risultare comodo avere accesso ai container terminati, questa volta non ce ne facciamo niente e decidiamo di eliminare il container appena creato.

Per eliminare il container non più in uso dalla lista, il comando è:

docker container rm 

Per container_id si intende il codice identificativo del container visibile nella prima colonna del comando docker container ls. In questo caso, l’istruzione da eseguire sarà:

docker container rm 9f0b8fee671c

In generale, quando viene richiesto di inserire il container id, è sufficiente inserire i primi caratteri dell’hash a condizione che non ci sia un altro container con lo stesso prefisso.

Container rimossi automaticamente

Quando sappiamo già non ci interessa persistere un container una volta terminato, possiamo eseguirlo direttamente con il flag –rm. Questa particolare notazione su occuperà di rimuovere automaticamente il container quando terminerà la sua esecuzione.
Nel caso di hello world avremmo potuto eseguire il comando in questo modo:

docker container run --rm hello-world

Start & Stop vs Pause & Unpause

Spesso si fa confusione tra questi comandi, ma il concetto è molto semplice.
Nel primo caso i container vengono terminati e riavviati. Nel secondo caso vengono “congelati” e “risvegliati”.

Per fermare e riavviare i container si usano:

docker container start
docker container stop

Per sospendere e risvegliare un container, invece si usano:

docker container pause 
docker container unpause

I comandi saranno sempre seguiti dal o dai container_id dei container ai quali vogliamo applicare l’istruzione.

Il modo migliore per spiegare cosa cambia tra i due è mediante un esempio. In questo caso ho scelto di utilizzare un’immagine di Alpine Linux sulla quale eseguirò lo start e lo stop di un container.
Pause e unpause hanno la stessa notazione, quindi eviterò di riportarli.

Proviamo con il primo set di comandi, ovvero container start e container stop. Dato che abbiamo imparato che i container possono terminare l’esecuzione appena dopo l’avvio, mettiamo le mani avanti e impostiamo uno sleep di 1000 secondi. In questo modo il container rimarrà in esecuzione per 1000 secondi prima di terminare.

Per impostare lo sleep è sufficiente digitare il comando ‘sleep 1000’ in coda all’istruzione di run.

docker container run alpine sleep 1000

Quando parleremo nel dettaglio di Dockerfile, la modalità con la quale viene invocato questo comando sarà più chiara, per ora immaginate che il comando specificato in coda al comando run, sovrascrive l’ultimo comando che eseguirebbe il container all’avvio.

Ora per 1000 secondi il terminale rimarrà occupato, poi, secondo il normale flusso, il container terminerà rilasciando il terminale.

Apriamo allora un nuovo tab e cerchiamo il nostro container in esecuzione con:

docker container ls

Questa volta vediamo che l container figura nella lista. Nel mio case il container id è ‘61652fca85f0′, quindi per fermare il container mi basterà digitare:

docker container stop 616

Al termine di questa istruzione il container non sarà più in esecuzione, quindi se proviamo a cercarlo con docker container ls non lo troveremo nella lista.

Per visualizzarlo, ci basterà eseguire il comando docker container ls –all, e lo troveremo con stato Exited(137) e non più Exited(0).
Per far ripartire il container, ci basterà eseguire:

docker container start 616

Eseguendo docker container ls vediamo che il container è attivo perché la colonna relativa allo stato riporterà: Up.

Curiosità

Abbiamo assimilato i rudimenti della CLI per quanto riguarda i container.
Dopo aver letto lo scorsa sezione, a qualcuno potrebbe venire la curiosità di sapere cosa succede sotto il cofano quando viene avviato un container.

Se ti riconosci in questa descrizione, le prossime righe sono per te!

Quando lanciamo il comando docker container run, il Client converte l’istruzione in una chiamata POST all’endpoint che il demone di Docker ha predisposto a tale scopo.
Il demone invoca containerd che che chiede a runc di istanziare il container.
runc crea il container e termina, mentre il processo containerd-shim diventerà il nuovo padre del container.


Prosegui su: Immagini

Immagine: Viaggio foto creata da freepik – it.freepik.com