Elaborazione
Loading
Articolo numero: faq-142 Articolo numero: faq-142

Come installare Docker su CentOS 7

Posted on 16-09-2016 - By Admin  NTLab

Vediamo come installare Docker su CentOS

L'installazione di Docker è molto semplice. Per procedere al'installazione di docker in RHEL/CentOS è necessario installare il pacchetto omonimo con il comando:

yum install docker

Docker è un framework basato su container per automatizzare e l'implementazione di applicazioni. Il progetto Docker parte dall'esigenza di implementare, tramite un engine open source per la gestione dei container, una soluzione più performante che garantisse un buon livello di isolamento applicativo e di risorse.

SETUP Fedora 22+ si può installare utilizzando l'utility dnf:

dnf install docker

Una volta installato è possibile avviare e opzionalmente abilitare il servizio in systemd con i comandi:

systemctl enable docker systemctl start docker

DOCKER IMAGES Le immagini utilizzate per eseguire i container possono essere create dall'amministratore o dallo sviluppatore o scaricate da un Registry, che può essere pubblico o privato. Nella sua configurazione di default Docker utilizza il Docker Hub (docker.io) ovvero il registro pubblico dove varie community pubblicano le proprie immagini con le personali customizzazioni. Da un punto di vista di sicurezza questa pratica può mettere a rischio sistemi di produzione in quanto le immagini caricate sul public registry non sono sottoposte a controlli accurati sul loro effettivo contenuto. Se si utilizza RHEL la best practice consigliata è la configurazione di registry.access.redhat.com come default registry. Tutte le Docker images in questo registry sono testate e certificate da Red Hat stessa. La customizzazione del default registry viene fatta editando il file /etc/sysconfig/docker. Per ricercare un'immagine nei registry si può utilizzare il comando:

docker search PATTERN

Per scaricare un'immagine individuata con la ricerca si utilizza il comando:

docker pull IMAGE_NAME

Durante il download si può notare che verranno scaricati diversi layer parallelamente. Una volta scaricate una o più immagini si può visualizzare l'elenco totale di tutte le immagini disponibili localmente con il comando

docker images

Ad ogni immagine viene associato un IMAGE_ID che corrisponde alle prime 12 cifre di un hash SHA256 calcolato sull’immagine. Lo stesso hash rimane l'identificativo univoco della Docker image scaricata. Per eliminare una Docker image scaricata:

docker rmi IMAGE_ID

Le immagini scaricate vengono copiate nella direcotry /var/lib/docker/ ma in questa directory non si troveranno file omonimi alle immagini in quanto Docker utilizza gli hash per gestire le immagini. Le immagini Docker sono multilayer. Un'immagine è composta da N strati read-only che rappresentano le modifiche al filesystem (in un modo simile a degli snapshot cumulativi). Docker utilizza una strategia Copy-on-Write per le immagini e i container al fine di ottimizzare l’uso di risorse e le performance. Quando si scarica una nuova immagine si può vedere il comando:

docker pull

scaricare contemporaneamente ognuno di questi layer. Questo approccio ha il vantaggio di poter utilizzare layer comuni su immagini diverse: si pensi ad una immagine base Fedora modificata per avere un http server e alla stessa immagine modificata installando PostgreSql. Queste tre immagini avranno in comune un layer di base e verranno poi aggiunti layer successivi. Quando si avvia un nuovo container partendo dall'immagine viene creato un layer read/write in cima alla pila. questo layer scrivibile è di fatto la principale differenza tra un container e la sua immagine. Avviare più container dalla stessa immagine significa creare più layer r/w “appoggiati” sullo stack ro. Docker Container Per lo storage Docker fa uso, in RHEL/Fedora/CentOS, del driver/framework Device Mapper. Originariamente Docker girava su Ubuntu e Debian e utilizzava AUFS come backend di storage. Anche se inizialmente Red Hat ha cercato di integrare AUFS nel mainline del kernel, successivamente si è optato per una tecnologia differente ed è nata una collaborazione con Docker Inc. per integrare il driver devicemapper. Quando si installa Docker utilizzando il driver devicemapper viene creato un thin pool (a partire da block device disponibili o da loop device). Nel pool viene creato un base device, ovvero un thin device con un filesystem: ogni nuovo image layer sarà uno snapshot copy-on-write di questo base device. I layer dei container saranno anch’essi snapshot copy-on-write dell’immagine da cui sono creati. Il driver devicemapper alloca spazio ai vari layer quando viene effettuatata un’operazione di scrittura. Docker ESECUZIONE DI CONTAINER Una volta scaricate le immagini necessarie si possono avviare i container con il comando:

docker run IMAGE_NAME

Le opzioni applicabili sono numerose e permettono di influenzare il comportamento del container sotto molti aspetti. Per un elenco più dettagliato delle opzioni del comando run: https://docs.docker.com/engine/reference/run/ In questo modo verrà avviato un container che eseguirà un eventuale entry point. Si può eseguire un qualsiasi altro processo (a condizione che i binari esistano nel filesystem) con il comando:

docker run -it IMAGE_NAME COMMMAND

Le opzioni -i e -t rispettivamente abilitano lo stdin e una pseudo tty. Ad esempio, se si vuole eseguire una shell bash su un generico container “centos” scaricato dal public registry:

docker run -it docker.io/centos /bin/bash

L’approccio CoW permette di ridurre drasticamente i tempi di avvio di un container in quanto è necessario soltanto creare il thin layer r/w. Notare che quando viene creato il nuovo container viene restituito un UUID e che lo stesso, omonimo, corrisponde ad una sottodirectory di /var/lib/docker/containers. E’ possibile eseguire un ulteriore processo anche una volta che il container è stato avviato (ad esempio una shell per fare un eventuale troubleshooting) con:

docker exec -it "CONTAINER_NAME_OR_ID" COMMAND

CONTAINER_NAME_OR_ID corrisponde al nome pseudocasuale generato per il container o il suo ID univoco. A volte può essere necessario eseguire un container in modalità detached, assicurandosi che il suo entry point non venga eseguito in foreground (ad esempio perché non si vogliono messaggi in stdout). Per fare ciò si può passare una semplice opzione -d (detached):

docker run -d docker.io/wildfly

Questo comando eseguirà un’instanza standalone di wildfly senza inviare i messaggi a stdout come fa di default WildFly/JBoss con il suo CONSOLE handler. PERSISTENT STORAGE E’ possibile avviare un container utilizzando storage persistente dell’host ovvero una porzione di file system montato sotto un mount point specificato all’avvio:

docker run [OPTIONS] -v LOCAL_DIR:CONTAINER_MOUNT_POINT IMAGE_NAME [COMMAND]

Ad esempio, per montare le directory /bin e /sbin nel container e accedere così a tutta una serie di utility command line che potrebbero non essere installate nel container:

docker run -it -v /bin:/mnt/bin -v /sbin:/mnt/sbin docker.io/centos /bin/bash

I mount point all’interno del container vengono creati dinamicamente. PORT MAPPING E NETWORKING Quando viene eseguito, un container ha un indirizzo IP assegnato dinamicamente nella subnet definita (default 172.17.0.0/16). Non sappiamo ad ogni modo l’indirizzo del container in modo univoco e inoltre questo non è raggiungibile da un host remoto. Per rendere un servizio accessibile dall’esterno in modo persistente, a prescindere dall’indirizzo ip interno del container possiamo assegnare uno o più port mapping all'esecuzione del container.

docker run -p LOCAL_PORT:CONTAINER_PORT IMAGE_ID [COMMAND]

Ad esempio, per avviare un container httpd e rendere disponibile la porta 80 del container sulla 8080 dell’host:

docker run -p 8080:80 docker.io/httpd

Dal punto di vista della gestione della rete Docker utilizza di default un Linux Bridge denominato docker0, al quale assegna indirizzo 172.17.0.1/16. Al bridge docker0 vengono agganciate le interfacce vethxxxxxxx che corrispondono all’endpoint lato host delle interfacce ethX nel container, separate graziea un network namespace dedicato. Viene definita una rotta sul network 172.17.0.0/16 per il device docker0. Questo permette di pingare il container creato una volta noto il suo indirizzo. Un metodo semplice per visualizzare la configurazione di rete del container è l’esecuzione del comando:

docker inspect CONTAINER_NAME_OR_ID

