Conteneurs: Notions de base

Containers
Docker
I211
PO43
Lecture
Principes de base et utilisation de conteneurs, application avec Docker.
Auteur
Affiliations

Université de Toulon

LIS UMR CNRS 7020

Date de publication

2024-10-02

Source
Branch
  • develop (b23c847)
  • 2024-03-08 09:42:51
Java
  • OpenJDK Temurin-21.0.4+7
  • Apache Maven 3.9.9
Docker
  • Client: 27.2.0 - buildx v0.16.2 - compose v2.29.7
  • Server: 27.2.0

Ce document est une introduction à l’utilisation des conteneurs pour isoler les composants d’une application. En pratique, nous utilisons Docker. L’objectif est de pouvoir produire des environnements de compilation et d’exécution d’applications répétables, versionnables et portables.

Vocabulaire

Conteneur
Un conteneur est une sorte de bac à sable (sandbox) qui isole un ensemble de processus du système en utilisant les namespaces du noyau linux et leur associe une carte réseau virtuelle. Un conteneur est l’instance d’une image. Généralement, un conteneur fournit un et un seul service.
Image
Une image est système de fichier isolé qui contient tout ce qui est nécessaire pour l’exécution d’un conteneur. Elle contient aussi d’autres informations comme des metadonnées, des variables d’environnement ou la commande par défaut à exécuter. Souvent une image s’appuie sur une autre image à laquelle elle apporte des modificiations.
Container Daemon (containerd, Docker Daemon)
Le container daemon est un serveur qui gère les images, les conteneurs, les réseaux et les volumes de stockage. Il peut communiquer avec d’autres container daemons et offre une API standard.
Docker Client
Le docker client est un outils en ligne de commande pour envoyer des commandes à un ou plusieurs container daemons locaux ou distants.

Par exemple, un docker client peut demander à un container daemon d’exécuter une image ce qui produit un conteneur. Si l’image n’est pas disponible localement, elle est téléchargée par le container daemon depuis un registry.

Installation

Docker peut être installé directement sous linux, il partage alors le noyau de l’hôte. Il peut aussi être installé dans une machine virtuelle Linux (sous Linux, Windows et MacOs) par exemple via Docker Desktop.

Exécuter un conteneur

La commande docker container run permet d’executer la commande par défaut d’une image dans un espace isolé. Si l’image n’est pas disponible pour le docker daemon, elle est téléchargée à partir d’un registry (par défaut Docker Hub).

Comme premier exemple, exécutons l’image hello-world qui illustre ce fonctionnement (lire le résultat) :

Une commande docker commence par le type d’objet qu’elle manipule (container, image, …) suivi d’une sous-commande. Par exemple, il est possible de demander au docker daemon de précharger ou de mettre à jour une image avec la sous-commande pull.

Pour télécharger ou mettre à jour Alpine Linux qui est une distribution linux très légère :

Le format du nom d’une image est [registry_hostname[:port]][path]image_name[:tag]. Si l’adresse du registry est omise c’est celle de dockerhub qui est utilisé (docker.io). Si l’image est “officielle” path peut-être omis et vaut library, sinon il s’agit généralement du compte de l’utilisateur qui fourni l’image. tag permet de différencier les versions d’une image, s’il est omis il vaut latest.

Ainsi, le nom d’image alpine correspond en fait à docker.io/library/alpine:latest. La documentation indique les tags existants. En production, pour obtenir des exécutions reproductibles il est conseillé de spécifier la version

Il est possible de choisir la commande à utiliser au lancement du container en l’indiquant après le nom de l’image. Des variables d’environnement peuvent êtrée crées dans le conteneur avec l’option --env.

docker container run \
    alpine:3.17.2 uname -a
Unable to find image 'alpine:3.17.2' locally
3.17.2: Pulling from library/alpine

af6eaf76a39c: Pulling fs layer af6eaf76a39c: Downloading  32.77kB/3.262MBaf6eaf76a39c: Downloading  262.1kB/3.262MBaf6eaf76a39c: Downloading  1.212MB/3.262MBaf6eaf76a39c: Verifying Checksum af6eaf76a39c: Download complete af6eaf76a39c: Extracting  32.77kB/3.262MBaf6eaf76a39c: Extracting  3.262MB/3.262MBaf6eaf76a39c: Pull complete Digest: sha256:ff6bdca1701f3a8a67e328815ff2346b0e4067d32ec36b7992c1fdc001dc8517
Status: Downloaded newer image for alpine:3.17.2
Linux c247430e3361 6.10.4-linuxkit #1 SMP Mon Aug 12 08:47:01 UTC 2024 aarch64 Linux
docker container run --env FIRSTNAME="John" --env NAME="Doe" \
    alpine:3.17.2 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=c2866205ef3e
