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:docker:general:tips [2020/10/12 12:10] qduchemitechnique:docker:general:tips [2020/10/13 18:53] (Version actuelle) – [Spécifier un utilisateur lors de l'exécution de commandes] qduchemi
Ligne 4: Ligne 4:
 On pourra se référer à la [documentation officielle sur les bonnes pratiques](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/). On pourra se référer à la [documentation officielle sur les bonnes pratiques](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/).
  
-## Générales+## Import d'images stables
  
-### Import d'images stables 
 Lors de l'import d'une image dans un `Dockerfile` à l'aide de la directive ''FROM'', il est important de spécifier la version de l'image importée ; sinon la dernière version (//latest//) est importée, ce qui peut poser des problèmes de stabilité. Lors de l'import d'une image dans un `Dockerfile` à l'aide de la directive ''FROM'', il est important de spécifier la version de l'image importée ; sinon la dernière version (//latest//) est importée, ce qui peut poser des problèmes de stabilité.
  
-### Exécution de commandes au démarrage+## Réaliser des images minimales
  
-Pour lancer directement le binaire d'un service, on utilisera la directive `CMDavec la forme tableau (`[ "commande", "arg1", "arg2" ]`). +Chaque élément d'une image peut être un facteur de vulnérabilité ; même un paquet en apparence inoffensif peut dépendre d'une librairie contenant une vulnérabilité. Pour cette raison, il convient de réaliser des images aussi légères que possibles en évitant d'installer des paquets superflus ou en supprimant les paquets installés pour un besoin temporaire (par exemple pour compiler un programme) une fois qu'ils sont obsolètes. 
-Lorsque l'on a besoin d'exécuter des instructions préalables (injection de secrets, test du contact avec la base de données, etc...), on utilisera la directive `ENTRYPOINTen conjonction avec `CMD`, toujours sous la forme tableau.+ 
 +<bootnote> 
 +Préférez vous baser sur une image Alpine (peu de paquets, donc peu de vulnérabilités) ou Debian. 
 +Pour Debian, préférez la version stable la plus récente possible (Buster plutôt que Stretch) pour mitiger d'emblée un grand nombre de vulnérabilités. 
 +</bootnote> 
 + 
 +Préférez également les versions `slim`, qui contiennent moins de paquets et suffisent souvent. 
 + 
 +Pour les services basés sur un langage interprété comme Python, utilisez l'image officielle en appliquant les principes précédents. 
 + 
 +<bootnote> 
 +Il peut être intéressant d'avoir recours à des [[https://docs.docker.com/develop/develop-images/multistage-build/|multi-stage builds]]. L'idée est par exemple d'utiliser une image pour la compilation, et une image qui recevra uniquement l'exécutable et les dépendances nécessaires à l'exécution. Cette dernière sera utilisée en production. 
 +</bootnote> 
 + 
 +## Rendre les build reproductibles 
 + 
 +L'idée derrière un build "reproductible", c'est que si je lance le build d'un même `Dockerfile` à deux moments différents, l'image finale doit être la même quel que soit le temps écoulé entre les deux.  
 + 
 +<bootnote warning>Ce ne sont pas de **vrais** builds reproductibles, car la version des paquets installés peut avoir changé, par exemple. Mais l'idée est d'essayer de s'en rapprocher.</bootnote> 
 + 
 +<bootnote question>Pourquoi des builds reproductibles ?  
 + 
 +Pour pouvoir relancer le build d'une ancienne version et la remettre en production en cas de problème. Si on a l'ancien `Dockerfile`, alors on devrait pouvoir avoir quasiment la même ancienne image. 
 +</bootnote> 
 + 
 +Souvent, un `Dockerfile` va récupérer du code sur un dépôt Git, ou encore un binaire sur un site de téléchargement de releases. 
 +Il est fortement déconseillé de faire un `git clone` ou un `wget` de la dernière version (`latest`, `master`...), ce qui rend le build non reproductible et dépendant de cette dernière version. 
 + 
 +Il est donc important de gérer la version du service en question, par exemple avec une variable `ARG SERVICE_VERSION=1.0.1` qui sera ré-utilisée dans l'URL de téléchargement. 
 + 
 +En outre il existe deux solutions pour récupérer du code existant, versionné sur un dépôt Git distant : 
 + 
 +* Utiliser un `wget` sur une release particulière (Gitlab, Github), permettant de récupérer une archive contenant le code source d'une version spécifique. 
 +* Installer Git dans le `Dockerfile`, utiliser un `git clone` puis un `git checkout <tag>` sur la version souhaitée et copier le code dans l'image. 
 + 
 +<bootnote>En général, on préférera télécharger une release du code que d'utiliser Git.</bootnote> 
 + 
 +## Exécution de commandes au démarrage 
 + 
 +Pour lancer directement le binaire d'un service au démarrage, on utilisera la directive [CMD](https://docs.docker.com/engine/reference/builder/#cmd) avec la forme tableau (`[ "commande", "arg1", "arg2" ]`). En effet, cette forme permet de démarrer le processus directement, tandis que la forme sans tableau est exécutée par `/bin/sh`, ce qui pose des problèmes ([explications ici](https://engineeringblog.yelp.com/2016/01/dumb-init-an-init-for-docker.html)). 
 + 
 +Lorsque l'on a besoin d'exécuter des instructions préalables (injection de secrets, test du contact avec la base de données, etc...), on utilisera la directive [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint) en conjonction avec `CMD`, toujours sous la forme tableau.
  
-Dans ce cas, l'`entrypoint` recevra en argument le tableau passé à `CMD`.+En général, l'`entrypoint` sera un script shell, qui recevra en argument le tableau passé à `CMD`.
  
-### Installation de paquets par ordre alphabétique+## Installation de paquets par ordre alphabétique
  
 Pour éviter d'installer deux fois un paquet, lors de l'installation de plusieurs paquets en une instruction, il convient de les installer dans l'ordre alphabétique. Pour éviter d'installer deux fois un paquet, lors de l'installation de plusieurs paquets en une instruction, il convient de les installer dans l'ordre alphabétique.
  
-### Réduction du nombre de couches+## Réduction du nombre de couches 
 Un `Dockerfile` est composé de différentes couches (*layers*). Une couche correspond à une instruction exécutée par le `Dockerfile`.  Un `Dockerfile` est composé de différentes couches (*layers*). Une couche correspond à une instruction exécutée par le `Dockerfile`. 
  
Ligne 37: Ligne 78:
 <bootnote>Dans cet exemple, il n'y a qu'une seule instruction `RUN`, donc une seule couche est créée. Par contre, la moindre modification de cette instruction entraînera son ré-exécution complète lors de la reconstruction de l'image.</bootnote> <bootnote>Dans cet exemple, il n'y a qu'une seule instruction `RUN`, donc une seule couche est créée. Par contre, la moindre modification de cette instruction entraînera son ré-exécution complète lors de la reconstruction de l'image.</bootnote>
  
-=== Passage de paramètres à un conteneur === +## Contrôle de la santé d'un conteneur
-Les images Docker de l'association comportent une instruction ''ENTRYPOINT'' qui permet de lancer un script à l'instanciation de l'image ; le script ''entrypoint.sh'' associé à chaque conteneur initialise dans son conteneur les variables d'environnement qui sont nécessaires.+
  
-=== Contrôle de la santé d'un conteneur === +Les `HEALTHCHECK`, au sens Docker, permettent de vérifier qu'un conteneur est en "bonne santé"par exemple en lançant une commande de type `curl`
-Afin de faciliter la supervision de l'état d'un conteneur, il convient d'inclure dans les images Docker une instruction ''HEALTHCHECK'' contenant une commande permettant de vérifier la santé du conteneur.+
  
-==== Sécurité ==== +<bootnote warning>Souvent, les commandes comme `curl` et `wget` ne sont pas installées, il faut y penser.</bootnote>
-=== Options lors de l'instanciation d'images === +
-Certaines options permettent de désactiver des fonctionnalités importantes d'isolation fournies par Docker au lancement d'un conteneur. Ces options ne doivent pas être utiliséesElles comportent :  +
-  * ''--privileged'' : donne toutes les //capabilities// et lui permet de dépasser les limites qui lui seraient normalement imposées par son //cgroup// ; cela expose l'hôte à des attaques de type déni de service par l'absence de limitation sur la consommation de ressources et cela donne accès au conteneur à des capabilities dont il n'a pas nécessairement besoin et qui sont vecteurs de risques +
-  * ''--security-opt'' : désactive l'utilisation de seccomp. seccomp est une fonctionnalité du noyau Linux permettant de limiter le nombre d'appels systèmes disponibles à un conteneur ; la désactivation de ces limiations met donc l'hôte à risque+
  
-=== Montage de volumes === +<bootnote web>Référence pour les `HEALTHCHECK` : https://docs.docker.com/engine/reference/builder/#healthcheck</bootnote>
-Lors de la rédaction d'images, on peut être tenté d'exposer des volumes sur des images "temporaires". Par exemple, un conteneur nginx auquel on va exposer le volume ''/var/www/html''Le problème de cette pratique est que si l'on veut créer une image dokuwiki basée sur cette image nginx, il ne sera pas possible d'écrire par dessus le volume ''/var/www/html''. Même si au sein du Dockerfile on spécifie des commandes pour remplacer le contenu de l'image, au final, le contenu du dossier restera celui de l'image mère +
-Il convient également de ne pas monter de volume sensible (par exemple ''/etc'') dans un conteneur, parce que les conteneurs peuvent par défaut modifier les volumes montés avec des privilèges ''root''. Ainsi, il ne faut surtout pas monter la socket Docker (''/var/run/docker/sock'') dans un conteneur.+
  
-=== Présence d'informations confidentielles dans un Dockerfile ou un docker-compose.yml === +## Ne pas utiliser les directives VOLUME
-Il convient de ne pas saisir d'informations confidentielles, telles que des mots de passe ou des clés SSH, dans des fichiers tels que des Dockerfiles ou ''docker-compose.yml'' ; ces fichiers sont souvent versionnés et parfois moins protégés. Pour ces raisons, il convient pour passer des paramètres tels que des mots de passe à un conteneur d'utiliser des variables d'environnement comme mentionné plus haut.+
  
-=== Spécifier un utilisateur lors de l'exécution de commandes === +Lors de la rédaction d'imageson peut être tenté d'exposer des volumes sur des images "temporaires"Par exemple, une image `nginx` dans laquelle on va indiquer que `/var/www/html` doit être persistent grâce à la directive `VOLUME`.
-Dans un Dockerfileune commande exécutée par une directive ''RUN'' est exécutée par le compte root du conteneur par défautIl convient d'exécuter aussi peu de commandes que possible avec le compte root ; pour se faire, il est possible d'utiliser la directive ''USER''.+
  
-=== Réaliser des images minimales === +Le problème de cette pratique est que si l'on veut créer une image Dokuwiki basée sur cette image nginx, il ne sera pas possible d'écrire par dessus le volume ''/var/www/html''Même si au sein du `Dockerfile` on spécifie des commandes pour remplacer le contenu de l'image, au final, le contenu du dossier restera celui de l'image mère 
-Chaque élément d'une image peut être un facteur de vulnérabilité ; même un paquet en apparence inoffensif peut dépendre d'une librairie contenant une vulnérabilité. Pour cette raison, il convient de réaliser des images aussi légères que possibles en évitant d'installer des paquets superflus ou en supprimant les paquets installés pour un besoin temporaire (par exemple pour compiler un programme) une fois qu'ils sont obsolètes. + 
-Pour ce dernier besoin, il peut être intéressant d'avoir recours à des [[https://docs.docker.com/develop/develop-images/multistage-build/|multi-stage builds]].+On évitera donc les directives `VOLUME` au sein des `Dockerfile`, et on spécifiera les volumes au lancement du conteneur.
  
 +## Spécifier un utilisateur lors de l'exécution de commandes
  
-=== Documenter les paramètres nécessaires === +Dans un `Dockerfile`, une commande exécutée par une directive ''RUN'' est exécutée par le compte root du conteneur par défaut. Il convient d'exécuter aussi peu de commandes que possible avec le compte root ; pour se faire, il est possible d'utiliser la directive ''USER''.
-Chaque Dockerfile peut nécessiter le passage de paramètres tels que des mots de passe ; lorsque c'est le cas, il convient de les documenter dans le point d'entrée du conteneur et de leur y assigner une valeur par défaut.+
  
-==== Références ==== +<bootnote important>Un conteneur lancé en tant que `root` pose des risques de sécurité en cas de compromission, bien plus que quand il est lancé en tant que simple utilisateurPour certains services, c'est difficile, mais il faut essayer de s'en rapprocher le plus possible.</bootnote>
-[[https://docs.docker.com/develop/develop-images/dockerfile_best-practices/|Best practices for writing Dockerfiles]] +
-[[https://www.sans.org/reading-room/whitepapers/auditing/checklist-audit-docker-containers-37437|Recommandations de SANS]] +
-[[https://docs.docker.com/engine/reference/builder/|Dockerfile reference]] +
-[[https://www.miscmag.com/misc-n95-references-de-larticle-docker-les-bons-reflexes-a-adopter/|Références du MISC 95]]+
  
 +La dernière directive `USER` utilisée correspondra à l'utilisateur qui exécutera les commandes quand le conteneur sera lancé. Il est préférable de démarrer le conteneur avec un utilisateur non-privilégié. On s'assurera qu'il a bien les droits sur les fichiers qu'il doit utiliser, par exemple avec des instructions `chown` lors de la construction de l'image.
  • technique/docker/general/tips.1602497454.txt.gz
  • de qduchemi