Docker
Sommaire
- Introduction
- La plateforne Docker
- Les containers avec Docker
- Les images Docker
- Registry
- Stockage
- Docker Machine
- Docker Compose
- Dcocker Swarm
- Les logs de l’algorithme RAFT
- Backup d’un Swarm
- Tolerances aux pannes
- Network
- Securite
Introduction
Les containers Linux:
- un processus
- isole des autres processus
- avec sa propre vision du systeme sur lequel il tourne (Namespaces)
- limite dans les ressources qu’il peut utiliser (Control Groups)
- Partage le Kernel de la machine hote avec les autres containers
Namespaces:
- technologie linux pour isoler un processus
- les namespaces limitent ce qu’un container peut voir
- diferents namespaces
- pid: isolation de l’espace des processus
- net: donne une stack reseau privee
- mount: systeme de fichiers prive
- uts: nom du host
- ipc: isole les communications inter processus
- user: mapping des UID/GID entre l’hote et les containers
Control Groups (cgroups):
- technologie linux
- limite les ressources qu’un processus peut utiliser (RAM, CPU, I/O, Network)
Architecture micro-services
Avantages:
- Decoupage de l’application en processus (services) independants
- Chacun a sa propre responsabilite metier
- Equipe dediee pour chaque service
- Plus de liberte de choix dans le langage
- Mise a jour et scaling horizontale
- Containers tres adaptes pour les micro-services
inconvenient:
- Necessite des interfaces bien definies (API HTTP REST)
- Focus sur les tests d’integration
- Deplace la complexite dans l’orchestration de l’application globale (Docker Swarm, Kubernetes)
Application Cloud Native:
- Application orientee microservices
- Packagee dans des containers
- Orchestration dynamique
- Nombreux projets portes par la CNCF (Cloud Native Computing Foundation)
- Kubernetes
- Prometheus
- Fluentd
- etc…
- a voir cncf.io
DevOps:
- Un objectif: minimiser le temps de livraison d’une fonctionnalite
- Deploiements reguliers
- Mise en avant des tests
- Boucle d’amelioration courte
- Automatisation des processus
- provisioning / configuration
- infrastructure as code (IaC)
- integration continue / deploiement contunu (CI/CD)
- monitoring
DevoOps, quelques outils et produits:
- infrastructure: aws, google cloud platform, microsoft azure, digital ocean
- provisioning: terraform, cloudformation
- build: github, gitlab, docker
- test: selenium, jenkins, circle ci
- deploy: ansible, chef, docker registry
- run: kubernetes, docker swarm
- mesure: prometheus, elastic
Docker Hub
Registry officiel de docker, on retrouve des logiciels packages dans des images utilisable immediatement Docker Hub
Si l’image n’est pas present sur la machine, la commande docker container run
recupere l’image sur le docker hub.
On peut lancer un interpreteur interactif avec le flag -ti
(pour sortir d’un shell interactif du container lance sans le fermer Ctrl+P Ctrl+Q
) depuis une image docker (REPL), utile pour tester des commandes:
# pour lancer un interperteur python dans un container on utilise la commande "docker container run"
# l'interpreteur est package dans l'image "python" en version ou plutot de tag "3"
# le flag "-ti" permet l'interactivite avec le processus du container
# -t alloue un pseudo TTY et -i garde STDIN ouvert
$ docker container run -ti python:3
$ docker container run -ti ruby:2.5.1
# ici en fin de commande nous avons "-alpine" ceci permet d'utiliser une image qui est base sur
# la distibution Alpine Linux de taille tres reduite et beaucoup plus securise
$ docker container run -ti node:8.12-alpine
Exemple, BDD MongoDB:
# le flag "-d" permet d'executer l'application en tache de fond, on recupere un ID permettant d'interagir avec
# la flag "-p" permet de rendre disponible l'application MongoDB en local
$ docker container run -p 27017:27017 -d mongo:4.0
On peut ensuite lancer Compass pour explorer la BDD en utilisant le port 27017.
Exemple, Redmine (logiciel de gestion de projets):
permet d’avoir redmine (fait en ruby) dans un container. user/pass par defaut “admin”
# lancement du container base sur redmine 3
# ici on a mappe le port 3000 du container vers le port 80 de la machine hote
$ docker container run -p 80:3000 redmine:3
Des stacks completes
Elastic (gestion des logs)
Interface de visualisation:
- Kibana
Stockage, Indexation, Analyse:
- Elasticsearch
Ingestion:
- Logstash
- Beats
Creation d’un docker compose, chaque service est une image:
# docker-compose.yml
version: '3.6'
services:
logstash:
image: logstash:5.5.2
volumes:
- ./logstash.conf:/config/logstash.conf
command: ["logstash", "-f", "/config/logstash.conf"]
ports:
- 8080:8080
elasticsearch:
image: elasticsearch:5.5.2
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
kibana:
image: kibana:5.5.2
ports:
- 5601:5601
Il faut configurer la conf de logstash logstash.conf
.
Pour resumer, le bloc input{}
permet d’ecouter sur du HTTP.
Le bloc filter{}
permet sur chaque entrer de log d’extraire des parametres et d’en ajouter d’autres.
Le bloc output{}
une fois le filter fait, on renvoie le resultat sous JSON a elasticsearch.
puis on lance l’application sur la machine locale en utilisant le binaire docker compose:
$ docker-compose up -d
si par exemple dans le repertoire courant il y a un fichier log nginx.log
,
on utilise le script send-logs.sh
pour envoyer les logs a logstash.
$ ./send-logs.sh
puis on va sur l’interface de Kibana localhost:5601
. Quand un INDEX est trouve on peut cliquer sur le bouton Create
pour creer un index.
TICK (series temporelles)
Cette stack est bien adapte pour une application IOT.
Telegraf:
- sert a ingerer les donnees
Kapacitor:
- utilise pour la gestion des evenements en templs reel et la leve d’alerte
InfluxDB:
- BDD oriente serie temporelles
Chronograf:
- permet d’avoir une interface au dessus de Influx DB
Fichier docker compose:
# tick.yml
version: "3.6"
services:
telegraf:
image: telegraf
configs:
- source: TELEGRAF_CONF
target: /etc/telegraf/telegraf.conf
ports:
- 8186:8186
influxdb:
image: influxdb
chronograf:
image: chronograf
ports:
- 8888:8888
command: ["chronograf", "--influxdb-url=http://influxdb:8086"]
kapacitor:
image: kapacitor
environment:
- KAPACITOR_INFLUXDB_0_URLS_0=http://influxdb:8086
configs:
TELEGRAF_CONF:
file: ./telegraf.conf
puis on execute la stack en utilisant docker stack deploy qui permet de lancer l’application sur l’ensemble des machines:
$ docker stack deploy -c tick.yml tick
La plateforne Docker
Presentation:
- une plateforme de reference pour la construction, la distribution et le deploiement d’applications dans des containers
- agnostique du langage et de la stack applicative
- orchestration integree
- assure la scalabilite des applications
- accelere la mise en place de pipelines de deploiement automatiques et la frequence de releases
Le client docker communique avec le daemon dockerd via une API REST (ils sont ecrit en Go).
Doc api et daemon:
- https://docs.docker.com
- https://docs.docker.com/engine/api/v1.37
Cote serveur:
- processus
dockerd
- gestion des images, networks, volumes, cluster, …
- delegue la gestion des containers a
containerd
- expose une api rest
- ecoute sur la socket unix
/var/run/docker.sock
par defaut - peut-etre configure pour ecouter sur une socket tcp
Cote client:
- client docker
- binaire developpe en Go
- installe en meme temps que dockerd
- Communique avec le daemon local par defaut via
/var/run/docker.sock
- peut etre configure pour communiquer avec un daemon distant
On peut configuer un client pour communiquer avec un daemon distant en modifiant la variable d’environement DOCKER_HOST
.
Les concepts essentiels:
- Docker facilite l’utilisation des containers Linux
- Un container est lance a partir d’une image
- Une image contient une application et toutes ses dependances
- Fichier Dockerfile utilise pour la creation d’une image
- Une image est distribuee via un Registry (le registry stock les images)
- Containers lances sur un hote Docker unique ou sur un ensemble d’hotes regroupes en un cluster Swarm
Docker Hub:
- Le registry de stockage d’images de Docker: https://hub.docker.com
- Nombreuses images officielles: https://hub.docker.com/explore
- Utilise par defaut par le daemon docker
- Creation d’image publiques ou privees
- Facile a integrer dans un pipeline CI/CD
docker Swarm:
c’est un ensemble de docker host ayant dockerd mit en cluster, permet d’orchestrer des aplications (comme kubernetes) de tel sorte qu’elles tournent constament de la meme facon dont elles ont ete specifiees
Differentes editions
Docker Community Edition (CE):
- open source
- release stable tous les 6 mois (a partir de la 18.09)
- release test avant chaque release stable
- release nightly
Docker Entreprise Edition (EE):
- 3 tiers (basic, standard, avance)
- une release tous les trimestres avec 1an de support
- plateforme CaaS (Container as a Service)
- technologie certifiee: Infrastructure, Plugins, Containers,…
Installation: differents produits
- Versions CE pour de nombreuses plateformes
- Docker Desktop (macOS10.12, Win10)
- Docker Toolbox(old macOS win)
- Linux: version dediee
- Azure / AWS
- Versions EE
- Linux: version dediee
- IBM Z / Power
- Win Server 2016
- Azure/ AWS
Playground Docker en ligne: https://labs.play-with-docker.com/
- 5 instances pendant 4 heures
- templates pour la creation de clusters
- docker in docker (DinD)
Quelques commandes:
docker ps
(cmd historique remplace pardocker container ls
)docker container ls
docker cp [nom_machine_docker]:[path_src_dans_docker] [path_dest_dans_local]
permet de faire un copy d’un fichier sans creer de volumesudo systemctl status docker
sudo systemctl stop docker
sudo systemctl start docker
sudo dockerd -H 0.0.0.0:2373
#executer le daemon sur un portdocker version
docker info
#on s’adresse au deamon de la machine localedocker -H 192.168.99.100:2375 info
#executer la cmd sur une machine distante- (depuis la v18.09)
docker -H ssh://ubuntu@192.168.99.100 info
#idem via ssh - on peut exporter dans la variable d’environement
DOCKER_HOST
l’adresseexport DOCKER_HOST="ssh://ubuntu@192.168.99.100"
- copier sa cle publique locale vers un serveur distant
ssh-copy-id -i ~/.ssh/id_rsa.pub ubuntu@IP
- quitter un container
exit
(en interactif avec le flag-ti
) - arreter le process d’un container depuis ce dernier
Ctrl+C
docker kill
Configuration du daemon
Communication client / serveur
PDF : Communication entre Client/Daemon
Les containers avec Docker
Creation d’un container
- un container == un processus
- lancement d’un processus dans un contexte d’isolation
- creation a partir d’une image
- systeme de fichier / package complet d’une application
- disponible dans un registry (ex: docker hub) ou en local
docker container run [option] image [command] [arg...]
- https://docs.docker.com./engine/reference/commandline/run
exemple:
- executer
docker container run ubuntu echo hello
permet de printer “hello” dans le container ubuntu
Foreground vs Background
- Container lance en foreground par defaut
- Option “-d” pour lancer en background
- L’identifiant du container lance en background est retourne “ID 6da215f23….65a58”
exemple:
$ docker container run -d alpine ping 8.8.8.8
ici, si on ne le met pas en tache de fond alors on a plus la main sur le prompt, il faut ajouter -d
:
$ docker container run nginx:1.14-alpine
La publication de port
- permet de rendre le processus d’un container accessible depuis l’exterieur
- port d’ecoute du processus mappe sur un port de la machine hote
- allocation statique:
-p HOST_PORT:CONTAINER_PORT
- allocation dynamique de l’ensemble des ports:
-P
- conflit si plusieurs containers utilisent le meme port de la machine hote
exemple:
# le port 80 de l'instance Nginx tournant dans le container est publie sur le port 8080 de l'hote
$ docker container run -d -p 8080:80 nginx
sadd516545s6a4d5dg135d4fsd318d4fs9fd4d83sd84fg6sd4s684s6f81f6s12sd3sf4f8
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
sadd516545s6a4 nginx "nginx -g 'daemon of..." About a minut ago Up 2 seconds 0.0.0.0:8080->80/tcp angry_chandrasek
Bind-mount
(bind-mount Interdite en Production Askip)
- repertoire ou fichier de la machine hote monte dans un container
- a la creation d’un container avec l’option
-v
ou--mount
$ docker container run -v HOST_PATH:CONTAINER_PATH ...
$ docker container run --mount type=bind, src=HOST_PATH, dst=CONTAINER_PATH ...
- cas d’usage
- en dev: montage de code source dans un container
- donner acces a la socket unix du daemon Docker dans un container
Bind-mount: socket /var/run/docker.sock:
- socket utilisee pour la communication entre le client et le daemon docker
- donne acces a l’API du daemon docker depuis un container
Exemple 1, creation d’un container depuis un autre container:
# lancement d'un container avec bind mount de la socket
$ docker container run --name admin -ti -v /var/run/docker.sock:/var/run/docker.sock alpine
# creation d'un container depuis le container admin
/ curl -XPOST --unix-socket /var/run/docker.sock -d '{"Image":"nginx:1.12.2"}' -H 'Content-Type:application/json' http://localhost/containers/create
{"Id":"aa15w6ad54as6d51a6cac801fa3df5466a5s1d8a6cac2as1c3a4sxff84xfgg","Warnings":null}
# lancement du container depuis le container admin
/ curl -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/aa15...xfgg/start
Exemple 2, acces aux evenements du daemon docker depuis container:
# lancement d'un container avec bind mount de la socket
$ docker container run --name admin -ti -v /var/run/docker.sock:/var/run/docker.sock alpine
# suivi des evenements du daemon docker en temps reel
$ curl --unix-socket /var/run/docker.sock http://localhost/events
# resultat: voir le screenshot log-example.png ci-dessous
Exemple 3, gestion d’un hote docker depuis un container avec Portainer:
# lancement de Portainer, application web de gestion d'hotes Docker et de cluster Swarm
$ docker container run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
Portainer a besoin de la socket /var/run/docker.sock
pour avoir acces a l’API du daemon Docker sur lequel il tourne et pouvoir administrer.
ATTENTION: il est possible de supprimer des fichier de la machine hote via le container, pour eviter cela, il faut utiliser le flag ro
Read-Only
Exemple: suppression d’un fichier sur la machine hote
# lancement d'un container avec bind mount du systeme de fichiers de l'hote
$ docker run -v /:/host -ti alpine
/ rm /host/bin/sh
/ exit
$ sh
bash: sh: command not found
# bind-mount avec flag ro (read-only)
$ docker run -ti -v /:/host:ro alpine
/ rm /host/bin/sh
mv: can't rename 'sh': Read-only file system
Limitation des ressources (Bonne Pratique!)
- Pas de limite par defaut: RAM, CPU, I/O
- Necessaire d’imposer des limites pour ne pas impacter les autres processus
# limite de consommation de la RAM
## ici l'image estesp/hogit contient un process dont le seul but est de
## consommer de la ram, une fois que le process atteint 32Mo, Docker va kill le process
$ docker container run --memory 32m estesp/hogit
=> 00MKilled
# differentes valeurs de l'option --cpus sur un processeur 4-core
## utilisation des 4 cores soit 100% du CPU
## l'image progrium/stress permet de stresser les CPU
$ docker run -it --rm progrium/stress --cpu 4
=> CPU: 98% usr 1% sys 0% nic 0% idle 0% io 0% irq 0% sirq
## utilisation de 50% d'un core soit environ 12% du CPU
$ docker run --cpus 0.5 -it --rm progrium/stress --cpu 4
=> CPU: 12% usr 1% sys 0% nic 86% idle 0% io 0% irq 0% sirq
## utilisation de 2 cores soit 50% du CPU
$ docker run --cpus 2 -it --rm progrium/stress --cpu 4
=> CPU: 50% usr 2% sys 0% nic 47% idle 0% io 0% irq 0% sirq
## utilisation des cores 1 et 4
$ docker container run --cpuset-cpus 0,3
## utiliser pas plus de 3 CPU maximum
$ docker conainer run --cpus="3"
Lancer un container redis avec une limite RAM
- Soft de 2Go
- Hard de 4Go
$ docker container run --memory 4g --memory-reservation 2g -d redis
Les droits d’un container
- Par defaut un container est lance avec l’utilisateur root
- root dans le container == root sur la machine hote
- Bonne pratique: utiliser un utilisateur non root pour lancer un container
- definition de l’utilisateur a la creation de l’image
- utilisation de l’option –user au lancement
- changement de l’utilisateur dans le processus du container (gosu)
- Generalement fait dans les images officiels du Docker Hub
# lancement de la commande sleep dans un container base sur l'image Alpine
$ docker container run -d alpine sleep 1000
0as5d1g8c354df64sd2f4164sd8f4vc23s8d4f635497f5h4y842
# verification du owner du processus depuis la machine hote
## le container est lance avec l'user root
$ ps aux | grep sleep
591 root 0:00 sleep 1000
# lancement d'un container base sur l'image officielle de MongoDB
$ docker container run -d mongo:4.0
# verification du owner du processus depuis la machine hote
## le container n'est pas lance avec l'utilisateur root
## mais avec l'utilisateur dont le uid est 999
$ ps aux | grep mongo
5866 999 0:00 mongod --bind_ip_all
Des options utiles
# specification du nom avec le flag --name
$ docker container run -d --name debug alpine:3.7 sleep 1000
# suppression du container quand il est stoppe avec le flag --rm
## par defaut apres arret du container ses fichiers ne sont pas supprimes
$ docker container run -rm --name debug alpine:3.7 sleep 1000
# redemarrage automatique si le container s'arrete avec le flag --restart=on-failure
$ docker container run --name api --restart=on-failure username/api
Les commandes de base
run
: creation d’un containerls
: liste des containersinspect
: details d’un containerlogs
: visualisation des logsexec
: lancement d’un processus dans un container existantstop
: arret d’un containerstart
: relancer un containerrm
: suppression d’un container
les commandes de base ls
:
- liste des containers actifs:
docker container ls
- liste les containers actifs et stoppes:
docker container ls -a
- liste les identifiants des containers actifs et stoppes:
docker container ls -a -q
les commandes de base inspect
:
- vue detaillee d’un container
- commande disponible pour chaqune des primitives Docker (donc utilisable sur les images, les volumes, les networks, etc…)
docker container inspect [Id]/[name]
- utiliser le format “Go template” pour recuperer des donnes precises dans le json, voir: https://docs.docker.com/config/formatting/
- exemple, pour recuperer @IP du container:
docker container inspect --format '' 456efdd2f1ff2d1
- pour recuperer l’etat du container en json:
docker container inspect --format '' 456efdd2f1ff2d1 | jq
(jq
c’est commesed
pour json)
- exemple, pour recuperer @IP du container:
les commandes de base logs
:
- visualisation des logs d’un container
- option
-f
pour la mise a jour continue - recommandation: ecrire les logs sur la sortie / erreur standard
- ne pas ecrire les fichiers de logs dans le container
- en milleu de Prod, il faut utiliser un gestionnaire de log centralise dans lequel on pourra envoyer l’ensemble des log du container
$ docker container run -d --name ping ubuntu ping 8.8.8.8
3asd3sa212ad1a5da1sd31sa8dad
$ docker container logs -f ping
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=55 time=4.09 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=55 time=4.31 ms
les commandes de base exec
:
- permet de lancer un processus dans un container existant
- souvent utilisee avec les options
-t
et-i
pour avoir un shell interactif - utile pour faire un debug
# lancement du processus "sleep 10000" dans un container base sur alpine
$ docker container run -d --name debug alpine:3.6 sleep 10000
# execution d'un shell dans le container debug
$ docker container exec -ti debug sh
/ ps aux
PID USER TIME COMMAND
1 root 0:00 sleep 1000
5 root 0:00 sh
9 root 0:00 ps aux
Tant que le process PID 1
est en cours d’execution le container reste actif
les commandes de base stop
:
- Stoppe un ou plusieurs containers
$ docker container stop ID
$ docker container stop NAME
$ docker container stop $(docker container ls -q)
ici on arrete plusieurs containers en meme temps en recuperant la liste de ID grace als -q
- Les containers stoppes existent toujours
$ docker container ls -a
les commandes de base rm
:
- Un container doit etre stoppe avant d’etre supprime
- Option
-f
pour forcer l’arret du container - Supprime definitivement un ou plusieurs containers
$ docker container rm ID
$ docker container rm NAME
$ docker container rm -f $(docker container ls -aq)
on supprime tous les containers en recuperant la liste des ID grace als -aq
Des alias utiles
# liste les containers actifs
$ alias dls='docker container ls'
# liste les containers actifs et ceux arretes
$ alias dlsa='docker container la -a'
# stoppe tous les containers
$ alias dstopall='docker container stop $(docker container ls -aq)'
# supprime tous les containers
$ alias drmall='docker container rm $(docker container ls -aq)'
# lance un shell interactif (bash ou sh) dans un container
$ dshell (){
(docker container exec -ti $1 bash) || (docker container exec -ti $1 sh);
}
Exercices: Les commandes de bas de gestion des containers
Une commande utile
Lancer un shell dans la machine virtuelle dans laquelle tourne le daemon:
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
sur linux, le client et le daemon tourne sur la meme machine. Mais dans docker for Mac/Windows ou docker toolbox, le daemon tourne sur une VM alors que le client tourne sur la machine locale. Pour obtenir un shell dans la VM il faut lancer un container dans cette VM et utiliser quelques options pour que le process lance dans ce container au proces de PID 1 de la machine hote.
Les images Docker
Definition
- Un template pour instancier des containers
- Contient uneapplication et l’ensemble de ses dependances
- Portable sur n’importe quel environnemet ou tourne Docker
- Composee d’une ou de plusieurs layers
- chacune contient un systeme de fichiers et des meta data
- Distribuee via un Registry (ex: Docker Hub)
Que contient une image ?
- Le code applicatif “CODE (Ruby, Java, NodeJS)”
- Les librairies dont le code a besoin “DEPENDANCES (Module, Jar, Gem)”
- Un environnement d’execution “RUNTIME (Node,JRE,Ruby)”
- Les binaires et librairies systeme “OS (Ubuntu, CentOS, Alpine)”
Union filesystem
- Une image: un graph de layers en lecture seule
- Utilisation d’un storage / graph driver pour
- unifier l’ensemble des layers en un seul filesystem
- ajouter un layer en lecture-ecriture liee au container
- Differents drivers disponible en fonction du cas d’usage
- Layers stockees dans /var/lib/docker
- repertoire d’installation de Docker par defaut
Copy-on-Write
Modification d’un fichier dans le container:
- Le fichier original est copie dans la layer du container depuis une layer sous-jacente
- La modification est persistee dans la layer associee au container
en gros, on recupere les layers de l’image et on les copie dans le container, une fois dedans on peut modifier les layers car les layers de l’images sont en Read Only alors que une fois copie dans le container les layers sont en +RW.
La layer d’un container, est la layer read-write créé lorsqu’un container est lancé. C’est la layer dans laquelle tous les changements effectués dans le container sont sauvegardés. Cette layer est supprimée avec le container et ne doit donc pas être utilisée comme un stockage persistant.
Execices: Layer associee au container et system de fichier d’une image
Telecharger une image en local
$ docker image pull mongo:3.6
Voir les etapes de constructions d’une image
$ docker image history mongo:3.6
Si l’on regarde le Dockerfile utilisé pour la création de l’image, on peut voir que chaque entrée de la commande précédente correspond à une instruction du Dockerfile.
Obtenir l’ensemble des informations de l’image
$ docker image inspect mongo:3.6
lister les ports exposes
docker image inspect --format '{{json .ContainerConfig.ExposedPorts }}' mongo:3.6 | jq .
On peut exporter une image dans un fichier .tar
$ docker save -o mongo-3.6.tar mongo:3.6
# on peut ouvrir l'archive pour voir ce qu'il y a
$ tar -xvf mongo-3.6.tar
0669adaf1d338e563cddf2357c7cb23160d8351ed6531fda6361114610c7c8d8/
0669adaf1d338e563cddf2357c7cb23160d8351ed6531fda6361114610c7c8d8/VERSION
0669adaf1d338e563cddf2357c7cb23160d8351ed6531fda6361114610c7c8d8/json
0669adaf1d338e563cddf2357c7cb23160d8351ed6531fda6361114610c7c8d8/layer.tar
08cc52baf3ebad3caad28998914ac0baceb06e99269f85de99ff1df74aa8781f/
08cc52baf3ebad3caad28998914ac0baceb06e99269f85de99ff1df74aa8781f/VERSION
08cc52baf3ebad3caad28998914ac0baceb06e99269f85de99ff1df74aa8781f/json
08cc52baf3ebad3caad28998914ac0baceb06e99269f85de99ff1df74aa8781f/layer.tar
29ad4814ef0661b8b4d90bc74e3b3d006287ca94e6441b597ae01383d07b518e.json
2efd7711b196f9eb64b2f5ac68b27049db794f577c93be7cc543b2639254f278/
2efd7711b196f9eb64b2f5ac68b27049db794f577c93be7cc543b2639254f278/VERSION
2efd7711b196f9eb64b2f5ac68b27049db794f577c93be7cc543b2639254f278/json
2efd7711b196f9eb64b2f5ac68b27049db794f577c93be7cc543b2639254f278/layer.tar
3a12fe84f5307da469767e144d9927b8befac39a0d1d0c27ff48aeaf94601480/
3a12fe84f5307da469767e144d9927b8befac39a0d1d0c27ff48aeaf94601480/VERSION
3a12fe84f5307da469767e144d9927b8befac39a0d1d0c27ff48aeaf94601480/json
3a12fe84f5307da469767e144d9927b8befac39a0d1d0c27ff48aeaf94601480/layer.tar
4c62e5488f84ca6b3fd5da923f50f56c23270284a93c3b27113c615438cdd73c/
4c62e5488f84ca6b3fd5da923f50f56c23270284a93c3b27113c615438cdd73c/VERSION
4c62e5488f84ca6b3fd5da923f50f56c23270284a93c3b27113c615438cdd73c/json
4c62e5488f84ca6b3fd5da923f50f56c23270284a93c3b27113c615438cdd73c/layer.tar
6ce39836d016cfe616e38e93c370887e26bf1bb3bb28b61d9287bedc5dc5854b/
6ce39836d016cfe616e38e93c370887e26bf1bb3bb28b61d9287bedc5dc5854b/VERSION
6ce39836d016cfe616e38e93c370887e26bf1bb3bb28b61d9287bedc5dc5854b/json
6ce39836d016cfe616e38e93c370887e26bf1bb3bb28b61d9287bedc5dc5854b/layer.tar
6ffb582f37c3205cd326d5ba682105b7391bec8db1d8d218d1522a193c3629b1/
6ffb582f37c3205cd326d5ba682105b7391bec8db1d8d218d1522a193c3629b1/VERSION
6ffb582f37c3205cd326d5ba682105b7391bec8db1d8d218d1522a193c3629b1/json
6ffb582f37c3205cd326d5ba682105b7391bec8db1d8d218d1522a193c3629b1/layer.tar
7c6f095e2dee15e868558c4debf6272ccbe028e854fb146e5f6d518982084bec/
7c6f095e2dee15e868558c4debf6272ccbe028e854fb146e5f6d518982084bec/VERSION
7c6f095e2dee15e868558c4debf6272ccbe028e854fb146e5f6d518982084bec/json
7c6f095e2dee15e868558c4debf6272ccbe028e854fb146e5f6d518982084bec/layer.tar
b61eb2912db4a8e40d9ee142f40fdd3856f19433f2ae0285a49f240ce4011cde/
b61eb2912db4a8e40d9ee142f40fdd3856f19433f2ae0285a49f240ce4011cde/VERSION
b61eb2912db4a8e40d9ee142f40fdd3856f19433f2ae0285a49f240ce4011cde/json
b61eb2912db4a8e40d9ee142f40fdd3856f19433f2ae0285a49f240ce4011cde/layer.tar
bcc4785d63a54e4648c250b515e8f0962dcf488d8f8257eac3abd7db8726a392/
bcc4785d63a54e4648c250b515e8f0962dcf488d8f8257eac3abd7db8726a392/VERSION
bcc4785d63a54e4648c250b515e8f0962dcf488d8f8257eac3abd7db8726a392/json
bcc4785d63a54e4648c250b515e8f0962dcf488d8f8257eac3abd7db8726a392/layer.tar
e00c8c90283abfde2570ea569be13b16455f19df5ae3ddb56e783f4401bed19e/
e00c8c90283abfde2570ea569be13b16455f19df5ae3ddb56e783f4401bed19e/VERSION
e00c8c90283abfde2570ea569be13b16455f19df5ae3ddb56e783f4401bed19e/json
e00c8c90283abfde2570ea569be13b16455f19df5ae3ddb56e783f4401bed19e/layer.tar
e31c06a18fd79ea6fd5f42cb878d0c62eeafd12292fc5a8906a2501ec7080db7/
e31c06a18fd79ea6fd5f42cb878d0c62eeafd12292fc5a8906a2501ec7080db7/VERSION
e31c06a18fd79ea6fd5f42cb878d0c62eeafd12292fc5a8906a2501ec7080db7/json
e31c06a18fd79ea6fd5f42cb878d0c62eeafd12292fc5a8906a2501ec7080db7/layer.tar
e530bf9b170faaaa23f82dbcf2384dd7e99bb7b979893a19dfa513eeb69989b2/
e530bf9b170faaaa23f82dbcf2384dd7e99bb7b979893a19dfa513eeb69989b2/VERSION
e530bf9b170faaaa23f82dbcf2384dd7e99bb7b979893a19dfa513eeb69989b2/json
e530bf9b170faaaa23f82dbcf2384dd7e99bb7b979893a19dfa513eeb69989b2/layer.tar
manifest.json
repositories
On peut remarquer ici que cette archive est un ensemble de layer, pour chacune d’entre elle on a un répertoire contenant:
- une archive (fichier layer.tar)
- un fichier json
- un fichier VERSION
Si l’on extrait le contenu de l’une de ces layers, on obtient un système de fichier. L’ensemble de ces layers, et donc de ces systèmes de fichiers, constitue le système de fichier global de l’image.
Exemple de contenu de l’une des layers de l’image:
$ tar -xvf e00c8c90283abfde2570ea569be13b16455f19df5ae3ddb56e783f4401bed19e/layer.tar
etc/
etc/apt/
etc/apt/sources.list.d/
etc/apt/sources.list.d/mongodb-org.list
Pour supprimer l’image:
$ docker image rm mongo:3.6
Ou est stocke mon image ?
Stockage d’une image
Dans un execice precedent, nous avons cree une image nommee ping:1.0
, nous allons voir ici ou cette image est stockee.
Reprenons le Dockerfile de l’exercice:
FROM ubuntu:16.04
RUN apt-get update -y && apt-get install -y iputils-ping
ENTRYPOINT ["ping"]
CMD ["8.8.8.8"]
A partir de ce Dockerfile, l’image est buildée avec la commande suivante:
$ docker image build -t ping:1.0 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM ubuntu:16.04
---> 5e8b97a2a082
Step 2/4 : RUN apt-get update -y && apt-get install -y iputils-ping
---> Using cache
---> 4cd5304ad0fb
Step 3/4 : ENTRYPOINT ["ping"]
---> Using cache
---> d2846bbd30e8
Step 4/4 : CMD ["8.8.8.8"]
---> Using cache
---> 00a905f2bd5a
Successfully built 00a905f2bd5a
Successfully tagged ping:1.0
Pour lister les images présentes localement on utilise la commande docker image ls
(on reverra cette commande un peu plus loin). Pour ne lister que les images qui ont le nom ping
on le précise à la suite de ls
.
$ docker image ls ping
REPOSITORY TAG IMAGE ID CREATED SIZE
ping 1.0 00a905f2bd5a 4 weeks ago 159MB
Notre image est constituée d’un ensemble de layers, il faut voir chaque layer comme un morceau de système de fichiers. L’ID de l’image (dans sa version courte) est 00a905f2bd5a
, nous allons voir à partir de cette identifiant comment l’image est stockée sur la machine hôte (la machine sur laquelle tourne le daemon Docker).
Tout se passe dans le répertoire /var/lib/docker
, c’est le répertoire ou Docker gère l’ensemble des primitives (containers, images, volumes, networks, …). Et plus précisément dans /var/lib/docker/image/overlay2/
, overlay2
étant le driver en charge du stockage des images.
Note: si vous utilisez “Docker for Mac” ou “Docker for Windows”, il est nécessaire d’utiliser la commande suivante pour lancer un shell dans la machine virtuelle dans laquelle tourne le daemon Docker. On pourra ensuite explorer le répertoire /var/lib/docker
depuis ce shell.
$ docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
Plusieurs fichiers/répertoires ont un nom qui contient l’ID de notre image comme on peut le voir ci-dessous:
/var/lib/docker/image/overlay2 # find . | grep 00a905f2bd5a
./imagedb/content/sha256/00a905f2bd5aa3b1c4e28611704717679352a619bcdc4f8f6851cf459dc05816
./imagedb/metadata/sha256/00a905f2bd5aa3b1c4e28611704717679352a619bcdc4f8f6851cf459dc05816
./imagedb/metadata/sha256/00a905f2bd5aa3b1c4e28611704717679352a619bcdc4f8f6851cf459dc05816/lastUpdated
./imagedb/metadata/sha256/00a905f2bd5aa3b1c4e28611704717679352a619bcdc4f8f6851cf459dc05816/parent
Content :
le premier fichier contient un ensemble d’information concernant cette image, notamment les paramètres de configuration, l’historique de création (ensemble des commandes qui ont servi à construire le système de fichiers contenu dans l’image), et également l’ensemble des layers qui la constituent. Une grande partie de ces informations peuvent également être retrouvées avec la commande:
$ docker image inspect ping:1.0
Parmi ces éléments, on a donc les identifiants de chaque layer:
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2",
"sha256:d7ff1dc646ba52a02312b535446d6c9b72cd09fda0480524e4828554efb2f748",
"sha256:686245e78935e73b737c9a82111c3c7df35f5529d06ce8c2f9a7cd32ec90b456",
"sha256:d73dd9e652956dccbbef716de4b172cc15fff644cc92fc69d221cc3a1cb89a39",
"sha256:2de391e51d731ba02b708038a7f98b7103061b916727bcd165e9ee6402f4cdde",
"sha256:3045bfad4cfefecabc342600d368863445b12ed18188f5f2896c5389b0e84b66"
]
}
Si l’on considère la première layer (celle dont l’ID est 6448...
), on voit dans /var/lib/docker/image/overlay2
qu’il y a un répertoire dont le nom correspond à l’ID de cette layer, celui-ci contient plusieurs fichiers :
/var/lib/docker/image/overlay2 # find . | grep '644879075e24394efef8a7dddefbc133aad42'
./layerdb/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2
./layerdb/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2/size
./layerdb/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2/tar-split.json.gz
./layerdb/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2/diff
./layerdb/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2/cache-id
./distribution/v2metadata-by-diffid/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d
Ceux-ci contiennent différentes information sur la layer en question. Parmi celles-ci, le fichier cache-id
nous donne l’identifiant du cache qui a été généré pour cette layer.
/var/lib/docker/image/overlay2 # cat ./layerdb/sha256/644879075e24394efef8a7dddefbc133aad42002df6223cacf98bd1e3d5ddde2/cache-id
49908d07e177f9b61dc273ec7089efed9223d3798ad1d86c78d4fe953e227668
Le système de fichier construit dans cette layer est alors accessible dans le répertoire:
/var/lib/docker/overlay2/49908d07e177f9b61dc273ec7089efed9223d3798ad1d86c78d4fe953e227668/diff/`
LastUpdated:
Ce fichier contient la date de dernière mise à jour de l’image:
/var/lib/docker/image/overlay2 # cat ./imagedb/metadata/sha256/00a905f2bd5...459dc05816/lastUpdated
2018-07-31T07:32:04.6840553Z
parent:
Ce fichier contient l’identifiant du container qui a servi à créer l’image.
/var/lib/docker/image/overlay2 # cat ./imagedb/metadata/sha256/00a905f2bd5459dc05816/parent
sha256:d2846bbd30e811ac8baaf759fc6c4f424c8df2365c42dab34d363869164881ae
On retrouve d’ailleurs ce container dans l’avant dernière étape de création de l’image.
Step 3/4 : ENTRYPOINT ["ping"]
---> Using cache
---> d2846bbd30e8
Ce container est celui qui a été commité pour créer l’image finale.
En résumé: il est important de garder en tête qu’une image est constituée de plusieurs layers. Chaque layer est une partie du système de fichiers de l’image finale. C’est le rôle du driver de stockage de stocker ces différentes layers et de construire le système de fichiers de chaque container lancé à partir de cette image.
Dockerfile
- fichier texte
- Serie d’instructions pour construire le systeme de fichier d’une image
- Flow standard
- specification d’une image de base
- ajout des dependances
- ajout du code applicatif
- definition de la commande a lancer
- $ docker image build …
Exemple de Dockerfile utilise pour une application Node.js:
# image de base
FROM node:8.11.1-alpine
# copie de la liste des dependances
COPY package.json /app/package.json
# installation/compilation des dependances
RUN cd /app && npm install
# copie du code applicatif
COPY . /app/
# exposition du port HTTP
EXPOSE 80
# positionnement du repertoire de travail
WORKDIR /app
# commande executee au lancement d'un container (c'est un alias equivalent a node index.js)
CMD ["npm","start"]
Les instructions principales
- FROM: image de base
- ENV: definition de variables d’environnement
- RUN: execution d’une commande, construction du filesystem de l’image
- COPY / ADD: copie de ressources depuis la machine local dans le filesystem de l’image
- EXPOSE: expose un port de l’application
- HEALTHCHECK: verifie l’etat de sante de l’application
- VOLUME: definition d’un volume pour la gestion des donnees
- WORKDIR: definition du repertoire de travail
- USER: utilisateur auquel appartient le processus du container
- ENTRYPOINT / CMD: definie la commande executee au lancement du container
FROM:
- definit l’image de base
- differentes possibilite
- image d’un OS (Alpine, CentOS, Ubuntu…)
- serveur applicatif // contiennent l’OS de base
- environnement d’execution // contiennent l’OS de base
- … // contiennent l’OS de base
- scratch: une image particuliere (image vide, par exemple utilise pour une appli en Go car pas besoin d’etre package dans un systeme de fichier)
- pour construire une image de base
ENV:
- definition de variables d’environnement
- valeur utilisee dans les instructions suivantes du build
-
dans l’environnement des containers crees a partir de l’image
FROM nginx:1.14.0 WORKDIR ${path} COPY . $path
CPOY / ADD:
- copie des fichiers et repertoires dans le systeme de fichiers de l’image
- engendre la creation d’une nouvelle layer
- option
--chown
pour la mise specification des droits ADD
- permet de specifier une URL pour la source
- unpack un fichier tar.gz
- privilegier l’utilisation de
COPY
RUN:
- execute une commande dans une nouvelle layer
- 2 formats:
Shell: lance dans un shell avec /bin/sh -c
par defaut
RUN apt-get update -y && apt-get install
Exec: non lance dans un shell
RUN ["/bin/bash", "-c", "echo hello"]
EXPOSE:
- information des ports utilises par l’application
- peut etre modifie au lancement du container
- option
-p CONTAINER_PORT
- option
-p HOST_PORT:CONTAINER_PORT
- option
-P
- option
# exemple mongoDB
EXPOSE 27017
CMD ["mongod"]
# exemple nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
VOLUME:
- decouple les donnees du cycle de vie d’un container
- gestion des donnees en dehors de l’union file-system
- par defaut, creation d’un repertoire sur la machine hote
- initialisation du volume avec les donnees presentes dans l’image
# exemple mongoDB
RUN mkdir -p /data/db /data/configdb \
&& chown -R mongodb:mongodb /data/db /data/configdb
VOLUME /data/db /data/configdb
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 27017
CMD ["mongod"]
USER: (Bonne pratique!)
- username ou uid/gid utilises pour lancer les process du container
- interet: ne pas lancer le processus du container en root
- comportement par defaut
- root dans le container <=> root sur la machine hote
- utilise par les instructions RUN, CMD, ENTRYPOINT qui suivent
- utilisateur parfois change dynamiquement au lancement du container
HEALTHCHECK: (Bonne pratique!)
- verification de l’etat de sante du container
- Ex: verification du endpoint/health toutes les 5 secondes (c’est une bonne pratique, ca permet par exemple a un orchestrateur de redemarrer un container qui ‘est pas en bon etat de sante)
FROM node:8.11-alpine
RUN apk update && apk add curl
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl -f http://localhost:8000/health || exit 1
COPY package.json /app/package.json
WORKDIR /app
RUN npm install
COPY . /app
CMD ["npm","start"] #(equivalent a node index.js pour executer le serveur)
ENTRYPOINT / CMD:
- definition de la commande executee dans le container
ENTRYPOINT
: binaire de l’applicationCMD
: arguments par defaut- concatenation de
ENTRYPOINT
etCMD
- 2 formats possibles
- Shell, ex:
/bin/ping localhost
- Exec, ex:
["ping","localhost"]
- Shell, ex:
ENTRYPOINT ["curl"]
CMD ["--help"]
Creation d’images
- A partir d’un Dockerfile
docker image build [option] path | url | -
- des options courantes
-f
: specifie le fichier a utiliser pour la construction (Dockerfile par defaut)--tag
/-t
: specifie le nom de l’image[registry/]user/repository:tag
--label
: ajout de metadonnees a l’image
- le
.
en fin de commande est le repertoire ou se trouve le Dockerfile - le fichier
Dockerfile
doit etre a la racine du projet
Exemple: Application Node.js
le serveur web index.js
renvoie l’heure a chaque requete recu:
//index.js
var express = require('express');
var util = require('util');
var app = express();
app.get('/', function(req,res){
res.setHeader('Content-Type','text/plain');
res.end(util.format("%s - %s", new Date(), 'Got HTTP Get Request'));
});
app.listen(process.env.PORT || 80)
//package.json
{
"name": "testnode",
"version": "0.0.1",
"main": "index.js",
"script": {
"start": "node index.js"
},
"dependencies": {"express": "^4.14.0"}
}
# dokerfile
# image de base
FROM node:8.11-alpine
# copie de la liste des dependances
COPY package.json /app/package.json
# installation / compilation des dependances
RUN cd /app && npm install
# copie du code applicatif
COPY . /app/
WORKDIR /app
#port d'ecoute
EXPOSE 80
# commande a lancer (equivalent a node index.js)
CMD ["npm","start"]
Ensuite on construit l’image avec:
$ docker image build -t app:1.0 .
Les instruction du dockerfile sont execute de facon sequentielle. Enfin, nous pouvons lancer un container de notre appli:
$ docker container run -p 8080:80 app:1.0
Exemple: Application Python
Cette cree un QRcode contenant le string "Hello"
.
# barcode.py
# https://githb.com/mmulqueen/pyStrich
from pystrich.datamatrix import DataMatrixEncoder
encoder = DataMatrixEncoder('Hello')
print(encoder.get_ascii())
#dockerfile
FROM python:3
ADD barcode.py /
RUN pip install pystrich
CMD [ "python", "/barcode.py" ]
On cree ensuite l’image de l’application python avec:
$ docker image build -t barcode .
Enfin nous pouvons utiliser le container:
$ docker container run barcode
Exemple: Application Java
Execute un "Hello World"
// Main.java
public class Main
{
public static void main(String[] args){
System.out.println("Hello World")
}
}
# dockerfile
FROM openjdk:10
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java","Main"]
On build l’image puis on lance le container:
$ docker image build -t hellojava:v1.0 .
$ docker container run hellojava:1.0
Hello World
Exemple: Application Php
A chaque fois qu’une requet GET
est recu, on envoie les informations du serveur.
# index.php
<?php
pprint_r($_SERVER)
?>
# dockerfile pour servir les pages php avec apache
# autre possibilite avec Nginx+php-fpm
FROM php:7-appache
COPY . /var/www/html/
on build et on execute le container:
$ docker image build -t phpreq:1.0 .
$ docker run -p 8888:80 phpreq:1.0
Exercices: creation d’image
Creer une image a partir d’un container existant avec commit
Soit un container “c1” ayant Alpine pour OS et curl installe. Pour creer une image “myping” a partir du container “c1”, il faut utiliser la commande suivante:
$ docker container commit c1 myping
Maintenant on peut creer un container avec l’image “myping” avec curl deja installe:
$ docker container run -ti myping
Nous avons donc lancé un container, ajouté un binaire dans ce container et commité le tout en une nouvelle image. Le binaire est donc présent dans cette image. Commiter un container pour créer une image n’est pas l’approche recommandée. La création d’une image se fait à partir d’un Dockerfile, fichier texte contenant l’ensemble des commandes nécessaires.
le fichier .dockerignore
il est possible de creer un fichier .dockerignore
et mettre tous fichiers/dossiers qui ne doivent pas etre dans le build.
Entrypoint vs Cmd
Nous allons illustrer sur plusieurs exemples l’utilisation des instructions ENTRYPOINT
et CMD
. Ces instructions sont utilisées dans un Dockerfile pour définir la commande qui sera lancée dans un container.
Dans un Dockerfile, les instructions ENTRYPOINT
et CMD
peuvent être spécifiées selon 2 formats:
- le format shell, ex:
ENTRYPOINT /usr/bin/node index.js
. Une commande spécifée dans ce format sera exécutée via un shell présent dans l’image. Cela peut notamment poser des problématiques car les signaux ne sont pas forwardés aux processus forkés. - le format exec, ex:
CMD [“node”, “index.js”]
. Une commande spécifiée dans ce format ne nécessitera pas la présence d’un shell dans l’image. On utilisera souvent le format exec pour ne pas avoir de problème si aucun shell n’est présent.
Reecriture a l’execution du container
ENTRYPOINT
et CMD
sont 2 instructions du Dockerfile, mais elle peuvent cependant être écrasées au lancement d’un container:
- pour spécifier une autre valeur pour l’
ENTRYPOINT
, on utilisera l’option--entrypoint
, par exemple:docker container run –entrypoint echo alpine
- pour spécifier une autre valeur pour
CMD
, on précisera celle-ci après le nom de l’image, par exemple:docker container run alpine echo “hello”
Instruction ENTRYPOINT
utilisée seule
L’utilisation de l’instruction ENTRYPOINT
seule permet de créer un wrapper autour de l’application. Nous pouvons définir une commande de base et lui donner des paramètres suplémentaires, si nécessaire, au lancement d’un container. Dans ce premier exemple, vous allez créer un fichier Dockerfile-v1
contenant les instructions suivantes:
FROM alpine
ENTRYPOINT ["ping"]
Créez ensuite une image, nommée ping:1.0, à partir de ce fichier.
$ docker image build -f Dockerfile-v1 -t ping:1.0 .
Lancez maintenant un container basé sur l’image ping:1.0
$ docker container run ping:1.0
La commande ping est lancée dans le container (car elle est spécifiée dans ENTRYPOINT
), ce qui produit le message suivant:
BusyBox v1.26.2 (2017-05-23 16:46:25 GMT) multi-call binary.
Usage: ping [OPTIONS] HOST
Send ICMP ECHO_REQUEST packets to network hosts
# en gros il manque @IP a la commande
Par défaut, aucune machine hôte n’est ciblée, et à chaque lancement d’un container il est nécessaire de préciser un FQDN ou une IP. La commande suivante lance un nouveau container en lui donnant l’adresse IP d’un DNS Google (8.8.8.8), nous ajoutons également l’option -c 3
pour limiter le nombre de ping envoyés.
$ docker container run ping:1.0 -c 3 8.8.8.8
Nous obtenons alors le résultat suivant.
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=37 time=8.731 ms
64 bytes from 8.8.8.8: seq=1 ttl=37 time=8.503 ms
64 bytes from 8.8.8.8: seq=2 ttl=37 time=8.507 ms
La commande lancée dans le container est donc la concaténation de l’ENTRYPOINT et de la commande spécifiée lors du lancement du container (tout ce qui est situé après le nom de l’image).
Comme nous pouvons le voir dans cet exemple, l’image que nous avons créée est un wrapper autour de l’utilitaire ping et nécessite de spécifier des paramêtres supplémentaires au lancement d’un container.
Instruction CMD
utilisée seule
De la même manière, il est possible de n’utiliser que l’instruction CMD
dans un Dockerfile, c’est d’ailleurs très souvent l’approche qui est utilisée car il est plus simple de manipuler les instructions CMD
que les ENTRYPOINT
.
Créez un fichier Dockerfile-v2
contenant les instructions suivantes:
FROM alpine
CMD ["ping"]
Créez une image, nommée ping:2.0, à partir de ce fichier.
$ docker image build -f Dockerfile-v2 -t ping:2.0 .
Si nous lançons maintenant un nouveau container, il lancera la commande ping comme c’était le cas avec l’exemple précédent dans lequel seul l’ENTRYPOINT
était défini.
Nous n’avons cependant pas le même comportement que précédemment, car pour spécifier la machine à cibler, il faut redéfinir la commande complète à la suite du nom de l’image.
Si nous ne spécifions que les paramètres de la commande ping, nous obtenons un message d’erreur car la commande lancée dans le container ne peut pas être interpretée.
Il faut redéfinir la commande dans sa totalité, ce qui est fait en la spécifiant à la suite du nom de l’image
$ docker container run ping:2.0 ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=37 time=10.223 ms
64 bytes from 8.8.8.8: seq=1 ttl=37 time=8.523 ms
64 bytes from 8.8.8.8: seq=2 ttl=37 time=8.512 ms
Instruction ENTRYPOINT
et CMD
utilisée seule
Il est également possible d’utiliser ENTRYPOINT
et CMD
en même temps dans un Dockerfile, ce qui permet à la fois de créer un wrapper autour d’une application et de spécifier un comportement par défaut.
Nous allons illustrer cela sur un nouvel exemple et créer un fichier Dockerfile-v3
contenant les instructions suivantes:
FROM alpine
ENTRYPOINT ["ping"]
CMD ["-c3", "localhost"]
Ici, nous définissons ENTRYPOINT
et CMD
, la commande lancée dans un container sera la concaténation de ces 2 instructions: ping -c3 localhost
.
Créez une image à partir de ce Dockerfile, nommez la ping:3.0
, et lançez un nouveau container à partir de celle-ci.
$ docker image build -f Dockerfile-v3 -t ping:3.0 .
$ docker container run ping:3.0
Vous devriez alors obtenir le résultat suivant:
PING localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.062 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.102 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.048 ms
Nous pouvons écraser la commande par défaut et spécifier une autre adresse IP
$ docker container run ping:3.0 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=38 time=9.235 ms
64 bytes from 8.8.8.8: seq=1 ttl=38 time=8.590 ms
64 bytes from 8.8.8.8: seq=2 ttl=38 time=8.585 ms
Il faut alors faire un CTRL-C pour arrêter le container car l’option -c3 limitant le nombre de ping n’a pas été spécifiée. Cela nous permet à la fois d’avoir un comportement par défaut et de pouvoir facilement le modifier en spécifiant une autre commande.
Multi-stages build
- disponible depuis Docker 17.05
- decoupage du build en plusieurs etapes
- un cas d’usage courant
- 1ere etape
- utilisation d’une image de base contenant le tooling de build
- creation d’artefacts
- 2eme etape
- utilisation d’une image pour la production
- copie des artefacts generes lors de la premiere etape
- 1ere etape
- plusieurs instructions FROM dans dockerfile
Sur l’exemple de l’application Java:
Execute un "Hello World"
// Main.java
public class Main
{
public static void main(String[] args){
System.out.println("Hello World")
}
}
# dockerfile traditionnel
FROM openjdk:10
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java","Main"]
Nous allons faire un multi-stages build. on utilise une image de base qui contient un JDK complet, on copie les sources avec l’instruction COPY
et on les compilent dans l’instruction RUN
pour generer un binaire. une fois le binaire cree, on a plus besoin de l’environnement JDK pour le cree mais juste d’un environnement pour le faire tourner soit le JRE.
# dockerfile avec multi stage build
FROM openjdk:10 as build
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
FROM openjdk:10-jre-slim
COPY --form=build /usr/src/myapp/Main.class /usr/src/myapp/
WORKDIR /usr/src/myapp
CMD ["java","Main"]
Exemple: Multi-stages build application Reactjs
# creation d'une application React
$ npm init react-app api
# dockerfile avec multi stage build
FROM node:8.11-alpine as build
WORKDIR /app
COPY package.json ./package.json
RUN npm install
COPY . ./
RUN npm run build
FROM nginx:1.14.0
COPY --form=build /app/build/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
puis on build et on lance un container:
$ docker image build -t app:1.0 .
$ docker run -p 9000:80 app:1.0
Exercice: Multistage build
Comme nous l’avons vu, le Dockerfile contient une liste d’instructions qui permet de créer une image. La première instruction est FROM, elle définit l’image de base utilisée. Cette image de base contient souvent beaucoup d’éléments (binaires et librairies) dont l’application finale n’a pas besoin (compilateur, …). Ceci qui peut impacter de façon considérable la taille de l’image et également sa sécurité puisque cela peut considérablement augmenter sa surface d’attaque. C’est la qu’intervint le multistage build.
Le multi-stage build, introduit dans la version 17.05 de Docker permet, au sein d’un seul Dockerfile, d’effectuer le process de build en plusieurs étapes. Chacune des étapes peut réutiliser des artefacts (fichiers résultant de compilation, assets web, …) créés lors des étapes précédentes. Ce Dockerfile aura plusieurs instructions FROM mais seule la dernière sera utilisée pour la construction de l’image finale.
Si nous reprenons l’exemple du serveur http ci dessus, nous pouvons dans un premier temps compiler le code source en utilisant l’image golang contenant le compilateur. Une fois le binaire créé, nous pouvons utiliser une image de base vide, nommée scratch, et copier le binaire généré précédemment.
# appli web en Go 300Mo->6Mo grace au multistage build
FROM golang:1.9.0-alpine as build
WORKDIR /go/src/github.com/lucj/who
COPY http.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o http .
FROM scratch
COPY --from=build /go/src/github.com/lucj/who/http .
CMD ["./http"]
L’exemple que nous avons utilisé ici se base sur une application écrite en Go. Ce langage a la particularité de pouvoir être compilé en un binaire static, c’est à dire ne nécessitant pas d’être “linké” à des librairies externes. C’est la raison pour laquelle nous pouvons partir de l’image scratch. Pour d’autres langages, l’image de base utilisée lors de la dernière étape du build pourra être différente (alpine, …)
Pour cette simple application, le multistage build a permit de supprimer 270M de binaires et librairies dont la présence est inutile dans l’image finale. L’exemple d’une application écrite en go est extrème, mais le multistage build fait partie des bonnes pratiques à adopter quel que soit le language de développement de l’application.
Prise en compte du cache
- mecanisme d’utilisation des layers deja creees
- accelere la creation d’image
- ex: permet d’eviter la recompilation des dependances suite a une typo dans le source
- invalidation du cache
- modification d’une instruction dans le dockerfile
- ex: valeur d’une variable d’environnement
- modifications de fichiers copies dans l’image (instruction ADD / COPY)
- utile pour forcer la creation d’une nouvelle image
- modification d’une instruction dans le dockerfile
Chaque instruction utilise le cache cree lors du build precedent, pour chaque step lors du build nous aurons des Using cache
donc creation de l’image quasi immediate.
Il faut que les element qui sont susceptible d’etre le plus souvent modifie soient le plus bas possible dans le dockerfile de tel sorte qu’on puisse faire appel au cache un maximum.
Exercice: Cache
Nous observons que chaque étape du build à été effectuée une nouvelle fois. Il serait intéressant de faire en sorte qu’une simple modification du code source ne déclenche pas le build des dépendances.
une bonne pratique, à suivre dans l’écriture d’un Dockerfile, est de faire en sorte que les éléments qui sont modifiés le plus souvent (code de l’application par exemple) soient positionnés plus bas que les éléments qui sont modifiés moins fréquemment (liste des dépendances).
Dockerfile de base:
FROM node:10.15-alpine
COPY . /app/
RUN cd /app && npm install
WORKDIR /app
EXPOSE 80
CMD ["npm", "start"]
On peut alors modifier le Dockerfile de la façon suivante:
FROM node:10.15-alpine
COPY package.json /app/package.json
RUN cd /app && npm install
COPY . /app/
WORKDIR /app
EXPOSE 80
CMD ["npm", "start"]
L’approche suivie ici est la suivante:
- copie du fichier package.json qui contient les dépendances
- build des dépendances
- copie du code source
On rebuild alors une nouvelle fois l’image en lui donnant le tag pong:1.2. Un cache est créé pour chaque étape du build. On fait la modification suivante dans le fichier pong.js. puis l’on build une nouvelle fois l’image, en la nommant cette fois pong:1.3.
Nous observons ici que le cache est utilisé jusqu’au step 3 (—> Using cache). Il est ensuite invalidé lors du step 4 car le daemon Docker a détecter le changement de code que nous avons effectué. La prise en compte du cache permet souvent de gagner beaucoup de temps lors de la phase d build.
Le contexte de build
- repertoire utilise par le docker daemon lors du build
- contenu package dans un .tar et envoye au daemon
$ docker image build -t app:v2.0 .
Sending build context to Docker daemon 4.096kB
- gestion avec le fichier .dockerignore
- rapidite du build
- exclusion des donnees sensibles
# fichier .dockerignore
.git
node_modules
Donc faire attention a ce que contient dossier ou on se trouve car si on lance un build, tout le contenu du dossier sera present dans l’image.
Les commandes de base
Les differentes commandes pour la primitive image
.
Pour avoir plus d’info, voir docker image --help
.
docker image build
Build an image from a Dockerfiledocker image history
Show the history of an imagedocker image import
Import the contents from a tarball to create a filesystem imagedocker image inspect
Display detailed information on one or more imagesdocker image load
Load an image from a tar archive or STDINdocker image ls
List imagesdocker image prune
Remove unused imagesdocker image pull
Pull an image or a repository from a registrydocker image push
Push an image or a repository to a registrydocker image rm
Remove one or more imagesdocker image save
Save one or more images to a tar archive (streamed to STDOUT by default)docker image tag
Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
les commandes de base pull
:
- download une image depuis un registry (Docker Hub par defaut) pour qu’elle puisse etre utilise localement
- chaque layer de l’image est downloadee
- image automatiquement downloadee lors de la creation d’un container
- format de nommage:
USER/IMAGE:VERSION
- on peut voir les images telechargees avec
docker image ls
les commandes de base push
:
- upload une image dans un registry (Docker Hub par defaut)
- Une fois uploadee, l’image pourra etre utilisee par les utilisateurs autorises
- Login necessaire pour uploader une image sur le Docker Hub
- $
docker login
- $
les commandes de base inspect
:
Permet d’avoir une vue detaille d’une image.
$ docker image inspect alpine
$ docker image inspect -f '' alpine
les commandes de base history
:
Permet de donner l’historique des layers qui sont creer lors de la creation de l’image.
- montre comment l’image a ete creee
- une entree d’historique par layer
$ docker history alpine
les commandes de base ls
:
Permet d’enumerer les images presentes localement. Soit les images telecharge depuis un registry et les images creees avec la commande build.
- le flag
-a
renvoie aussi les images temporaires - les images n’ayant ni nom ni version sont des images “dangling”, ils ne sont plus referencees.
- on peut filtrer les images contenant node par exemple:
docker image ls node
- on peut aussi utiliser l’option
--filter
:docker image ls --filter dangling=true
renvoie toutes les images dans l’etat dangling
- pour supprimer les images en etat de dangling:
docker image prune
les commandes de base save
/ load
:
La commande save
permet d’exporter une image dans un fichier .tar
et la commande load
permet de creer une image depuis un fichier .tar
.
Exemple:
# liste les images presents en local
$ docker images
alpine ...
# on save l'image dans un fichier tar
$ docker save -o alpine.tar alpine
# on verifie que le tar existe dans notre dossier
$ ls
alpine.tar
# on supprime l'image localement
$ docker image rm alpine
# liste les images presents en local
$ docker images
"vide"
# on load l'image present dans le tar
$ docker load < alpine.tar
Loaded image: alpine:latest
# liste les images presents localement
$ docker image ls
alpine ...
- supprime une image avec l’ensemble de ses layers
- plusieurs images peuvent etre supprimees en meme temps
$ docker image ls
$ docker image rm ubuntu
$ docker image ls -q
$ docker image rm $(docker image ls -q)
Registry
- librairie d’images
- facilite la distribution entre environnements
- upload d’une image:
docker image push IMAGE
- downmoad d’une image:
docker image pull IMAGE
- Docker Hub utilise par defaut
Il faut se logguer pour pouvoir push une image dans un repository docker hub grace a la commande docker login
.
$ docker image pull alpine:3.8
$ docker image push username/www:2.0
# autre exemple avec l'appli pong en nodejs
# on ajoute un tag
$ docker image tag pong:1.3 username/pong:1.3
# se log a docker hub
$ docker login -u username
# push l'image
$ docker image push username/pong:1.3
Differents providers: les registries Docker
- Docker Hub - hyyps://hub.docker.com
- Registry officiel hoste par Docker
- Docker Registry
- Solution open-source
- Docker Trusted Registry
- Solution commerciale
- Disponible avec Docker-EE standard / avance
Differents providers: autres registries de l’ecosysteme
- Amawon EC2 container registry
- Google Container Registry
- Quay.io (CoreOS)
- GitLab container registry
- etc…
Docker Hub: overview
- images officielles
- images publiques ou privees
- integration avec github ou bitbucket
- integration dans un pipeline d’integration continu / deploiement continu
- differentes versions de docker CE/EE
- plugin (autoriwation, loggin, network, volume)
Differentes categories:
- distributions linux (ubuntu, alpine, centos)
- serveur HTTP (nginx, appache)
- bases de donnees (mongo, redis, mysql)
- runtime (nodejs, java clojure, php)
- …
Les images officielles sont scannees regulierement au niveau binaire (CVE) pour detecter les vulnerabilitees.
Un utilisateur peut creer son propre repository:
- contient l’ensemble des versions d’une image
- tag =>
user/repository:version
- image publique
- utilisable librement
- mais a utiliser avec precaution
- peut contenir des failles de securite
- mises a jour non assurees
- image privee
- visibilite limitee aux utilisateurs autorises
Docker Hub: CI/CD
- au centre d’un pipeline de build automatique
- creation d’image declachee par le push de code ou HTTP Post
- integration avec des outils de versionning de code source
- github, gitlab, bitbucket,…
- webhook pour l’integration avec des outils tiers
Exercice: Docker Hub
Docker Open Source Registry
docker fournit un registry open-source que l’on peut monter dans un container
- https://hub.docker.com/_/registry
- API HTTP Rest pour le management des images
- A la base du Docker Hub
- Configurable via un fichier de configuration
- securisation avec TLS
- methode d’authentification
- selection du backend de stockage
- https://docs.docker.com/registry/configuration/#list-of-configuration-options
Un daemon docker ne peut communiquer avec un registry que de facon securise:
- connexion depuis un docker daemon
- option
--insecure-registry
si communication non securisee - loaclhost exception
- option
- image prefixee par l’url du registry
HOST:PORT/NAME:PORT[:VERSION]
# lancement du registry dans un container
$ docker container run -d -p 5000:5000 registry:2.6.2
# liste des images presentes dans le registry
$ curl localhost:5000/v2/_catalog
{"repositories":[]}
# download de l'image nginx depuis le Docker Hub
$ docker image pull nginx:1.14
# tag de l'image nginx selon le format URL:PORT/NAME:VERSION
$ docker image tag nginx:1.14 localhost:5000/nginx:1.14
# upload de l'image dans le registry
$ docker image push localhost:5000/nginx:1.14
# liste des images presentes dans le registry
$ curl localhost:5000/v2/_catalog
{"repositories":["nginx"]}
Si le registry ne tourne pas sur la meme machine ex: docker image push 198.168.0.1:5000/nginx:1.14
, alors il faut un certificat ou ajouter l’option --insecure-registry
, on peut faire un systemctl status docker
et recuperer la valeur dans Loaded
le service docker soit /lib/systemd/system/docker.service
puis ajouter a la ligne ExecStart=.....
ceci --insecure-registry [@IP:PORT du registry]
puis relancer le docker daemon systemctl daemon-reload
puis systemctl restart docker
.
Une autre facon de faire est de cree un fichier dans /etc/docker/daemon.json
puis mettre la liste de registry ou il peut se connecter:
{
"insecure-registries":["192.168.0.1:5000"]
}
Exercice: Registry open source
Exercice: Configuration du registry open source
Stockage
Container et persistance de donnees
- les changements sont persistes dans la layer associee au container
- layer en lecture/ecriture superposee aux layers de l’image
- creee et suprimee avec le container
- pour etre decouplees du cycle de vie du container, les donnees doivent etre gerees en dehors de l’union filesystem
# creation du fichier /tmp/test_file dans un container test
$ docker container run -ti --name test alpine sh
touch /tmp/test_file
exit
# le fichier /tmp/test_file est visible depuis la machine hote
$ find /var/lib/docker -name 'test_file'
/var/lib/docker/overlay2/3aw3d3sd4ds51da...as85das1da/diff/tmp/test_file
# suppression du container
$ docker container rm test
test
# le fichier a ete supprime avec le container
$ find /var/lib/docker -name 'test_file'
'vide'
Volume
- repertoires / fichiers existant en dehors de l’union filesystem
- utilise pour decoupler les donnees du cycle de vie d’un container
- cree de differentes facons
- instruction
VOLUME
dans dockerfile - option
-v
/--mount
a la creation d’un container - via la commande
docker volume create
- instruction
- cas d’usage
- persistance des donnees d’une base de donnees
- persistance des logs en dehors du container
Definition a l’execution:
- option
-v
dans la commandedocker container run
docker container run -v CONTAINER_PATH IMAGE
- meme resultat que la definition dans Dockerfile
- Creation d’un volume et copie du contenu du repertoire du container
Les commandes de base avec la primitive volume
create
create a volumeinspect
display detailed information on one or more volumesls
list volumesprune
remove all unused volumesrm
remove one or more volunes
Exemple:
# creation du volume db-data en utilisant le driver par defaut (stockage sur la machine hote)
$ docker volume create --name db-data
db-data
$ docker volume ls
DRIVER VOLUME NAME
local db-data
$ docker volume inspect db-data
[
{
"CreateAt": "2019-09-01T01:12:11Z"
"Driver": "local",
"Label": {},
"Mountpoint": "/var/lib/docker/volumes/db-data/_data",
"Name": "db-data",
"Option": {},
"Scope": "local"
}
]
Utilisation/reutilisation d’un volume existant:
# le volume db-data est monte dans le container
$ docker container run -d --name db -v db-data:/data/db mongo:4.0
# le contenu du repertoire du container est visible dans le volume via cette commande
$ ls /var/lib/docker/volumes/db-data/_data
Exercice: Volumes
voir l’exo il y a plein d’exemple de montage, partage de volume host->container.
Drivers de volumes
- permettent la creation de volumes sur differents types de stockage
- driver par defaut:
local
- stockage sur la machine hote
- dans
/var/lib/docker/volume/ID
- ajout de drivers supplementaires via l’installation de plugins
- permet l’utilisation de solution de stockage externes
- exemples: Ceph, AWS, GCE, Azure
Exemple: plugin sshfs
- stockage sur un systeme de fichier via ssh
- https://github.com/vieux/docker-volume-sshfs
- illustre l’utilisation de plugin => n’est pas fait pour une utilisation en production
# installation du plugin
$ docker plugin install vieux/sshfs
Plugin "vieux/sshfs" is requesting the following privileges:
- network: [host]
- mount: [/var/lib/docker/plugins/]
- mount: []
- device: [/dev/fuse]
- capabilities: [CAP_SYS_ADMIN]
...
#telechargement depuis le docker hub
On peut lister les plugins avec:
docker plugin ls
# creation du repertoire dur le serveur ssh
$ ssh USER@HOST mkdir /tmp/data
# creation du volume avec le driver vieux/sshfs
## ici le flag -d permet de specifier le driver soit vieux/sshfs
## puis les flgs -o pour les options dont le driver a besoin pour se connecter
## donc ce volume est dans un serveur distant
$ docker volume create -d vieux/sshfs -o sshcmd=USER@HOST:/tmp/data -o password=PASSWORD data
# Liste des volumes
## on verifie que le volume est bien monte
$ docker volume ls
vieux/sshfs:lasest data
# utilisation du volume dans un container
$ docker run -it -v data:/data alpine
touch /data/test
# verification sur le serveur ssh
$ ssh USER@HOST ls /tmp/data
test
Dans le Docker Hub on peut voir les plugin, il y a differentes categories tel que volume, authorisation login, network…
Docker Machine
Utililitaire pour provisioner des hotes docker.
Utilisation:
- creation d’une VM ou utilisation d’une VM ou machine physique existante
- installation de la plateforme Docker (client + daemon) et possibilite de communiquer les options avec lesquels le daemon sera lance
- permet au client local de communiquer avec le daemon distant via des variables d’environnement
- creation de certificats pour securiser la communication
different types de drivers:
- en local
- Oracle Virtualbox
- VMWare
- sur une infrastructure cloud
- DigitalOcean
- Amazon Web Service
- Microsoft Azure
- Google Compute Engine
- Generique
Utilisation de Docker Machine pour creer des hotes Docker en local ou sur un Cloud provider:
Configuration du client local pour qu’il communique avec un daemon Docker distant:
Possibilite de configurer le client docker de tel facon qu’il s’adresse au daemon distant et non au daemon local is suffit de faire eval $(docker machine env [NOM_MACHINE_DISTANTE])
puis a partir de ce moment la toute les commandes lance avec le client docker s’adresseront au daemon distant.
Installation:
- docker for mac / docker for windows / docker toolbox
- docker machine installe par defaut
- mise a jour automatique
- installation du binaire
- https://docs.docker.com/machine/install-machine/#/installing-machine-directly
- installation de VirtualBox pour la creation en local
les commandes docker-machine
$ docker-machine --help
Usage: docker-machine [OPTIONS] COMMAND [arg...]
Options:
...
Commands:
active Print which machine is active
config Print the connection config for machine
Create Create a machine
env Display the commands to set up the environment for Docker client
inspect Inspect information about a machine
ip Get the IP address of a machine
kill Kill a machine
ls List machine
regenerate-certs Regenerate TLS Certificates for machine
restart Restart a machine
rm Remove a machine
ssh Log into or run a command on a machine with SSH
scp Copy files between machines
....
- gestion du cycle de vie (create, rm, start, stop, restart, kill, upgrade)
- information sur l’hote (ip, url, config, status, version)
- liste des hotes crees (ls)
- cope de fichiers (scp)
- lancement d’un shell (ssh)
Creation
$ docker-machine create [OPTIONS][arg...]
- option
--driver [MON_DRIVER]
a specifier - options specifiques au driver utilise
- identifiants du cloud provider
- type d’instance (region, RAM, cpu, …)
- …
- option non specifiques au driver utilise
- configuration du docker daemon
- configuration d’un cluster swarm
- …
exemple: creation d’un hote sur VirtualBox
option par defaut:
$ docker-machine create --driver virtualbox node1
option additionnelles:
$ docker-machine create --driver virtualbox --virtualbox-memory=2048 --virtualbox-disk-size=5000 node3
exemple: creation d’un hote sur DigitalOcean
# un token d'identification doit etre specifie
## ici elle est dan la var env TOKEN
$ docker-machine create --driver digitalocean --digitalocean-access-tocken=$TOKEN node1
option additionnelles:
$ docker-machine create --driver digitalocean --digitalocean-access-token=$TOKEN --digitalocean-region=lon1 --digitalocean-size=1gb node2
exemple: creation d’un hote sur Amazon EC2
# des cles d'identification doivent etre specifiees
$ docker-machine create --driver amazonec2 --amazonec2-access-key=${ACCESS_KEY_ID} --amazonec2-secret-key=${SECRET_ACCESS_KEY} node1
option additionnelles:
$ docker-machine create --driver amazonec2 --amazonec2-access-key=${ACCESS_KEY_ID} --amazonec2-secret-key=${SECRET_ACCESS_KEY} --amazonec2-region=eu-west-1 --amazonec2-ami=ami-ed82e39e --amazonec2-instance-type=t2.large node2
Communication avec un hote distant
- client local communique avec le daemon local par defaut (via unix socket)
- variables d’environnemt pour cibler un hote distant
$ docker-machine env node1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.102:2376"
export DOCKER_CERT_PATH="/Users/luc/.docker/machine/machines/node1"
export DOCKER_MACHINE_NAME="node1"
- definition des variables d’environnement
$ eval $(docker-machine env node1)
- machine cible definie via la variable
DOCKER_HOST
$ env | grep DOCKER
DOCKER_HOST=tcp://192.168.99.102:2376
DOCKER_MACHINE_NAME=node1
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/Users/luc/.docker/machine/mochines/node1
- les commandes Docker ciblent l’hote distant
$ docker image ls
- suppression des variables pour cibler le daemon local
$ eval $(docker-machine env -u)
$ docker image ls
en gros par defaut le docker local communique avec le daemon local.
si on lance docker-machine env node1
on voit le parametrage du node1 soit la VM lance dans virtualbox. si on fait eval $(docker-machine env node1)
on a sette ces variables d’environnement. Si on fait un docker ps
on se trouve dans un autre contexte soit le daemon de node1. pour unset les variables d’enironnement on fait eval $(docker-machine env -u)
.
puis une fois fini on peut faire un docker-machine rm node1
pour supprimer la vm de “l’hyperviser” virtualbox.
Exercice: Creation en local
Exercice: Creation sur DigitalOcean
PDF : creation sur digital ocean
Docker Compose
Outil tres utile pour gerer les applications multi-container.
- le fichier docker-compose.yml (format utilise pour definire une appli multi-container)
- le binaire docker-compose (sert a gerer le cycle de vie d’une appli defini dans le format compose)
- service discovery (permet aux appli de communiquer les uns avec les autres)
- exemple avec la Volting App (appli multi-service maintenu par docker tres utilise pour les demos)
Presentation
- outil permettant de definir et de gerer des applications complexes
- convient bien a une architecture de micro-services
- format de fichier:
- docker-compose.yml
- definition de l’ensemble des services d’une application
- binaire:
- docker-compose
- permet de lancer l’application definie dans le fichier docker-compose.yml
docker-compose.yml: structure
- definition des composants de l’application
- services
- volumes
- networks
- secrets (Swarm)
- configs (Swarm)
- https://docs.docker.com/compose/compose-file/
Exemple d’application web: docker-compose.yml:
version: '3.7'
volume:
data:
networks:
frontend:
backend:
services:
web:
image: org/web:2.3
networks:
- frontend
ports:
- 80:80
api:
image: org/api:1.2
networks:
- backend
- frontend
db:
image: mongo:4.0
volumes:
- data:/data/db
networks:
- backend
- version du format
- definition du volume
data
qui sera utilise par un service - definition de 2 networks pour isoler les services
- definition des 3 services qui constituent l’application
- pour chaque service:
- l’image utilisee
- les volumes utilises pour la persistance des donnees
- les networks auxquels chaque service est attache
- les ports exposes a l’exterieur
docker-compose.yml: definition d’un service
Ces instructions peuvent etre definies pour chaque service:
- image utilisee (obligatoire)
- nombre de replicas
- ports publies a l’exterieur
- health check
- strategie de redemarrage
- contraintes de deploiement (Swarm)
- configuration des mises a jour (Swarm)
- secret utilises (Swarm)
- config utilisees (Swarm)
docker-compose.yml: developpement / production
- meme fichier de base pour differents environnements
- des containtes et des options differentes
- bind-mount du code applicatif (tres utile durant le develeoppement mais INTERDIT EN PRODUCTION)
- binding des ports (des ports static en dev mais en prod pour des raison de scalabilite/portabilite dynamique)
- variables d’environnement
- regles de redemarrage
- services supplementaires (tls, logs, monitoring, …)
- contraintes de placement
- contraintes d’utilisation des ressources (RAM, CPU)
Le binaire docker-compose
- gestion du cycle de vie d’une application au format Compose
- installation independante
- https://docs.docker.com/compose/install/
docker-compose [-f <qrg>...]][options][COMMAND][ARGS...]
- ex:
docker-compose up
- ex:
docker-compose up --scale api=3
- ex:
- se base sur le fichier docker-compose.yml par defaut
- plusieurs fichiers peuvent etre utilises
Les commandes docker-compose les plus utilisees
up
/down
creation/suppression d’une application (services, volumes, reseaux)start
/stop
demmrrage/arret d’une applicationbuild
build des images des services (si instruction build utilisee) (souvent utilise en developpement)pull
telechargement d’une imagelogs
visualisation des logs de l’applicationscale
modification du nombre de container pour un serviceps
liste les containers de l’application- voir plus de cmds avec
docker-compose --help
Service discovery
- utilisation du DNS du daemon Docker pour la resolution des services
- un service communique avec un autre service via son nom
Extrait d’un fichier docker-compose.yml (en ROUGE db:
):
version: '3'
services:
db:
image: mongo:3.4
volumes:
- data:/data/db
restart: always
api:
image: org/api:1.2
restart: always
volumes:
data:
Extrait d’un code Node.js: connexion a la base de donnees
// Mongodb connection string
url = 'mongodb://db/todos'; //ici le db en ROUGE
// Connection to database
MongoClient.connect(url, (err,conn) =>{
if(err){
return callback(err);
}else{
return callback(null,conn);
}
});
en rouge: LE SERVICE API UTILISE LE SERVICE DE BASE DE DONNEES PAR SON NOM SOIT db
Voting App
- https://github.com/dockersamples/example-voting-app
- application open source maintenue par docker
- utilisee pour des demos/presentations
- 5 services
- differents langages
- Node.js / Python / .NET
- differents bases de donnees
- Redis / Postgres
- differents langages
Schema de l’application:
voting-app (Python) result-app (Node.js)
| |
v v
redis (redis) db (PostgreSQL)
\ /
\====> worker (.NET) <===/
fonctionnement:
Un utilisateur vote depuis voting-app
, il choisit CAT ou DOG, le vote est stocke dans la bdd redis
, le service worker
va recuperer le vote depuis redis
et va l’enregistrer dans la base de resultat db
, puis les utilisateurs ont acces aux resultat fournit par result-app
.
L’instruction build:
dans le docker-compose.yml:
Permet de preciser ou se trouve le contexte de build cad le dockerfile et l’ensembles des fichiers necessaire pour construire l’image.
Au lieu d’executer l’app nodejs avec node server.js
il faut plutot utiliser nodemon server.js
ce dernier sera charge de lancer et superviser le process du service result-app
de plus il peut detecter les fichiers qui changent et redemarrer automatiquement l’appli qui tourne dans le container pour prendre en compte ces changements. avec cette approche on peut faire des modifications de code depuis l’IDE sur la machine de dev et avoir l’appli qui se MAJ automatiquement dans le container.
en production on deploie les images des services et on monte pas le code applicatif dans les container.
Exercice: Voting App
pour preciser un fichier en particulier avec le flag -f
:
$ docker-compose -f docker-compose-simple.yml up -d
augmenter le nombre de worker à 2:
$ docker-compose scale worker=2
- il n’est pas possible de scaler les services vote et result car ils spécifient tous les 2 un port, plusieurs containers ne peuvent pas utiliser le même port de la machine hôte
- il n’est pas non plus possible de scaler les services db et redis car ils spécifient tous les 2 l’option container_name, plusieurs containers ne peuvent pas avoir le même nom.
Exercice: la stack Elastic
Voir l’exo, pas mal d’explication sur ELK.
ex: envoyer par http des log a eleastic:
while read -r line; do curl -s -XPUT -d "$line" http://localhost:8080 > /dev/null; done < ./nginx.log
Dcocker Swarm
Solution d’orchestration dev par docker.
- swarm mode (represente le mode dans lequel tourne un docker daemon une fois qu’un cluster swarm a ete initialise)
Historique: le cluster Swarm avant Docker 1.12
- Utilisation d’un key-value store externe (Consul/Etcd/Zookeeper)
- Deploiement complexe
- options specifiques du docker daemon
- Setup manuel des elements de securite
- certificatscles privees/cles publiques
Historique: le cluster Swarm a partir de Docker 1.12
- juin 2016
- orchestration integree au docker daemon
- Swarm mode
- Simplicite de mise en place
- securise par defaut
- utilisable sur un poste de developpement
Swarm mode
- orchestrateur
- permet le deploiement et la gestion des applications containeurisees
- definition des applications au format docker compose
- boucle de reconciliation
- gestion des mises a jour (rolling update)
- load balancing bas niveau
- chiffrement des donnees en transit et au repos
Le swarm mode utilise la notion de VIP soit Virtual IP, @IP virtuelle pour chaque service. Quand un service est appele par son nom c’est le DNS du daemon qui renvoie la VIP. Quand une requete arrive sur cette VIP, il y a un loadbalancing qui va rediriger le trafique vers l’un des containers du service en question.
Swarm mode: les primitives
Node
: machine membre d’un cluster SwarmService
: specification des containers d’une applicationStack
: groupe de servicesSecret
: donnees sensiblesConfig
: configuration de l’application
Les commandes sur l’image sont adresse aux nodes Managers. Les nodes Workers font tourner les applications
Swarm mode: les commandes de base
- creation d’un swarm
docker swarm init
- ajout d’un node
docker swarm join
- management des cles d’encryption
$ docker swarm --help
Usage: docker swarm COMMAND
Manage Swarm
Options:
--help Print usage
Commands:
init initialize a swarm
join join a swarm as a node and/or manager
join-token manage join tokens
leave leave the swarm
unlock unlock swarm
unlock-key manage the unlock key
update update the swarm
Raft - Algo de consensus distribue
voir: http://thesecretlivesofdata.com/raft/
Node
- machine faisant partie du Swarm
- manager
- schedule et orchestre les containers
- gere l’etat du cluster
- algorithme de consensus RAFT
- Worker
- execute les containers
- par defaut, un manager est aussi un worker
Node: plusieurs etats possibles (availability)
- Active
- le scheduler peut assigner des taches a ce node
- Pause
- le scheduler ne peut pas assigner de nouvelles taches a ce node
- les taches tournant sur ce node sont inchangees
- Drain
- le scheduler ne peut pas assigner de nouvelles taches a ce node
- les taches tournant sur ce node sont stoppees et relancees sur d’autres nodes
Node: les commandes de base
Toutes les commandes docker executes sur un swarm doivent etre adresse a un node de type manager et pas un worker.
$ docker node --help
Usage: docker node COMMAND
Manage Swarm nodes
Commands:
demote Demote one or more nodes from manager in the swarm
inspect Display detailed information on one or more nodes
ls List nodes in the swarm
promote Promote one or more nodes to manager in the swarm
ps List tasks running on one or more nodes, defaults to current node
rm Remove one or more nodes from the swarm
update Update a node
Node: initialisation du Swarm
- Le node sur lequel le Swarm est initialise devient Leader
- Les instructions pour ajouter des nodes supplementaires sont fournies
# option obligatoire si plusieurs interfaces reseau --advertise-addr
## elle renvoie la commande qu'il faut executer pour ajouter d'autres machine en tant que membre du swarm qui sera de TYPE WORKER
docker@node1:~$ docker swarm init [--advertise-addr 192.168.99.100]
to add worker:
docker swarm joint --token SWMTKN-1-07990ineggmfdldkdkdosm 192.168.99.100:2377
# cmd pr ajouter un manager
to add manager: docker swarm joint-token manager
docker@node1:~$ docker node ls
...
Node: ajout d’un worker
- Machine pouvant communiquer avec le manager du Swarm
- Les commandes Docker doivent etre lancees sur un manager
# on ajoute node2 au swarm
docker@node2:~$ docker swarm join --token SWMTKN-1-07990ineggmfdldkdkdosm 192.168.99.100:2377
This node joined a swarm as a worker
docker@node1:~$ docker node ls
ID HOSTNAME ...
val name ...
docker@node2:~$ docker node ls
Error response from daemon: This node is not a swarm manager...
Node: ajout d’un manager
- recuperation d’un token depuis le manager
- un manager non leader a le statut
Reachable
cad il participe dans le concensus RAFT pour la gestion du cluster mais il n’est pas le leader actuel
# pour recuperer la commande a executer pour ajouter une machine au swarm aui sera de TYPE MANAGER
docker@node1:~$ docker swarm joint-token manager
...
docker swarm join --token SWMTKN-1-07990kfkffhgndmd1152 192.168.99.100:2377
docker@node3:~$ docker swarm join --token SWMTKN-1-07990kfkffhgndmd1152 192.168.99.100:2377
This node joined a swarm as a manager
Node: promotion / destitution
- Commandes lancees depuis un manager
# destitution d'un node manager en workerd
docker@node1:~$ docker node demote node1
Manager node1 demoted in the swarm.
# promotion d'un node worker en manager
docker@node3:~$ docker node promote node2
# etat du swarm
docker@node2:~$ docker node ls
node 3 leader
node 1
node 2 rechable
Node: availability
- une valeur parmi: Active/Pause/Drain
- controle le deploiement des taches sur un node
# le node ne pourra plus recevoir de nouvelles taches
docker@node1:~$ docker node update --availability pause node2
# les taches du node2 seront schedulees sur d'autres node du cluster
docker@node1:~$ docker node update --availability drain node2
# le node2 repasse en mode actif et pourra recevoir de nouvelles taches
docker@node1:~$ docker node update --availability acive node2
Node: labels
- permet l’organisation des nodes
- peut-etre utilise dans les contraintes de deploiement d’un service
# list des labels du node1
$ docker node inspect -f '' node1
{}
# ajout du label Memcached avec la valeur true
$ docker node update --label-add Memcached=true node1
node1
# Liste des labels du node1
$ docker node inspect -f '' node1 | jq .
{
"Memcached": "true"
}
Exercice: Creation en local
Exercice: Creation sur DigitalOcean
Service (visible dans le docker-compose)
permet de lancer un container replique si besoin dans un cluster swarm
- definition de la facon de lancer les containers d’une application
- mode replicated ou global (–mode global est utilise pour lancer un service de telle sorte qu’un replica tourne sur chaque noeud swarm)
- service discovery
- VIP: adresse IP virtuelle
- DNS round robin
- Publication d’un port / Routing Mesh
Service: configuration
- image utilisee
- mode de deploiement
- ports exposes
- secrets/configs utilises
- strategie de redemarrage
- contraintes de deploiement
- configuration des mises a jour
- limitation des ressources
- healt check
Service: tasks
- Service instancie en une ou plusieurs taches
- chaque tache est deployee sur un node
- une tache execute un container
Service: les commandes de base
$ docker service --help
Usage: docker service COMMAND
Manage services
Options:
--help Print usage
Commands:
create create a new service
inspect display detailed information on one or more services
ls list services
ps list the tasks of a service
rm remove one or more services
scale scale one or multiple replicated services
update update a service
Service: Exemple d’un serveur HTTP
# a faire sur un manager
$ docker service create --name www -p 8080:80 --replicas 3 nginx
ad855fgigp
$ docker service ls
ad855fgigp
$ docker service ps www
node1
node2
node3
$ docker service scale www=1
www scaled to 1
$ docker service ps www
"il ne reste plus que 1"
Service: Exemple ed base de donnees
$ docker service create --mount type=volume,src=data,dst=/data/db --name db --publish 27017:27017 mongo:4.0
$ docker service ls
ID NAME ...
id db...
$ docker volume ls
DRIVER VOLUME NAME
local onfa1684asd13 #volune defini dans le dockerfile de mongo: VOLUME /data/configdb
local data #volume cree par l'instruction mount
Exercice: Creation d’un service
Service: rolling upgrade
- processus de mise a jour d’un service
- effectue de facon incrementale
- reduit le temps d’indisponibilite de l’applicationdes options pour specifier:
- le nombre de taches a mettre a jour simultanement
- le delai entre les mises a jour
creation d’un service en specifiant les parametres de MAJ, ici les taches seront MAJ 2 par 2 toutes les 10 secondes:
# creation du service de 4 taches
$ docker service create --update-parallelism 2 --update-delay 10s --publish 8080:80 --replicas 4 --name vote instavote/vote
# les taches sont MAJ 2 par 2 ttes les 10sec
$ docker service update --image instavote/vote:indent vote
# supprimer le service
$ doceer service rm vote
Service: Rollback
- permet un retour a la specification precedente
- manuel
- automatise (sur un rollout defaillant)
$ docker service create --publish 8080:80 --name vote instavote/vote
# la cle SPEC affiche par inspect contient la conf du service
$ docker service inspect vote
# si on met le service a jour cela rajoute une nouvelle cle PREVIOUSSPEC
$ docker service update --image instavote/vote:indent vote
# on peut voir PreviousSpec via
$ docker service inspect vote
# pour faire un rollback
$ docker service rollback vote
Exercice: Rollong update & Rollback
voir pdf, pas mal d’explications.
Secret
- donnee sensible (cles ssh, login/password,…)
- implementation dans l’api du docker daemon depuis la version 1.13
- sauvegardee dans les logs cryptes utilises par RAFT
- utilisee par des services
- disponible a l’execution dans
run/secrets/SECRET
(tmpfs) - accessible en clair que par les container des services qui utilisent ce secret
# creation d'un secret
node1 $ echo "akdond56" | docker secret create password -
fgfgd1g68g7rg4g687
# creation d'un node avec le secret
node1 $ docker create --name=api --secret=password username/api
sekkf0ff55f
node1 $ docker service ps api
node2
# voir le secret dans node2 dans /run/secrets/password
node2 $ docker exec -ti $(docker ps --filter name=api -q) sh
akdond56
# si on maj le service et qu'on lui enleve le secret, il est plus dispo
node1 $ docker service update --secret rm="password" api
node2 $ docker exec -ti$(docker ps --filter name=api -q) sh
# cat /run/secrets/password
no such file or directory
Config
- donnee non sensible
- decouple la configuration du cycle de vie du service
en gros on injecte un fichier nginx.conf dans le service via un objet CONFIGS
(qui contient la source server_config
), ca permet de gerer la config a l’exterieur de l’application au lieu de maintenir une image:
version: '3.3'
services:
proxy:
image: nginx:1.14
configs: #ICI
-source: server_config
target: /etc/nginx/nginx.conf
mode: 0444
uid: '33'
gid: '33'
...
Exercice: Utilisation des secrets et configs
Stack
- un groupe de services
- deployee a partir d’un fichier au format docker compose
$ docker stack --help
deploy deploy a new stack or update an existing stack
ls list stacks
ps list the tasks in the stack
rm remove the stack
services list the services in the stack
Stack: exemple avec la Voting App
$ git clone https://github.com/docker/example-voting-app/ && cd example-voting-app
$ docker stack deploy -c docker-stack.yml vote
creating network vote_backend
creating network vote_default
creating network vote_frontend
creating service vote_visualizer
creating service vote_redis
creating service vote_db
creating service vote_vote
creating service vote_result
$ docker stack ls
NAME SERVICES
vote 6
$ docker service ls
ID NAME MODE REPLICAS IMAGE
asdadofof vote_vote...
(liste de tt les services)
Exercice: Deploiement de la stack TICK
Stack: swarmprom
Solution open source pour le monitoring d’un swarm (https://github.com/stefanprodan/swarmprom)
- dockerd-exporter: expose les metrics du daemon
- cadvisor: expose les metrics des contenairs sur le swarm
- prometheus: collect et sauvegarde ces metrics
- grafana: permet de visualiser ces metrics dans des dashboard
- alertmanager: permet de definir des alerts en se basant sur ces metrics
- unsee: un dashboard pour alertmanager
- caddy: serveur web qui expose le point d’entree de l’application
la cle deploy
n’est pas prise en compte par le binaire docker-compose. cette cle permet de definir les option de deploiement du service sur un swarm.
Routing Mesh
- Exposition de services a l’exterieur (de tel sorte que les ports soient accessible depuis toutes les nodes du swarm)
- base sur ipvs et iptables
- load balancer L4 (au niveau ip)
- chaque node accepte des requetes sur le port publie par un service
# le service app est sur 2 replicas, le port 80 est publie sur le port 8000
# le routing mesh expose le port 8000 sur chaque node du cluster swarm
# le trafic destine a l'appli peut arriver sur n'importe quel node
$ docker service create --name app --replicas 2 --network appnet -p 8000:80 nginx
Ici on voit que le trafic arrive sur le host-C
qui n’a pas de service, la requete est redirige vers un replicat du service qui tourne sur un autre node:
Interface web de gestion: Portainer
- open source
- https://portainer.io
- gestion d’hotes docker et de clusters swarm
# recuperation du fichier Compose definissant l'application
$ curl -L https://portainer.io/download/portainer-agent-stack.yml -o portainer-agent-stack.yml
# lancement de l'application en tant que Stack
$ docker stack deploy -c portainer-agent-stack.yml portainer
Interface web de gestion: Swarmpit
Contrairement a Portainer, Swarmpit est essentielement dedie a Swarm et propose beaucoup moins de fonctionnalites.
- open source
- https://swarmpit.io/
Les logs de l’algorithme RAFT
RAFT logs
Dans cette mise en pratique, nous allons démistifier les logs qui sont utilisés par l’algorithme Raft afin de gérer l’état du cluster.
Ces logs sont crées sur le Leader des managers et répliqués sur chaque manager.
Swarm architecture
Creation d’un swarm
Une solution simple est d’utiliser Docker Machine pour créer 2 hôtes Docker. Nous initialiserons le swarm sur l’un des 2 hôtes et ajouterons le second.
Dans cet exemple, j’utilise le driver virtualbox afin de créer les machines virtuelles en local.
Creation des VMs
$ docker-machine create --driver virtualbox node01
$ docker-machine create --driver virtualbox node02
Vous pouvez utiliser un autre driver si vous le souhaitez afin de créer les VMs sur un autre hyperviseur (Hyper-V) ou sur un cloud provider (AWS, DigitalOcean, Microsoft Azure, …)
Sur ma machine de développement, les VMs sont créées avec les IPs suivantes, les votres pourront être différentes.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
node01 - virtualbox Running tcp://192.168.99.100:2376 v17.10.0-ce
node02 - virtualbox Running tcp://192.168.99.101:2376 v17.10.0-ce
Initialisation du swarm
Lorsque nous sommes sur un node qui n’est pas en mode Swarm, le répertoire swarm du dossier d’installation de Docker est vide.
$ ls /var/lib/docker/swarm
Depuis node01, lancez la commande suivante en utilisant l’adresse que vous avez obtenue pour ce node.
```bash$ docker swarm init –advertise-addr 192.168.99.100 Swarm initialized: current node (311ep1n2wcgrje263l45sjrix) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-081l3uznies41wgvgtjnnyc1c2tczcdnopqiyoo1z249xq043b-3haunf75clx1dfabv5gvgt9fv 192.168.99.100:2377
To add a manager to this swarm, run ‘docker swarm join-token manager’ and follow the instructions.
Depuis node02 lancez la commande de join telle qu'elle est précisée par la commande précédente.
```bash
$ docker swarm join --token SWMTKN-1-081l3uznies41wgvgtjnnyc1c2tczcdnopqiyoo1z249xq043b-3haunf75clx1dfabv5gvgt9fv 192.168.99.100:2377
This node joined a swarm as a worker.
Vous pouvez alors lister les nodes de votre swarm avec la commande suivante:
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
311ep1n2wcgrje263l45sjrix * node01 Ready Active Leader
t0u519i3rusd7442d8nfypf4e node02 Ready Active
A partir du moment ou le daemon Docker est en mode Swarm (suite à la commande d’initialisation précédente), plusieurs éléments sont présents dans le répertoire swarm.
$ find /var/lib/docker/swarm
/var/lib/docker/swarm
/var/lib/docker/swarm/docker-state.json
/var/lib/docker/swarm/certificates
/var/lib/docker/swarm/certificates/swarm-root-ca.crt
/var/lib/docker/swarm/certificates/swarm-node.key
/var/lib/docker/swarm/certificates/swarm-node.crt
/var/lib/docker/swarm/worker
/var/lib/docker/swarm/worker/tasks.db
/var/lib/docker/swarm/state.json
/var/lib/docker/swarm/raft
/var/lib/docker/swarm/raft/snap-v3-encrypted
/var/lib/docker/swarm/raft/wal-v3-encrypted
/var/lib/docker/swarm/raft/wal-v3-encrypted/0.tmp
/var/lib/docker/swarm/raft/wal-v3-encrypted/0000000000000000-0000000000000000.wal
Les logs sont situés dans le répertoire raft, les clés d’encryption dans le répertoire certificates.
Creation d’un secret
La commande suivante permet de créer un secret nommé passwd et contenant une chaine de caractère.
$ echo 'A2e5bc21' | docker secret create passwd -
Si nous listons les secrets existants sur notre swarm, seul le secret précédemment créé apparait, son contenu n’est plus visible.
$ docker secret ls
ID NAME CREATED UPDATED
4mbzd3pt9jk9z2lqehm7e77bb passwd 8 seconds ago 8 seconds ago
Nous verrons par la suite que ce secret est en clair dans les logs de Raft.
A propos des logs de Raft
La version 1.13 de la plateforme Docker a introduit la gestion des secrets dans le contexte d’un swarm. Ce sont des information sensibles, par exemple des identifiants de connexion à des services tiers. Les secrets sont stockées en clair dans les logs utilisés par l’implémentation de l’algorithme Raft et c’est notamment pour cette raison que logs sont cryptés, afin d’assurer la confidentialité de ces informations.
Les secrets sont généralement créés par les Ops lors du lancement de l’application puis fournis au service qui en ont besoin. Ils seront alors accessibles, dans les containers du service, depuis un système de fichiers temporaire sous /run/secrets/NOM_DU_SECRET
.
Decryptage des logs
Nous allons utiliser ici l’utilitaire swarm-rafttool, un binaire qui se trouve dans la librairie SwarmKit utilisée par Docker pour la gestion des clusters swarm.
Swarm Rafttool
Afin d’éviter l’installation de cet utilitaire, nous le lançons directement depuis un container.
$ docker run --rm -ti -v /var/lib/docker/swarm/:/var/lib/docker/swarm:ro -v $(pwd):/tmp/ zaggash/docker-rafttool
Nous pouvons alors voir les différentes options possibles:
/ # swarm-rafttool
Tool to translate and decrypt the raft logs of a swarm manager
Usage:
/root/go/bin/swarm-rafttool [command]
Available Commands:
decrypt Decrypt a swarm manager’s raft logs to an optional directory
dump-wal Display entries from the Raft log
dump-snapshot Display entries from the latest Raft snapshot
dump-object Display an object from the Raft snapshot/WAL
Flags:
-h, --help help for /root/go/bin/swarm-rafttool
-d, --state-dir string State directory (default “/var/lib/swarmd”)
--unlock-key string Unlock key, if raft logs are encrypted
Use "/root/go/bin/swarm-rafttool [command] — help" for more information about a command.
Dans la suite, nous utiliserons la command dump-wal afin de décrypter et visualiser les entrées du fichier de logs.
Decryptage
Afin de décrypter le fichier de log, nous créons un script shell qui va tout d’abord copier le fichier puis lancer le binaire swarm-rafttool sur cette copie. L’étape de copie est nécessaire car swarm-rafttool ne permet pas de décrypter les logs en cours d’usage.
Avec un petit coup de vi, copiez dans un fichier dump.sh (dans le container lancé précédemment) le contenu suivant:
d=$(date "+%Y%m%dT%H%M%S")
SWARM_DIR=/var/lib/docker/swarm
WORK_DIR=/tmp
DUMP_FILE=$WORK_DIR/dump-$d
STATE_DIR=$WORK_DIR/swarm-$d
cp -r $SWARM_DIR $STATE_DIR
$GOPATH/bin/swarm-rafttool dump-wal --state-dir $STATE_DIR > $DUMP_FILE
echo $DUMP_FILE
Nous pouvons alors lancer ce script et observer le contenu des logs.
/ # chmod +x ./dump.sh
/ # ./dump.sh | xargs cat
La sortie est relativement verbeuse, et peux être décomposée en plusieurs Entry. Je vous invite à examiner les premières qui sont relative à la mise en place du Swarm..
La dernière Entry (ci-dessous) concerne la création du secret passwd.
Entry Index=12, Term=2, Type=EntryNormal:
id: 102286946670602
action: <
action: STORE_ACTION_CREATE
secret: <
id: "4mbzd3pt9jk9z2lqehm7e77bb"
meta: <
version: <
index: 11
>
created_at: <
seconds: 1499070018
nanos: 989531240
>
updated_at: <
seconds: 1499070018
nanos: 989531240
>
>
spec: <
annotations: <
name: "passwd"
>
data: "A2e5bc21\n"
>
>
Comme nous pouvons le voir, le contenu du secret est en clair dans le log. L’encryption des logs est donc obligatoire pour préserver la sécurité de ces informations sensibles.
Sortons du container
/ # exit
Autolock
Si un manager est compromis, les logs cryptés et les clés d’encryption sont récupérables. Il est alors facile pour un hacker de décrypter les logs et d’avoir ainsi accès aux données sensibles, comme nous venons de le faire. Pour empêcher cela, un Swarm peut être locké. Une clé d’encryption est alors générée et utilisée pour encrypter les clés publique / privée (celles servant à encrypter / décrypter les logs).
Cette nouvelle clé, appelée Unlock key, doit être sauvegardée offline et fournie manuellement au daemon Docker après un restart.
Nous visualisons le contenu des clés situées dans le sous répertoire certificates.
$ cat /var/lib/docker/swarm/certificates/swarm-node.crt
$ cat /var/lib/docker/swarm/certificates/swarm-node.key
La commande suivante met à jour le Swarm et active la fonctionnalité d’Autolock.
Note: il est également possible d’activer l’Autolock lors de la création du Swarm.
$ docker swarm update --autolock=true
Swarm updated.
To unlock a swarm manager after it restarts, run the `docker swarm unlock`
command and provide the following key:
SWMKEY-1-y4plj3mAYXoS4OiHHU9TC23vjKM6dgmcjdFju/2YTX0
Please remember to store this key in a password manager, since without it you will not be able to restart the manager.
Si nous observons une nouvelle fois le contenu des clés, nous pouvons voir qu’elles ont été encryptées.
$ cat /var/lib/docker/swarm/certificates/swarm-node.crt
$ cat /var/lib/docker/swarm/certificates/swarm-node.key
Redemarage du daemon
Reperez le PID du processus dockerd et envoyez lui un signal KILL.
$ ps aux | grep dockerd
root 1232 0.4 6.8 442076 70220 ? Sl 07:35 0:13 /usr/local/bin/dockerd -D -g /var/lib/docker -H unix:// -H tcp://0.0.0.0:2376 --label provider=virtualbox --tlsverify --tlscacert=/var/lib/boot2docker/ca.pem --tlscert=/var/lib/boot2docker/server.pem --tlskey=/var/lib/boot2docker/server-key.pem -s aufs
$ kill 1232
le PID du daemon Docker tournant sur votre VM sera différent de celui ci-dessus
Afin de conserver les mêmes options que précédemment, redémarrez le daemon avec la commande listée lors du ps. Nous la lancerons en tâche de fond en l’encadrant avec nohup … & comme dans l’exemple ci-dessous.
$ nohup /usr/local/bin/dockerd -D -g /var/lib/docker -H unix:// -H tcp://0.0.0.0:2376 --label provider=virtualbox --tlsverify --tlscacert=/var/lib/boot2docker/ca.pem --tlscert=/var/lib/boot2docker/server.pem --tlskey=/var/lib/boot2docker/server-key.pem -s aufs &
Il n’est alors pas possible de lancer de commande sur le swarm tant que le manager n’a pas été unlocké.
$ docker node ls
Error response from daemon: Swarm is encrypted and needs to be unlocked bef
ore it can be used. Please use "docker swarm unlock" to unlock it.
Nous délockons alors le manager en lui précisant le token récupéré lors de l’opétation de lock.
$ docker swarm unlock
Please enter unlock key:
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
311ep1n2wcgrje263l45sjrix * node01 Ready Active Leader
t0u519i3rusd7442d8nfypf4e node02 Ready Active
Conclusion
Cet exercice donne un rapide aperçu des logs générés par l’algorithme de consensus Raft. Nous l’avons illustré en nous basant sur un Swarm simplement composé d’un seul manager. Vous trouverez des détails supplémentaires dans cet article: https://medium.com/lucjuggery/raft-logs-on-swarm-mode-1351eff1e690
Backup d’un Swarm
Comme nous l’avons vu, les nodes de type manager sont les garants de l’état du swarm. Chaque action effectuée dans le swarm (création d’un service, mise à jour d’un service, création d’un network, ….) est consignée dans un fichier de log sur le leader des managers. Ce fichier étant répliqué en temps réel sur les autres managers.
Sur chaque manager, ces logs, les clés servant à les chiffrer et d’autres fichiers relatifs à l’état du swarm sont stockés dans le répertoire /var/lib/docker/swarm
. Ce répertoire est créé lors de l’initialisation du swarm (docker swarm init
).
Backup
Afin de réaliser le backup d’un swarm, il suffit de réaliser les opérations suivantes:
- se connecter sur un manager
- arrêter le daemon Docker de façon à ce que des données ne soient pas en train d’être écrites au moment du backup
- copier le répertoire /var/lib/docker/swarm dans un endroit sûr
- redémarrer le daemon
Restauration du backup
Afin de réaliser la restauration d’un backup, les opérations suivantes sont nécessaires:
- se connecter sur la machine sur laquelle l’on souhaite effectuer la restauration
- arrêter le daemon Docker
- supprimer le répertoire /var/lib/docker/swarm
- copier le contenu du backup dans /var/lib/docker/swarm
- redémarrer le daemon (si le swarm est locké, il faudra fournir la clé de unlock pour pouvoir effectuer cette action)
- forcer la ré-initialisation du swarm avec la commande
docker swarm init --force-new-cluster
Cette commande force la création d’un nouveau cluster à partir de l’état courant. - ajouter des managers et des workers
Tolerances aux pannes
Network
Container Network Model
- aka CNM
- Sandbox: stack reseau
- Endpoint: interface reseau
- Network: ensemble de Endpoint
- technologies linux utilisees dans l’implementation des drivers CNM
- bridge linux
- namespaces reseau
- paire d’interfaces virtuelles (veth pairs)
- iptables
- les fonctionnalitees
- regles de gestion du trafic
- segmentation reseau
- service discovery
- load balancing
- routing mesh
Network CLI
docker network -h
Flag shorthand -h has been deprecated, please use --help
Usage: docker network COMMAND
Manage networks
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks
Run 'docker network COMMAND --help' for more information on a command.
Les differents drivers
- definit le type et les fonctionnalites du network
- plusieurs drivers natifs disponibles
- host
- none
- bridge
- overlay
- macvlan
$ docker network create --driver DRIVER [OPTION] NAME
- exemples de solutions externes
- weave
- calico
- flannel
- compatibles avec le CNM
- installees via des plugins
Les networks d’un hote Docker
- bridge
- represente par le bridge Linux
docker0
creee sur la machine hote - bridge auquel les containers sont attaches par defaut
- permet la communication des containers sur un meme hote
- represente par le bridge Linux
- host
- stack reseau de la machine hote
- none
- pas de connexion sur l’exterieur
$ docker network ls
bidge
host
none...
Les networks d’un hote Docker: bridge
- containers attaches a ce network par defaut
- representee par l’interface bridge Docker0
- creation d’une paire d’interfaces virtuelles pour lier le network namespace du container et celui de l’hote
node1 $ ip a show docker0
@IP/mask
# the other end of the veth pair is attached to the docker0 bridge network
$ brctl show
# creation of an alpine container
$ docker container run -ti alpine:3.8 sh
$ the other end of the vert pair is attached to the docker0 bridge network
$ brctl show
(ajout de `vetha5412f12` dans la colone interfaces)
Les containers attaches au bridge0 ne peuvent pas communiquer entre eux via leur nom:
# recuperation du network id du bridge
$ docker network ls
$ docker run -d --name+nginx nginx:1.14
$ docker inspect -f '' 0eee
172.17.0.2
$ docker network inspect [ID du bridge]
(json contenant les ip de alpine et nginx)
Les networks d’un hote Docker: host
- donne acces a la stack reseau de la machine hote
- pas de separation au niveau network
$ docker container run -ti --network=host alpine sh
Les networks d’un hote Docker: none
- ne configure aucune interface reseau dans le namespace du container
- isole des autres containers et de l’hote
$ docker container run -ti --network=none alpine sh
Les networks d’un hote Docker: user defined bridge
- utilisation du driver bridge (defaut)
- creation d’un bridge linux
- paires d’interfaces virtuelles
- DNS interne pour la resolution de noms des containers
Les containers attaches au nouveau bridge peuvent communiquer entre eux via leur nom:
Les networks d’un hote Docker: Macvlan
- pas d’utilisation de bridge
- leger et performant
- donne a un container un acces direct a une interface de la machine hote
- IP routable pour chaque container
- souvent utilise lors de la migration d’applications (VM to Container)
Exercice: Networks sur un hote
Les networks dans un Swarm
Quand on fait un docker swarm init
nous avons en plus de bridge, host et none:
docker_gwbridge
permet aux containers de se connecter a l’hote (assure la connectivite des containers vers l’exterieur)ingress
intervient dans les fonctionnalites de routing mesh (network de type overlay cad il s’ettend sur differentes machines du swarm en utilisant la techno VXLAN, il intervient dans les fonctionnalites de load balancing et de routing mesh)
Les networks dans un Swarm: overlay
- communication entre des containers sur des hotes differents
- utilise les fonctionnalites VXLAN du kernel Linux
- network cree dans un contexte de cluster paires d’interfaces virtuelles (veth pairs)
node1 $ docker network create --driver overlay ovnet
node1 $ docker network create --driver overlay ovnet alpine sleep 1000
Exercice: Networks dans un Swarm
Securite
- un container est un processus
- compose d’un ensemble de fonctionnalites d’isolation du kernel linux
------Namespaces-------
Mount (permet de tourner sur son propre FS)
PID (permet a un container d'avoir son propre arbre de process isole du systeme)
Net (fournit un stack reseaux independant de la machine hote)
IPC (permet d'avoir un espace de memoire partage utilise pour la communication inter process dans le container)
UTS (permet d'avoir son propre hostname)
User (permet de mapper un user non root de la machine hote avec un user root dans le container)
------Control Groups-------
RAM
CPU
I/O
------Securite-------
SELinux
AppArmor
Seccomp
Capabilites
- travaux tres actifs sur la securite des containers
Hardening
Le but du hardening est de mettre en place un maximum d’elements de securite pour minimisier le plus possible la surface d’un systeme.
Exemples de menaces
- compromission de l’hote par un container
- epuisement des ressources
- acces au filesystem de l’hote
- compromission d’un container par un autre container
- utilisation massive des ressources
- probleme dans un environnement multi-tenants
- corruption de l’image
- compromission de l’application tournant dans un container
Hardening: Center for Internet Security benchmark
liste les best practices de securite du CIS voir la doc:
- https://learn.cisecurity.org/benchmarks
- configuration de la machine hote
- configuration du daemon docker
- fichiers de configuration du daemon docker
- images et dockerfile
- environnement d’execution d’un container
- operations
Hardening: Security Bench
- Outil base sur les recommandations du CIS
- Verification des bonnes pratiques autour du deploiement des containers docker en production
- plusieurs donaines couverts
- configuration de la machine hote
- configuration du daemon docker
- …
- https://github.com/docker/docker-bench-security
voici la commande permettant d’executer la batterie de test Security Bench, ln voit le resultat des test point par point en fonction de chaque chapitre du document:
# --net host permet de se ratacher a la stack reseaux de la machine hote il aura donc acces a l'ensemble des interfaces reseaux
# --pid host permet de se ratacher au namespace pid de la machine hote et donc avoir acces a l'ensemble des processus sur le systeme dont le docker daemon
# --cap-add audit_control qui va ajouter la capabilie qui permet l'audit du kernel
$ docker run -it --net host --pid host --cap-add audit_control -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST -v /var/lib:/var/lib -v /var/run/docker.sock:/var/run/docker.sock -v /etc:/etc --label docker_bench_security docker/docker-bench-security
...
[INFO] 1 - Host Configuration
[WARN] 1.1 - Ensure a separate partition for containers has been created
[NOTE] 1.2 - Ensure the container host has been Hardened
...
Linux Capabilities
C’est des primitifs present dans le kernel linux et elles permettent de decouper les droits roots en plusieurs categories.
- controle d’acces granulaire qui ameliore la dichotomie root vs non root
- differents groupes d’acces
CAP_CHOWN
: permet la modification des uid/gidCAP_SYS_ADMIN
: permet des operations administratives cote systemeCAP_NET_ADMIN
: permet des operations administratives cote networkCAP_ET_BIND_SERVICE
: permet d’affecter un port privilegie (<1024) a un processus- …
- peuvent etre ajoutees/supprimees au lancement d’un container avec
--cap-add
et--cap-drop
Exemple: Capabilities
La capabilite SYS_ADMIN
est necessaire pour modifier le hostname (appel a la fonction sethostname
), on utilise le flag --cap-add
pour ajouter une capability:
$ docker run -ti alpine sh
hostname foo
hostname sethostname: Operation not permitted
$ docker run -ti --cap-add=SYS_ADMIN alpine sh
hostname foo
hostname
foo
La capabilite NET_RAW
est necessaire pour utiliser la commande ping
, on utilise le flag --cap-drop
pour supprimer une capability:
$ docker run alpine ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=37 time=0.394 ms
...
$ docker run --cap-drop=NET_RAW alpine ping 8.8.8.8
ping: permission denied (are you root?)
Linux Security Modules
- AppArmor / SELinux
- Implementation du MAC (Mandatory Access Control)
- Etend le DAC (Discretionary Access Control)
|
v
__________
-rw-r--r-- 1 root root 3106 Sept 23 2019 .bashrc
- renforce la securite en ajoutant des droits d’acces supplementaires
Linux Security Modules: AppArmor
- autorisation basee sur le chemin d’acces aux ressources
- un profil de securite par application pour limiter les droits
- specifie quels fichiers une application peut lire / ecrire / executer
- disponible par defaut sur les distribution Ubuntu, OpenSuse
AppArmor est utilise par defaut par docker, si on souhaite donner plus de privilege a un container, il suffit de desactiver le profile AppArmor avec cette option --security-opt
soit --security-opt apparmor:unconfinded
:
$ docker run -ti --security-opt apparmor:unconfined alpine sh
Linux Security Modules: SELinux
- applique sur le systeme entier
- defini des attributs etendus sur le systeme de fichiers
- contexte de securite associe aux sujets et aux objets
- sujet: utilisateur, application, processus
- objet: fichier, repertoire, device, interface, …
- regles definissant les objets auquel un sujet a acces
ici on peut voir les attributs etendus avec ls -Z
, ci-dessous on a par exemple docker_exec_t
. Quand on essaie d’acceder a index.html
nous avons une erreur car ce fichier n’a pas le meme contexte de securite. en desactivant SELinux avec l’option --security-opt label:disable
nous pouvons acceder au fichier index.html
:
PDF : explication fonctionnement SELinux
Seccomp (Secure Computing Mode)
- Secure Computing Mode
- Controle les appels aux fonctions systeme (mount, mkdir, ptrace, reboot, …)
- Profil par defaut qui desactive 44 appels (sur +300 pour un Linux x86-64)
- Un profil custom peut-etre fourni au lancement d’un container
- Utilisation de l’outil strace pour lister les appels systeme et affiner le profil
- Intersection avec les capabilitees
Exemple, ici nous allons interdire la commande mkdir
:
# l'appel a mkdir est autorise avec le profil par defaut
$ docker container run -it alpine sh
mkdir test
exit
# fichier json definissant un profil custom qui interdit l'appel a mkdir
$ cat policy.json
{
"defaultAction": "SCMP_ACT_ALLOW", #ici on autorise tout a la base
"syscalls": [
{
"name": "mkdir",
"action": "SCMP_ACT_ERRNO" #puis on interdit mkdir
}
]
}
# un container utilisant ce profil n'est pas autorise a faire un appel a mkdir
$ docker run -it --security-opt seccomp:policy.json alpine sh
mkdir test
mkdir: can't create directory 'test': Operation not permitted
Scan de vulnerabilites
- analyse des images
- recherche de CVE (Common Vulnerabilities and Exposures)
De nombreux outils (open source et commerciaux)
- Anchore Engine
- Clair
- Docker Security Scanning …
- Recommendation: images basees sur Alpine
- faible surface d’attaque
- integration dans un pipeline de CI/CD
Content Trust
- base sur The Update Framework (TUF)
- securite des systemes lors de mises a jour logicielles
- utilisation de cles pour le chiffrement au niveau du tag de l’image
- signature d’une image lors du push
- verification de l’integrite et de son origine lors du pull
- intervient au niveau du tag de l’imagea
- activite par une variable d’environement
export DOCKER_CONTENT_TRUST=1
# build et push d'une image avec le tag 1.0
$ docker image build -t username/app:1.0 .
$ docker image push username/app:1.0
# activation de docker content trust
$ export DOCKER_CONTENT_TRUST=1
# build et push d'une image avec le tag 2.0
# creation de 2 cles (root key, taging key) qui sont utilisees pour signer le tag de l'image
$ docker image build -t username/app:2.0 .
$ docker image push username/app:2.0
...
# activation de Docker Content Trust sur une autre machine
$ export DOCKER_CONTENT_TRUST=1
# download de la version 1.0 de l'image (tag non signe)
$ docker image pull username/app:1.0
No trust data for 1.0
# download de la version 2.0 de l'image (tag signe)
$ docker image pull username/app:2.0
...
De nombreuses solutions commerciales
- Cloud vs on-premises
- Differentes fonctionnalites
- scanning de vulnerabilites
- regles de deploiement des images
- detection des composants open source utilises
- analyse des flux reseaux entre les containers
- algorithmes de ML pour filtrer les communications
- regles RBAC
- …
Comments