FIRSTNAME=John
NAME=Doe
HOME=/root
docker container run \
    alpine:3.17.2 sh -c "echo 'Hi !' > hello.txt ; cat hello.txt"
Hi !

Si la commande est interactive (comme un shell bash par exemple il faut préciser --interactive ou -it) pour utiliser l’entrée et la sortie standard.

Il est aussi possible de donner un nom unique à conteneur qui peut être utilisé à la place de son ID avec l’option --name.

(
cat <<EOF
cd /root
ls -al
EOF
) | docker run --interactive --quiet --name my-bash ubuntu:22.04 bash - 
total 16
drwx------ 2 root root 4096 Sep 11 14:52 .
drwxr-xr-x 1 root root 4096 Oct  2 04:43 ..
-rw-r--r-- 1 root root 3106 Oct 15  2021 .bashrc
-rw-r--r-- 1 root root  161 Jul  9  2019 .profile

L’exécution d’un conteneur peut être faite tâche de fond avec l’option --detach ou -d.

Par exemple, pour lancer redis, une base de donnée NoSQL de type clé/valeur :

docker run --quiet --detach --name my-redis redis:7.0.9
a796bfaf5d1f52d4ee24e0eee23e02eb22a54a35681dec276c4d8cce551b64a9

Les conteneurs produisent généralement le log du service sur la sortie standard. Quand --detach est utilisé la commande logs permet de le consulter (ajouter -f pour un suivi en continu).

docker logs my-redis
1:C 02 Oct 2024 04:43:37.747 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 02 Oct 2024 04:43:37.747 # Redis version=7.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 02 Oct 2024 04:43:37.747 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 02 Oct 2024 04:43:37.748 * monotonic clock: POSIX clock_gettime
1:M 02 Oct 2024 04:43:37.748 * Running mode=standalone, port=6379.
1:M 02 Oct 2024 04:43:37.748 # Server initialized
1:M 02 Oct 2024 04:43:37.749 * Ready to accept connections

La sous-commande ls permet d’obtenir tous les conteneurs en cours d’exécution.

docker container ls
CONTAINER ID   IMAGE         COMMAND                  CREATED                  STATUS                  PORTS      NAMES
a796bfaf5d1f   redis:7.0.9   "docker-entrypoint.s…"   Less than a second ago   Up Less than a second   6379/tcp   my-redis

La commande ls -a affiche aussi ceux qui se sont arrêtés.

docker container ls -a
CONTAINER ID   IMAGE           COMMAND                  CREATED          STATUS                      PORTS      NAMES
a796bfaf5d1f   redis:7.0.9     "docker-entrypoint.s…"   1 second ago     Up Less than a second       6379/tcp   my-redis
69f7049b2982   ubuntu:22.04    "bash -"                 7 seconds ago    Exited (0) 6 seconds ago               my-bash
0e307073c958   alpine:3.17.2   "sh -c 'echo 'Hi !' …"   12 seconds ago   Exited (0) 11 seconds ago              relaxed_ptolemy
c2866205ef3e   alpine:3.17.2   "env"                    12 seconds ago   Exited (0) 11 seconds ago              optimistic_davinci
c247430e3361   alpine:3.17.2   "uname -a"               13 seconds ago   Exited (0) 11 seconds ago              exciting_almeida
4fb713c0f3ab   hello-world     "/hello"                 19 seconds ago   Exited (0) 18 seconds ago              unruffled_leakey

Un conteneur s’arrête quand sa commande termine. Il peut être arrêté manuellement avec la sous-commande stop.

docker container stop my-redis
my-redis

Un conteneur arrêté peut être relancé avec les même paramètres avec la commande start.

docker container start my-redis
my-redis

Un container arrêté peut être détruit avec la commande rm en indiquant son Id ou son nom.

docker rm my-bash
my-bash

