Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
technique:tech_team:tuto_docker [2021/10/16 20:03] – ↷ Page déplacée et renommée de technique:docker:general:tuto à technique:tech_team:tuto_docker qduchemitechnique:tech_team:tuto_docker [2021/11/15 23:57] (Version actuelle) qduchemi
Ligne 1: Ligne 1:
-{{indexmenu_n>5}}+{{indexmenu_n>20}}
  
-## Formation pour la team technique : créer son image Docker+## Comment on fabrique une image Docker ? 
 + 
 +On va essayer de fabriquer notre première image Docker. Soyons fous, on va faire une image d'un serveur web assez connu, `nginx`, pour mettre en ligne notre premier site sur les serveurs de Picasoft. ^_^ 
 + 
 +On va supposer pour cette page que tu es [[technique:tech_team:ssh|connecté sur la machine de test]]. 
 + 
 +Tu arrives dans ton dossier personnel, et tu vas pouvoir créer un fichier `Dockerfile`, qui est la recette de cuisine que va suivre Docker pour **construire** l'image. En d'autres termes, Docker va partir d'une image pré-existante, exécuter toutes tes instructions, et enregistrer le tout dans une nouvelle image. 
 + 
 +## Un serveur web vierge 
 + 
 +<bootnote> 
 +Pour créer et éditer un fichier, tu peux utiliser l'éditeur `nano`, dans le terminal. Une fois que tu as fini d'éditer ton fichier, appuie sur `Ctrl+X`, `Y` et `Enter` pour quitter. Tu peux ensuite utiliser la commande `cat` pour lire le contenu du fichier. 
 +{{ :technique:tech_team:nano.gif |}} 
 +</bootnote> 
 + 
 +### Écrire la recette de cuisine 
 + 
 +Un Dockerfile commence toujours par une instruction `FROM`. On indique de quelle image on veut partir. En général, c'est une distribution Linux. On utilise beaucoup Debian à Picasoft, une distribution robuste et stable. La première ligne sera donc : 
 + 
 +``` 
 +FROM debian 
 +``` 
 + 
 +<bootnote>On peut préciser une version de l'image en la suffixant avec `:`. Exemples : `debian:strech`, `debian:buster`... C'est même une Bonne Pratique™, mais on va pas partir dans les détails. :-P</bootnote> 
 + 
 +Ensuite, on va utiliser l'instruction `RUN` pour indiquer les commandes qu'on veut lancer. C'est ici qu'il faut se demander : 
 + 
 +<bootnote question>Quelles commandes j'aurai lancé pour installer `nginx` sur ma machine Linux ?</bootnote> 
 + 
 +On regarde sur la [documentation d'installation](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#installing-prebuilt-debian-packages), et on voit qu'il y a deux commandes à lancer. Alors c'est parti, on rajoute des `RUN` dans le Dockerfile pour indiquer qu'il faut lancer ces commandes : 
 + 
 +``` 
 +RUN apt-get update 
 +RUN apt-get install -y nginx 
 +``` 
 + 
 +Petite subtilité, on a rajouté un `-y` (« yes » pour ne pas demander confirmation), puisque la commande sera exécutée sans interaction par Docker. 
 + 
 +Ensuite, il faut savoir qu'nginx écoute sur le port 80, le port standard pour le web. 
 + 
 +<bootnote question>C'est quoi un port ?</bootnote> 
 + 
 +Il faut imaginer un port comme une **porte de chambre dans un hôtel**. Puisque plusieurs applications peuvent tourner sur un même ordinateur, si on veut les contacter de l'extérieur, il faut bien un moyen pour indiquer que c'est à telle application que je veux parler et pas à une autre. C'est l'idée des ports : chaque application choisit un port sur lequel elle **écoute**, et dès que quelqu'un tape à son port, elle sait que ça lui est destiné. 
 + 
 +{{ :technique:tech_team:port.png |}} 
 + 
 +Conséquence : un port ne peut être écouté que par une seule application, sinon on se retrouve avec le problème de ne pas savoir à qui est destiné le message. 
 + 
 +On va donc indiquer à l'image qu'il faudra ouvrir le port 80, le rendre contactable par l'extérieur ! Pour ce faire on utilise l'instruction : 
 + 
 +``` 
 +EXPOSE 80 
 +``` 
 + 
 +On a presque fini. Il manque une instruction essentielle : celle qui va indiquer à l'image ce qu'il faut faire quand on démarrera un conteneur. 
 + 
 +<bootnote question>Il faut faire quoi ?</bootnote> 
 + 
 +Il faut lancer le processus nginx, pour que le serveur web tourne ! Et en l'occurrence, il faut le lancer en **premier plan**. 
 + 
 +<bootnote>Heu, comment ça en premier plan ?</bootnote> 
 + 
 +Sur Linux et ailleurs, les programmes peuvent se lancer en premier plan ou en arrière plan, dans un terminal. En premier plan, ça veut dire que tant que le logiciel n'a pas fini de s'exécuter, on ne peut plus rien faire dans le terminal. En arrière plan (on appelle ça un **démon**, ou //daemon//), on a la main sur le terminal et on peut continuer à taper des commandes. 
 + 
 +Sauf que si nginx est lancé en arrière plan, ce qui est le défaut, Docker va chercher une autre commande à exécuter, et comme il n'y en a pas, il va juste... s'arrêter. Voilà pourquoi il faut toujours que la commande lancée par le conteneur à son démarrage soit en premier plan. 
 + 
 +Bref, pour faire ça, il faut passer à nginx l'argument `-g 'daemon off;'`. La commande à lancer s'indiquer avec l'instruction `CMD`, suivi d'un tableau `["commande", "argument 1", "argument 2", "..."]`. Ce qui nous donne : 
 + 
 +``` 
 +CMD ["nginx", "-g", "daemon off;"
 +``` 
 + 
 +On arrive donc à notre premier Dockerfile : 
 + 
 +``` 
 +FROM debian 
 +RUN apt-get update 
 +RUN apt-get install -y nginx 
 +EXPOSE 80 
 +CMD ["nginx", "-g", "daemon off;"
 +``` 
 + 
 +### Lancer la cuisson 
 + 
 +<bootnote question>Alors c'est bien beau tout ça, mis comment on construit l'image ?</bootnote> 
 + 
 +``` 
 +docker build -t nginx:quentin . 
 +``` 
 + 
 +- `-t` (pour //tag//) permet de donner un nom (`nginx`) et une étiquette (`quentin`) à l'image que l'on va construire. Si on est plusieurs à construire l'image, chacun prendra une étiquette différente. 
 +- `.` indique le dossier où se trouve le Dockerfile. Dans le monde Linux, `.` indique le dossier courant. 
 + 
 +{{ :technique:tech_team:first_build.gif |}} 
 + 
 +On voit toutes les étapes défiler, la plus longue étant le téléchargement et l'installation de `nginx`.  
 +Docker maintient un **cache** de chacune des étapes de la construction de l'image, c'est-à-dire que si tu relances la construction d'une image, Docker n'aura pas besoin de tout refaire, il a gardé en mémoire les résultats intermédiaires de chaque étape, aussi appelée *layer*. Si tu relances une deuxième fois... 
 + 
 +{{ :technique:tech_team:second_build.gif |}} 
 + 
 +### À table ! 
 + 
 +Maintenant qu'on a notre image qui contient tout ce dont on a besoin, on va l'instancier, c'est-à-dire créer un conteneur à partir de l'image. C'est la commande `docker run` qui fait ça, et je te propose de lui passer quelques arguments : 
 + 
 +``` 
 +docker run -d --name quentin nginx:quentin 
 +``` 
 + 
 +- `-d` (//detach//) pour lancer le conteneur en arrière plan, sinon on reste bloqué tant qu'il n'est pas éteint 
 +- `--name` pour préciser le nom du conteneur, ici `quentin`. Sinon il prend un nom au hasard et c'est pas facile de le manipuler... 
 +- et enfin, l'image à lancer, celle qu'on vient de construire ! 
 + 
 +<bootnote>On peut lancer autant de conteneurs qu'on veut à partir de cette image, du moment qu'ils ont un nom différent.</bootnote> 
 + 
 +La commande `docker ps` permet de voir que le conteneur a bien été lancé.  
 + 
 +<bootnote learn>La commande `curl` permet de consulter une page web depuis un terminal. Il suffit de lui donner une URL, et par défaut, `curl` utilise le port 80.</bootnote> 
 + 
 +On devrait donc pouvoir accéder à la page d'accueil par défaut de `nginx` ! 
 + 
 +{{ :technique:tech_team:curl_failed.gif |}} 
 + 
 +<bootnote question>Comment ça, connexion refusée ? m(</bootnote> 
 + 
 +En fait, on a oublié un détail. C'est que le conteneur a ses propres ports. On a bien indiqué au conteneur d'ouvrir son port 80, mais quand on tape `pica01-test.picasoft.net`, on parle à la machine, pas au conteneur... La situation est la suivante : 
 + 
 +{{ :technique:tech_team:port_unlink.png |}} 
 + 
 +Il faudrait donc indiquer à Docker que quand on toque au port 80 de la machine hôte depuis Internet (ce que fait `curl`), il faut en réalité aller toquer au port 80 du conteneur `quentin` ! 
 + 
 +Pour ce faire, on utilise l'argument `-p <port machine>:<port conteneur>`.  
 + 
 +<bootnote critical> 
 +À chaque fois que tu crées un nouveau conteneur, il faudra supprimer l'ancien avant puisque deux conteneurs ne peuvent pas avoir le même nom. Pour ce faire : 
 + 
 +``` 
 +docker rm -f quentin 
 +``` 
 + 
 +où tu remplaces `quentin` par le nom de ton conteneur. 
 + 
 +Aussi, je n'aurais pas dû utiliser le port 80 de l'hôte car il est souvent utilisé par un autre service, appelé Traefik. Tu peux l'éteindre en faisant : 
 + 
 +``` 
 +docker stop traefik 
 +``` 
 +</bootnote> 
 + 
 +Ça nous donne : 
 + 
 +``` 
 +docker run -d --name quentin -p 80:80 nginx:quentin 
 +``` 
 + 
 +On se retrouve donc dans la situation suivante : 
 + 
 +{{ :technique:tech_team:port_ok.png |}} 
 + 
 +Alors, ça donne quoi ? 
 + 
 +{{ :technique:tech_team:curl_ok.gif |}} 
 + 
 +On voit bien le code de la page d'accueil de `nginx` ! Nickel ! :-D 
 + 
 +<bootnote question>Ouais enfin bon, un site web c'est quand même bien avec un peu de contenu, non ?</bootnote> 
 + 
 +## Pimper le serveur web 
 + 
 +Un serveur web sans contenu, c'est comme une fondue sans fromage : un peu sec. 
 + 
 +Alors, comment on change la page d'accueil de nginx, par exemple ? 
 + 
 +<bootnote>nginx sert les fichiers qui se trouvent dans le dossier `/var/www/html`. Si on ne lui précise rien, il sert le fichier `index.html`. Notre but, c'est donc de remplacer le fichier `/var/www/html/index.html` dans le conteneur nginx ! :-D</bootnote> 
 + 
 +Tu vas créer un dossier `website` dans lequel se trouvera un fichier `index.html`, avec le contenu que tu veux. 
 + 
 +Exemple : 
 + 
 +{{ :technique:tech_team:index_nginx.gif |}} 
 + 
 +### Modifier la recette 
 + 
 +Une des premières idées, c'est de modifier l'image pour qu'elle embarque avec elle le fichier `index.html`. 
 + 
 +Dans un Dockerfile, la syntaxe pour copier un fichier est la suivante : 
 + 
 +``` 
 +COPY <source> <destination> 
 +``` 
 + 
 +Bon, normalement, à ce stade, tu sais faire ! 
 + 
 +1. Supprimer le conteneur qui tourne 
 +2. Modifier le Dockerfile 
 +3. Reconstruire l'image 
 +4. Recréer le conteneur 
 +5. Faire un `curl` et croiser les doigts! 
 + 
 +Je te laisse retrouver les instructions et essayer, et je te montre en dessous ce que ça donne chez moi! 
 + 
 +{{ :technique:tech_team:index_custom_1.gif |}} 
 + 
 +Ça fonctionne bien.  
 + 
 +<bootnote question>Est-ce-que tu vois un problème avec cette façon de faire ?</bootnote> 
 + 
 +<bootnote question>Par exemple, projette toi : tu crées ton blog, tu publies des articles tous les jours. Est-ce-que tu te vois reconstruire l'image tous les jours ? Est-ce-que ça a du sens ?</bootnote> 
 + 
 +En fait, le « sens » de l'image, c'est d'avoir un serveur web qui est prêt à servir tous les fichiers qu'on lui donne. Rajouter un fichier particulier à l'image la rend très spécialisée, personne ne peut la ré-utiliser à part toi... Or, l'idée des images, c'est quand même d'être réutilisables ! 
 + 
 +Donc, ce qu'on voudrait, c'est lancer l'image et lui dire en même temps « hey, tu peux servir ces fichiers là au lieu de ta page d'accueil par défaut pourrie ? » 
 + 
 +### Monter des fichiers dans un conteneur 
 + 
 +<bootnote>On parle de montage quand on insère un clé USB et que ça donne un dossier... là c'est un peu pareil, on veut prendre un dossier de la machine et en faire un dossier du conteneur, sans modifier l'image.</bootnote> 
 + 
 +Il y a une syntaxe pour ça, quand on lance un conteneur : 
 + 
 +``` 
 +-v <chemin machine>:<chemin conteneur> 
 +``` 
 + 
 +- `-v` pour //volumes//. On en parle juste après! ;-) 
 +- Le chemin de la machine doit être **absolu**, pas relatif à là où tu te trouves. Pour savoir le chemin absolu du dossier courant, utilise la commande `pwd`. 
 + 
 +À ce stade, tu devrais aussi pouvoir faire l'opération. Concrètement, l'idée c'est de : 
 + 
 +- Supprimer l'ancien conteneur 
 +- Changer le fichier `index.html` de l'hôte, pour bien voir le changement par rapport au fichier qu'on a copié dans l'image 
 +- Lancer le conteneur en montant le dossier `website` sur `/var/www/html` (le chemin machine sera le résultat de la commande `pwd` + `/website`) 
 +- Lancer un `curl` et observer la magie ! 
 + 
 +Je te montre ce que ça donne chez moi : 
 + 
 +{{ :technique:tech_team:index_custom_2.gif |}} 
 + 
 +<bootnote>Si tu modifies ton fichier sur la machine pendant que le conteneur tourne et que tu refais un `curl`, tu verras que la modification se voit immédiatement, parce que le dossier est monté dans le conteneur! Plus besoin de recréer le conteneur, donc.</bootnote> 
 + 
 +Il y a une dernière méthode, c'est d'utiliser un **volume Docker**. 
 + 
 +### Passer aux choses sérieuses 
 + 
 +<bootnote question>C'est quoi le problème ? C'est pas bien les montages ?</bootnote> 
 + 
 +Si, c'est chouette, mais ça pose quelques « inconvénients ». Je vais pas rentrer dans les détails, mais imagine que quelqu'un supprime ton dossier par erreur, ben c'est un peu dommage... Le chemin du dossier dépend de l'hôte, c'est pas très « portable ». Aussi, quand tu montes un dossier vide sur un dossier non-vide, ça l'écrase, parfois ce n'est pas le comportement attendu... Alors, c'est souvent plus pratique d'utiliser les volumes Docker ! 
 + 
 +<bootnote critical>Un volume Docker n'est pas supprimé quand le conteneur qui le monte est supprimé. Les volumes Docker sont souvent utilisés pour persister des données qui sinon seraient perdues, par exemple les données d'une base de données.</bootnote> 
 + 
 +Un volume Docker, c'est un dossier caché quelque par dans `/var/lib/docker/volumes` sur la machine et qui est géré par Docker. Il a un nom, on peut lister les volumes avec des commandes Docker, et quand on monte un volume vide sur un dossier non vide, le contenu du dossier est **copié** dans le volume. Ça peut être utile, mais passons les détails. 
 + 
 +Pour regarder les volumes déjà créés, tu peux taper : 
 + 
 +``` 
 +docker volume ls 
 +``` 
 + 
 +Pour créer un nouveau volume : 
 + 
 +``` 
 +docker volume create quentin 
 +``` 
 + 
 +avec le nom que tu veux. 
 + 
 +Ensuite, pour monter le volume dans le conteneur, c'est la même syntaxe, sauf qu'au lieu du chemin sur la machine, on utilise le nom du volume !  
 +On pense à supprimer le conteneur, et ça nous donne ceci : 
 + 
 +``` 
 +docker rm -f quentin 
 +docker run -d --name quentin -p 80:80 -v quentin:/var/www/html nginx:quentin 
 +``` 
 + 
 +<bootnote question>À ton avis, que contient le volume Docker après le lancement ?</bootnote> 
 + 
 +Si on revient un peu en arrière, on se rappelle qu'on a copié `index.html` directement dans l'image qu'on utilise. Comme on a monté un volume vide (`quentin`) sur un dossier non-vide, le contenu du dossier a été copié. Le volume `quentin` contient donc l'`index.html` **de l'image**, pour le moment. 
 + 
 +Si tu fais un `curl`, tu devrais donc voir ton premier message apparaître. 
 + 
 +<bootnote question>Comment remplacer le fichier dans le volume, alors ?</bootnote> 
 + 
 +On va tout simplement copier notre fichier dans `/var/www/html`, qui sera directement copié dans le volume! Et comme c'est un volume, il ne sera pas supprimé quand on supprimera le conteneur, ce qui veut dire qu'on pourra le monter une prochaine fois avec le bon fichier dedans. Pour ce faire, une commande : 
 + 
 +``` 
 +docker cp <chemin machine> <nom du conteneur>:<chemin conteneur> 
 +``` 
 + 
 +Je te laisse essayer, et je te montre ce que ça donne chez moi : 
 + 
 +{{ :technique:tech_team:index_custom_3.gif |}} 
 + 
 +## C'est un peu lourd, non ? 
 + 
 +On a vu comment créer une image Docker, comment relier ses ports à ceux de la machine, et comment relier ses dossiers à ceux de la machine. 
 + 
 +Mais oui, c'est un peu lourd... parce que ça implique qu'à chaque fois qu'on recrée un conteneur, par exemple pour faire une mise à jour, il faut écrire une ligne de commande longue comme le bras, ne pas se tromper, bien connaître l'ensemble des ports et des volumes... c'est un coup à devenir fou. :-P 
 + 
 +Heureusement, chez Picasoft (et partout ailleurs, en fait), on fait autrement. Et c'est grâce à [[technique:tech_team:pres_compose|Docker Compose]], qui permet de décrire la configuration des conteneurs une fois pour toutes dans un fichier et de les lancer avec une commande qui tient dans la main!
  • technique/tech_team/tuto_docker.1634407404.txt.gz
  • de qduchemi