Skip to content

Lab 1 : Travailler avec des conteneurs

Dans ce lab1, vous apprendrez à travailler avec des conteneurs. Vous explorerez différentes façons d'exécuter des conteneurs et comment connecter des conteneurs ensemble. Dans ce lab, nous utiliserons Docker comme environnement d'exécution de conteneurs.

Prérequis

Avant de commencer le Lab, vous devez installer docker.

Warning

Il est recommandé d'installer docker en mode rootless

Partie 1 : Exécuter des conteneurs Docker

Il existe différentes façons d'utiliser des conteneurs. Celles-ci incluent :

  1. Pour exécuter une tâche unique : Il peut s'agir d'un script shell ou d'une application personnalisée.
  2. De manière interactive : Cela vous connecte au conteneur de manière similaire à la façon dont vous vous connectez en SSH à un serveur distant.
  3. En arrière-plan : Pour des services de longue durée comme des sites web et des bases de données.

Dans cette section, vous essaierez chacune de ces options et verrez comment Docker gère la charge de travail.

Exécuter une tâche unique dans un conteneur Alpine Linux

Dans cette étape, nous allons démarrer un nouveau conteneur et lui demander d'exécuter la commande hostname. Le conteneur démarrera, exécutera la commande hostname, puis se terminera.

  1. Exécutez la commande suivante dans votre console Linux.

    docker container run alpine hostname
    

    La sortie ci-dessous montre que l'image alpine:latest n'a pas pu être trouvée localement. Lorsque cela se produit, Docker la télécharge automatiquement depuis Docker Hub.

    Une fois l'image téléchargée, le nom d'hôte du conteneur s'affiche (888e89a3b36b dans l'exemple ci-dessous).

    Unable to find image 'alpine:latest' locally
    latest: Pulling from library/alpine
    88286f41530e: Pull complete
    Digest: sha256:f006ecbb824d87947d0b51ab8488634bf69fe4094959d935c0c103f4820a417d
    Status: Downloaded newer image for alpine:latest
    888e89a3b36b
    
  2. Docker maintient un conteneur en cours d'exécution tant que le processus qu'il a démarré à l'intérieur du conteneur est toujours en cours d'exécution. Dans ce cas, le processus hostname se termine dès que la sortie est écrite. Cela signifie que le conteneur s'arrête. Cependant, Docker ne supprime pas les ressources par défaut, donc le conteneur existe toujours dans l'état Exited.

    Listez tous les conteneurs.

    docker container ls --all
    

    Notez que votre conteneur Alpine Linux est dans l'état Exited.

    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS            PORTS               NAMES
    888e89a3b36b        alpine              "hostname"          50 seconds ago      Exited (0) 49 seconds ago                       awesome_elion
    

    Note : L'ID du conteneur est le nom d'hôte que le conteneur a affiché. Dans l'exemple ci-dessus, c'est 888e89a3b36b.

Les conteneurs qui effectuent une tâche puis se terminent peuvent être très utiles. Vous pourriez construire une image Docker qui exécute un script pour configurer quelque chose. N'importe qui peut exécuter cette tâche simplement en exécutant le conteneur - ils n'ont pas besoin des scripts réels ou des informations de configuration.

Exécuter un conteneur Ubuntu interactif

Vous pouvez exécuter un conteneur basé sur une version différente de Linux que celle qui s'exécute sur votre hôte Docker.

Dans l'exemple suivant, nous allons exécuter un conteneur Ubuntu Linux

  1. Exécutez un conteneur Docker et accédez à son shell.

    docker container run --interactive --tty --rm ubuntu bash
    

    Dans cet exemple, nous donnons à Docker trois paramètres :

    • --interactive indique que vous voulez une session interactive.
    • --tty alloue un pseudo-tty.
    • --rm indique à Docker de supprimer le conteneur lorsqu'il a terminé son exécution.

    Les deux premiers paramètres vous permettent d'interagir avec le conteneur Docker.

    Nous disons également au conteneur d'exécuter bash comme processus principal (PID 1).

    Lorsque le conteneur démarre, vous vous retrouverez dans le shell bash avec l'invite par défaut root@<container id>:/#. Docker s'est attaché au shell dans le conteneur, relayant les entrées et sorties entre votre session locale et la session shell dans le conteneur.

  2. Exécutez les commandes suivantes dans le conteneur.

    ls / listera le contenu du répertoire racine dans le conteneur, ps aux montrera les processus en cours d'exécution dans le conteneur, cat /etc/issue montrera quelle distribution Linux le conteneur exécute.

    ls /
    
    ps aux
    
    cat /etc/issue
    
  3. Tapez exit pour quitter la session shell. Cela terminera le processus bash, provoquant la sortie du conteneur.

    exit
    

    Note : Comme nous avons utilisé le flag --rm lors du démarrage du conteneur, Docker a supprimé le conteneur lorsqu'il s'est arrêté. Cela signifie que si vous exécutez un autre docker container ls --all, vous ne verrez pas le conteneur Ubuntu.

  4. Vérifiez la version du système d'exploitation hôte.

    cat /etc/issue
    
    ou
    cat /etc/os-release
    

