wiki/HowtoDocker.md

1096 lines
35 KiB
Markdown
Raw Permalink Normal View History

2016-10-28 19:57:02 +02:00
---
title: Howto Docker
categories: docker
---
2023-10-09 13:17:31 +02:00
* Documentation : <https://docs.docker.com/>
* Rôle Ansible : <https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/docker-host>
* Statut de cette page : test / bookworm
2016-10-28 19:57:02 +02:00
2023-10-09 13:17:31 +02:00
[Docker](https://www.docker.com/) est une solution qui permet de créer, déployer et gérer des conteneurs Linux.
Il intègre une gestion avancée des images permettant de les compiler, les compléter, les héberger, etc.
2016-10-28 19:57:02 +02:00
# Installation
2016-10-28 19:57:02 +02:00
2023-10-09 13:17:31 +02:00
Nous utilisons le paquet `docker-ce` des dépôts du projet Docker :
2023-10-09 13:17:31 +02:00
~~~
2017-09-11 20:07:36 +02:00
# apt install apt-transport-https
2023-10-09 13:17:31 +02:00
# echo "deb https://download.docker.com/linux/debian bullseye stable" > /etc/apt/sources.list.d/docker.list
2021-06-08 10:25:46 +02:00
# wget -O /etc/apt/trusted.gpg.d/docker.asc https://download.docker.com/linux/debian/gpg
# dos2unix /etc/apt/trusted.gpg.d/docker.asc
# chmod 644 /etc/apt/trusted.gpg.d/docker.asc
2016-10-28 19:57:02 +02:00
# apt update
2017-09-11 20:07:36 +02:00
# apt install docker-ce
# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; preset: enabled)
Active: active (running) since Thu 2023-11-02 16:11:27 CET; 2 weeks 5 days ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 1255 (dockerd)
Tasks: 17
Memory: 115.2M
CPU: 3min 43.816s
CGroup: /system.slice/docker.service
└─1255 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
# docker -v
Docker version 24.0.7, build afdd53b
2023-10-09 13:17:31 +02:00
~~~
2023-10-09 13:17:31 +02:00
Sur sa machine de travail, il est utile d'ajouter son utilisateur dans le groupe _docker_ pour pouvoir interagir avec le démon _dockerd_ sans passer root à chaque fois :
2018-01-18 15:41:10 +01:00
2023-10-09 13:17:31 +02:00
~~~
# adduser $USER docker
~~~
2016-10-28 19:57:02 +02:00
# Utilisation basique
2016-10-28 19:57:02 +02:00
Une image Docker contient un système minimal avec un ou plusieurs services.
2016-10-28 19:57:02 +02:00
Un conteneur quant à lui est une instance (créée à partir d'une image)
en cours d'exécution.
2016-10-28 19:57:02 +02:00
## Gérer les conteneurs
### Lister les conteneurs
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker ps
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
Options utiles :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
-a : lister tous les conteneurs (y compris ceux qui sont "éteint")
-l : lister les conteneurs récemment lancés
-q : lister uniquement les ID des conteneurs
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
Pour voir les [ressources utilisées par les conteneurs](#voir-les-ressources-utilisées-par-les-conteneurs)
### Instancier un nouveau conteneur
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker run <image> [commande]
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
2023-09-06 15:36:38 +02:00
Si une commande spécifiée, celle-ci remplacera celle du `CMD` de l'image
Options utiles :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
--name NOM : donner un nom au conteneur
-p port_hôte:port_conteneur : rendre un port accessible depuis l'hôte
-d : lancer le conteneur en mode 'détaché'
-it : lancer le conteneur en mode intéractif avec tty
--network NOM : lancer le conterneur sur un réseau docker spécifique existant
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
### Démarrer un conteneur existant
2016-10-28 19:57:02 +02:00
Un conteneur existant est un conteneur précédemment instancié avec `docker
run`.
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker start <ID ou nom du conteneur>
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
### Éteindre ou tuer un conteneur
2023-10-09 13:17:31 +02:00
~~~
$ docker stop|kill <ID ou nom du conteneur>
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
Lorsque le conteneur n'est plus en fonction, il existe toujours et peut être
2018-06-19 11:06:11 +02:00
listé à l'aide de la commande `docker ps -a`
2016-10-28 19:57:02 +02:00
### Autostart d'un conteneur
Pour s'assurer qu'un conteneur démarre ou non au démarrage du démon Docker, il existe un paramètre RestartPolicy :
2023-10-09 13:17:31 +02:00
~~~
$ docker inspect -f "{{ .HostConfig.RestartPolicy.Name }}" monconteneur
2023-10-09 13:17:31 +02:00
~~~
Les valeurs possibles sont :
2023-10-09 13:17:31 +02:00
~~~
no Ne redémarre pas automatiquement le conteneur. (défaut)
on-failure Redémarre le conteneur s'il crash suite à une erreur (code de sortie non nul)
always Toujours redémarrer le conteneur sil sarrête. S'il est arrêté manuellement, il est redémarré uniquement lorsque le démon Docker redémarre ou que le conteneur lui-même est redémarré manuellement.
unless-stopped Semblable à always, sauf que lorsque le conteneur est arrêté (manuellement ou autrement), il n'est pas redémarré même après le redémarrage du démon Docker.
2023-10-09 13:17:31 +02:00
~~~
Pour mettre à jour la politique :
2023-10-09 13:17:31 +02:00
~~~
$ docker update --restart=always monconteneur
2023-10-09 13:17:31 +02:00
~~~
### Supprimer un conteneur
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker rm <ID ou nom du conteneur>
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
### Exécuter des commandes dans un conteneur en fonctionnement
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker exec <ID ou nom du conteneur> <commande>
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
Options utiles :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
-t : alloue un TTY
2018-06-19 11:06:11 +02:00
-i : attache stdin (mode interactif)
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
2018-06-19 11:06:11 +02:00
On utilise habituellement la commande suivante pour obtenir un shell dans un conteneur en fonctionnement :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
2018-06-19 11:06:11 +02:00
$ docker exec -ti <ID ou nom du conteneur> bash
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
### Visionner les journaux d'un conteneur
Il s'agit en fait de la sortie standard et la sortie d'erreur du processus
lancé à l'intérieur du conteneur :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker logs <ID ou nom du conteneur>
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
Options utiles :
2023-10-09 13:17:31 +02:00
~~~
-f : suivre les logs en direct
-t : afficher un timestamp devant chaque ligne
2023-10-09 13:17:31 +02:00
~~~
2018-01-19 16:18:40 +01:00
### Afficher les informations d'un conteneur
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
2018-06-19 11:06:11 +02:00
$ docker inspect <ID ou nom du conteneur>
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
Cette commande s'applique généralement à n'importe quel objet Docker
(conteneur, image, service, réseau…) et donne une liste exhaustive des
attributs de l'objet, formaté en JSON.
2016-10-28 19:57:02 +02:00
2018-09-19 20:12:32 +02:00
Il est aussi possible de récupérer une sous partie en utilisant l'argument --format
2023-10-09 13:17:31 +02:00
~~~
2018-09-19 20:12:32 +02:00
# Récupérer les IP du container d05daab5c59e
$ docker inspect d05daab5c59e --format "{{range .NetworkSettings.Networks }}{{ .IPAddress }}{{ end }}"
# Récupérer l'IP du container f4bae02ef1407adc92f1aa2cc32c8e9fae75dac87126e2bf4964db265e9ad55d sur l'interface docker_gwbridge
$ docker inspect docker_gwbridge --format "{{ .Containers.f4bae02ef1407adc92f1aa2cc32c8e9fae75dac87126e2bf4964db265e9ad55d.IPv4Address }}"
2023-10-09 13:17:31 +02:00
~~~
2018-09-19 20:12:32 +02:00
Note : De notre expérience, l'option --format peut se montrer capricieuse, notamment s'il y a en jeu des identifiant de containers. Une alternative est de parser du json manuellement avec [jq](https://wiki.evolix.org/TipsShell#json-avec-jq )
### Communication entre un conteneur et son hôte
#### Exposer un service du conteneur dans l'hôte
Le principe de Docker est de compartimenter ou isoler un service de l'hôte qui l'héberge.
Pour exposer un service du conteneur, on lancera le conteneur avec l'option `-p` pour mapper un port de l'hôte vers le conteneur :
~~~
$ docker run -p <HOST_INTERFACE_IP>:<HOST_PORT>:<CONTAINER_PORT> (…)
~~~
On utilisera de préférence `127.0.0.1` comme `<HOST_INTERFACE_IP>`. Si on a besoin d'exposer le service à l'extérieur, on mettra en place un reverse proxy qui gèrera la teminaison SSL, [avec Nginx par exemple](https://wiki.evolix.org/HowtoNginx#proxy_pass).
Documentation : <https://docs.docker.com/engine/reference/commandline/run/#publish>
#### Fournir un service de l'hôte au conteneur
Pour faire l'inverse, c'est-à-dire permettre au conteneur d'accèder à un service de l'hôte, il faut :
1. Faire écouter ce service sur l'IP de l'interface réseau Docker commune entre l'hôte et le conteneur (par défaut `docker0`).
2. Autoriser l'IP du conteneur sur ce port dans le firewall de l'hôte.
## Gérer les images
2016-10-28 19:57:02 +02:00
### Lister les images locales
2016-10-28 19:57:02 +02:00
2023-10-09 13:17:31 +02:00
~~~
$ docker image ls
2023-10-09 13:17:31 +02:00
~~~
### Construire une image
Pour construire ou mettre à jour une image :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker build <repertoire>
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
Le répertoire doit contenir un fichier _Dockerfile_ décrivant l'image à
construire.
Option utiles :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
-t : ajoute un tag à l'image
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
### Ajouter un tag à une image existante
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
$ docker tag <tag actuel> <nouveau tag>
2023-10-09 13:17:31 +02:00
~~~
### Pousser une image sur un dépôt distant
2023-10-09 13:17:31 +02:00
~~~
$ docker push <image>
2023-10-09 13:17:31 +02:00
~~~
Avant de pousser une image, il est nécessaire de lui attribuer le bon _tag_ qui
doit contenir l'adresse du dépôt distant.
Par exemple pour pousser l'image _foo-image_ sur le dépôt Docker _registry.example.net:5000_ :
2023-10-09 13:17:31 +02:00
~~~
$ docker tag foo-image registry.example.net:5000/foo-image
$ docker push registry.example.net:5000/foo-image
2023-10-09 13:17:31 +02:00
~~~
### Récupérer une image d'un dépôt distant
2023-10-09 13:17:31 +02:00
~~~
$ docker pull <image>
2023-10-09 13:17:31 +02:00
~~~
### Copier des fichiers dans/depuis un conteneur
2018-05-30 18:13:07 +02:00
2023-10-09 13:17:31 +02:00
~~~
2018-05-30 18:13:07 +02:00
$ docker cp <fichier> <conteneur>:<chemin>
2023-10-09 13:17:31 +02:00
~~~
2018-05-30 18:13:07 +02:00
On peut aussi le faire de conteneur à conteneur :
2023-10-09 13:17:31 +02:00
~~~
2018-05-30 18:13:07 +02:00
$ docker cp <conteneurA>:<fichier> <conteneurB>:<chemin>
2023-10-09 13:17:31 +02:00
~~~
2018-05-30 18:13:07 +02:00
## Astuces
Éteindre/Tuer/Supprimer tous les conteneurs :
2023-10-09 13:17:31 +02:00
~~~
$ docker ps -aq |xargs -r docker stop|kill|rm
2023-10-09 13:17:31 +02:00
~~~
Supprimer toutes les images :
2023-10-09 13:17:31 +02:00
~~~bash
$ docker images -q |xargs -r docker image rm
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
2018-04-30 22:13:52 +02:00
Démarrer un conteneur existant avec un shell bash (pour des fins de debug par exemple) :
2018-01-19 16:18:40 +01:00
2023-10-09 13:17:31 +02:00
~~~
2016-10-28 19:57:02 +02:00
$ docker run -it IMAGE bash
2023-10-09 13:17:31 +02:00
~~~
# Configuration
2023-10-09 13:17:31 +02:00
## Changer le chemin de stockage
2023-10-09 13:17:31 +02:00
Créer le fichier `/etc/docker/daemon.json` et y mettre :
~~~
{
"data-root": "<VOTRE_CHEMIN>",
"storage-driver": "overlay"
}
~~~
NB : Anciennement il fallait "graph" à la place de "data-root".
## TLS
2023-10-09 13:17:31 +02:00
Lorsque le docker-engine est exposé, il est important de le sécuriser avec TLS.
Au moment de l'installation, une version altérée de shellpki est copiée dans le
répertoire docker/tls. Ensuite, les certificats et la clé sont créés pour le
serveur. (`shellpki init`)
Pour autoriser des hôtes à se connecter à l'engine, il faut leur créer une clé
et un certificat.
Pour ce faire, il suffit de lancer le script:
~~~
/home/docker/tls$ ./shellpki create
~~~
Les fichiers seront créés, par défaut, dans le répertoire
`/home/docker/tls/files/$CN`
# Plomberie
2023-10-09 13:17:31 +02:00
## Le démon docker et son client
2023-10-09 13:17:31 +02:00
Docker est une application en mode client-serveur. *dockerd* est un démon qui
fournit une API REST afin d'interagir avec les conteneurs. *docker* est un
client qui permet d'interagir avec le démon en ligne de commande. Il peut
interagir avec un démon en local (sur la même machine) ou avec un démon sur une
machine distante.
## Image
2023-10-09 13:17:31 +02:00
Une image est un template contenant des instructions pour créer un conteneur
docker. Ces instructions sont listées dans un fichier nommé *Dockerfile*. La
plupart du temps, une image se base sur une autre image ce qui crée un système
de couches. Lorsqu'on modifie une image, seules les couches qui sont modifiées
sont reconstruites.
Une fois qu'une image est créée on peut la publier dans un *registry* (`docker
push`).
## Conteneur
2023-10-09 13:17:31 +02:00
Un conteneur est une instance exécutable d'une image.
## Stack, service et task
2023-10-09 13:17:31 +02:00
Lorsque Docker fonctionne en _swarm mode_ (en cluster), les notions de
_stack_, _service_ et _task_ sont introduites, en plus des précédentes.
Un _service_ est un objet qui contient des informations comme l'image Docker à
instancier, des contraintes de placement ou de limitation de ressources, des
objets à lier au conteneur qui sera lancé (volumes, réseau, etc…), le nombre
de réplicas à démarrer, etc…
Les services se manipulent avec la commande `docker service`, mais généralement
on les définit dans un fichier YAML. On peut définir plusieurs services dans un
fichier YAML ainsi que d'autres objets dont les services font référence
(_volume_, _network_, _secret_, _config_, etc…). Cet ensemble représente alors
une _stack_, que l'on peut manipuler avec la commande `docker stack`.
Un _service_ démarre donc un ou plusieurs réplicas. Ces réplicas sont appelés
des _tasks_. Chaque _task_ lance un et un seul conteneur. Les conteneurs étant
des processus Docker indépendant, l'ordonnanceur de Docker Swarm introduit la
notion de _task_ afin de manipuler les conteneurs. Concrètement, une _task_
représente un conteneur et un état dans lequel il est (_running_, _failed_,
_stopped_ et des états transitoires). Sur le même principe qu'une unité
systemd, monit ou supervisord peut surveiller ses processus et les redémarrer
en cas de besoin, une _task_ Docker se comporte de la même manière avec son
conteneur.
Voir : [https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/#services-tasks-and-containers](https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/#services-tasks-and-containers)
# docker registry
2023-10-09 13:17:31 +02:00
Un *registry* sert à héberger des images docker. Il existe des registres
publics tels que *docker hub* ou *docker cloud* mais il est possible d'héberger
son propre *registry*.
2016-10-28 20:14:41 +02:00
# Dockerfile
2017-09-11 20:07:36 +02:00
Les fichiers _Dockerfile_ décrivent les étapes de construction d'une image. Ils
permettent de reconstruire à l'identique votre image et de connaitre exactement
ce qui a été fait. Ainsi au lieu de distribuer une image potentiellement
volumineuse, on distribue uniquement la procédure de construction
(_Dockerfile_) et les quelques fichiers annexes.
2018-05-10 21:56:17 +02:00
Référence pour la syntaxe des fichiers _Dockerfile_ : [https://docs.docker.com/engine/reference/builder/](https://docs.docker.com/engine/reference/builder/)
## Procédure de création d'une nouvelle image
Exemple avec une image exécutant rsyslog :
- depuis un répertoire vierge, création d'un fichier _Dockerfile_ :
2017-09-11 20:07:36 +02:00
2023-10-09 13:17:31 +02:00
~~~
~/docker-images/rsyslog $ $EDITOR Dockerfile
# Image sur laquelle notre image se base
2017-09-11 20:07:36 +02:00
FROM debian:stretch
# Champs optionnels
LABEL maintainer="John Doe <jdoe@example.com>"
# Installation des paquets voulus
2017-09-11 20:07:36 +02:00
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends rsyslog procps \
&& rm -rf /var/lib/apt/lists/*
# Configuration de rsyslog. On peut modifier la configuration directement ou
# bien copier des fichiers de notre machine
RUN sed -i 's/^#\(module(load="imudp")\)/\1/; s/^#\(input(type="imudp" port="514")\)/\1/' /etc/rsyslog.conf
COPY custom.conf /etc/rsyslog.d/
2018-06-19 11:06:11 +02:00
# /var/log/ est un volume qui doit être monté depuis l'extérieur lors de
# l'exécution du conteneur
VOLUME /var/log/
2017-09-11 20:07:36 +02:00
# Le port 514/udp est rendu public à l'extérieur du conteneur à son exécution
EXPOSE 514/udp
2017-09-11 20:07:36 +02:00
2023-09-06 15:29:07 +02:00
# La commande suivante est exécutée lorsque le conteneur est exécuté, en mode "shell"
CMD /usr/sbin/rsyslogd -n
2023-09-06 15:29:07 +02:00
# Equivalent à CMD ["/bin/sh","-c","/usr/sbin/rsyslogd","-n"] (format JSON)
2023-10-09 13:17:31 +02:00
~~~
2017-09-11 20:07:36 +02:00
Dans la mesure du possible, voici quelques bonnes pratiques à respecter :
- la commande spécifiée par _CMD_ doit s'exécuter en avant plan et ne pas
forker. Si la commande rend la main, le conteneur sera alors arrêté ;
- la commande doit envoyer ses logs sur stdout et/ou stderr. Cela permet de
les consulter directement à l'aide de `docker logs`.
Un exemple à ne **pas** écrire :
2023-10-09 13:17:31 +02:00
~~~
CMD /usr/bin/foo -d; tail -f /var/log/foo.log
2023-10-09 13:17:31 +02:00
~~~
2017-09-11 20:07:36 +02:00
Dans le cas où `/usr/bin/foo -d` lance le démon foo en arrière plan, le
conteneur s'exécutera correctement mais Docker va monitorer le processus tail
et non plus foo. Si foo crash, le conteneur ne passera pas en _failed_ et ne
pourra pas être redémarré automatiquement.
À supposer que foo ne puisse pas envoyer ses logs sur stdout, le bon example
serait :
2017-09-11 20:07:36 +02:00
2023-10-09 13:17:31 +02:00
~~~
CMD touch /var/log/foo.log && tail -f /var/log/foo.log & /usr/bin/foo
2023-10-09 13:17:31 +02:00
~~~
À noter que seulement une seule directive `CMD` est acceptée dans un _Dockerfile_.
2023-09-06 15:29:07 +02:00
Ici l'exécutable est spécifié pas l'instruction `CMD` mais ce n'est pas toujours le cas.
L'instruction `ENTRYPOINT` peut être utilisé pour spécifier l'exécutable, tandis que CMD permettra de donner les arguments.
Dans ce cas `ENTRYPOINT` et `CMD` doivent être spécifiés au format JSON.
À noter qu'il peut y avoir plusieurs instructions `ENTRYPOINT` dans un _Dockerfile_ mais seul la dernière sera prise en compte.
On peut ensuite construire notre image, en lui donnant ici le _tag_ _rsyslog_ :
2023-10-09 13:17:31 +02:00
~~~
~/docker-images/rsyslog $ ls
Dockerfile custom.conf
~/docker-images/rsyslog $ docker build -t rsyslog .
~/docker-images/rsyslog $ docker image ls
2017-09-11 20:07:36 +02:00
REPOSITORY TAG IMAGE ID CREATED SIZE
rsyslog latest 4bea99cda08c 8 minutes ago 470MB
2017-09-11 20:07:36 +02:00
debian stretch 5b712ae16dd7 3 days ago 100MB
2023-10-09 13:17:31 +02:00
~~~
# Utilisation avancée
## Docker registry
Un _docker registry_ permet d'héberger des images Docker.
On peut le déployer avec la _stack_ suivante :
2023-10-09 13:17:31 +02:00
~~~
version: "3.6"
services:
registry:
image: registry:2
deploy:
replicas: 1
restart_policy:
condition: on-failure
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /run/secrets/certificate
REGISTRY_HTTP_TLS_KEY: /run/secrets/certificate
REGISTRY_HTTP_ADDR: 0.0.0.0:5000
volumes:
- registry:/var/lib/registry
secrets:
- certificate
ports:
- :5000:5000
volumes:
registry:
secrets:
certificate:
file: certificate.pem
2023-10-09 13:17:31 +02:00
~~~
Attention, le certificat doit absolument être valide et le Common Name doit correspondre avec le nom utilisé pour y accéder.
Lors d'un déploiement d'une stack existante, en cas d'erreur :
2023-10-09 13:17:31 +02:00
~~~
image registrydocker.example.com:5000/XXX could not be accessed on a registry to record
its digest. Each node will access registrydocker.example.com:5000/XXX independently,
possibly leading to different nodes running different
versions of the image.
2023-10-09 13:17:31 +02:00
~~~
Il peut s'agir de différents problèmes (réseau, SSL, etc…), Docker n'est pas très bavard sur la cause de l'échec. Un bon moyen de déboguer la situation est avec :
2023-10-09 13:17:31 +02:00
~~~
2018-06-21 09:53:56 +02:00
$ curl https://registrydocker.example.com:5000/v2/_catalog
2023-10-09 13:17:31 +02:00
~~~
La requête devrait retourner la liste des images hébergées au format json.
## Swarm
Swarm permet de mettre en communication plusieurs hôtes Docker afin d'en former
un cluster. On pourra ainsi déployer des applications multi-conteneurs sur
plusieurs machines.
### Initialiser le cluster
2023-10-09 13:17:31 +02:00
~~~
docker0# docker swarm init
2023-10-09 13:17:31 +02:00
~~~
Joindre les autres machines au cluster créé (il vous suffit généralement de copier-coller la commande retournée par `docker swarm init` :
2023-10-09 13:17:31 +02:00
~~~
docker1# docker swarm join --token <token> <IP du premier node>
2023-10-09 13:17:31 +02:00
~~~
Par défaut la machine sur laquelle le cluster a été initialisée a le rôle de
_manager_, et les suivantes ont le rôle de _worker_. On ne peut déployer de
nouveaux services que depuis les _managers_. Les _workers_ se contentent de
recevoir les services à rouler.
Pour ajouter des machines plus tard, il suffit de générer un nouveau token :
2023-10-09 13:17:31 +02:00
~~~
docker0# docker swarm join-token <manager|worker>
2023-10-09 13:17:31 +02:00
~~~
### Lister les machines du cluster
2023-10-09 13:17:31 +02:00
~~~
# docker node ls
2023-10-09 13:17:31 +02:00
~~~
### Ajouter des labels à une machine
2023-10-09 13:17:31 +02:00
~~~
# docker node update --label-add <clé>=<valeur> <machine>
2023-10-09 13:17:31 +02:00
~~~
Les _labels_ servent notamment à définir des contraintes de placement des
services lors de l'utilisation de _docker stack_.
### Drainer une machine du cluster
Pour effectuer un maintenance ou autre sur une machine (node), il faut la drainer (déplacer tous ses conteneurs vers les autres noeuds)
~~~
# docker node update --availability drain <noeud>
~~~
On peut verifier qu'il n'y a plus de services qui tournent sur cette machines [#lister-toute-les-tasks-de-tous-les-services-sans-docker-service](#lister-toute-les-tasks-de-tous-les-services-sans-docker-service)
2023-11-15 11:25:18 +01:00
### Supprimer une machine du cluster
2023-11-15 11:25:18 +01:00
De préférence après avoir drainé le noeud
2023-11-15 11:25:18 +01:00
~~~
# docker node rm <hash-machine>
~~~
## Compose/stack (docker stack)
Docker permet de déployer des infrastructures multi-conteneurs (_stacks_) simplement à l'aide de `docker stack` (anciennement Docker Compose, logiciel tier). Il est très utile dans le cadre de déploiement sur un cluster Swarm.
L'infra est à décrire dans un fichier YAML.
### Déployer (ou mettre à jour à chaud) une nouvelle _stack_
2023-10-09 13:17:31 +02:00
~~~
# docker stack deploy -c <stack_name.yml> <stack name>
2023-10-09 13:17:31 +02:00
~~~
### Lister les _stacks_
2023-10-09 13:17:31 +02:00
~~~
# docker stack ls
2023-10-09 13:17:31 +02:00
~~~
### Lister les _services_, toutes _stacks_ confondues ou pour une _stack_ donnée
2023-10-09 13:17:31 +02:00
~~~
# docker service ls
# docker stack services <stack name>
2023-10-09 13:17:31 +02:00
~~~
### Lister les _tasks_ (replicas) d'une _stack_ ou d'un _service_ donné
2023-10-09 13:17:31 +02:00
~~~
# docker stack ps <stack name>
# docker service ps <service name>
2023-10-09 13:17:31 +02:00
~~~
### Lister toute les _tasks_ de tous les _services_ (sans docker service)
~~~
2023-12-14 12:25:17 +01:00
# for stack_name in $(docker stack ls --format "table {{.Name}}");do docker stack ps $stack_name 2>&1 | sed 1d | grep -v "nothing found in stack" ;done ;
# On peut aussi filtrer avec les task uniquement sur un noeud de la swarm
2023-12-14 12:25:17 +01:00
# for stack_name in $(docker stack ls --format "table {{.Name}}");do docker stack ps $stack_name --filter "NODE=nodeName" 2>&1 | sed 1d | grep -v "nothing found in stack" ;done ;
~~~
### Supprimer une _stack_
2023-10-09 13:17:31 +02:00
~~~
# docker stack rm <stack name>
2023-10-09 13:17:31 +02:00
~~~
## Réseaux (docker network)
Docker permet de gérer différentes topologies de réseaux pour connecter les conteneurs entre eux à l'aide de `docker network`.
_drivers_ réseau supportés :
- _bridge_ : utilise les bridges Linux, chaque bridge est isolée des autre reseaux et docker et permet de resoudre les noms des conteneurs au sein du bridge.
- _host_ : Aucune isolation réseaux, le conteneur tourne comme un processus normal dans le namespace par défaut : il partage le réseaux avec son hôte.
- _overlay_ : utilisé dans le cas d'un cluster Swarm, permet d'avoir un
réseau unique partagé entre tous les hôtes Docker et permet de faire du
load-balancing entre les conteneurs (replicas) d'un service ;
- _macvlan_ : permet d'assigner directement des adresses IP publiques aux
conteneurs (chaque conteneur à une mac unique), donc aucun NAT n'est fait contrairement aux précédents.
> Attention l'interface de l'hôte doit être configurée pour fonctionner en mode "promiscuité" pour que cela fonctionne.
- _ipvlan_ : Comme _macvlan_ sauf que la mac utilisée est celle de l'hôte donc pas besoin d'activer la promiscuité.
- _none_ : Isolation complete du conteneur
Créer un réseau :
2023-10-09 13:17:31 +02:00
~~~
# docker create -d <driver> […] <network name>
2023-10-09 13:17:31 +02:00
~~~
Lister les réseaux créés :
2023-10-09 13:17:31 +02:00
~~~
# docker network ls
2023-10-09 13:17:31 +02:00
~~~
Informations détaillées sur un réseau :
2023-10-09 13:17:31 +02:00
~~~
# docker network inspect <network name>
2023-10-09 13:17:31 +02:00
~~~
## Volumes (docker volume)
Les volumes Docker sont des répertoires ou fichiers présents sur l'hôte qui
peuvent être montés à l'intérieur des conteneurs. L'intérêt est de pouvoir
écrire des données persistantes depuis les conteneurs, qui seront alors
conservés après arrêt et suppression du conteneur et accessible depuis l'hôte.
Les volumes sont indépendants des conteneurs.
Ils sont généralement utilisés pour stocker des fichiers de logs, des
2018-06-22 11:05:13 +02:00
répertoires de bases de données ou des configuration.
Sauf si un chemin est spécifié, Docker mets ses volumes dans
_/var/lib/docker/volumes/_, et les données sont accessibles dans
2018-06-22 11:05:13 +02:00
_/var/lib/docker/volumes/"nom du volume"/\_data/_.
On peut gérer les volumes avec la commande `docker volume` :
2023-10-09 13:17:31 +02:00
~~~
# docker volume create vol1
vol1
# docker volume ls
DRIVER VOLUME NAME
local vol1
# docker volume rm vol1
vol1
2023-10-09 13:17:31 +02:00
~~~
Dans le cas d'un cluster Docker Swarm, les volumes ne sont pas répliqués entre
les membres du cluster. Si un conteneur monte un volume, il faut bien penser à
restreindre son placement à un Docker node spécifique, ou bien mettre en place
un système de réplication de fichiers tier.
## Fichiers de configuration (docker config)
2018-06-22 10:35:41 +02:00
Les _config_ sont des objets permettant de stocker un unique fichier,
généralement un fichier de configuration d'un service.
À la différence des volumes, les _config_ sont répliqués au sein d'un cluster
Swarm, ce qui permet de ne pas avoir à copier le fichier de configuration sur
chaque serveur. Lors de la création de l'objet config, il sera rendu accessible
à tous les membres du cluster qui en auront besoin.
À l'intérieur des conteneurs, les objets _config_ sont montés en lecture seule.
Un processus qui a besoin de réécritre lui-même sa configuration durant son
exécution ne marchera donc pas, et il faudra préférer l'utilisation d'un
volume.
Les _config_ ne peuvent pas non plus être mis à jour lors d'un redéploiement de
stack :
2023-10-09 13:17:31 +02:00
~~~
2018-06-22 10:35:41 +02:00
failed to update config foo: Error response from daemon: rpc error: code = InvalidArgument desc = only updates to Labels are allowed
2023-10-09 13:17:31 +02:00
~~~
2018-06-22 10:35:41 +02:00
Il faut obligatoirement soit supprimer l'objet _config_ et donc les objets qui
en dépendent (conteneurs), soit créer un nouveau _config_ (avec un nouveau nom
donc) et l'ajouter au conteneur en question (non testé). On perd donc la
souplesse des volumes dans ce cas là.
Les _config_ se gèrent avec la commande `docker config`. Le serveur doit avoir
le rôle _manager_ dans le cluster Swarm pour pouvoir créer un objet _config_,
puisque cela impacte l'ensemble du cluster :
2023-10-09 13:17:31 +02:00
~~~
# docker config create vim-config .vimrc
07kaw58mhvtkqem46ipkd97i1
# docker config ls
ID NAME DRIVER CREATED UPDATED
07kaw58mhvtkqem46ipkd97i1 vim-config Less than a second ago Less than a second ago
# docker config rm vim-config
vim-config
2023-10-09 13:17:31 +02:00
~~~
## Fichiers sensibles (docker secrets)
2018-05-02 16:51:24 +02:00
Ce sont des objets permettant de stocker des fichiers, au même titre que les
_configs_, mais dédiés aux fichiers sensibles comme les clés privée ou fichiers
contenant des mots de passe.
Ils sont rendus accessibles dans _/run/secrets/_ dans les conteneurs.
Les _secret_ héritent des mêmes caractéristiques que les _config_ (voir
ci-dessus), à savoir qu'ils sont accessibles partout dans un cluster Swarm mais
ne peuvent ni être modifiés depuis un conteneur ni depuis l'hôte, ils doivent
être recréés en cas de modification.
Les _config_ se gèrent avec la commande `docker secret`.
## Fichier YAML de description de _stack_ (anciennement _docker-compose.yml_)
Les _stacks_ Docker se décrivent à l'aide d'un fichier YAML. Anciennement le
déploiement d'une _stack_ se faisait à l'aide de Docker-compose et le fichier
s'appelait couramment _docker-compose.yml_. Docker stack ne définit pas de nom
par défaut donc il peut porter n'importe quel nom.
Le fichier contient une description de tous les objets Docker à créer pour
déployer une _stack_ de zéro.
Tout ce que l'on peut faire avec le fichier YAML peut être fait avec les
commandes docker équivalentes (`docker service`, `docker volume`, `docker
config`, etc…). Le nom des commandes et options sont exactement les mêmes. Le
format YAML permet simplement de rendre plus simple la description d'une
_stack_ qu'une série de commandes.
Référence sur le format du fichier :
[https://docs.docker.com/compose/compose-file/](https://docs.docker.com/compose/compose-file/)
Voici un aperçu :
2023-10-09 13:17:31 +02:00
~~~
$ cat ma-stack.yml
version: "3.6"
services:
web:
image: my-website:latest
deploy:
replicas: 2
restart_policy:
condition: on-failure
ports:
- "80:80"
- "443:443"
environment:
- MYSQL_DB=foo_dev
- MYSQL_USER=foo_dev
- MYSQL_PASS=deb2Ozpifut?
secrets:
- ssl_cert
configs:
- source: nginx
target: /etc/nginx/nginx.conf
mysql:
image: mariadb:latest
deploy:
replicas: 1
restart_policy:
condition: on-failure
placement:
constraints:
- node.labels.role == sql
volumes:
- mysql-datadir:/var/lib/mysql
volumes:
mysql-datadir:
configs:
nginx:
file: nginx.conf
secrets:
ssl_cert:
file: example.com.pem
2023-10-09 13:17:31 +02:00
~~~
On lance ici 2 _services_, _web_ et _mysql_. On spécifie que _web_ doit avoir 2
réplicas, il y aura donc 2 _tasks_ (et donc 2 conteneurs) qui seront démarrés,
peu importe où sur le cluster.
Pour le service _mysql_ par contre, on spécifie une contrainte de placement de
la _task_, elle doit être démarré sur une machine du cluster ayant le label
_role == sql_. La raison est que comme on lui a associé un volume pour son
_datadir_, il doit toujours être exécuter sur la même machine (les volumes ne
sont pas répliqués entre les machines d'un cluster).
On ne spécifie pas de chemin pour le _volume_ _mysql_datadir_, donc Docker le
créera par défaut dans _/var/lib/docker/volumes/ma-stack_mysql-datadir/ sur la
machine hôte.
Le _service_ _web_ à besoin d'une _config_ appelée _nginx_ et d'un _secret_
appelé _ssl_cert_ que l'on déclare tout en bas et qui contiennent
respectivement le fichier _nginx.conf_ et _example.com.pem_ dans notre
répertoire courant, à côté du _ma-stack.yml_. Lorsque Docker crée une _config_
ou un _secret_ (au déploiement de la _stack_), il les rend disponible à tous
les membres du cluster, ce qui fait qu'on n'a pas besoin de spécifier de
contrainte de placement pour _web_.
Ensuite on peut déployer notre _stack_ :
2023-10-09 13:17:31 +02:00
~~~
$ ls
ma-stack.yml nginx.conf example.com.pem
$ docker stack deploy -c ma-stack.yml ma-stack
2023-10-09 13:17:31 +02:00
~~~
### Surcharge de paramètre
On peut surcharger certains paramètres définit dans le premier en créant un
second fichier contenant seulement les paramètres à surcharger. Les 2 fichiers
devront être passer en paramètre de `docker stack deploy` et ils seront alors
fusionnés. Cela permet de réutiliser un fichier de _stack_ pour différents
environnement (preprod, prod…) en changeant uniquement des variables
d'environement, mots de passe, etc…
2023-10-09 13:17:31 +02:00
~~~
$ cat ma-stack.dev.yml
version: "3.6"
services:
web:
environment:
- DEBUG=1
- MYSQL_DB=foo_dev
- MYSQL_USER=foo_dev
- MYSQL_PASS=deb2Ozpifut?
2023-10-09 13:17:31 +02:00
~~~
On peut ensuite déployer ainsi :
2023-10-09 13:17:31 +02:00
~~~
$ docker stack deploy -c ma-stack.yml -c ma-stack.dev.yml ma-stack
2023-10-09 13:17:31 +02:00
~~~
# FAQ
2018-05-02 16:51:24 +02:00
## Les conteneurs ont des problèmes de connectivités entre eux/vers l'extérieur
C'est très probablement lié à un outil manipulant les règles netfilter qui a
effacé les règles spécifiques à Docker, notamment dans la table _nat_.
Pour restaurer les règles netfilter de Docker, il n'y a pas d'autre moyen que
de redémarrer le démon :
2023-10-09 13:17:31 +02:00
~~~
# /etc/init.d/docker restart
2023-10-09 13:17:31 +02:00
~~~
## Voir les ressources utilisées par les conteneurs
On peut voir quelle conteneur prennent le plus de ressources (Cpu, Memoire, Disque) avec docker stats.
Par defaut docker stats fonctionne un peu comme htop et met a jour la sortie
~~~
$ docker stats [OPTIONS] [CONTAINER...]
~~~
Options utiles :
~~~
-a : lister tous les conteneurs (y compris ceux qui sont "éteint")
--no-stream : avoir un seule sortie
--no-trunc : ne pas tronquer la sortie (largeur des colonnes)
~~~
Avoir une sortie style ps:
~~~
docker stats --no-stream --no-trunc
~~~
## Espace insuffisant lors du build d'une image
Solutions:
- Vérifier que le "build context" n'est pas trop grand.
- Modifier la variable d'environnement DOCKER_TMPDIR .
- Créer un fichier .dockerignore pour exclure des fichiers et répertoires
du "build context"
*Build context: Tout ce qui se trouve à la racine du Dockerfile.*
2023-09-06 15:59:58 +02:00
## Voir et modifier le _ENTRYPOINT_ et le _CMD_ d'une image pour déboguer
2023-09-06 15:59:58 +02:00
Voir les commandes
2023-10-09 13:17:31 +02:00
~~~
2023-09-06 15:59:58 +02:00
$ docker image inspect $nomImage -f "{{ .Config.Entrypoint }}{{ .Config.Cmd}}"
2023-10-09 13:17:31 +02:00
~~~
2023-09-06 15:59:58 +02:00
Lancer l'image en changeant le Entrypoint
2023-10-09 13:17:31 +02:00
~~~
2023-09-06 15:59:58 +02:00
$ docker run -d --entrypoint="/bin/sleep" $imageConteneur infinity
$ docker run -d --entrypoint="" $imageConteneur echo hello
2023-10-09 13:17:31 +02:00
~~~
2023-09-06 15:59:58 +02:00
L'option `--entrypoint` remplace le Entrypoint et Cmd
Re-créer l'image en modifiant un conteneur de cette image
2023-10-09 13:17:31 +02:00
~~~
2023-09-06 15:59:58 +02:00
$ docker commit -c 'ENTRYPOINT ["/bin/sleep"]' -c 'CMD ["infinity"]' $idConteneur $nomImage:tag
2023-10-09 13:17:31 +02:00
~~~
2023-09-06 15:59:58 +02:00
## Lors d'un redéploiement d'une stack Docker (docker stack deploy), les services ne sont pas redémarrer avec la nouvelle image
2018-05-02 16:51:24 +02:00
Vérifier que le tag _latest_ est bien précisé dans le nom de l'image dans le _docker-stack.yml_ :
2023-10-09 13:17:31 +02:00
~~~
2018-05-02 16:51:24 +02:00
image: registrydocker.example.com:5000/foo:latest
2023-10-09 13:17:31 +02:00
~~~
2018-05-02 16:51:24 +02:00
## Est-il possible de ne faire écouter un service d'une stack que sur une interface précise de la machine hôte (par exemple sur un LAN privé) ?
2018-05-02 16:51:24 +02:00
Non, Docker ne supporte pas ça.
Il faut bloquer le port en question dans le pare-feu, dans la chaîne iptables DOCKER-USER.
Pour bloquer l'accès au _registry_ Docker depuis l'extérieur par exemple :
2023-10-09 13:17:31 +02:00
~~~
2018-05-02 16:51:24 +02:00
# iptables -A DOCKER-USER -i eth0 -p tcp -m tcp --dport 5000 -j DROP
2023-10-09 13:17:31 +02:00
~~~
2018-05-02 18:15:30 +02:00
## Au sein des conteneurs, un `getent <service>` ou `getent tasks.<service>` ne retourne pas l'adresse IP du service alors que celui-ci est bien lancé.
2018-05-02 18:15:30 +02:00
Stopper le conteneur du service avec un `docker stop <conteneur du service>`. Docker stack devrait le relancer automatiquement.
### Comment obtenir l'adresse IP des _tasks_ d'un _service_ (dans le cadre de l'utilisation de Docker stack) au sein de conteneur (à des fins de debug) ?
L'adresse IP virtuelle qui redirige aléatoirement sur chacune des _tasks_ (si le réseau utilise le driver _overlay_, cas par défaut) :
2023-10-09 13:17:31 +02:00
~~~
$ getent hosts <nom du service>
2023-10-09 13:17:31 +02:00
~~~
L'adresse IP des différentes _tasks_ d'un service :
2023-10-09 13:17:31 +02:00
~~~
$ getent hosts tasks.<nom du service>
2023-10-09 13:17:31 +02:00
~~~
## Comment trouver le PID d'un processus roulant dans un conteneur ?
Docker et autres technologies de conteneurs, utilisent des namespace
linux pour isoler les processus. Lutilité pgrep(1) est capable
de filtrer les processus par namespace sur la base dun autre
processus.
2023-10-09 13:17:31 +02:00
~~~
$ pgrep --ns $dockerPID $query
2023-10-09 13:17:31 +02:00
~~~
Ce concept provient du système Plan9
## Accéder au namespace réseau (ou autres) d'un conteneur
Docker ne met pas le namespace réseau qu'il utilise pour ses conteneurs dans `var/run/netns/`, on ne voit donc rien si on joue `ip netns` car le namespace est dans `/proc/${pid_conteneur}/ns/`.
On peut monter `/proc/${pid_conteneur}/ns/` dans `var/run/netns/` pour utiliser ip OU plus simplement utiliser nsenter à la place :
2023-10-09 13:17:31 +02:00
~~~bash
# nsenter -t $pid_conteneur -n
2023-10-09 13:17:31 +02:00
~~~
Nous permet d'avoir un shell dans le namespace, `-t` indique d'utiliser le pid target et `-n` indique que nous voulons aller dans le namespace netns
## Accéder aux ports locaux (docker-proxy) avec minifirewall
Vous devez autoriser activer `DOCKER=on` et autoriser ce que vous voulez sur l'interface `docker0`
2023-10-09 13:17:31 +02:00
~~~
DOCKER='on'
/sbin/iptables -A INPUT -i docker0 -j ACCEPT
2023-10-09 13:17:31 +02:00
~~~
## Problème de résolution DNS dans Docker
En général on utilise une stack réseau distincte dans les conteneurs, et donc la résolution DNS dans les conteneurs
est assurée par « Docker DNS embedded server » qui va transférer aux serveurs DNS définis sur l'hôte.
Si les serveurs DNS de l'hôte fonctionnent bien mais que la résolution DNS ne se fait plus dans les conteneurs,
vous pouvez en désespoir de cause redémarrer le démon Docker.
## Redémarrer le démon Docker sans redémarrer les conteneurs ?
2024-01-10 12:04:15 +01:00
L'option `"live-restore"` à ne pas confondre avec le [RestartPolicy](#autostart-dun-conteneur) permet de redémarrer le démon Docker sans redémarrer les conteneurs
2024-01-10 12:04:15 +01:00
> **Important** On ne peut pas utiliser cette option pour les services de docker swarm.
Dans la configuration, si `grep "live-restore" /etc/docker/daemon.json` n'est pas à `"live-restore": true` on peut le mettre et reload docker `systemctl reload docker`
Après quoi on peut redémarrer le démon docker sans éteindre les conteneurs `systemctl restart docker`.
## docker service ls `Error response from daemon: rpc error: code = Unimplemented desc = unknown method ListServiceStatuses`
Cette erreur peut arriver quand un noeud de la swarm est dans une version plus ancienne, différente de celle des autres noeud
~~~
# docker node ls
xxxxxxxxxxxxxxx appxx Ready Active Leader 18.09.7
~~~
Il faut alors mettre à jour docker sur ce noeud.
2024-01-02 14:49:49 +01:00
## `Operation not permitted`
Si une application executée dans Docker génère des erreurs du style `Operation not permitted`, il est peut être necessaire de lancer le conteneur avec une (ou les deux) de ces options :
- `--security-opt seccomp=unconfined`
- `--security-opt apparmor=unconfined`
Par exemple, ça nous a déjà permis de corriger l'erreur suivante :
```
OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 4: Operation not permitted
```