Tutoriel : Conserver des données dans une application conteneur à l’aide de volumes dans VS Code

Dans ce tutoriel, vous allez apprendre à conserver les données dans une application conteneur. Lorsque vous l’exécutez ou la mettez à jour, les données sont toujours disponibles. Il existe deux principaux types de volumes utilisés pour conserver les données. Ce tutoriel se concentre sur les volumes nommés.

Vous découvrirez également les montages de liaison, qui contrôlent le point de montage exact sur l’hôte. Vous pouvez utiliser des montages de liaison pour rendre les données persistantes, mais ils peuvent également ajouter des données dans des conteneurs. Lorsque vous travaillez sur une application, vous pouvez utiliser un montage de liaison pour monter le code source dans le conteneur afin de lui permettre de voir les modifications du code, de répondre et de voir immédiatement les modifications.

Ce tutoriel présente également la superposition d’images, la mise en cache des couches et les builds multi-étapes.

Dans ce tutoriel, vous allez apprendre à :

  • Comprendre les données entre les conteneurs.
  • Conserver les données à l’aide de volumes nommés.
  • Utilisation de montages de liaisons.
  • Afficher les couches d’images.
  • Mettez en cache les dépendances.
  • Comprendre les builds multi-étapes.

Prérequis

Ce didacticiel vient à la suite du didacticiel précédent, Créer et partager une application Docker avec Visual Studio Code. Commencez par celui-ci, qui inclut les prérequis.

Comprendre les données entre les conteneurs

Dans cette section, vous allez démarrer deux conteneurs et créer un fichier dans chacun d’eux. Les fichiers créés dans un conteneur ne sont pas disponibles dans un autre.

  1. Démarrez un conteneur ubuntu à l’aide de cette commande :

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    Cette commande démarre appelle deux commandes à l’aide de &&. La première partie sélectionne un seul nombre aléatoire et l’écrit dans /data.txt. La deuxième commande surveille un fichier pour maintenir le conteneur en cours d’exécution.

  2. Dans VS Code, dans la zone Docker, cliquez avec le bouton droit sur le conteneur Ubuntu et sélectionnez Attacher l’interpréteur de commandes.

    Screenshot shows the Docker extension with a container selected and a context menu with Attach Shell selected.

    Un terminal s’ouvre et exécute un interpréteur de commandes dans le conteneur Ubuntu.

  3. Exécutez la commande suivante pour afficher le contenu du fichier /data.txt.

    cat /data.txt
    

    Le terminal affiche un nombre compris entre 1 et 10 000.

    Pour utiliser la ligne de commande pour afficher ce résultat, obtenez l’ID de conteneur à l’aide de la commande docker ps, puis exécutez la commande suivante.

    docker exec <container-id> cat /data.txt
    
  4. Démarrez un autre conteneur ubuntu.

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. Utilisez cette commande pour examiner le contenu du dossier.

    docker run -it ubuntu ls /
    

    Il ne doit y avoir aucun fichier data.txt, car il a été écrit dans l’espace de travail uniquement pour le premier conteneur.

  6. Sélectionnez ces deux conteneurs Ubuntu. Cliquez avec le bouton droit et sélectionnez Supprimer. À partir de la ligne de commande, vous pouvez les supprimer à l’aide de la commande docker rm -f.

Conserver vos données todo à l’aide de volumes nommés

Par défaut, l’application todo stocke ses données dans une base de données SQLite dans /etc/todos/todo.db. SQLite Database est une base de données relationnelle qui stocke des données dans un fichier unique. Cette approche fonctionne pour les petits projets.

Vous pouvez conserver le fichier unique sur l’hôte. Lorsque vous le rendez disponible pour le conteneur suivant, l’application peut reprendre là où elle s’est arrêtée. En créant un volume et en l’attachant ou en le montant au dossier dans lequel les données sont stockées, vous pouvez conserver les données. Le conteneur écrit dans le fichier todo.db, et ces données sont conservées dans l’hôte dans le volume.