La distribution de Linux à l'intérieur du conteneur n'a pas besoin de correspondre à la distribution de Linux s'exécutant sur l'hôte Docker.

Cependant, les conteneurs Linux nécessitent que l'hôte Docker exécute un noyau Linux. Par exemple, les conteneurs Linux ne peuvent pas s'exécuter directement sur des hôtes Docker Windows. Il en va de même pour les conteneurs Windows - ils doivent s'exécuter sur un hôte Docker avec un noyau Windows.

Les conteneurs interactifs sont utiles lorsque vous assemblez votre propre image. Vous pouvez exécuter un conteneur et vérifier toutes les étapes nécessaires pour déployer votre application, et les capturer dans un Dockerfile.

Vous pouvez commit un conteneur pour créer une image à partir de celui-ci - mais vous devriez éviter cela autant que possible. Il est bien préférable d'utiliser un Dockerfile répétable pour construire votre image. Vous verrez cela sous peu.

Exécuter un conteneur MySQL en arrière-plan

Les conteneurs en arrière-plan sont la façon dont vous exécuterez la plupart des applications. Voici un exemple simple utilisant MySQL.

  1. Exécutez un nouveau conteneur MySQL avec la commande suivante.

    docker container run \
    --detach \
    --name mydb \
    -e MYSQL_ROOT_PASSWORD=my-secret-pw \
    mysql:latest
    
    • --detach exécutera le conteneur en arrière-plan.
    • --name le nommera mydb.
    • -e utilisera une variable d'environnement pour spécifier le mot de passe root (NOTE : Cela ne devrait jamais être fait en production).

    Comme l'image MySQL n'était pas disponible localement, Docker l'a automatiquement téléchargée depuis Docker Hub.

    Unable to find image 'mysql:latest' locallylatest: Pulling from library/mysql
    aa18ad1a0d33: Pull complete
    fdb8d83dece3: Pull complete
    75b6ce7b50d3: Pull complete
    ed1d0a3a64e4: Pull complete
    8eb36a82c85b: Pull complete
    41be6f1a1c40: Pull complete
    0e1b414eac71: Pull complete
    914c28654a91: Pull complete
    587693eb988c: Pull complete
    b183c3585729: Pull complete
    315e21657aa4: Pull complete
    Digest: sha256:0dc3dacb751ef46a6647234abdec2d47400f0dfbe77ab490b02bffdae57846ed
    Status: Downloaded newer image for mysql:latest
    41d6157c9f7d1529a6c922acb8167ca66f167119df0fe3d86964db6c0d7ba4e0
    

    Tant que le processus MySQL est en cours d'exécution, Docker maintiendra le conteneur en cours d'exécution en arrière-plan.

  2. Listez les conteneurs en cours d'exécution.

    docker container ls
    

    Notez que votre conteneur est en cours d'exécution.

    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS            NAMES
    3f4e8da0caf7        mysql:latest        "docker-entrypoint..."   52 seconds ago      Up 51 seconds       3306/tcp            mydb
    
  3. Vous pouvez vérifier ce qui se passe dans vos conteneurs en utilisant quelques commandes Docker intégrées : docker container logs et docker container top.

    docker container logs mydb
    

    Cela montre les logs du conteneur Docker MySQL.

      <output truncated>
      2017-09-29T16:02:58.605004Z 0 [Note] Executing 'SELECT * FROM INFORMATION_SCHEMA.TABLES;' to get a list of tables using the deprecated partition engine. You may use the startup option '--disable-partition-engine-check' to skip this check.
      2017-09-29T16:02:58.605026Z 0 [Note] Beginning of list of non-natively partitioned tables
      2017-09-29T16:02:58.616575Z 0 [Note] End of list of non-natively partitioned tables
    

    Regardons les processus s'exécutant à l'intérieur du conteneur.

      docker container top mydb
    

    Vous devriez voir que le démon MySQL (mysqld) s'exécute dans le conteneur.

    PID                 USER                TIME                COMMAND
    2876                999                 0:00                mysqld
    

    Bien que MySQL soit en cours d'exécution, il est isolé dans le conteneur car aucun port réseau n'a été publié vers l'hôte. Le trafic réseau ne peut pas atteindre les conteneurs depuis l'hôte à moins que les ports ne soient explicitement publiés.

  4. Listez la version de MySQL en utilisant docker container exec.

    docker container exec vous permet d'exécuter une commande à l'intérieur d'un conteneur. Dans cet exemple, nous utiliserons docker container exec pour exécuter l'équivalent en ligne de commande de mysql --user=root --password=$MYSQL_ROOT_PASSWORD --version à l'intérieur de notre conteneur MySQL.

    docker exec -it mydb \
    mysql --user=root --password=$MYSQL_ROOT_PASSWORD --version
    

    Vous verrez le numéro de version de MySQL, ainsi qu'un avertissement utile.

    mysql: [Warning] Using a password on the command line interface can be insecure.
    mysql  Ver 14.14 Distrib 5.7.19, for Linux (x86_64) using  EditLine wrapper
    
  5. Vous pouvez également utiliser docker container exec pour vous connecter à un nouveau processus shell à l'intérieur d'un conteneur déjà en cours d'exécution. L'exécution de la commande ci-dessous vous donnera un shell interactif (sh) à l'intérieur de votre conteneur MySQL.

    docker exec -it mydb sh
    

    Notez que votre invite de shell a changé. C'est parce que votre shell est maintenant connecté au processus sh s'exécutant à l'intérieur de votre conteneur.

  6. Vérifions le numéro de version en exécutant à nouveau la même commande, mais cette fois depuis la nouvelle session shell dans le conteneur.

    mysql --user=root --password=$MYSQL_ROOT_PASSWORD --version
    

    Notez que la sortie est la même qu'avant.

  7. Tapez exit pour quitter la session shell interactive.

    exit
    

