Introduzione a Docker
Cosa è
Docker è un sistema di "containerizzazione" che punta a costruire uno stack tecnologico virtuale per far girare le applicazioni allo stesso modo in qualunque contesto di delivery.
La virtualizzazione avviene a livello di kernel di sistema operativo quindi risulta molto più leggero un docker rispetto ad una macchina virtuale: virtualizza solo ciò che è necessario al funzionamento dello stack anziché virtualizzare l'intero sistema operativo.
Con stack tecnologico intendo l'insieme di applicazioni server, librerie e componenti software in generale necessarie a far funzionare una certa applicazione.
Docker è stato sviluppato per semplificare il delivery di applicazioni complesse e supporta le architetture a microservizi.
Docker si presenta come un demone che va installato sulle macchine che devono far girare i "docker". Esistono dei meccanismo di bilanciamento per garantire l'affidabilità e la continuità del servizio oltre a gestire al meglio il carico rappresentato per l'infrastruttura hardware sottostante. Questi ultimi aspetti non verranno coperti qui.
Installazione
Docker va scaricato ed installato nella versione adeguata al proprio sistema operativo. Qui uso Linux che, al momento, lo supporta al meglio, qui installo insieme docker e docker-compose:
$
sudo apt install docker.io docker-compose
ATTENZIONE AGGIORNAMENTO: il compose che prima si installava scaricando la versione adeguata ora lo si può installare con l'istruzione precedente. Lascio questa parte per esigenze specifiche magari su vecchie versioni-
Un'altra componente da installare è il docker compose stado attenti a rispettare la compatibilità fra le versioni riportata in questa tabella:
Qui l'installazione della versione 1.26.2.
Scaricare il pacchetto:
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
Renderlo eseguibile:
sudo chmod +x /usr/local/bin/docker-compose
Creare un link nella directory /usr/bin
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
Verifica della versione appena installata:
$ docker-compose --version
docker-compose version 1.26.2, build 1110ad01
Uso base
Per usare docker, oltre ad installarlo, è necessario scaricare o creare una "immagine". L'immagine è un "container" pronto, composto dalle componenti necessarie a far girare la nostra applicazione. Oltre a comporre da se i propri docker se ne possono scaricare di già pronti da internet e partire da quelli per costruire quello più adatto alle proprie esigenze.
Ai comandi seguenti premettere "sudo" se la distribuzione usata richiede l'installazione come superutente.
Per conoscere la versione di docker installata:
Per conoscere quali immagini docker sono presenti sul mio sistema:
Ai comandi seguenti premettere "sudo" se la distribuzione usata richiede l'installazione come superutente.
Per verificare la presenza e lo stato del demone:
$ sudo systemctl status docker
$ docker version
Per conoscere quali immagini docker sono presenti sul mio sistema:
$ docker image ls
Per eseguire una immagine docker:
$ docker run hello-world
$ docker container ls
(oppure: $ docker ps)Per fermarne uno:
$ docker container stop CONTAINER_ID
Per cercare e scaricare i docker ci si può rivolgere al repository ufficiale:
Scarichiamo un docker dimostrativo: "hello world":
$ docker pull hello-world
Dopo aver scaricato l'immagine chiedendo la lista delle immagini disponibili dovrebbe comparire. Eseguendo lo specifico docker si avrà l'esecuzione immediata di questo.
Quindi se si ha bisogno di una componente software per la propria applicazione la si può cercare nel repository e scaricarla, lanciarla e si avranno immediatamente disponibili tutti i servizi implementati dalla specifica immagine.
Componenti
Come tutte le soluzioni moderne anche docker ragiona per componenti. Ad esempio l'immagine con Tomcat ha bisogno, per funzionare, di una serie di componenti, delle quali è dipendente, a più basso livello che scaricherà se non sono disponibili nel nostro repository locale. Ad esempio provando a scaricare l'immagine di Tomcat:$ docker pull tomcat
Qui si vede come l'invocazione del comando per vedere la lista delle immagini locali all'inizio non comprendeva l'immagine di Tomcat. L'invocazione del "pull" di Tomcat ha generato una serie di pull automatiche per scaricare tutte le immagini delle quali quella di Tomcat dipende ed, infine, il pull di Tomcat che ora è disponibile.
Usiamolo
Ora proviamo ad usare un docker lanciando l'immagine di Tomcat, ipotizziamo di avere già un servizio attivo sulla macchina host sulla porta 8080 (la porta di default di Tomcat) e quindi di avere la necessità di spostare il servizio sulla porta 9090. Il comando sarà:
$ docker run -p 9090:8080 tomcat
Il parametro "-p" consente di mappare una porta interna con una porta esterna. Mi è successo che usando un docker con Eclipse Che non ho dovuto specificare il parametro -p e il servizio funzionava perfettamente se interrogato da un host esterno. Con il docker di MongoDb ho dovuto specificare:
-p27017:27017
Perché funzionasse altrimenti mi rifiutava le connessioni.
se si assegna un nome all'istanza essa sarà referenziabile anche "fra" docker. Ad esempio il docker di Odoo richiede il servizio Postgres, questo viene chiamato con l'opzione --name db. Il docker di Odoo l'ho configurato con il --name odoo. Per lanciarli basterà iniziare con postgres:
$ sudo docker start -a db
e poi
$ sudo docker start -a odoo
Per recuperare tutte le informazioni su un container può risultare utile il comando:
$ sudo docker inspect CONTAINER_ID
Per verificare quali container sono in esecuzione sulla macchina locale:
$ sudo docker ps -a
Se voglio ottenere il servizio, implementato dal docker container che sto per lanciare, sull'ip della macchina host sulla quale lancio il docker (e non sull'ip della rete virtuale del docker daemon):
$ sudo docker run --network host CONTAINER_ID
Se i nostri container devono sempre essere tirati su all'avvio del sistema, anzi in effetti lo farebbero al riavvio del demone, va impostato il parametro "restart"
$ sudo docker run --network host --restart always CONTAINER_ID
Persistenza
Un aspetto di sicuro interesse è come rendere persistente su disco ciò che viene creato con una applicazione in un docker. Perché si pone questo problema ? I docker hanno una parte "statica" (dove vengono impacchettate le applicazioni e gli script sulle dipendenze e l'avvio di queste) ed una parte "dinamica". Nella parte dinamica è possibile scrivere: tiro su un docker con "Eclipse Che" scarico un progetto da GitHub, ci lavoro su. Salvo le modifiche al codice, compilo e funziona tutto. Se fermo il docker e lo rilancio troverò "Eclipse Che", perchè era pre-installato nel docker, ma del progetto scaricato da GitHub non ci sarà traccia, ne tanto meno delle mie modifiche.
Questo perché un docker non ha persistenza interna. Per rendere persistenti i "prodotti" ottenuti dal docker, file, db, immagini o qualunque altra cosa, ho bisogno di fargli scrivere sul file system della macchina host ospitante.
$ docker run --name myMongoDb --volume /my/own/datadir:/data/db -d mongo
Genera una istanza di MongoDb con nome myMongoDb e fa in modo che la director nella quale il MongoDb scrive i dati (/data/db) sia rimappata nella directory della macchina host "/my/own/datadir" e quindi i dati rimarranno scritti anche dopo lo spegnimento del docker. Il path del host parte dalla directory di lavoro di Docker. Ad esempio il linux questi path partono da:
/var/lib/docker/volumes
Pulizia
L'occupazione di spazio, specie nelle fasi di sperimentazione e/o di sviluppo può diventare un problema. Ogni immagine abbandonata, perché se ne è buildata un'altra versione più aggiornata occuperà spazio su disco, così come ogni container abbandonato e volume collegato. Qui seguono una serie di comandi per la pulizia.
Per eliminare tutti i volumi appesi, quando elimini i container i volumi non vengono rimossi, eseguire questo comando:
$ sudo docker volume rm $(sudo docker volume ls -qf dangling=true)
Oppure quando rimuovi il container puoi esplicitare l'opzione di rimozione dei relativi volumi:
$ sudo docker rm -v CONTAINER_ID
Per eliminare una specifica immagine (docker preserverà le immagini collegate da dipendenze e ci impedirà di cancellarle):$ sudo docker image rm IMGE_ID
Per eliminare tutto ciò che è appeso (container non in esecuzione, tutte le immagini non utilizzate, tutte le reti virtuali non utilizzate e tutti i volumi locali non utilizzati) invece:
$ sudo
docker container prune
$ sudo
docker image prune
$ sudo
docker network prune
$ sudo
docker volume prune
*** ATTENZIONE QUI DISTRUGGO TUTTO ***
Se si ha bisogno di ripulire tutto il proprio sistema da tutto quanto immagini, container e volumi:
$ sudo
docker rm -vf $(docker ps -aq)
$ sudo docker rmi -f $(docker images -aq)
$ sudo docker volume prune -f
Limitiamo l'occupazione di spazio da parte dei log
Tutto ciò che finisce in console e nei log docker lo dirotta in un file che riporta l'hash del container-json.log come ad esempio:eed11fc6dd1a6a8c4db448be1d8ee4d65d8bd005e00bcd9e186d9ebe43a6fcd2-json.log
Questi file vengono generati per ogni container che viene eseguito sul server nella directory :
/var/lib/docker/containers/
*** La procedura che segue vale per Ubuntu 19.04 ****
Per controllare le dimensioni di questi file, che possono diventare importanti e costituire il problema descritto al paragrafo precedente suggerisco di impostare il rolling a massimo 3 file da 100 megabyte l'uno:
Per prima cosa fermiamo il demone:
$ sudo systemctl stop docker
Poi creiamo (se esiste già sarebbe il caso di crearne una copia e mergiare i due file) il file "/etc/default/docker" e inseriamoci dentro una variabile d'ambiente che punta al file di configurazione:
$ sudo echo 'DOCKER_OPTS="--config-file=/etc/docker/daemon.json"' > /etc/default/docker
Editiamo il file /etc/docker/daemon.json:
$ sudo nano daemon.json
Inserendo al suo interno solo questo:
{
"log-driver": "json-file",
"log-opts": {"max-size": "100m", "max-file": "3"}
}
Dopo aver salvato il file precedente, facciamo ripartire il demone:
Questa comportamento verrà applicato ai log generati dai nuovi container creati a seguito di questa impostazione.
{
"log-driver": "json-file",
"log-opts": {"max-size": "100m", "max-file": "3"}
}
Dopo aver salvato il file precedente, facciamo ripartire il demone:
$ sudo systemctl start docker
Questa comportamento verrà applicato ai log generati dai nuovi container creati a seguito di questa impostazione.
Modifichiamolo
E se si ha bisogno di "entrare" in un container e fare qualche modifica ? Possiamo aprire una shell all'interno del docker:
$ docker exec -i -t CONTAINER_ID /bin/bash
oppure
$ docker exec -i -t CONTAINER_ID /bin/sh
Dipende dal tipo di shell a disposizione.
Se voglio eseguire il comando "date" in un container basterà:
Se voglio eseguire il comando "date" in un container basterà:
$ docker exec -i -t CONTAINER_ID date
Se vogliamo eliminare una delle immagini a disposizione:
$ sudo docker image rm IMAGE_ID
Se vogliamo eliminare uno dei container:
$ sudo docker container rm CONTAINER_ID
Se vogliamo rinominare l'immagine con UID 6292588dfa50 :
$ sudo docker tag 6292588dfa50 NOME_REP:TAG
Per creare una immagine da un dockerfile nella dir attuale:
$ docker build .
Dockerizziamo le nostre app
Con il termine "dockerizzare" intendo ottenere una immagine docker da una applicazione disponibile.
(da https://medium.com/better-practices/deploying-a-scalable-web-application-with-docker-and-kubernetes-a5000a06c4e9)
Questa esigenza nasce nel momento in cui, ad esempio, si decide di sviluppare in una architettura a microservizi. Quindi si avranno delle componenti fornite da una infrastruttura, già dockerizzate, ed altre autoprodotte da dockerizzare.
Questa esigenza nasce nel momento in cui, ad esempio, si decide di sviluppare in una architettura a microservizi. Quindi si avranno delle componenti fornite da una infrastruttura, già dockerizzate, ed altre autoprodotte da dockerizzare.
Otteniamo una immagine
Se si usa un framework che è orientato a docker, come JHipster, questo ci preparerà già il file "Dockerfile" che guida il processo di dockerizzazione. Dettaglio solo questo scenario perchè è quello di interessa al momento.
Per buildare e ottenere una immagine sull'host attuale a partire da un progetto JHipster conviene usare il comando:
$ mvn package -Pprod verify jib:dockerBuild
Questo produrrà una immagine con il nome del progetto disponibile fra le immagini docker sull'host:
$ docker images
Gestiamo le immagini
Ma come faccio a "distribuire" questa immagine ? Cioè spostarla sugli impianti di produzione o di test ? Ho tre possibilità che elenco qui. Scartate quella di prendere il file disponibile nell'archivio locale, in /var/lib/docker, perché questi file sono costruiti sulle specifiche interne del docker che li ha creati e quindi non adatti ad essere eseguiti su un server docker diverso. Quindi serve una sorta di "esportazione":
- usare hub.docker.com con un account e tenere privata o pubblica la gestione delle docker images usando l'account gratuito limitato o comprando i servizi professionali. Licenza commerciale.
- "docker save" salva una immagine oppure "docker export" esporta un container e quindi si ottiene un file portabile su un'altra piattaforma. Quindi:
$ docker save 'miaazienda/miaapp' > miaazienda.miaapp.latest.tar
Crea un file che è poi possibile importare sull'altro docker server con: "docker load" or "docker import":
$ docker load -i mia.app.image.tar
in alternativa:
$ cat mia.app.image.tar | sudo docker load
Da: http://webgamecafe.com/questions/954/in-docker-qual-e-la-differenza-tra-un-contenitore-e-un-immagine
- gestire un proprio docker registry privato, disponibile QUI, è una implementazione che fornisce la gestione delle richieste dei docker daemons e quindi consente l'archiviazione ed il recupero di immagini docker. Licenza Apache. Vedi dettagli al link fornito prima.
- usare un docker registry alternativo come Quay. Licenza commerciale.
In questo contesto si può avere bisogno di fare un collage fra applicazioni per ottenere funzionalità più complesse. Se volessimo costruire una infrastruttura per raccogliere log da applicazioni varie, trasformarli in una forma utile al tipo di informazioni che si vogliono raccogliere e filtrarli per poi caricarli in un database e costruirci sopra dei cruscotti di sintesi, avremmo bisogno di quello che viene detto "stack" cioè una serie di applicazioni diverse integrate. In questo ci viene in contro "Docker compose". Avendo a disposizione un file di configurazione "docker-compose.yml" basterà usare il comando per lanciare in background i docker di una composizione che è:
Docker compose
In questo contesto si può avere bisogno di fare un collage fra applicazioni per ottenere funzionalità più complesse. Se volessimo costruire una infrastruttura per raccogliere log da applicazioni varie, trasformarli in una forma utile al tipo di informazioni che si vogliono raccogliere e filtrarli per poi caricarli in un database e costruirci sopra dei cruscotti di sintesi, avremmo bisogno di quello che viene detto "stack" cioè una serie di applicazioni diverse integrate. In questo ci viene in contro "Docker compose". Avendo a disposizione un file di configurazione "docker-compose.yml" basterà usare il comando per lanciare in background i docker di una composizione che è:
$ docker-compose up -d
Per arrestare la composizione:
Qualche informazione sullo spazio occupato da questa soluzione di virtualizzazione.
Partiamo dal sorgente di una applicazione jhipster di un microservizio che con maven (che tiene fuori tutte le librerie) occupa 5 MB.
Un microservizio jhipster con tutte le librerie incluse invece occupa
Le dimensioni dei container sono date dalle dimensioni dell'immagine dalla quale si generano (livello invariante) più la parte variante dello specifico container (livello variante):
Quindi la parte invariante delle immagini sarà condivisa fra tutti i container generati da essa. La parte variabile è unica per container.
Ogni immagine si porta dietro le componenti software necessarie a far girare ciò che implementa e ciò che implementa.
Per vedere le dimensioni delle immagini basta:
Quindi lanciando 10 istanze di "elastalert" avremo una occupazione di 280MB dell'immagine + 1.90MB delle 10 istanze. Sesso all'immagine dalla quale facciamo partire il container sono collegate altre immagini di supporto come quelle inerenti, ad esempio, al linguaggio di programmazione usato per buildare l'immagine finale.
Oltre a queste bisogna considerare i volumi, i file di configurazione dei container, la memoria se swappata su disco e lo spazio occupato dai file di log.
Per avere la lista dei volumi:
Così si conosce la collocazione dei volumi sul filesystem ("Mountpoint") e si può andare a guardare lo spazio occupato:
Oppure per vedere le ultime 100 righe di log:
$ docker-compose down
Dimensionamento degli ambienti
Qualche informazione sullo spazio occupato da questa soluzione di virtualizzazione.
Partiamo dal sorgente di una applicazione jhipster di un microservizio che con maven (che tiene fuori tutte le librerie) occupa 5 MB.
Un microservizio jhipster con tutte le librerie incluse invece occupa
Le dimensioni dei container sono date dalle dimensioni dell'immagine dalla quale si generano (livello invariante) più la parte variante dello specifico container (livello variante):
$ sudo docker ps -s
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
811d1d38c1e3 0fe7ae08c70c "npm start" 4 days ago Up 4 days elastalert 190kB (virtual 280MB)
Quindi la parte invariante delle immagini sarà condivisa fra tutti i container generati da essa. La parte variabile è unica per container.
Ogni immagine si porta dietro le componenti software necessarie a far girare ciò che implementa e ciò che implementa.
Per vedere le dimensioni delle immagini basta:
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
bitsensor/elastalert 3.0.0-beta.0 0fe7ae08c70c 6 weeks ago 280MB
Quindi lanciando 10 istanze di "elastalert" avremo una occupazione di 280MB dell'immagine + 1.90MB delle 10 istanze. Sesso all'immagine dalla quale facciamo partire il container sono collegate altre immagini di supporto come quelle inerenti, ad esempio, al linguaggio di programmazione usato per buildare l'immagine finale.
Oltre a queste bisogna considerare i volumi, i file di configurazione dei container, la memoria se swappata su disco e lo spazio occupato dai file di log.
Per avere la lista dei volumi:
$ sudo docker volume ls
Così si conosce la collocazione dei volumi sul filesystem ("Mountpoint") e si può andare a guardare lo spazio occupato:
$ sudo du -hs MOUNTPOINT/_data
Per un tuning dell'impianto vedi qui.
Raccolta comandi rapidi
Vedere i log di un container:$ docker logs
CONTAINER_ID
$ docker logs --tail 100
CONTAINER_ID
O ancora per mettersi in coda (tail) al file e vedere le nuove righe:
$ docker logs -f
CONTAINER_ID
Per ispezionare un container e conoscere con quali parametri sta girando, quali volumi sta usando ecc.:
$ docker container inspect CO
NTAINER_ID
O ancora per mettersi in coda (tail) al file e vedere le nuove righe:
Per ispezionare un container e conoscere con quali parametri sta girando, quali volumi sta usando ecc.:
$ docker logs -f
CONTAINER_ID
Per ispezionare un container e conoscere con quali parametri sta girando, quali volumi sta usando ecc.:
$ docker container inspect CO
NTAINER_ID
Note
1) Mi è successo di non "avere i permessi" per fermare un container:
$ sudo docker container stop 7901c21e5380
Error response from daemon: cannot stop container: 7901c21e5380: Cannot kill container 7901c21e538003d38e4d154fcb5be77428d76324ebb9947d9d25c20163c1c54a: unknown error after kill: docker-runc did not terminate sucessfully: container_linux.go:387: signaling init process caused "permission denied"
La soluzione sta nell'eliminare pulire il servizio di AppArmor:
Verificare lo stato:
$ sudo aa-status
Fermare il servizio:
$ sudo systemctl disable apparmor.service --now
Eliminare i profili:
$ sudo service apparmor teardown
Verificare ora lo stato:
$ sudo aa-status
Si noteranno tutti i profili azzerati.
Fermare il container, che ora obbedirà e si fermerà:
$ sudo docker container stop 7901c21e5380
# docker-compose up
ERROR:
Can't find a suitable configuration file in this directory or any
parent. Are you in the right directory?
Supported filenames: docker-compose.yml, docker-compose.yaml
# sudo snap remove docker
docker removed
# sudo apt install docker.io docker-compose
3) Specificare il parametro network "host" in docker-compose nello specifico service:
network_mode: "host"
4) Specificare in un volume "solo un file" e non tutta la directory:
nel service:
volumes:
- /hapi-application:/usr/local/tomcat/hapiconf/
- /hapi-application/hapi.properties:/usr/local/tomcat/conf/hapi.properties
Nella sezione finale "volumes":
volumes:
hapi-application:
Quest'opera è distribuita con Licenza Creative Commons Attribuzione - Condividi allo stesso modo 4.0 Internazionale
Commenti
Posta un commento