Docker 101: principes de base et pratique

Si vous êtes fatigué d'entendre vos collègues faire l'éloge de Docker et de ses avantages à chaque occasion, ou si vous êtes fatigué de hocher la tête et de vous éloigner chaque fois que vous vous trouvez dans l'une de ces conversations, vous êtes à droite endroit.

Aussi, si vous cherchez une nouvelle excuse pour vous éloigner sans vous faire virer, continuez à lire et vous me remercierez plus tard.

Docker

Voici la définition de Docker, selon Wikipedia:

Docker est un programme informatique qui effectue la virtualisation au niveau du système d'exploitation.

Assez simple, non? Eh bien, pas exactement. Très bien, voici ma définition de ce qu'est docker:

Docker est une plate-forme pour créer et exécuter des conteneurs à partir d' images .

Toujours perdu? Pas de soucis, c'est parce que vous ne savez probablement pas ce que sont les conteneurs ou les images .

Les images sont des fichiers uniques contenant toutes les dépendances et configurations requises pour exécuter un programme, tandis que les conteneurs sont les instances de ces images. Allons de l'avant et voyons un exemple de cela dans la pratique pour rendre les choses plus claires.

Remarque importante: avant de continuer, assurez-vous d'installer docker en suivant les étapes recommandées pour votre système d'exploitation.

Partie 1. "Hello, World!" à partir d'une image Python

Disons que vous n'avez pas Python installé sur votre machine - ou du moins pas la dernière version - et que vous avez besoin de python pour imprimer "Hello, World!" dans votre terminal. Que faire? Vous utilisez docker!

Allez-y et exécutez la commande suivante:

docker run --rm -it python:3 python

Ne vous inquiétez pas, je vais vous expliquer cette commande dans une seconde, mais pour le moment, vous voyez probablement quelque chose comme ceci:

Cela signifie que nous sommes actuellement dans un conteneur docker créé à partir d'une image docker python 3 , exécutant la pythoncommande. Pour terminer l'exemple, tapez print("Hello, World!")et observez la magie opérer.

D'accord, vous l'avez fait, mais avant de commencer à vous féliciter, prenons du recul et comprenons comment cela a fonctionné.

Le décomposer

Commençons par le début. La docker runcommande est l'outil standard de docker pour vous aider à démarrer et à exécuter vos conteneurs.

L' --rmindicateur est là pour dire au démon Docker de nettoyer le conteneur et de supprimer le système de fichiers après la fermeture du conteneur. Cela vous aide à économiser de l'espace disque après avoir exécuté des conteneurs de courte durée comme celui-ci, que nous avons seulement commencé à imprimer "Hello, World!".

L' -t (or --tty)indicateur indique à Docker d'allouer une session de terminal virtuel dans le conteneur. Ceci est couramment utilisé avec l' -i (or --interactive)option, qui maintient STDIN ouvert même s'il s'exécute en mode détaché (plus d'informations à ce sujet plus tard).

Remarque: ne vous inquiétez pas trop de ces définitions pour le moment. Sachez simplement que vous utiliserez l' -itindicateur à chaque fois que vous voudrez taper des commandes sur votre conteneur.

Enfin, python:3est l'image de base que nous avons utilisée pour ce conteneur. À l'heure actuelle, cette image est livrée avec la version 3.7.3 de python installée, entre autres. Maintenant, vous vous demandez peut-être d'où vient cette image et ce qu'il y a à l'intérieur. Vous pouvez trouver les réponses à ces deux questions ici, ainsi que toutes les autres images python que nous aurions pu utiliser pour cet exemple.

Dernier point mais non le moindre, pythonla commande que nous avons dit à Docker d'exécuter à l'intérieur de notre python:3image, qui a démarré un shell python et a permis à notre print("Hello, World!")appel de fonctionner.

Encore une chose