Partie 2 : Créer des conteneurs

Créer des images de conteneurs

Dans cette section, vous apprendrez comment créer vos propres images de conteneurs. Une image Docker est un package logiciel léger, autonome et exécutable qui inclut tout ce qui est nécessaire pour exécuter un logiciel, y compris le code, l'environnement d'exécution, les bibliothèques, les variables d'environnement et les fichiers de configuration. Les images sont construites à l'aide de fichiers déclaratifs spéciaux, les Dockerfiles.

Étape 1 : Créer un Dockerfile

Un Dockerfile est un document texte qui contient toutes les commandes pour assembler une image. Nous allons maintenant voir comment créer un serveur web python.

Pour commencer, créons un serveur hello world (enregistrez-le sous le nom app.py) :

# app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

et écrivez un requirements.txt pour installer les dépendances requises :

# requirements.txt
flask
Enfin, vous pourrez créer un Dockerfile comme tel (créez un fichier appelé Dockerfile avec le contenu suivant) :

# Use an official Python image
FROM python:3.12

# Set the working directory in the container
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Run app.py when the container launches
CMD ["python", "app.py"]

Info

Il existe des bonnes pratiques établies par la communauté sur la façon de créer des Dockerfiles. Parmi elles, les builds multi-étapes réduisent la taille d'un conteneur.

Étape 2 : Construire l'image Docker

Pour construire l'image Docker à partir du Dockerfile, utilisez la commande docker build. Exécutez la commande suivante dans le répertoire contenant votre Dockerfile :

docker build -t python-app .

Cette commande construit l'image et la tague comme python-app.

Note

Vous pouvez utiliser des tags pour publier différentes versions du même conteneur (par exemple, une pour x86 et une pour arm)

Étape 3 : Exécuter le conteneur Docker

Une fois l'image construite, vous pouvez exécuter un conteneur en utilisant la commande docker run :

docker run -p 9000:5000 python-app
L'application sera isolée par défaut de l'hôte. Nous devons donc informer docker de mapper le port 5000 dans le conteneur vers le port 9000 sur l'hôte.

Maintenant vous pouvez accéder à l'application à :

http://0.0.0.0:9000/
et vous verrez le serveur web répondre avec Hello, World!

Note

Le mécanisme de mappage de port est différent du mécanisme d'espace de noms réseau. Dans ce cas, un port sur votre hôte est mappé au port du conteneur via des règles iptables.

