# Docker
![Schema Docker Swarm](images/docker-wave-whale.svg "Schema Docker Swarm")
**Les conteneurs - Utilisation avancée**
Maxime Poullain • Christian Tritten
## Docker run
Permet d'instancier un conteneur à partir d'une image
### Docker run - options
Option | Description
- | -
`--detach`, `-d` | Mode _détaché_ : lance le conteneur à l'arrière plan
`--name NAME` | Donne un nom au conteneur
`--interactive`, `-i` | Conserve STDIN ouvert
`--tty`, `-t` | Alloue un pseudo-TTY
### Docker run - restart policies
On utilise l'option `--restart` avec ces paramètres :
Policy | Result
-------------------------|---------------------------------------
`no` | Par défaut. Ne redémarre pas
`on-failure[:max-retries]` | Redémarre seulement avec un _non-zero exit status_
`always` | Redémarre tout le temps et indéfiniment. Démarre aussi en même temps que le démon Docker
`unless-stopped` | Redémarre tout le temps sauf si il a été coupé
### Contraintes sur les ressources
* Par défaut une application conteneurisée peut consommer l'intégralité de la mémoire disponible de l'hôte.
* Lorsqu'il n'y a plus de mémoire disponible, le kernel Linux :
1. lance une _Out Of Memory Exception_ (OOME),
2. commence à [tuer des processus](https://www.kernel.org/doc/gorman/html/understand/understand016.html) pour libérer de la mémoire.
Il est possible de limiter les ressources allouées à un conteneur grâce à la technologie des _control groups_.
Option | Description
-------------------------|---------------------------------------
`--memory` | Limite mémoire, minimum 4M
`--memory-swap` | Limite mémoire totale, (memoire + swap)
`--cpu` | Limite sur l'utilisation du cpu
https://docs.docker.com/config/containers/resource_constraints/
Sur un système en production il est recommandé d'appliquer systématiquement des limitations sur l'utilisation de la mémoire.
## Docker exec
Permet d'éxécuter des commandes dans le conteneur en plus du processus de base.
Notamment utile pour débugger.
### Docker exec - options
Option | Shorthand | Default | Description
----------------|-------------|---------|------------------------------
`--detach` | `-d` | False | Detached mode: run command in the background
`--interactive` | `-i` | False | Keep STDIN open even if not attached
`--tty` | `-t` | False | Allocate a pseudo-TTY
`--user` | `-u` | False | Username or UID
### Docker exec - exemple
* Ouvrir une console Bash dans le conteneur :
`$ docker exec -it mon_conteneur /bin/bash`
* Créer un fichier à l'intérieur du conteneur :
`$ docker exec mon_conteneur touch /tmp/test`
## Docker cp
Permet de copier des fichiers/dossiers depuis et vers un conteneur en cours d'exécution.
* Copie un fichier de l'hôte vers le conteneur :
`$ docker cp fichier nom-du-conteneur:/chemin/vers/fichier`
* Créer un fichier du conteneur vers l'hôte :
`$ docker cp nom-du-conteneur:/chemin/vers/fichier fichier`
## Docker diff
Affiche les modifications effectuées sur le système de fichiers du conteneur.
```
$ docker diff mon-conteneur
C /var
C /var/lib
C /var/lib/apt
C /var/lib/apt/lists
A /var/lib/apt/lists/lock
A /var/lib/apt/lists/auxfiles
A /var/lib/apt/lists/deb.debian.org_debian_dists_bullseye_InRelease
A /var/lib/apt/lists/partial
```
## Monitoring simple des conteneurs
### Docker Stats
Docker embarque un équivalent de la commande `top` permettant d'afficher les métriques des conteneurs en cours d'exécution.
`docker stats`
```none
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
5098617c3654 0.05% 0B / 0B 0.00% 648B / 0B 0B / 0B 2
b2d0efcb5151 1.00% 0B / 0B 0.00% 648B / 0B 0B / 0B 3
cbdb1069b241 0.10% 0B / 0B 0.00% 648B / 0B 0B / 0B 5
```
### ctop
Métriques des conteneurs dans une interface à la `top`.
![ctop](images/ctop.gif)
Source : [https://ctop.sh/](https://ctop.sh/)
## Configuration d'une application
### Variables d'environnement
- Il est possible d'injecter des variables d'environnement dans le conteneur au moment de sa création.
- L'option `--env` permet de spécifier une variable et sa valeur.
- L'option `--env-file` permet de passer un fichier de variables.
- Une fois injectées dans le conteneur ces variables sont accessibles par le premier processus lancé.
#### Exemple
- L'image de _MariaDB_ est conçue pour être configurée à l'exécution à l'aide de variables d'environnement.
- Ces variables vont permettre d'initialiser une base avec un utilisateur et mot de passe personnalisés.
```none
$ docker run --name mariadb \
--env MYSQL_ROOT_PASSWORD=docker \
--env MYSQL_DATABASE=docker \
--env MYSQL_USER=docker \
--env MYSQL_PASSWORD=docker \
mariadb:10.7.1
```
#### Exemple avec --env-file
`mariadb-vars`
```none
MYSQL_ROOT_PASSWORD=docker
MYSQL_DATABASE=docker
MYSQL_USER=docker
MYSQL_PASSWORD=docker
```
```none
$ docker run --name mariadb \
--env-file=mariadb-vars \
mariadb:10.7.1
```
#### Mini TP : variables d'environnement
![Travaux pratiques](images/tp.gif)
Créer un conteneur basé sur l'image `xian310/who-is-there:21` avec la configuration suivante :
- Lancer le conteneur au premier plan.
- Publier le port d'écoute dans le conteneur (8080) vers le port de votre choix sur l'hôte.
Accéder à l'application dans le navigateur web en précisant le chemin `/env` dans l'url.
Créer le conteneur :
```none
$ docker run --rm --publish 8080:8080 xian310/who-is-there:21
```
Accéder à l'application :
```none
$ firefox http://127.0.0.1:8080/env
```
- Supprimer le conteneur.
- Créer un nouveau conteneur en lui passant au moins 2 variables d'environnement de votre choix.
- Recharger la page web.
Créer un nouveau conteneur en lui passant des variables d'environnement :
```none
$ docker run --rm --publish 8080:8080 \
--env MA_VARIABLE=hello --env MON_AUTRE_VARIABLE="au revoir" \
xian310/who-is-there:21
```
- On peut configurer la couleur de fond de la page `/env` en passant la variable `COLOR` au conteneur.
- Pour une liste des couleur possibles :
https://fr.wikipedia.org/wiki/Couleur_du_Web
Changer la couleur de fond de la page `/env` :
```none
$ docker run --rm --publish 8080:8080 \
--env COLOR=lightblue \
xian310/who-is-there:21
```
```none
$ docker run --rm --publish 8080:8080 \
--env COLOR=pink \
xian310/who-is-there:21
```
## Gestion des données
Persistance des données avec Docker
### 3 mécanismes différents
Docker offre trois mécanismes permettant de monter des données de l'hôte dans un conteneur :
- volumes
- bind mounts
- tmpfs volumes
Dans le doute les _volumes_ sont presque toujours le bon choix.
![Type de montage](images/types-of-mounts.png)
Source : documentation officielle Docker
Quel que soit le type de montage utilisé, les données seront présentées de la même façon au conteneur, c'est à dire sous la forme d'un dossier ou d'un fichier unique.
### Volumes
![Type de montage](images/types-of-mounts-volume.png)
- Les _volumes_ sont stockés sur le système de fichiers de l'hôte dans le dossier
`/var/lib/docker/volumes/`.
- Un volume est créé et _managé_ par Docker.
- Les processus autres que Docker ne devraient donc pas y modifier directement les données.
- Un volume peut être monté simultanément dans plusieurs conteneurs
* en lecture/écriture ou lecture seule,
* très utile pour partager des données entre conteneurs.
- Un volume est indépendant des conteneurs.
- Le contenu persiste même si aucun conteneur n'utilise le volume.
- Un volume doit être explicitement supprimé.
- un volume peut être nommé ou anonyme.
(Un volume anonyme se voit attribuer un identifiant unique sur l'hôte)
- Les volumes supportent la notion de _volume drivers_.
(pour stocker les données sur des hôtes distant ou dans le cloud par exemple)
- un volume peut être sauvegardé, restoré ou migré facilement d'un hôte Docker à l'autre.
(Stopper les conteneurs utilisant le volume, sauvegarder le dossier `/var/lib/docker/volumes//`).
1. Créer un volume
`$ docker volume create VOLUME`
2. Lister les volumes
`$ docker volume ls`
3. Inspecter un volume
`$ docker volume inspect VOLUME`
4. Supprimer un volume
`$ docker volume rm VOLUME`
5. Supprimer tous les volumes non-utilisés
`$ docker volume prune`
```none
$ docker volume create mon-volume
```
```none
$ docker volume inspect mon-volume
```
```json
[
{
"CreatedAt": "2018-01-16T10:15:51+01:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mon-volume/_data",
"Name": "mon-volume",
"Options": {},
"Scope": "local"
}
]
```
* `docker volume inspect` permet de connaître le chemin réel du volume sur le système hôte.
### Bind mounts
![Type de montage](images/types-of-mounts-bind.png)
- Les _bind mounts_ permettent de "monter" n'importe quel fichier ou dossier du système hôte à l'intérieur du conteneur.
- Exemples d'utilisation de Bind mount :
* partager des fichiers de configuration entre l'hôte et des conteneurs.
* partager du code source entre l'hôte et un conteneur (sur un poste de développement).
- Le fichier ou le répertoire source peut ne pas exister au préalable sur l'hôte (il sera créé le cas échéant).
- __Attention !__
Si un dossier ou fichier critique pour le système hôte est monté dans le conteneur, ce dernier peut y accéder sans restriction.
### tmpfs mounts
![Type de montage](images/types-of-mounts-tmpfs.png)
- Les _tmpfs mounts_ sont stockés directement dans la mémoire de l'hôte.
- Les données sont donc volatiles et détruites en même temps que le conteneur qui les utilise (ou en cas de reboot du système).
- Les _tmpfs mounts_ ne peuvent pas être partagés entre plusieurs conteneurs.
### Exemples de montages
1. Monte un volume nommé
```none
$ docker run --detach \
--name apache \
--publish 8080:80 \
--mount source=mon-volume,target=/var/www/mon-app \
httpd:2.4
```
2. Monte un répertoire local (bind mount)
```none
$ docker run --detach \
--name apache \
--publish 8080:80 \
--mount type=bind,source=/home/debian/mon-app,destination=/var/www/mon-app \
httpd:2.4
```
3. Monte un répertoire local (bind mount) en read-only
```none
$ docker run --detach \
--name apache \
--publish 8080:80 \
--mount type=bind,source=/home/debian/mon-app,destination=/var/www/mon-app,readonly \
httpd:2.4
```
### -v ou --mount ?
- A l'origine , les options `-v` et `--volume` étaient utilisées pour les conteneurs _standalone_ et l'option `--mount` pour les _services_ Swarm.
- On peut utiliser les 2 syntaxes.
### Volumes et droits d'accès
Attention : les accès sur les fichiers
restent soumis aux droits Linux.
### Travaux pratiques
![Travaux pratiques](images/tp.gif)
[TP Docker Volumes](../travaux-pratiques/slides/docker/tp-volumes.html)
### Accéder à la racine du système de fichiers du conteneur
```none
# docker inspect -f '{{.State.Pid}}' mon-conteneur
11384
# cd /proc/11384/root/
total 52
drwxr-xr-x 19 root root 4096 déc. 18 17:26 ./
drwxr-xr-x 19 root root 4096 déc. 18 17:26 ../
drwxr-xr-x 2 root root 12288 mars 9 2017 bin/
drwxr-xr-x 5 root root 360 déc. 18 17:08 dev/
-rwxr-xr-x 1 root root 0 déc. 18 17:08 .dockerenv*
drwxr-xr-x 2 root root 4096 déc. 18 17:08 etc/
drwxr-xr-x 2 99 99 4096 mars 9 2017 home/
dr-xr-xr-x 263 root root 0 déc. 18 17:08 proc/
drwxr-xr-x 2 root root 4096 déc. 18 17:23 root/
dr-xr-xr-x 13 root root 0 déc. 18 17:11 sys/
drwxrwxrwt 2 root root 4096 mars 9 2017 tmp/
drwxr-xr-x 3 root root 4096 mars 9 2017 usr/
drwxr-xr-x 4 root root 4096 mars 9 2017 var/
```
Spécification RunC : [https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md)
### Connaître l'espace disque utilisé par Docker
`docker system df`
```none
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 23 2 3.871GB 3.794GB (98%)
Containers 11 0 2.989GB 2.989GB (100%)
Local Volumes 4 0 94.22MB 94.22MB (100%)
```
### Nettoyer le système
`docker system prune`
* Supprime les conteneurs arrêtés
* Supprimes les images qui n'ont plus de tag associé
* avec `-a` supprime aussi les images non utilisées
* Supprime les volumes orphelins
* Supprime les réseaux vides
## Travaux pratiques
![Travaux pratiques](images/tp.gif)
[TP application complète Web + DB](../travaux-pratiques/slides/docker/tp-web-db.html)
## Docker Compose
Application multi-conteneurs
- _Compose_ est un outil permettant de configurer et déployer une application composée de plusieurs conteneurs.
- _Compose_ n'est pas inclus dans Docker il faut donc l'installer en plus.
[https://docs.docker.com/compose/](https://docs.docker.com/compose/)
- L'application (mono-conteneur ou multi-conteneurs) est décrite dans un fichier texte au format _yml_.
- Chaque service (conteneur) possède sa propre section de configuration.
- Par défaut un réseau personnalisé de type Bridge est automatiquement créé pour que l'ensemble des conteneurs de l'application :
* puissent communiquer entre eux,
* être isolés des autres conteneurs.
### docker-compose.yml
```yaml
version: '3'
services:
web:
build: . # ici l'image sera construite à partir d'un Dockerfile
ports:
- "5000:5000"
volumes:
- .:/code
- logvolume01:/var/log
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
redis:
image: redis # ici on reprend une image déjà existante
volumes:
logvolume01: {}
```
Directive | Description
-------------------------|---------------------------------------
`image` | Spécifie l'image Docker à utiliser
`build` | Spécifie le chemin pour build l'image
`ports` | Configure la publication de ports
`depends_on` | Exprime la dépendance entre plusieurs services et ainsi l'ordre de lancement
`restart` | Spécifie le configuration de redémarrage du conteneur
`healthcheck` | Configure une vérification de la santé du service
`...` | https://docs.docker.com/compose/compose-file/
Derrière un proxy :
```yaml
version: "3"
services:
my-service:
build:
context: my-folder/
args:
- http_proxy=http:///my.proxy.url:3128
- https_proxy=https:///my.proxy.url:3128
```
Pour vérifier la syntaxe du fichier docker-compose.yml :
`$ docker-compose config`
Pour déployer la stack applicative
en une seule commande :
`$ docker-compose up -d`
Pour arrêter la stack sans supprimer les conteneurs :
`$ docker-compose stop`
Pour supprimer complètement la stack :
`$ docker-compose down`
Cela entraîne la suppression des conteneurs et des réseaux associés.
Pour afficher les logs
de tous les conteneurs de la stack :
`$ docker-compose logs`
### Le fichier `.env`
* Permet de déclarer des variables dont la valeur sera automatiquement substituée lors du traitement du fichier _docker-compose.yml_.
* Est localisé dans le même dossier que _docker-compose.yml_.
* `docker-compose config` permet de visualiser le fichier final.
`.env`
```bash
MA_VARIABLE=hello-world
MON_AUTRE_VARIABLE=bonjour
```
`docker-compose.yml`
```yaml
version: '3'
services:
hello:
image: ${MA_VARIABLE}
environment:
- env_var_name=${MON_AUTRE_VARIABLE}
```
### Travaux pratiques
![Travaux pratiques](images/tp.gif)
[TP Docker Compose](../travaux-pratiques/slides/docker/tp-compose.html)
## Docker CMD --format
Permet d'utiliser des templates Go pour personnaliser l'affichage
```none
$ docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
```
```none
IMAGE ID REPOSITORY TAG
1afc387274f5 cheatsheet latest
258031686918 node 8.1.2
a2ff708b7413 debian latest
00f017a8c2a6 busybox latest
00f017a8c2a6 10.6.30.30:5000/mpoullain/busybox latest
```
[https://docs.docker.com/engine/admin/formatting/](https://docs.docker.com/engine/admin/formatting/)
## API du démon Docker
* Le démon Docker fourni une API qui permet de :
- récupérer des informations sur le démon et les objets Docker
- lancer des commandes
* Documentation : [https://docs.docker.com/develop/sdk/](https://docs.docker.com/develop/sdk/)
Lister les conteneurs :
```none
$ curl --unix-socket /var/run/docker.sock http://1.24/containers/json | jq .
```
```json
[
{
"Id": "4ab7c5898ac9a594b42406c0afc2250b1b7da36e8966348c09e32fde84515608",
"Names": [
"/determined_bohr"
],
"Image": "debian:stretch",
"ImageID": "sha256:8626492fecd368469e92258dfcafe055f636c21a5865a98a0a6...",
"Command": "bash"
}
...
]
```
Démarrer un conteneur :
```none
$ curl --unix-socket /var/run/docker.sock -X POST \
http://1.24/containers/4ab7c5898ac9/start
```
Afficher les logs d'un conteneur :
```none
$ curl --unix-socket /var/run/docker.sock \
http://1.24/containers/4ab7c5898ac9/logs?stdout=1
```
## Mode debug
Il est possible d'activer le mode debug
du démon Docker :
```none
root@node1: # sudo cat /etc/docker/daemon.json
{"debug":true}
```
```none
root@node1: # systemctl reload docker
```
```none
root@node1: # tail -f /var/log/syslog
c'est beaucoup plus verbeux !!
```