Pour cette section, utilisez un volume nommé. Docker gère l’emplacement physique du volume sur le disque. Reportez-vous au nom du volume, et Docker fournit les données appropriées.

  1. Créez un volume à l’aide de la commande docker volume create.

    docker volume create todo-db
    
  2. Sous CONTENEURS, sélectionnez Bien démarrer et cliquez avec le bouton droit. Sélectionnez Arrêter pour arrêter le conteneur d’application.

    Pour arrêter le conteneur à partir de la ligne de commande, utilisez la commande docker stop.

  3. Démarrez le conteneur getting-started à l’aide de la commande suivante.

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    Le paramètre volume spécifie le volume à monter et l’emplacement, /etc/todos.

  4. Actualisez votre navigateur pour recharger l’application. Si vous avez fermé la fenêtre du navigateur, accédez à http://localhost:3000/. Ajoutez des éléments à votre liste de tâches.

    Screenshot shows the sample app with several items added to the list.

  5. Supprimez le conteneur getting-started de l’application todo. Cliquez avec le bouton droit sur le conteneur dans la zone Docker et sélectionnez Supprimer ou utilisez les commandes docker stop et docker rm.

  6. Démarrez un nouveau conteneur à l’aide de la même commande :

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    Cette commande monte le même lecteur qu’auparavant. Actualisez votre navigateur. Les éléments que vous avez ajoutés figurent toujours dans votre liste.

  7. Supprimez à nouveau le conteneur getting-started.

Les volumes nommés et les montages de liaison, décrits ci-dessous, sont les principaux types de volumes pris en charge par une installation du moteur Docker par défaut.

Propriété Volumes nommés Montages liés
Emplacement de l’hôte Docker choisit Vous contrôlez
Exemple de montage (à l’aide de -v) my-volume:/usr/local/data /path/to/data:/usr/local/data
Remplit le nouveau volume avec le contenu du conteneur Oui Non
Prend en charge les pilotes de volume Oui Non

De nombreux plug-ins de pilotes de volume sont disponibles pour prendre en charge NFS, SFTP, NetApp, etc. Ces plug-ins sont particulièrement importants pour exécuter des conteneurs sur plusieurs hôtes dans un environnement en cluster comme Swarm ou Kubernetes.

Si vous vous demandez où Docker stocke réellement vos données, exécutez la commande suivante.

docker volume inspect todo-db

Examinez la sortie, semblable à ce résultat.