Vous pouvez exporter le contenu d'un conteneur sous forme de fichier archive en utilisant la commande export.

Question 1

En utilisant la commande docker export, enregistrez le conteneur python-app en tant qu'archive tar. Ensuite, extrayez-la et analysez-la. Quelle est la structure de l'archive ? Quelles informations sur le conteneur pouvez-vous trouver ?

Warning

Le conteneur doit être en cours d'exécution pour pouvoir être exporté.

Nettoyage

docker rm python-app --force

Partie 3 : Réseaux de conteneurs

Les conteneurs ont besoin d'un espace de noms réseau pour communiquer avec d'autres conteneurs et avec Internet. Dans cette partie du Lab, nous explorerons comment les conteneurs sont liés ensemble ou isolés.

Réseaux Docker par défaut et leurs différences

Par défaut, Docker est équipé de trois réseaux différents. Regardons-les :

docker network ls 
La sortie devrait être similaire à :

NETWORK ID     NAME                               DRIVER    SCOPE
a19bac0e4c32   bridge                             bridge    local
9e1ac4485c0a   host                               host      local
bcd75e804e3c   none                               null      local
  • Réseau Bridge : Le réseau bridge est le réseau par défaut pour les conteneurs. Il permet aux conteneurs qui y sont connectés de communiquer entre eux, tout en fournissant une isolation des conteneurs non connectés au bridge. Les conteneurs sur le réseau bridge peuvent accéder aux réseaux externes, y compris Internet, via l'interface réseau de l'hôte. Bridge est également le nom du pilote utilisé pour ce type de réseau.

  • Réseau Host : Comme son nom l'indique, le réseau host est le même réseau que le système d'exploitation hôte. Les conteneurs utilisant le réseau host partagent la pile réseau de l'hôte et peuvent utiliser directement l'adresse IP de l'hôte. Cela signifie que les conteneurs peuvent accéder à tous les ports ouverts sur l'hôte.

  • Réseau None : Le réseau none désactive le réseau pour le conteneur.

Info

Lectures complémentaires sur le réseau Docker : IPvlan, Macvlan, Overlay.

Question Bonus

Quelle est l'adresse IP d'un conteneur attaché à un réseau Macvlan ? Et qui l'attribue ?

Déployer des conteneurs dans différents réseaux

Dans cette partie, vous déploierez deux conteneurs utilisant différents réseaux Docker. Ce tutoriel vous donnera une compréhension pratique du fonctionnement de ces réseaux et de la façon dont les conteneurs communiquent dans Docker.

Note

Chaque fois qu'un nouveau conteneur est créé, docker créera un nouveau namespace (réseau) pour le conteneur.

