Passa ai contenuti principali

Docker





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 verificare la presenza e lo stato del demone:


$ sudo systemctl status docker



Per conoscere la versione di docker installata:

 
$ docker version


Per conoscere quali immagini docker sono presenti sul mio sistema:

 
$ docker image ls



Per eseguire una immagine docker: 

 
$ docker run hello-world

Per avere la lista dei docker che sono in esecuzione (ognuno avrà un id univoco):

 
$ 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:


$ 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à:



$ 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.

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






- 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.


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:


$ 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
Oppure per vedere le ultime 100 righe di log:


$ 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 CONTAINER_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


2) docker-compose non rileva il file di configurazione, seppur presente:

# 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 
Il problema è dovuto al fatto che docker è stato installato con snap di Ubuntu che crea un ambiente virtuale che non gli consente di vedere il file system. Si risolve disinstallando docker da snap ed installandolo, con docker-compose, dal repository di Ubuntu:


# 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:


Commenti

Post popolari in questo blog

Telecamere Ip con accesso "nascosto"

Telecamere Ip con accesso "nascosto" Storia triste di un auto-hacking obbligato che mi ha fatto scoprire come la nostra privacy è realmente messa a rischi. Storia Ho acquistato dal mercatino/fiera del Radioamatore di Fasano quattro telecamere IP. La scatola riporta "Smart Camera" LF4810. Ne ho montata una e testata in tutte le sue funzionalità per oltre un mese. Chiaramente la manualistica scarsissima, come da tradizione in questi prodotti cinesi di costo molto concorrenziale, consiste in un "pieghevole" di 4 facciate. Chiaramente non erano documentate le impostazioni necessarie per attivare i protocolli ONVIF e RTSP che mi sono indispensabili per l'uso che ne devo fare. Nonostante questa scarsa documentazione dopo l'installazione base fatta con l'apposita app: tutto sembrava corretto. Chiaramente la prima azione che ho compiuto è stata quella di cambiare la password che di default è "123". Subito dopo h

Dynamic DNS con Duckdns.org in HTTPS

Obiettivo Avere un dominio https con certificato valido da usare come endpoint pubblico per Homeassistant e per un WebHook per i bot telegram. Fase 1 Registrazione del dominio in un servizio di dynamic DNS come https://www.duckdns.org/   : Scegliere per quale sistema operativo installare il client che si occuperà dell'aggiornamento dell'ip: Seguire la semplice guida per la configurazione del processo cron: Fase 2 Creazione del certificato e installazione sul server. Di tutto questo si occuperà una applicazione che si chiama certbot. $ sudo add-apt-repository ppa:certbot/certbot $ sudo apt install python-certbot-apache $ sudo certbot --apache -d ol3.duckdns.org -d www.ol3.duckdns.org Fase 3 Esporre il servizio https sulla rete pubblica. Aprire o reindirizzare la porta 443 verso l'host sul quale si è fatta la configurazione di certbot dal proprio router. Il certificato di certbot è valido per novanta giorn

JHipster - Uso base

Cosa è JHipster è un "generatore di applicazioni" che fornisce tutto lo stack necessario ad implementare una applicazione web e/o a microservizi basata su Spring Boot e AngularJs. E' dotato di un marketplace di componenti già pronte: https://www.jhipster.tech/#modules E' dotato di uno strumento web per la modellazione dello schema E-R: https://start.jhipster.tech/jdl-studio/ Prerequisiti - Java 8  - Node.js (usare la versione LTS 64-bit) - NPM viene installato con Node.js ma va aggiornato      $ npm install -g npm - Per usare il JHipster Marketplace, installare Yeoman:       $ npm install -g yo Uso base Gli step, presi dal sito ufficiale sono questi: 1. Installare JHipster:       $ npm install -g generator-jhipster Nota: per installare una versione specifica del generator:   $ npm install -g generator-jhipster@6.0.5 2. Crea una nuova directory ed entra dentro questa:   $ mkdir myApp   $ cd M yApp 3. Eseguire JHipster e