La commande exec permet d’exécuter une commande dans un conteneur en fonctionnement. Par exemple, pour utiliser l’interface en ligne de commande de redis depuis le conteneur my-redis (qui exécute déjà le serveur) pour ajouter une valeur de clé “a.b@x.fr”.

docker exec my-redis redis-cli SET a.b@x.fr "Pierre,Durand,12"
OK

L’option --rm de la commande run provoque la suppression du conteneur dès l’arrêt.

On peut alors créer un autre conteneur éphémère pour exécuter la commande de récupération d’une valeur de redis via le réseau (la partie réseau est expliquée plus tard) à partir de l’image du serveur.

docker run --rm --link my-redis redis:7.0.9 redis-cli -h my-redis GET a.b@x.fr
Pierre,Durand,12
docker container ls 
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                  PORTS      NAMES
a796bfaf5d1f   redis:7.0.9   "docker-entrypoint.s…"   2 seconds ago   Up Less than a second   6379/tcp   my-redis
docker container stop my-redis
docker container rm my-redis
my-redis
my-redis

Tous les containeurs arrêtés peuvent être supprimés avec la commande container prune.

docker container prune --force
Deleted Containers:
0e307073c958eca054d0d1772e71f1ec111212a31410d2bcb90c35ed6a28f637
c2866205ef3ec226e5c4906285747e1dc6e346db57c4aecff166ef0779ae8657
c247430e336106e06f14c8b4cf297811766cca775afa6d1779cbb444400cf10d
4fb713c0f3ab46c2769e7c24a2ea68e9d42776e9aaacc50a6288433db4d83a67

Total reclaimed space: 5B
docker container ls -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Gérer les images

Il est possible de lister ou supprimer les images disponibles localement avec la commande image et les sous-commandes ls et rm.

docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
ubuntu        22.04     981912c48e9a   2 weeks ago     69.2MB
alpine        latest    c157a85ed455   3 weeks ago     8.83MB
hello-world   latest    ee301c921b8a   17 months ago   9.14kB
redis         7.0.9     edf4b3932692   19 months ago   111MB
alpine        3.17.2    d74e625d9115   19 months ago   7.46MB
docker image rm hello-world redis:7.0.9 alpine
Untagged: hello-world:latest
Untagged: hello-world@sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
Deleted: sha256:ee301c921b8aadc002973b2e0c3da17d701dcd994b606769a7e6eaa100b81d44
Deleted: sha256:12660636fe55438cc3ae7424da7ac56e845cdb52493ff9cf949c47a7f57f8b43
Untagged: redis:7.0.9
Untagged: redis@sha256:e50c7e23f79ae81351beacb20e004720d4bed657415e68c2b1a2b5557c075ce0
Deleted: sha256:edf4b3932692a4fdbd04e0bdb070e95f9e6ad4b534739a74d9da1b92215314c6
Deleted: sha256:91bff5f3e5746daba37a232fb1eb74836b6475ec31bfcb35ac6e0ea4d0bfa3c4
Deleted: sha256:2666794cc78a523f303e45aad123e15c751adaff07b958e09c5a19d06b88f5f4
Deleted: sha256:1bab36e27e8fec54fe04a4087e4a653b285b5a2ffdf3fc21c51360a9917bd416
Deleted: sha256:fabe0a0282bcf61eea2d81ad3a734d7f27d93c22f9893bd70ef68f0c6d98c98c
Deleted: sha256:098e5ea0c61165e2bd555c1a9ffaccc2c8cc59d032c253d82903508dbf477b3f
Deleted: sha256:ace73ce182d142e2b77b7b617660b45f8a766cc9c5d36174f4a8b686e3af695e
Untagged: alpine:latest
Untagged: alpine@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d
Deleted: sha256:c157a85ed455142fd79bff5dce951fd5f5b0d0c6e45e6f54cfd0c4e2bdec587b
Deleted: sha256:16113d51b7181f20135f51e8ffbaead20a7671cd783017601f198748ce8a8ebf
docker image prune --force
Total reclaimed space: 0B

Volumes

Un conteneur est généralement éphémère et sans état pour pouvoir être arrêté et relancé sans perte de données. Les fichiers créés devant être persistants doivent donc l’être en dehors du conteneur. Pour cela, on utilise les montage de systeme d fichiers (bind mount) ou les volumes.