In questo modo si produce un output in formato JSON contenente tutte le impostazioni di runtime del container. Di seguito un ipotetico output di un container postgresql:

[ { "Id": "6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075", "Created": "2016-07-07T18:11:18.753318833Z", "Path": "/docker-entrypoint.sh", "Args": [ "postgres" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 18763, "ExitCode": 0, "Error": "", "StartedAt": "2016-07-07T18:11:19.810927964Z", "FinishedAt": "0001-01-01T00:00:00Z" }, "Image": "sha256:247a11721cbdbd3f3ed1001116b88f8cc0a4087b228ac61db7b6c6927995b9d4", "ResolvConfPath": "/var/ lib/ docker/ containers/ 6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075/ resolv.conf", "HostnamePath": "/var/ lib/ docker/ containers/ 6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075/ hostname", "HostsPath": "/var/ lib/ docker/ containers/ 6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075/ hosts", "LogPath": "", "Name": "/adoring_ramanujan", "RestartCount": 0, "Driver": "devicemapper", "MountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c181,c431", "ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c181,c431", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LogConfig": { "Type": "journald", "Config": {} }, "NetworkMode": "default", "PortBindings": {}, "RestartPolicy": { "Name": "no", "MaximumRetryCount": 0 }, "VolumeDriver": "", "VolumesFrom": null, "CapAdd": null, "CapDrop": null, "Dns": [], "DnsOptions": [], "DnsSearch": [], "ExtraHosts": null, "GroupAdd": null, "IpcMode": "", "Links": null, "OomScoreAdj": 0, "PidMode": "", "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "SecurityOpt": null, "UTSMode": "", "ShmSize": 67108864, "ConsoleSize": [ 0, 0 ], "Isolation": "", "CpuShares": 0, "CgroupParent": "", "BlkioWeight": 0, "BlkioWeightDevice": null, "BlkioDeviceReadBps": null, "BlkioDeviceWriteBps": null, "BlkioDeviceReadIOps": null, "BlkioDeviceWriteIOps": null, "CpuPeriod": 0, "CpuQuota": 0, "CpusetCpus": "", "CpusetMems": "", "Devices": [], "KernelMemory": 0, "Memory": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": -1, "OomKillDisable": false, "PidsLimit": 0, "Ulimits": null }, "GraphDriver": { "Name": "devicemapper", "Data": { "DeviceId": "542", "DeviceName": "docker-253:3-1441817-37934a3cdd7e3d7ff1fd54951988241d02a43a29b0defa0c2b5818c882e79c68", "DeviceSize": "107374182400" } }, "Mounts": [ { "Name": "ba5f53004f49fd22b5f6e42bd91dd80c0ad37fd146951d69ed1dec99a63dbe57", "Source": "/var/ lib/ docker/ volumes/ ba5f53004f49fd22b5f6e42bd91dd80c0ad37fd146951d69ed1dec99a63dbe57/ _data", "Destination": "/var/ lib/ postgresql/ data", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ], "Config": { "Hostname": "6eab63c34478", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "5432/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/lib/postgresql/9.5/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "GOSU_VERSION=1.7", "LANG=en_US.utf8", "PG_MAJOR=9.5", "PG_VERSION=9.5.3-1.pgdg80+1", "PGDATA=/var/lib/postgresql/data" ], "Cmd": [ "postgres" ], "Image": "docker.io/postgres", "Volumes": { "/var/lib/postgresql/data": {} }, "WorkingDir": "", "Entrypoint": [ "/docker-entrypoint.sh" ], "OnBuild": null, "Labels": {} }, "NetworkSettings": { "Bridge": "", "SandboxID": "c28f03093e9b7972a1199762a54538ac56a238f2c57c4f3ae9237577c6b531c7", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "5432/tcp": null }, "SandboxKey": "/var/ run/ docker/ netns/ c28f03093e9b", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "884f6a93231c0869d7e25ff729fcb31e8602b76c3ceeecc03b9aa030bf004572", "Gateway": "172.17.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "MacAddress": "02:42:ac:11:00:02", "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "c292fc6b3e9bfb0b348001751cc4eb91d8d9d552931c501b5663a2f2fc5fdcdd", "EndpointID": "884f6a93231c0869d7e25ff729fcb31e8602b76c3ceeecc03b9aa030bf004572", "Gateway": "172.17.0.1", "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:02" } } } } ]