Réseau Bridge

  1. Créez un nouveau conteneur connecté au réseau bridge.

    docker container run -d --name app1 --network bridge nginx
    

    Cette commande exécute un conteneur Nginx en arrière-plan en utilisant le réseau bridge par défaut.

  2. Créez un autre conteneur connecté au réseau bridge.

    docker container run -d --name app2 --network bridge nginx
    

    Les deux conteneurs sont maintenant connectés au réseau bridge et devraient pouvoir communiquer entre eux.

  3. Vérifiez que les conteneurs sont en cours d'exécution et connectés au réseau bridge.

    docker container ls # Affiche les conteneurs en cours d'exécution
    docker network inspect bridge # Affiche le contenu du réseau
    

    Le résultat de ces commandes devrait ressembler à :

    CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
    0b5a9d812179   nginx     "/docker-entrypoint.…"   16 seconds ago   Up 15 seconds   80/tcp    app2
    28755a17cd52   nginx     "/docker-entrypoint.…"   25 seconds ago   Up 20 seconds   80/tcp    app1
    
    [
        {
            "Name": "bridge",
            "Id": "ba3606add1244c0fd7ba5189f6fceb7b7c52acee5a8e9e177537cc417b11e545",
            "Created": "2025-02-13T11:47:39.796570164Z",
            "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": {
                "0b5a9d812179115094030c3bc6307c942f92de483bf345fe3f9ff035ee0e9127": {
                    "Name": "app2",
                    "EndpointID": "1e2211dcc219da9cf7f68514d578fbd30acc35f438b71a7d83c2b250e7d0045f",
                    "MacAddress": "02:42:ac:11:00:03",
                    "IPv4Address": "172.17.0.3/16",
                    "IPv6Address": ""
                },
                "28755a17cd52af14d84035c24fb238ee57334c7f801da0d972f1402fb95fbe98": {
                    "Name": "app1",
                    "EndpointID": "1a598cb3746789fe8717845e57d6cf774c33508b4fbc284c10fef43a9857598f",
                    "MacAddress": "02:42:ac:11:00:02",
                    "IPv4Address": "172.17.0.2/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": "65535"
            },
            "Labels": {}
        }
    ]
    

Ces commandes montrent que les deux conteneurs sont dans le même réseau et qu'ils ont les IP : 172.17.0.2/16 et 172.17.0.3/16.

Question 2

Quelle est l'adresse de la passerelle de ce réseau bridge ?

Pouvez-vous voir le réseau avec la commande ifconfig ou ip a ?

Warning

Si vous utilisez MacOS ou Windows, vous devez d'abord vous connecter en SSH à la VM Docker.

Réseau Host

  1. Créez un nouveau conteneur connecté au réseau host.

    docker container run -d --name host-container1 --network host nginx
    

    Cette commande exécute un conteneur Nginx en arrière-plan en utilisant le réseau host.

  2. Créez un autre conteneur connecté au réseau host.

    docker container run -d --name host-container2 --network host alpine sleep infinite
    

    Les deux conteneurs sont maintenant connectés au réseau host

    Question 3

    Quelle est l'adresse IP des deux conteneurs ? Pouvez-vous voir quels ports sont ouverts sur le conteneur ? Que se passe-t-il si vous exécutez la commande : docker container run -d --name host-container3 --network host nginx ?

Réseau None

  1. Créez un nouveau conteneur sans réseau.

    docker container run -d --name none-container1 --network none nginx
    

    Cette commande exécute un conteneur Nginx en arrière-plan sans réseau.

    Question 4

    Pouvez-vous vous connecter à Internet en utilisant ce réseau ?

  2. Créez un autre conteneur sans réseau.

    docker container run -d --name none-container2 --network none nginx
    

    Les deux conteneurs sont maintenant isolés sans connectivité réseau.

  3. Vérifiez que les conteneurs sont en cours d'exécution et n'ont pas de réseau.

    docker container ls
    docker network inspect none
    

Déployer des conteneurs dans le même namespace

Il y a des cas où vous souhaitez déployer des conteneurs dans les mêmes namespaces et laisser les conteneurs communiquer entre eux en utilisant uniquement localhost. Ce cas est très courant, par exemple dans Kubernetes. Cette section du lab vous apprendra comment y parvenir dans docker.

Étape 1 : Créer le premier conteneur

Démarrez un conteneur normalement. Par exemple :

docker run -d --name first alpine sleep 300000

Étape 2 : Exécuter le deuxième conteneur

Utilisez le flag --network container:<container_name pour attacher le deuxième conteneur au namespace réseau du conteneur principal :

docker run -d --name second --network container:first alpine sleep 300000

Étape 3 : Vérifier la connectivité

D'abord, nous vérifions que les conteneurs sont en cours d'exécution :

docker ps 
CONTAINER ID   IMAGE     COMMAND          CREATED          STATUS          PORTS     NAMES
88fef5507abf   alpine    "sleep 300000"   6 seconds ago    Up 2 seconds              second
20d60f685794   alpine    "sleep 300000"   48 seconds ago   Up 41 seconds             first

Dans cette configuration, le deuxième conteneur partage la pile réseau du premier conteneur. Les deux conteneurs partagent maintenant la même adresse IP et peuvent communiquer entre eux via localhost.

Vérifiez l'adresse IP des conteneurs en exécutant :

docker exec -it first ifconfig
docker exec -it second ifconfig

Il est maintenant temps de vérifier la connectivité entre les conteneurs. Sur deux terminaux séparés, exécutez :

docker exec -it first nc -nlp 1234
et
docker exec -it second nc localhost 1234

Chaque caractère écrit dans le deuxième conteneur devrait maintenant apparaître dans le premier et ils devraient pouvoir communiquer en utilisant localhost.

Questions 5-8

  • Quel est le nom d'hôte des conteneurs first et second ?
  • Que se passe-t-il pour le conteneur second si vous supprimez le conteneur first ?
  • Dans quel cas déploieriez-vous deux conteneurs dans le même namespace réseau ?
  • Que se passe-t-il si first et second écoutent sur le même port ?

Étape 4 : Nettoyage

docker stop first
docker stop second
docker rm first
docker rm second