Un bind volume, c’est-à-dire un montage du système de fichier de l’hôte vers un répertoire du conteneur, est définit avec l’option -v <source>:<destination> de la commande run que l’on peut uiliser plusieurs fois. Le chemin doit être absolu mais peut utiliser la variable d’environement ${PWD} pour simuler un chemin relatif.

Dans l’exemple suivant, un premier conteneur crée le fichier text.txt dans un volume monté de l’hôte (le répertoire /tmp/mydata) dans le conteneur (à l’emplacement /data). Le conteneur est ensuite détruit. Un second conteneur monte le même répertoire de l’hôte (/tmp/mydata dans le nouveau conteneur mais dans /databis) et affiche le contenu.

docker container run --rm \
    -v /tmp/mydata:/data \
    ubuntu:22.04 \
        bash -c "echo 'hello'>/data/test.txt"
docker container run --rm \
    -v /tmp/mydata:/databis \
    ubuntu:22.04 \
        cat /databis/test.txt
hello

un volume peut aussi être un volume nommé géré par le docker daemon de façon transparente pour l’utilisateur. Il n’est alors plus aussi simple de partager des fichiers entre l’hôte et les conteneurs mais il n’y a plus de problème d’UID du propriétaire ni de droits. Il s’agit de la meilleure solution quand le docker daemon est exécuté sur un cluster et non sur une seule machine car alors le système de fichier peut être distribué.

L’exemple suivant exécute une base de données via l’image de PostgreSQL dans un premier conteneur. Le volume nommé postgres-test-data est monté dans le répertoire indiqué dans la documentation de l’image pour contenir la base de données (/var/lib/postgresql/data). Comme celui-ci est initialement vide, l’image est construite pour créer alors automatiquement une base de données à partir des variables d’environnement fournies.

docker run -d --rm --quiet \
  --name postgres-test \
  --env POSTGRES_PASSWORD=mysecretpassword \
  -v postgres-test-data:/var/lib/postgresql/data \
  postgres:15.2
d75fdd3f66239a25cacd6055c82bb05ab164391c98a5a2190e87df045c178628

Il est possible de monter le même volume et un bind volume dans une autre conteneur par exemple pour faire une sauvegarde. En pratique, on préfère pg_dump, mais nous l’illustrons ici avec un simple tar. Dans le cas d’une base de données relationnelles l’arrêt du conteneur (ou un snapshot) est obligatoire pour garder la cohérence.

ontainer_name=postgres-test
[[ $(docker ps --filter "name=^/$container_name$" --format '{{.Names}}') == $name ]] ||
    docker container stop $container_name 
docker run --rm \
    -v postgres-test-data:/var/lib/postgresql/data \
    -v /tmp/backup:/backup \
    ubuntu:22.04 \
        tar zcf /backup/mydb.tar.gz /var/lib/postgresql/data 