Come si può vedere questo JSON contiene tutte le informazioni che ci servono sul container e in particolare i “NetworkSettings” ci danno non solo l’indirizzo IP (172.17.0.2/16) del nostro container ma anche la porta esposta (5432/tcp per postgresql), il default gateway (che è giustamente l’indirizzo ip che vediamo assegnato al device docker0) e il mac address generato per il device eth0 interno al container. Un altro metodo possibile per visualizzare l’indirizzo IP del container senza accedervi o eseguire comandi docker è il file /var/ lib/ docker/ containers/ "CONTAINER_ID" /hosts. Questo è il file hosts che verrà copiato nella directory /etc/hosts del container e contiene le risoluzioni nomi di eventuali altri container e, sempre, la risoluzione tra IP assegnato e hostname generato. L’hostname generato corrisponde ai primi 12 caratteri dell’hash del container. Oltre al file host si può notare che nella directory è presente molto altro: il file resolv.conf, il file hostname contente l’hostname generato del container, e i file hostconfig.json e config.v2.json. Quest’ultimo in particolare è esattamente il contenuto dell’output del comando docker inspect lanciato prima. Altra via, dunque, di visualizzare le impostazioni generali. Un altro modo per avere informazioni di rete più dettagliate sui container agganciati al bridge docker0 è l’uso del comando:

docker network inspect bridge

Questo mostrerà un output in formato json con informazioni sul bridge docker0 e su tutti i container attivi al momento, tra cui MAC address e indirizzo IP. STOP E COMMIT Per fermare un container avviato:

docker stop CONTAINER_NAME_OR_ID

Una volta fermato un container il suo thin layer r/w non è stato ancora completamente eliminato. Se si sono fatte delle modifiche a questo layer (nel caso più tipico installazioni e configurazioni) si può creare un nuovo layer read-only andando di fatto a generare una nuova Docker image che condivide tutti i precedenti layer più il nuovo. Per fare questo si esegua il comando:

docker commit CONTAINER_NAME_OR_ID [REPOSITORY[:TAG]]

E’ buona abitudine, anche se opzionale, assegnare alla nuova immagine un riferimento al registry e al nome e un tag. Ad esempio, per creare una nuova immagine dopo aver modificato la configurazione di un container JBoss:

docker commit high_morse docker.io/jboss/JBossEAP7:latest

JBossEAP7 è il nome scelto facoltativmente per l’immagine e latest è il tag di default. E’ prassi comune assegnare la versione al tag: le immagini con il tag latest vengono preferite nella creazione di container nel caso esistano più immagini omonime. Il comando docker commit restuisce un hash calcolato al momento. Si può riscontrare che lo stesso hash corrisponde ad un file omonimo di metadati json in /var/ lib/ docker/ image/ devicemapper/ imagedb/ content/ sha256/ e alla directory omonima in /var/ lib/ docker/ image/ devicemapper/ imagedb/ metadata/ sha256/ Per eliminare definitivamente un container si esegue il comando:

docker rm CONTAINER_NAME_OR_ID

Questo comando elimina anche l’omonima directory in /var/ lib/ docker/ containers. DOCKER REMOTE API Non sempre possiamo accedere direttamente al client docker. In tal caso possiamo utilizzare le remote API. Di default Docker è in ascolto su /var/run/docker.sock ma ovviamente può essere messo in ascolto su un inet socket e diventare raggiungibile dall’esterno. Questa possibilità apre a possibili rischi di sicurezza e va quindi valutata con molta attenzione. Nel seguente esempio otteniamo lo stesso output ottenuto con il comando Docker inspect, ovvero il json di configurazione a runtime del container:

curl --unix-socket /var/ run/ docker.sock "GET /containers/ 6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075/ json"

Notare che è un semplicissimo metodo GET dove l’unico parametro variabile è il nome del container. Possiamo allo stesso modo ottenere un elenco dei container attivi:

curl --unix-socket /var/run/docker.sock "GET /containers/json"

Oppure ottenere un elenco delle immagini disponibili localmente:

curl --unix-socket /var/run/docker.sock "GET /images/json"

Per poi andare a vedere nel dettaglio le informazioni di una singola immagine:

curl --unix-socket /var/ run/ docker.sock "GET /images/ docker.io/ postgres:latest/ json"

Questo mi riporta un output esteso in formato JSON delle caratteristiche dell’immagine, compresi, ad esempio, i suoi entry point (ovvero i comandi che vengono eseguiti all’avvio del container). Questo è ad esempio l’output del comando appena descritto che mostra le caratteristiche in un’immagine postgres presa dal Docker Hub.

{ "Architecture": "amd64", "Author": "", "Comment": "", "Config": { "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "postgres" ], "Domainname": "", "Entrypoint": [ "/docker-entrypoint.sh" ], "Env": [ "PATH=/usr/lib/postgresql/9.5/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "GOSU_VERSION=1.7", "LANG=en_US.utf8", "PG_MAJOR=9.5", "PG_VERSION=9.5.3-1.pgdg80+1", "PGDATA=/var/lib/postgresql/data" ], "ExposedPorts": { "5432/tcp": {} }, "Hostname": "b0cf605c7757", "Image": "e1e1bf742f6a850ebb2cd78daddc184f82ebe58ce8b01f2fac85c15af4368e05", "Labels": {}, "OnBuild": [], "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": { "/var/lib/postgresql/data": {} }, "WorkingDir": "" }, "Container": "4c659084edf3be4d7e00155cb16e903f10a31806fb214b3c1ecddf13b93c1a33", "ContainerConfig": { "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", "#(nop) CMD ["postgres"]" ], "Domainname": "", "Entrypoint": [ "/docker-entrypoint.sh" ], "Env": [ "PATH=/usr/lib/postgresql/9.5/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "GOSU_VERSION=1.7", "LANG=en_US.utf8", "PG_MAJOR=9.5", "PG_VERSION=9.5.3-1.pgdg80+1", "PGDATA=/var/lib/postgresql/data" ], "ExposedPorts": { "5432/tcp": {} }, "Hostname": "b0cf605c7757", "Image": "e1e1bf742f6a850ebb2cd78daddc184f82ebe58ce8b01f2fac85c15af4368e05", "Labels": {}, "OnBuild": [], "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": { "/var/lib/postgresql/data": {} }, "WorkingDir": "" }, "Created": "2016-05-24T06:23:40.855876854Z", "DockerVersion": "1.9.1", "GraphDriver": { "Data": { "DeviceId": "447", "DeviceName": "docker-253:3-1441817-96047bb9151fe579547e21b394dd18af970db17768552e81c6fbd6037a03b83a", "DeviceSize": "107374182400" }, "Name": "devicemapper" }, "Id": "sha256:247a11721cbdbd3f3ed1001116b88f8cc0a4087b228ac61db7b6c6927995b9d4", "Os": "linux", "Parent": "", "RepoDigests": [], "RepoTags": [ "docker.io/postgres:latest" ], "Size": 265813152, "VirtualSize": 265813152 }

L’output JSON correttamente formattato è ottenuto mettendo in pipe alla fine della curl un modulo Python, json.tool, nel seguente modo:

curl --unix-socket /var/run/docker.sock "GET /images/docker.io/postgres:latest/json" | python -m json.tool

Appare chiaro ormai che tramite le remote API si può fare tutto ciò che si fa con la CLI. Facciamo ancora qualche esempio interessante. Per visualizzare i processi attivi in un container che sta girando:

curl --unix-socket /var/run/docker.sock "GET /containers/6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075/top"

Altra cosa interessante: apriamo un tail sullo standard output e standard error del nostro container. Immaginiamo il potenziale di questo comando per monitare remotamente i container attivi su un cluster OpenShift ad esempio…

curl --unix-socket /var/run/docker.sock "GET /containers/6eab63c34478e87e7d99e7db28b6353e1c521350ca4c6016c9b075/logs?stderr=1&stdout=1×tamps=1&follow=1"

Si possono cercare immagini sul Registry predefinito. Con il seguente comando ad esempio si ricercano nel Docker Hub tutte le immagini che hanno nel nome o nella descrizione la stringa “openshift”:

curl --unix-socket /var/run/docker.sock "GET /images/search?term=openshift"

Layer Docker

Valutazione articolo

Torna a Ricerca