Pour quitter python et terminer notre conteneur, vous pouvez utiliser CTRL / CMD + D ou exit(). Allez-y et faites-le maintenant. Après cela, essayez à nouveau d'exécuter notre docker runcommande et vous verrez quelque chose d'un peu différent, et beaucoup plus rapide.

C'est parce que nous avons déjà téléchargé l' python:3image, donc notre conteneur démarre beaucoup plus rapidement maintenant.

Partie 2. "Hello World!" Automatisé à partir d'une image Python

Quoi de mieux que d'écrire "Hello, World!" dans votre terminal une fois? Vous l'avez, écrivez-le deux fois!

Puisque nous avons hâte de voir "Hello, World!" imprimé dans notre terminal à nouveau, et nous ne voulons pas passer par la hâte d'ouvrir python et de taper à printnouveau, allons-y et automatisons un peu ce processus. Commencez par créer un hello.pyfichier où vous le souhaitez.

# hello.py
print("Hello, World!")

Ensuite, continuez et exécutez la commande suivante à partir de ce même dossier.

docker run --rm -it -v $(pwd):/src python:3 python /src/hello.py

C'est le résultat que nous recherchons:

Remarque: j'ai utilisé lsavant la commande pour vous montrer que j'étais dans le même dossier dans lequel j'ai créé le hello.pyfichier.

Comme nous l'avons fait plus tôt, prenons du recul et comprenons comment cela a fonctionné.

Le décomposer

Nous exécutons à peu près la même commande que nous avons exécutée dans la dernière section, à l'exception de deux choses.

L' -v $(pwd):/srcoption indique au démon Docker de démarrer un volume dans notre conteneur . Les volumes sont le meilleur moyen de conserver les données dans Docker. Dans cet exemple, nous disons à Docker que nous voulons que le répertoire actuel - récupéré $(pwd)- soit ajouté à notre conteneur dans le dossier /src.

Remarque: vous pouvez utiliser tout autre nom ou dossier de votre choix, pas seulement/src

Si vous souhaitez vérifier qu'il /src/hello.pyexiste réellement dans notre conteneur, vous pouvez changer la fin de notre commande de python hello.pyà bash. Cela ouvrira un shell interactif à l'intérieur de notre conteneur, et vous pourrez l'utiliser comme vous vous en doutez.

Remarque: nous ne pouvons l'utiliser bashqu'ici car il est pré-installé dans l' python:3image. Certaines images sont si simples qu'elles n'en ont même pas bash. Cela ne signifie pas que vous ne pouvez pas l'utiliser, mais vous devrez l'installer vous-même si vous le souhaitez.

Le dernier bit de notre commande est l' python /src/hello.pyinstruction. En l'exécutant, nous demandons à notre conteneur de regarder dans son /srcdossier et d'exécuter le hello.pyfichier en utilisant python.

Peut-être que vous pouvez déjà voir les merveilles que vous pouvez faire avec ce pouvoir, mais je vais quand même le souligner pour vous. En utilisant ce que nous venons d'apprendre, nous pouvons à peu près exécuter n'importe quel code à partir de n'importe quel langage à l' intérieur de n'importe quel ordinateur sans avoir à installer de dépendances sur la machine hôte - à l'exception de Docker, bien sûr.C'est beaucoup de texte en gras pour une phrase, alors assurez-vous de le lire deux fois!

Partie 3. Le plus simple "Hello, World!" possible à partir d'une image Python en utilisant Dockerfile

Êtes-vous fatigué de dire bonjour à notre belle planète? C'est dommage, car on va le refaire!

La dernière commande que nous avons apprise était un peu verbeuse, et je peux déjà me voir me fatiguer de taper tout ce code à chaque fois que je veux dire «Hello, World! Automatisons un peu plus les choses maintenant. Créez un fichier nommé Dockerfileet ajoutez-y le contenu suivant:

# Dockerfile
FROM python:3
WORKDIR /src/app
COPY . .
CMD [ "python", "./hello.py" ]

Maintenant, exécutez cette commande dans le même dossier que vous avez créé le Dockerfile:

docker build -t hello .

Il ne reste plus qu'à devenir fou en utilisant ce code:

docker run hello

Vous savez déjà comment c'est. Prenons un moment pour comprendre comment fonctionne un Dockerfile maintenant.

Le décomposer

A partir de notre Dockerfile, la première ligne FROM python:3est dit Docker pour commencer tout avec l'image de base que nous connaissons déjà, python:3.

La deuxième ligne, WORKDIR /src/appdéfinit le répertoire de travail dans notre conteneur. Ceci est pour certaines instructions que nous exécuterons plus tard, comme CMDou COPY. Vous pouvez voir le reste des instructions prises en charge WORKDIRici.

La troisième ligne COPY . .indique essentiellement à Docker de copier tout ce qui se trouve dans notre dossier actuel (en premier .) et de le coller /src/app(en second .). L'emplacement de collage a été défini avec la WORKDIRcommande juste au-dessus.

Remarque: nous pourrions obtenir les mêmes résultats en supprimant l' WORKDIRinstruction et en remplaçant l' COPY . .instruction par COPY . /src/app. Dans ce cas, nous aurions également besoin de changer la dernière instruction CMD ["python", "./hello.py"]en CMD ["python", "/src/app/hello.py"].

Enfin, la dernière ligne CMD ["python", "./hello.py"]fournit la commande par défaut pour notre conteneur. Cela dit essentiellement que chaque fois que nous runun conteneur de cette configuration, il doit s'exécuter python ./hello.py. Gardez à l'esprit que nous exécutons implicitement au /src/app/hello.pylieu de seulement hello.py, car c'est ce vers quoi nous nous sommes dirigés WORKDIR.

Remarque: la CMDcommande peut être écrasée lors de l'exécution. Par exemple, si vous souhaitez exécuter à la bashplace, vous le feriez docker run hello bashaprès la création du conteneur.

Avec notre Dockerfile terminé, nous allons de l'avant et commençons notre buildprocessus. La docker build -t hello .commande lit toute la configuration que nous avons ajoutée à notre Dockerfile et crée une image docker à partir de celui-ci. C'est vrai, tout comme l' python:3image que nous avons utilisée pour tout cet article. Le .à la fin indique à Docker que nous voulons exécuter un Dockerfile à notre emplacement actuel, et l' -t hellooption donne à cette image le nom hello, afin que nous puissions facilement la référencer au moment de l'exécution.

Après tout cela, il ne nous reste plus qu'à exécuter l' docker runinstruction habituelle , mais cette fois avec le hellonom de l' image à la fin de la ligne. Cela démarrera un conteneur à partir de l'image que nous avons récemment construite et imprimera enfin le bon vieux "Hello, World!" dans notre terminal.

Élargir notre image de base

Que faisons-nous si nous avons besoin d'une dépendance pour exécuter notre code qui n'est pas préinstallé avec notre image de base? Pour résoudre ce problème, docker a les RUNinstructions.

En suivant notre exemple python, si nous avions besoin de la numpybibliothèque pour exécuter notre code, nous pourrions ajouter l' RUNinstruction juste après notre FROMcommande.

# Dockerfile
FROM python:3
# NEW LINERUN pip3 install numpy
WORKDIR /src/app
COPY . .
CMD [ "python", "./hello.py" ]

L' RUNinstruction donne essentiellement une commande à exécuter par le terminal du conteneur. De cette façon, puisque notre image de base est déjà pip3installée, nous pouvons utiliser pip3 install numpy.

Remarque: pour une vraie application python, vous ajouteriez probablement toutes les dépendances dont vous avez besoin à un requirements.txtfichier, le copieriez dans le conteneur, puis mettriez à jour l' RUNinstruction dans RUN pip3 install -r requirements.txt.

Partie 4. "Hello, World!" à partir d'une image Nginx utilisant un conteneur détaché de longue durée