[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint est l’emplacement réel où les données sont stockées. Sur la plupart des ordinateurs, vous avez besoin d’un accès racine pour accéder à ce répertoire à partir de l’hôte.

Utilisation de montages de liaisons

Avec les montages de liaison, vous contrôlez le point de montage exact sur l’hôte. Cette approche conserve les données, mais est souvent utilisée pour fournir davantage de données dans les conteneurs. Vous pouvez utiliser un montage de liaison pour monter le code source dans le conteneur afin de lui permettre de voir les modifications du code, de répondre et de voir immédiatement les modifications.

Pour exécuter votre conteneur afin de prendre en charge un workflow de développement, vous devez effectuer les étapes suivantes :

  1. Supprimez tous les conteneurs getting-started.

  2. Dans le dossier app, exécutez la commande suivante.

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:20-alpine sh -c "yarn install && yarn run dev"
    

    Cette commande contient les paramètres suivants.

    • -dp 3000:3000 Identique à avant. Exécutez en mode détaché et créez un mappage de port.
    • -w /app Répertoire de travail à l’intérieur du conteneur.
    • -v ${PWD}:/app" Liez le répertoire actif à partir de l’hôte dans le conteneur dans le répertoire /app.
    • node:20-alpine Image à utiliser. Cette image est l’image de base de votre application à partir du fichier Dockerfile.
    • sh -c "yarn install && yarn run dev" Une commande. Elle démarre un interpréteur de commandes à l’aide de sh et exécute yarn install pour installer toutes les dépendances. Ensuite, elle exécute yarn run dev. Si vous regardez dans le package.json, le script dev démarre nodemon.
  3. Vous pouvez surveiller les journaux à l’aide de docker logs.

    docker logs -f <container-id>
    
    $ nodemon src/index.js
    [nodemon] 2.0.20
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching path(s): *.*
    [nodemon] watching extensions: js,mjs,json
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    Lorsque vous voyez l’entrée finale dans cette liste, l’application est en cours d’exécution.

    Lorsque vous avez terminé de regarder les journaux, sélectionnez n’importe quelle touche dans la fenêtre de terminal ou appuyez sur Ctrl+C dans une fenêtre externe.

  4. Dans VS Code, ouvrez src/static/js/app.js. Modifiez le texte du bouton Ajouter un élément à la ligne 109.

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    Enregistrez vos modifications.

  5. Actualisez votre navigateur. Vous devriez voir la modification.

    Screenshot shows the sample app with the new text on the button.

Afficher les couches d’une image

Vous pouvez examiner les couches qui composent une image. Exécutez la commande docker image history pour afficher la commande utilisée pour créer chaque couche dans une image.

  1. Utilisez docker image history pour afficher les couches de l’image getting-started que vous avez créée précédemment dans le tutoriel.

    docker image history getting-started
    

    Votre résultat doit ressembler à cette sortie.

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    Chacune des lignes représente une couche dans l’image. La sortie montre la base en bas avec la couche la plus récente en haut. À l’aide de ces informations, vous pouvez voir la taille de chaque couche, ce qui vous aide à diagnostiquer les images volumineuses.

  2. Plusieurs lignes sont tronquées. Si vous ajoutez le paramètre --no-trunc, vous obtiendrez la sortie complète.

    docker image history --no-trunc getting-started
    

Mettre en cache les dépendances

Une fois qu’une couche change, toutes les couches en aval doivent également être recréées. Voici à nouveau le fichier Dockerfile :

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Chaque commande du fichier Dockerfile devient une nouvelle couche dans l’image. Pour réduire le nombre de couches, vous pouvez restructurer votre fichier Dockerfile pour prendre en charge la mise en cache des dépendances. Pour les applications basées sur un nœud, ces dépendances sont définies dans le fichier package.json.

L’approche consiste à copier uniquement ce fichier d’abord, à installer les dépendances, puis à copier tout le reste. Le processus recrée les dépendances yarn uniquement en cas de modification de package.json.

  1. Mettez à jour le fichier Dockerfile pour qu’il copie dans le package.json d’abord, installe les dépendances, puis copie tout le reste. Voici le nouveau fichier :

    FROM node:20-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. Générez une nouvelle image à l’aide de docker build.

    docker build -t getting-started .
    

    Des résultats similaires à ce qui suit doivent s’afficher :

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    Toutes les couches ont été reconstruites. Ce résultat est attendu, car vous avez modifié le fichier Dockerfile.

  3. Apportez une modification à src/static/index.html. Par exemple, modifiez le titre pour qu’il dise « The Awesome Todo App ».

  4. Générez maintenant l’image Docker en utilisant docker build à nouveau. Cette fois, votre sortie devrait être un peu différente.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    Étant donné que vous utilisez le cache de build, elle devrait être beaucoup plus rapide.

Builds multi-étapes

Les builds multi-étapes sont un outil incroyablement puissant qui permet d’utiliser plusieurs phases pour créer une image. Elles présentent plusieurs avantages :

  • Séparer les dépendances au moment de la génération des dépendances d’exécution
  • Réduire la taille globale de l’image en expédiant uniquement ce dont votre application a besoin pour s’exécuter

Cette section fournit de brefs exemples.

Exemple Maven/Tomcat

Lorsque vous générez des applications Java, un JDK est nécessaire pour compiler le code source en bytecode Java. Ce JDK n’est pas nécessaire en production. Vous utilisez peut-être des outils comme Maven ou Gradle pour faciliter la création de l’application. Ces outils ne sont pas non plus nécessaires dans votre image finale.

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

Cet exemple utilise une étape, build, pour effectuer la build Java réelle à l’aide de Maven. La deuxième étape, à partir de « FROM tomcat », copie les fichiers de l’étape build. L’image finale est uniquement la dernière étape créée, qui peut être substituée à l’aide du paramètre --target.

Exemple React

Lorsque vous générez des applications React, vous avez besoin d’un environnement Node pour compiler le code JavaScript, les feuilles de style SASS et autres en HTML statique, JavaScript et CSS. Si vous n’effectuez pas de rendu côté serveur, vous n’avez même pas besoin d’un environnement Node pour la build de production.

FROM node:20-alpine AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

Cet exemple utilise une image node:20 pour effectuer la génération, qui optimise la mise en cache des couches, puis copie la sortie dans un conteneur nginx.

Nettoyer les ressources

Conservez tout ce que vous avez fait jusqu’à présent pour continuer cette série de tutoriels.

Étapes suivantes

Vous avez découvert les options permettant de conserver les données pour les applications conteneur.

Que voulez-vous faire ensuite ?