963 lines
30 KiB
Markdown
963 lines
30 KiB
Markdown
---
|
||
title: Howto Docker
|
||
categories: docker
|
||
---
|
||
|
||
* 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 / bullseye
|
||
|
||
[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.
|
||
|
||
## Installation
|
||
|
||
Nous utilisons le paquet `docker-ce` des dépôts du projet Docker :
|
||
|
||
~~~
|
||
# apt install apt-transport-https
|
||
# echo "deb https://download.docker.com/linux/debian bullseye stable" > /etc/apt/sources.list.d/docker.list
|
||
# 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
|
||
# apt update
|
||
# apt install docker-ce
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
# adduser $USER docker
|
||
~~~
|
||
|
||
## Utilisation basique
|
||
|
||
Une image Docker contient un système minimal avec un ou plusieurs services.
|
||
Un conteneur quant à lui est une instance (créée à partir d'une image)
|
||
en cours d'exécution.
|
||
|
||
### Gérer les conteneurs
|
||
|
||
#### Lister les conteneurs
|
||
|
||
~~~
|
||
$ docker ps
|
||
~~~
|
||
|
||
Options utiles :
|
||
|
||
~~~
|
||
-a : lister tous les conteneurs
|
||
-l : lister les conteneurs récemment lancés
|
||
-q : lister uniquement les ID des conteneurs
|
||
~~~
|
||
|
||
#### Instancier un nouveau conteneur
|
||
|
||
~~~
|
||
$ docker run <image> [commande]
|
||
~~~
|
||
|
||
Si une commande spécifiée, celle-ci remplacera celle du `CMD` de l'image
|
||
|
||
Options utiles :
|
||
|
||
~~~
|
||
--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
|
||
~~~
|
||
|
||
#### Démarrer un conteneur existant
|
||
|
||
Un conteneur existant est un conteneur précédemment instancié avec `docker
|
||
run`.
|
||
|
||
~~~
|
||
$ docker start <ID ou nom du conteneur>
|
||
~~~
|
||
|
||
#### Éteindre ou tuer un conteneur
|
||
|
||
~~~
|
||
$ docker stop|kill <ID ou nom du conteneur>
|
||
~~~
|
||
|
||
Lorsque le conteneur n'est plus en fonction, il existe toujours et peut être
|
||
listé à l'aide de la commande `docker ps -a`
|
||
|
||
#### 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 :
|
||
|
||
~~~
|
||
$ docker inspect -f "{{ .HostConfig.RestartPolicy.Name }}" monconteneur
|
||
~~~
|
||
|
||
Les valeurs possibles sont :
|
||
|
||
~~~
|
||
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 s’il s’arrê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.
|
||
~~~
|
||
|
||
Pour mettre à jour la politique :
|
||
|
||
~~~
|
||
$ docker update --restart=always monconteneur
|
||
~~~
|
||
|
||
#### Supprimer un conteneur
|
||
|
||
~~~
|
||
$ docker rm <ID ou nom du conteneur>
|
||
~~~
|
||
|
||
#### Exécuter des commandes dans un conteneur en fonctionnement
|
||
|
||
~~~
|
||
$ docker exec <ID ou nom du conteneur> <commande>
|
||
~~~
|
||
|
||
Options utiles :
|
||
|
||
~~~
|
||
-t : alloue un TTY
|
||
-i : attache stdin (mode interactif)
|
||
~~~
|
||
|
||
On utilise habituellement la commande suivante pour obtenir un shell dans un conteneur en fonctionnement :
|
||
|
||
~~~
|
||
$ docker exec -ti <ID ou nom du conteneur> bash
|
||
~~~
|
||
|
||
#### 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 :
|
||
|
||
~~~
|
||
$ docker logs <ID ou nom du conteneur>
|
||
~~~
|
||
|
||
Options utiles :
|
||
|
||
~~~
|
||
-f : suivre les logs en direct
|
||
-t : afficher un timestamp devant chaque ligne
|
||
~~~
|
||
|
||
#### Afficher les informations d'un conteneur
|
||
|
||
~~~
|
||
$ docker inspect <ID ou nom du conteneur>
|
||
~~~
|
||
|
||
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.
|
||
|
||
Il est aussi possible de récupérer une sous partie en utilisant l'argument --format
|
||
|
||
~~~
|
||
# 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 }}"
|
||
~~~
|
||
|
||
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 )
|
||
|
||
### Gérer les images
|
||
|
||
#### Lister les images locales
|
||
|
||
~~~
|
||
$ docker image ls
|
||
~~~
|
||
|
||
#### Construire une image
|
||
|
||
Pour construire ou mettre à jour une image :
|
||
|
||
~~~
|
||
$ docker build <repertoire>
|
||
~~~
|
||
|
||
Le répertoire doit contenir un fichier _Dockerfile_ décrivant l'image à
|
||
construire.
|
||
|
||
Option utiles :
|
||
|
||
~~~
|
||
-t : ajoute un tag à l'image
|
||
~~~
|
||
|
||
#### Ajouter un tag à une image existante
|
||
|
||
~~~
|
||
$ docker tag <tag actuel> <nouveau tag>
|
||
~~~
|
||
|
||
#### Pousser une image sur un dépôt distant
|
||
|
||
~~~
|
||
$ docker push <image>
|
||
~~~
|
||
|
||
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_ :
|
||
|
||
~~~
|
||
$ docker tag foo-image registry.example.net:5000/foo-image
|
||
$ docker push registry.example.net:5000/foo-image
|
||
~~~
|
||
|
||
#### Récupérer une image d'un dépôt distant
|
||
|
||
~~~
|
||
$ docker pull <image>
|
||
~~~
|
||
|
||
#### Copier des fichiers dans/depuis un conteneur
|
||
|
||
~~~
|
||
$ docker cp <fichier> <conteneur>:<chemin>
|
||
~~~
|
||
|
||
On peut aussi le faire de conteneur à conteneur :
|
||
|
||
~~~
|
||
$ docker cp <conteneurA>:<fichier> <conteneurB>:<chemin>
|
||
~~~
|
||
|
||
### Astuces
|
||
|
||
Éteindre/Tuer/Supprimer tous les conteneurs :
|
||
|
||
~~~
|
||
$ docker ps -aq |xargs -r docker stop|kill|rm
|
||
~~~
|
||
|
||
Supprimer toutes les images :
|
||
|
||
~~~bash
|
||
$ docker images -q |xargs -r docker image rm
|
||
~~~
|
||
|
||
Démarrer un conteneur existant avec un shell bash (pour des fins de debug par exemple) :
|
||
|
||
~~~
|
||
$ docker run -it IMAGE bash
|
||
~~~
|
||
|
||
## Configuration
|
||
|
||
#### Changer le chemin de stockage
|
||
|
||
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
|
||
|
||
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
|
||
|
||
### Le démon docker et son client
|
||
|
||
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
|
||
|
||
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
|
||
|
||
Un conteneur est une instance exécutable d'une image.
|
||
|
||
### Stack, service et task
|
||
|
||
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
|
||
|
||
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*.
|
||
|
||
|
||
## Dockerfile
|
||
|
||
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.
|
||
|
||
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_ :
|
||
|
||
~~~
|
||
~/docker-images/rsyslog $ $EDITOR Dockerfile
|
||
|
||
# Image sur laquelle notre image se base
|
||
FROM debian:stretch
|
||
|
||
# Champs optionnels
|
||
LABEL maintainer="John Doe <jdoe@example.com>"
|
||
|
||
# Installation des paquets voulus
|
||
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/
|
||
|
||
# /var/log/ est un volume qui doit être monté depuis l'extérieur lors de
|
||
# l'exécution du conteneur
|
||
VOLUME /var/log/
|
||
|
||
# Le port 514/udp est rendu public à l'extérieur du conteneur à son exécution
|
||
EXPOSE 514/udp
|
||
|
||
# La commande suivante est exécutée lorsque le conteneur est exécuté, en mode "shell"
|
||
CMD /usr/sbin/rsyslogd -n
|
||
# Equivalent à CMD ["/bin/sh","-c","/usr/sbin/rsyslogd","-n"] (format JSON)
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
CMD /usr/bin/foo -d; tail -f /var/log/foo.log
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
CMD touch /var/log/foo.log && tail -f /var/log/foo.log & /usr/bin/foo
|
||
~~~
|
||
À noter que seulement une seule directive `CMD` est acceptée dans un _Dockerfile_.
|
||
|
||
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_ :
|
||
|
||
~~~
|
||
~/docker-images/rsyslog $ ls
|
||
Dockerfile custom.conf
|
||
~/docker-images/rsyslog $ docker build -t rsyslog .
|
||
~/docker-images/rsyslog $ docker image ls
|
||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||
rsyslog latest 4bea99cda08c 8 minutes ago 470MB
|
||
debian stretch 5b712ae16dd7 3 days ago 100MB
|
||
~~~
|
||
|
||
## Utilisation avancée
|
||
|
||
### Docker registry
|
||
|
||
Un _docker registry_ permet d'héberger des images Docker.
|
||
|
||
On peut le déployer avec la _stack_ suivante :
|
||
|
||
~~~
|
||
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
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
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.
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
$ curl https://registrydocker.example.com:5000/v2/_catalog
|
||
~~~
|
||
|
||
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
|
||
|
||
~~~
|
||
docker0# docker swarm init
|
||
~~~
|
||
|
||
Joindre les autres machines au cluster créé (il vous suffit généralement de copier-coller la commande retournée par `docker swarm init` :
|
||
|
||
~~~
|
||
docker1# docker swarm join --token <token> <IP du premier node>
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
docker0# docker swarm join-token <manager|worker>
|
||
~~~
|
||
|
||
#### Lister les machines du cluster
|
||
|
||
~~~
|
||
# docker node ls
|
||
~~~
|
||
|
||
#### Ajouter des labels à une machine
|
||
|
||
~~~
|
||
# docker node update --label-add <clé>=<valeur> <machine>
|
||
~~~
|
||
|
||
Les _labels_ servent notamment à définir des contraintes de placement des
|
||
services lors de l'utilisation de _docker stack_.
|
||
|
||
### 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_ :
|
||
|
||
~~~
|
||
# docker stack deploy -c <stack_name.yml> <stack name>
|
||
~~~
|
||
|
||
Lister les _stacks_ :
|
||
|
||
~~~
|
||
# docker stack ls
|
||
~~~
|
||
|
||
Lister les _services_, toutes _stacks_ confondues ou pour une _stack_ donnée :
|
||
|
||
~~~
|
||
# docker service ls
|
||
# docker stack services <stack name>
|
||
~~~
|
||
|
||
Lister les _tasks_ (replicas) d'une _stack_ ou d'un _service_ donné :
|
||
|
||
~~~
|
||
# docker stack ps <stack name>
|
||
# docker service ps <service name>
|
||
~~~
|
||
|
||
Supprimer une _stack_ :
|
||
|
||
~~~
|
||
# docker stack rm <stack name>
|
||
~~~
|
||
|
||
### 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 :
|
||
|
||
~~~
|
||
# docker create -d <driver> […] <network name>
|
||
~~~
|
||
|
||
Lister les réseaux créés :
|
||
|
||
~~~
|
||
# docker network ls
|
||
~~~
|
||
|
||
Informations détaillées sur un réseau :
|
||
|
||
~~~
|
||
# docker network inspect <network name>
|
||
~~~
|
||
|
||
### 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
|
||
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
|
||
_/var/lib/docker/volumes/"nom du volume"/\_data/_.
|
||
|
||
On peut gérer les volumes avec la commande `docker volume` :
|
||
|
||
~~~
|
||
# docker volume create vol1
|
||
vol1
|
||
# docker volume ls
|
||
DRIVER VOLUME NAME
|
||
local vol1
|
||
# docker volume rm vol1
|
||
vol1
|
||
~~~
|
||
|
||
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)
|
||
|
||
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 :
|
||
|
||
~~~
|
||
failed to update config foo: Error response from daemon: rpc error: code = InvalidArgument desc = only updates to Labels are allowed
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~
|
||
# 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
|
||
~~~
|
||
|
||
### Fichiers sensibles (docker secrets)
|
||
|
||
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 :
|
||
|
||
~~~
|
||
$ 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
|
||
~~~
|
||
|
||
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_ :
|
||
|
||
~~~
|
||
$ ls
|
||
ma-stack.yml nginx.conf example.com.pem
|
||
$ docker stack deploy -c ma-stack.yml ma-stack
|
||
~~~
|
||
|
||
#### 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…
|
||
|
||
~~~
|
||
$ cat ma-stack.dev.yml
|
||
version: "3.6"
|
||
services:
|
||
web:
|
||
environment:
|
||
- DEBUG=1
|
||
- MYSQL_DB=foo_dev
|
||
- MYSQL_USER=foo_dev
|
||
- MYSQL_PASS=deb2Ozpifut?
|
||
~~~
|
||
|
||
On peut ensuite déployer ainsi :
|
||
|
||
~~~
|
||
$ docker stack deploy -c ma-stack.yml -c ma-stack.dev.yml ma-stack
|
||
~~~
|
||
|
||
## FAQ
|
||
|
||
### 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 :
|
||
|
||
~~~
|
||
# /etc/init.d/docker restart
|
||
~~~
|
||
|
||
### 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.*
|
||
|
||
|
||
### Voir et modifier le _ENTRYPOINT_ et le _CMD_ d'une image pour déboguer
|
||
|
||
Voir les commandes
|
||
|
||
~~~
|
||
$ docker image inspect $nomImage -f "{{ .Config.Entrypoint }}{{ .Config.Cmd}}"
|
||
~~~
|
||
|
||
Lancer l'image en changeant le Entrypoint
|
||
|
||
~~~
|
||
$ docker run -d --entrypoint="/bin/sleep" $imageConteneur infinity
|
||
$ docker run -d --entrypoint="" $imageConteneur echo hello
|
||
~~~
|
||
|
||
L'option `--entrypoint` remplace le Entrypoint et Cmd
|
||
|
||
Re-créer l'image en modifiant un conteneur de cette image
|
||
|
||
~~~
|
||
$ docker commit -c 'ENTRYPOINT ["/bin/sleep"]' -c 'CMD ["infinity"]' $idConteneur $nomImage:tag
|
||
~~~
|
||
|
||
### Lors d'un redéploiement d'une stack Docker (docker stack deploy), les services ne sont pas redémarrer avec la nouvelle image
|
||
|
||
Vérifier que le tag _latest_ est bien précisé dans le nom de l'image dans le _docker-stack.yml_ :
|
||
|
||
~~~
|
||
image: registrydocker.example.com:5000/foo:latest
|
||
~~~
|
||
|
||
### 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é) ?
|
||
|
||
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 :
|
||
|
||
~~~
|
||
# iptables -A DOCKER-USER -i eth0 -p tcp -m tcp --dport 5000 -j DROP
|
||
~~~
|
||
|
||
### 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é.
|
||
|
||
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) :
|
||
|
||
~~~
|
||
$ getent hosts <nom du service>
|
||
~~~
|
||
|
||
L'adresse IP des différentes _tasks_ d'un service :
|
||
|
||
~~~
|
||
$ getent hosts tasks.<nom du service>
|
||
~~~
|
||
|
||
### 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. L’utilité pgrep(1) est capable
|
||
de filtrer les processus par namespace sur la base d’un autre
|
||
processus.
|
||
|
||
~~~
|
||
$ pgrep --ns $dockerPID $query
|
||
~~~
|
||
|
||
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 :
|
||
|
||
~~~bash
|
||
# nsenter -t $pid_conteneur -n
|
||
~~~
|
||
|
||
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 l'interface docker0
|
||
|
||
~~~
|
||
# Autorisation Docker
|
||
/sbin/iptables -A INPUT -i docker0 -j ACCEPT
|
||
~~~
|