tar: Removing leading `/' from member names

Ensuite, un autre conteneur PostgreSQL peut être lancé en utilisant la même base.

docker run -d --rm \
  --name postgres-test \
  --env POSTGRES_PASSWORD=mysecretpassword \
  -v postgres-test-data:/var/lib/postgresql/data \
  postgres:15.2
docker: Error response from daemon: Conflict. The container name "/postgres-test" is already in use by container "d75fdd3f66239a25cacd6055c82bb05ab164391c98a5a2190e87df045c178628". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.
docker logs postgres-test
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok


Success. You can now start the database server using:

    pg_ctl -D /var/lib/postgresql/data -l logfile start

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
waiting for server to start....2024-10-02 04:43:53.171 UTC [49] LOG:  starting PostgreSQL 15.2 (Debian 15.2-1.pgdg110+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
2024-10-02 04:43:53.172 UTC [49] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2024-10-02 04:43:53.173 UTC [52] LOG:  database system was shut down at 2024-10-02 04:43:53 UTC
2024-10-02 04:43:53.175 UTC [49] LOG:  database system is ready to accept connections
 done
server started

/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*

waiting for server to shut down....2024-10-02 04:43:53.288 UTC [49] LOG:  received fast shutdown request
2024-10-02 04:43:53.289 UTC [49] LOG:  aborting any active transactions
2024-10-02 04:43:53.290 UTC [49] LOG:  background worker "logical replication launcher" (PID 55) exited with exit code 1
2024-10-02 04:43:53.290 UTC [50] LOG:  shutting down
2024-10-02 04:43:53.290 UTC [50] LOG:  checkpoint starting: shutdown immediate
2024-10-02 04:43:53.293 UTC [50] LOG:  checkpoint complete: wrote 3 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.001 s, total=0.003 s; sync files=2, longest=0.001 s, average=0.001 s; distance=0 kB, estimate=0 kB
2024-10-02 04:43:53.294 UTC [49] LOG:  database system is shut down
 done
server stopped

PostgreSQL init process complete; ready for start up.

2024-10-02 04:43:53.401 UTC [1] LOG:  starting PostgreSQL 15.2 (Debian 15.2-1.pgdg110+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
2024-10-02 04:43:53.402 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
2024-10-02 04:43:53.402 UTC [1] LOG:  listening on IPv6 address "::", port 5432
2024-10-02 04:43:53.403 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2024-10-02 04:43:53.406 UTC [63] LOG:  database system was shut down at 2024-10-02 04:43:53 UTC
2024-10-02 04:43:53.409 UTC [1] LOG:  database system is ready to accept connections
docker stop postgres-test
postgres-test

Il est possible de lister et de supprimer les volumes avec les commandes ls et rm. Les volumes dont les noms sont des hachés appelés volumes anonymes sont présentés plus tard.

docker volume ls
DRIVER    VOLUME NAME
local     ae5e7e841549fa3fa2216ee68a9c7be5e1ac3607ecfb11e71350cbb8b7812c54
local     postgres-test-data
docker volume rm postgres-test-data
postgres-test-data
docker volume ls
DRIVER    VOLUME NAME
local     ae5e7e841549fa3fa2216ee68a9c7be5e1ac3607ecfb11e71350cbb8b7812c54

Réseau

Chaque conteneur dispose d’une carte réseau virtuelle et d’au moins une adresse IP. Un conteneur peut utiliser le réseau de l’hôte (--host) mais généralement il appartient à un ou plusieurs réseau virtuels. Si aucun n’est précisé expliciement, le conteneur appartient à un réseau par défaut. Comme les conteneurs sont éphémères et que les adresses IP changent, elles ne sont généralement pas utilisées directement.

Les réseaux peuvent être créés, listés et détruits avec les commandes network {create, ls, rm}.

Ainsi deux conteneurs qui exécutent un shell sont sur le réseau par défaut.

docker run --quiet -dit --rm --name alpine1 alpine ash
docker run --quiet -dit --rm --name alpine2 alpine ash
1cc84fea8def39c74f8cf1005d2e6fce197760e84e28660e0f18c30028c0b603
99b38eb770b04e61607a423a510afa6f24825390830b271541135b947688e235
docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
16cb1a2ff3ee   bridge    bridge    local
ae33eca811d2   host      host      local
1b7d57c60e4f   none      null      local

La commande docker inspect {container, volume, network} <name|ID> permet de consulter le détails d’un objet docker. jq est un outils pour traiter des documents json.

docker network inspect bridge|jq
[
  {
    "Name": "bridge",
    "Id": "16cb1a2ff3ee52090d57adbf62558f23bf559805660ce8b400a7864da08e9c04",
    "Created": "2024-10-02T04:17:41.252158792Z",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
      "Driver": "default",
      "Options": null,
      "Config": [
        {
          "Subnet": "172.17.0.0/16",
          "Gateway": "172.17.0.1"
        }
      ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
      "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {
      "1cc84fea8def39c74f8cf1005d2e6fce197760e84e28660e0f18c30028c0b603": {
        "Name": "alpine1",
        "EndpointID": "7195fa6bad9e4bdcb19fb93c74c3f8f9e292503e4161e318533bf83d5a089a30",
        "MacAddress": "02:42:ac:11:00:02",
        "IPv4Address": "172.17.0.2/16",
        "IPv6Address": ""
      },
      "99b38eb770b04e61607a423a510afa6f24825390830b271541135b947688e235": {
        "Name": "alpine2",
        "EndpointID": "c3aa578b8b270384606a8079bb7b21946447cfaa0e9ed79629250456ec72622e",
        "MacAddress": "02:42:ac:11:00:03",
        "IPv4Address": "172.17.0.3/16",
        "IPv6Address": ""
      }
    },
    "Options": {
      "com.docker.network.bridge.default_bridge": "true",
      "com.docker.network.bridge.enable_icc": "true",
      "com.docker.network.bridge.enable_ip_masquerade": "true",
      "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
      "com.docker.network.bridge.name": "docker0",
      "com.docker.network.driver.mtu": "1500"
    },
    "Labels": {}
  }
]

Avec l’option --link lors du runun conteneur peut utiliser le nom d’un autre conteneur comme nom d’hôte. Ici on utilise un image qui contient des utilitaires réseaux.

docker run --rm --quiet \
    --link alpine1 --link alpine2 \
    nicolaka/netshoot:latest \
        ping -c 1 alpine1 
PING alpine1 (172.17.0.2) 56(84) bytes of data.
64 bytes from alpine1 (172.17.0.2): icmp_seq=1 ttl=64 time=0.117 ms

--- alpine1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.117/0.117/0.117/0.000 ms
docker container stop alpine1 alpine2
alpine1
alpine2

entre conteneurs

Pour que deux conteneurs puissent communiquer simplement, le mieux est qu’ils appartiennent à un même réseau et qu’ils soient nommés. Le nom du conteneur peut alors être utilisé comme un nom d’hôte sur le réseau.

si aucun réseau n’est précisé, le conteneur se trouvent dans le réseau par défaut. Il est alors obligatoire d’utiliser l’option --link qui est dépréciée.

docker network create mynet
3c8ffc92fbd840f23169c7278159d507fc476f787c718aa911017a208e0f1524

On peut alors exécuter un conteneur nommé nginx-01 (il fournit un serveur web nginx) sur ce réseau avec l’option --network.

docker run --rm -d --name nginx-01 --quiet \
           --network mynet \
           nginx:1.23
64d5aebdc9aaf539383175d8d21ce76f5babd0baea76f3ccf119f71fe949649d
docker network inspect mynet|jq
[
  {
    "Name": "mynet",
    "Id": "3c8ffc92fbd840f23169c7278159d507fc476f787c718aa911017a208e0f1524",
    "Created": "2024-10-02T04:44:26.046996507Z",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
      "Driver": "default",
      "Options": {},
      "Config": [
        {
          "Subnet": "172.18.0.0/16",
          "Gateway": "172.18.0.1"
        }
      ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
      "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {
      "64d5aebdc9aaf539383175d8d21ce76f5babd0baea76f3ccf119f71fe949649d": {
        "Name": "nginx-01",
        "EndpointID": "7d93f09d68e15744935a1654adc1c82177d5c10dc9a096037be84e466c7eb133",
        "MacAddress": "02:42:ac:12:00:02",
        "IPv4Address": "172.18.0.2/16",
        "IPv6Address": ""
      }
    },
    "Options": {},
    "Labels": {}
  }
]
docker run --quiet --detach --rm --name my-tshark \
           --network container:nginx-01 \
           nicolaka/netshoot:v0.9 \
             tshark -w /tmp/capture-output.pcap
20393db5df43af63a42dfd4282140cbcec8c9d0efd0af612db4820358cadcd77

ce serveur peut être atteint depuis un autre conteneur sur le même réseau en utilisant son nom. Ici on exécute la commande pandoc qui convertit un site web en texte :

docker run --quiet --network mynet --rm pandoc/core:3.1 --to plain http://nginx-01
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Welcome to nginx!

If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.
sleep 1
docker exec -it my-tshark \
  tshark -r /tmp/capture-output.pcap -Y http
docker stop my-tshark
    6 0.004140209   172.18.0.3 → 172.18.0.2   HTTP 123 GET / HTTP/1.1 
   10 0.004568334   172.18.0.2 → 172.18.0.3   HTTP 681 HTTP/1.1 200 OK  (text/html)
my-tshark
docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
16cb1a2ff3ee   bridge    bridge    local
ae33eca811d2   host      host      local
3c8ffc92fbd8   mynet     bridge    local
1b7d57c60e4f   none      null      local
docker stop nginx-01
docker network rm mynet
nginx-01
mynet

entre l’hôte et les conteneurs

Il est possible de mettre en place une redirection de ports entre l’hôte et les conteneurs avec l’option -p <port hote>:<port conteneur> à la création. Cela permet d’accéder aux services docker depuis l’hôte ou le réseau local.

On peut donc lancer deux serveur nginx de deux versions différents qui écoutent chacun sur le port 80 de leur conteneur respectif mais qui sont accessibles depuis les port 8081 et 8082 de l’hôte.

docker run --quiet --rm -d --name nginx-01 -p 8081:80 nginx:1.22
docker run --quiet --rm -d --name nginx-02 -p 8082:80 nginx:1.23
24677ae67743b627bf01cc21838856aee10ee1c24cd6e67701f2d11def1069a4
7ffe1ac933dce204da93f3098cc49f54274cf4f8781da2d0d8d92a556b2925b8

Le nom host.docker.internal permet d’accéder à l’hôte depuis les conteneurs.

docker run --rm --quiet \
    --add-host=host.docker.internal:host-gateway \
    curlimages/curl:7.88.1 --silent --head host.docker.internal:8081|head -n 2
HTTP/1.1 200 OK
Server: nginx/1.22.1
docker run --rm --quiet \
    --add-host=host.docker.internal:host-gateway \
    curlimages/curl:7.88.1 --silent --head host.docker.internal:8082|head -n 2
HTTP/1.1 200 OK
Server: nginx/1.23.4
docker stop nginx-01 nginx-02
nginx-01
nginx-02

Illustration avec une base de données relationnelles

Pour illustrer les concepts précédents, nous allons créer une base de données Postgresql à partir de l’image officielle dans un sous-réseau backnet et dont les données seront persistées dans un volume nommé postgres-01-data.

docker network create backnet
c1fa08a4b694a6e0be2254b2f311a932666f49c59255e8b879673288ba98b580
docker run -d --rm \
  --name postgres-01 \
  --network backnet \
  --env POSTGRES_USER=dba \
  --env POSTGRES_PASSWORD=mysecretpassword \
  --env POSTGRES_DB=mydb \
  -v postgres-01-data:/var/lib/postgresql/data \
  postgres:15.2
644891ffae1222691a24f590e17c53cc4cf4a2e4ec3ee6c84dfcabf9e860de90

Il est ensuite possible d’interroger cette base de données avec la commande psql exécutée dans un autre conteneur ephémère.

sleep 2 

docker run --rm \
    --network backnet \
    --env PGPASSWORD=mysecretpassword \
    postgres:15.2 \
        psql -h postgres-01 -U dba mydb -c \
            "CREATE TABLE IF NOT EXISTS 
                    PERSON(ID serial PRIMARY KEY,NAME VARCHAR NOT NULL);
            INSERT INTO PERSON (NAME) VALUES ('Pierre');
            INSERT INTO PERSON (NAME) VALUES ('Marie');"
CREATE TABLE
INSERT 0 1
INSERT 0 1

Le conteneur de la base de donnée peut être détruit (cf –rm) et un autre créé avec le même volume donc sans perte de données.

docker stop postgres-01

docker run -d --rm \
  --name postgres-01 \
  --network backnet \
  --env POSTGRES_USER=dba \
  --env POSTGRES_PASSWORD=mysecretpassword \
  --env POSTGRES_DB=mydb \
  -v postgres-01-data:/var/lib/postgresql/data \
  postgres:15.2
postgres-01
5658980f9d9996ee49d88d8c4eae7544bf6c03239363e5f8f3e38ae9c1fe9f34
sleep 2 

docker run --rm \
    --network backnet \
    --env PGPASSWORD=mysecretpassword \
    postgres:15.2 \
        psql -h postgres-01 -U dba mydb -c \
            "SELECT * FROM PERSON;"
 id |  name  
----+--------
  1 | Pierre
  2 | Marie
(2 rows)

On peut aussi utiliser un bind volume pour faire une sauvegarde sur l’hôte.

docker run --rm \
    -v /tmp/backup:/backup \
    --network backnet \
    --env PGPASSWORD=mysecretpassword \
    postgres:15.2 \
        sh -c "pg_dumpall -h postgres-01 -U dba|gzip > \
               /backup/pg01-$(date +'%Y%m%d_%H%M%S').backup.gz"

Pour finir, le conteneur postgres-01 est arrêté et donc détruit, ainsi que le réseau.
Dans notre exemple, le volume postgres-01-data est aussi détruit mais attention les données perdues.

docker stop postgres-01
docker network rm backnet
docker volume rm postgres-01-data
postgres-01
backnet
postgres-01-data

Réutilisation