Je sais que vous êtes probablement fatigué de m'entendre le dire, mais j'ai encore un "Bonjour" à dire avant de partir. Allons de l'avant et utilisons notre puissance de docker nouvellement acquise pour créer un simple conteneur de longue durée, au lieu de ces conteneurs de courte durée que nous utilisons jusqu'à présent.

Créez un index.htmlfichier dans un nouveau dossier avec le contenu suivant.

# index.html

Hello, World!

Maintenant, créons un nouveau Dockerfile dans le même dossier.

# Dockerfile
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY . .

Construisez l'image et donnez-lui le nom simple_nginx, comme nous l'avons fait précédemment.

docker build -t simple_nginx .

Enfin, exécutons notre image nouvellement créée avec la commande suivante:

docker run --rm -d -p 8080:80 simple_nginx

Vous pensez peut-être que rien ne s'est passé parce que vous êtes de retour à votre terminal, mais regardons de plus près la docker pscommande.

La docker pscommande affiche tous les conteneurs en cours d'exécution sur votre machine. Comme vous pouvez le voir dans l'image ci-dessus, j'ai un conteneur nommé en simple_nginxcours d'exécution sur ma machine en ce moment. Ouvrons un navigateur Web et voyons s'il nginxfait son travail en accédant à localhost:8080.

Tout semble fonctionner comme prévu, et nous servons une page statique tout au long de l' nginxexécution à l'intérieur de notre conteneur. Prenons un moment pour comprendre comment nous y sommes parvenus.

Le décomposer

Je vais sauter l'explication Dockerfile car nous avons déjà appris ces commandes dans la dernière section. La seule "nouveauté" dans cette configuration est l' nginx:alpineimage, que vous pouvez en savoir plus ici.

En dehors de ce qui est nouveau, cette configuration fonctionne car nginxutilise le usr/share/nginx/htmldossier pour rechercher un index.htmlfichier et commencer à le servir, donc puisque nous avons nommé notre fichier index.htmlet configuré le WORKDIRpour être usr/share/nginx/html, cette configuration fonctionnera dès la sortie de la boîte.

La buildcommande est exactement comme celle que nous avons utilisée dans la dernière section, nous n'utilisons que la configuration Dockerfile pour créer une image avec un certain nom.

Maintenant, pour la partie amusante, l' docker run --rm -d -p 8080:80 simple_nginxinstruction. Ici, nous avons deux nouveaux drapeaux. Le premier est le -ddrapeau détaché ( ), ce qui signifie que nous voulons exécuter ce conteneur en arrière-plan, et c'est pourquoi nous sommes de retour à notre terminal juste après avoir utilisé la docker runcommande, même si notre conteneur est toujours en cours d'exécution.

Le deuxième nouveau drapeau est l' -p 8080:80option. Comme vous l'avez peut-être deviné, c'est le portdrapeau, et il mappe essentiellement le port 8080de notre machine locale au port à l' 80intérieur de notre conteneur. Vous auriez pu utiliser n'importe quel autre port à la place de 8080, mais vous ne pouvez pas changer le port 80sans ajouter un paramètre supplémentaire à l' nginximage, car 80c'est le port standard que l' nginximage expose.

Remarque: Si vous souhaitez arrêter un conteneur détaché comme celui-ci, vous pouvez utiliser la docker pscommande pour obtenir le nom du conteneur (pas l'image), puis utiliser l' docker stopinstruction avec le nom du conteneur souhaité à la fin de la ligne.

Partie 5. La fin

C'est ça! Si vous lisez toujours ceci, vous disposez de toutes les bases pour commencer à utiliser Docker aujourd'hui sur vos projets personnels ou votre travail quotidien.

Faites-moi savoir ce que vous avez pensé de cet article dans les commentaires, et je m'assurerai d'écrire un article de suivi couvrant des sujets plus avancés comme docker-composequelque part dans un proche avenir.

Si vous avez des questions, n'hésitez pas à me le faire savoir.

À votre santé!