Dans les commentaires de mon article il y a eu de nombreuses demandes pour expliquer en quoi le Dockerfile décrit est si mauvais.
Résumé de l'épisode précédent: deux développeurs en situation de deadline serrée rédigent un Dockerfile. Pendant ce temps, Ops Igor Ivanovich entre. Le Dockerfile final est si mauvais que l'IA est au bord de l'infarctus.

Voyons ce qui ne va pas avec ce Dockerfile.
Alors, une semaine s'est écoulée.
Dev Petya rencontre Ops Igor Ivanovich à la cafétéria autour d'une tasse de café.
P : Igor Ivanovich, ĂȘtes-vous trĂšs occupĂ© ? J'aimerais comprendre oĂč nous avons fait des erreurs.
II : C'est bien, on ne rencontre pas souvent des développeurs intéressés par l'exploitation.
Pour commencer, convenons de certaines choses :
- L'idéologie de Docker : un conteneur - un processus.
- Plus le conteneur est petit, mieux c'est.
- Plus on utilise le cache, mieux c'est.
P : Pourquoi doit-il y avoir un seul processus dans un conteneur ?
II : Docker, lors du lancement du conteneur, suit l'état du processus avec le pid 1. Si le processus meurt, Docker essaie de redémarrer le conteneur. Supposons que vous ayez plusieurs applications lancées dans le conteneur ou que l'application principale ne soit pas exécutée avec le pid 1. Si le processus meurt, Docker ne le saura pas.
S'il n'y a plus de questions, montre-moi ton Dockerfile.
Et Petya a montré :
FROM ubuntu:latest
# Copier le code source
COPY .\/ \/app
WORKDIR \/app
# Mettre Ă jour la liste des paquets
RUN apt-get update
# Mettre Ă jour les paquets
RUN apt-get upgrade
# Installer les paquets nécessaires
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor
# Installer bundler
RUN gem install bundler
# Installer nodejs utilisé pour la construction de la statique
RUN curl -sL https:\/\/deb.nodesource.com\/setup_9.x | sudo bash -
RUN apt-get install -y nodejs
# Installer les dépendances
RUN bundle install --without development test --path vendor\/bundle
# Nettoyer les caches
RUN rm -rf \/usr\/local\/bundle\/cache\/*.gem
RUN apt-get clean
RUN rm -rf \/var\/lib\/apt\/lists\/* \/tmp\/* \/var\/tmp\/*
RUN rake assets:precompile
# Lancer le script au démarrage du conteneur, qui lancera le reste.
CMD ["\/app\/init.sh"]II : Oh, examinons cela point par point. Commençons par la premiÚre ligne :
FROM ubuntu:latestVous prenez la balise latest. L'utilisation de la balise latest entraßne des conséquences imprévisibles. Imaginez que le mainteneur de l'image compile une nouvelle version avec une autre liste de logiciels, cette image reçoit la balise latest. Et votre conteneur, dans le meilleur des cas, cesse de se construire, et dans le pire des cas, vous rencontrez des bogues qui n'étaient pas présents auparavant.
Vous prenez une image avec un systÚme d'exploitation complet avec une multitude de logiciels inutiles, ce qui gonfle la taille du conteneur. Et plus il y a de logiciels, plus il y a de failles de sécurité et de vulnérabilités.
De plus, plus l'image est grande, plus elle occupe d'espace sur l'hĂŽte et dans le registre (vous avez bien un endroit oĂč stocker les images) ?
P : Oui, bien sûr, nous avons un registre, c'est vous qui l'avez configuré.
IA : Alors, de quoi parlais-je ?... Ah oui, les tailles... La charge sur le rĂ©seau augmente Ă©galement. Pour une seule image, cela passe inaperçu, mais quand il y a une build continue, des tests et un dĂ©ploiement, cela se ressent. Et si vous n'avez pas le Godâs mode sur AWS, vous recevrez aussi une facture astronomique.
Il est donc important de choisir l'image la plus appropriée, avec la version exacte et un minimum de logiciels. Par exemple, prenez : FROM ruby:2.5.5-stretch
P : Oh, je comprends. Comment et oĂč puis-je voir les images disponibles ? Comment savoir laquelle j'ai besoin ?
IA : Généralement, les images sont prises sur , ne confondez pas avec Pornhub :). Pour une image, il existe généralement plusieurs constructions :
Alpine: des images construites sur une image Linux minimaliste, seulement 5 Mo. Son inconvénient : elle est construite avec une réalisation propre de libc, les paquets standard ne fonctionnent pas. La recherche et l'installation du bon paquet prendront pas mal de temps.
Scratch: une image de base, qui n'est pas utilisée pour construire d'autres images. Elle est uniquement conçue pour exécuter des binaires, des données préparées. Idéale pour exécuter des applications binaires qui contiennent tout le nécessaire, comme les applications Go.
Sur la base d'un systÚme d'exploitation, tel qu'Ubuntu ou Debian. Je pense qu'il n'est pas nécessaire d'expliquer cela.
IA : Maintenant, nous devons installer tous les paquets supplémentaires et nettoyer nos caches. Et vous pouvez immédiatement jeter apt-get upgrade. Sinon, à chaque construction, malgré le tag fixe de l'image de base, différentes images seront générées. La mise à jour des paquets dans l'image est une tùche pour le mainteneur, elle s'accompagne de changements de tag.
P : Oui, j'ai essayé de le faire, et cela a donné ceci :
WORKDIR /app
COPY ./ /app
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*IA : Pas mal, mais il y a aussi du travail Ă faire ici. Regarde, cette commande :
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ⊠ne supprime pas les données de l'image finale, mais crée simplement une couche supplémentaire sans ces données. Correctement dit :
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Mais ce n'est pas tout. Qu'avez-vous là , Ruby ? Dans ce cas, il n'est pas nécessaire de copier tout le projet au début. Il suffit de copier Gemfile et Gemfile.lock.
Avec cette approche, bundle install ne s'exécutera pas à chaque changement des sources, mais seulement si Gemfile ou Gemfile.lock ont changé.
Les mĂȘmes mĂ©thodes fonctionnent Ă©galement pour d'autres langages avec un gestionnaire de dĂ©pendances, comme npm, pip, composer et d'autres basĂ©s sur un fichier avec une liste de dĂ©pendances.
Et enfin, te souviens-tu, au dĂ©but, je parlais de l'idĂ©ologie Docker « un conteneur â un processus » ? Cela signifie que le superviseur n'est pas nĂ©cessaire. Il n'est pas non plus conseillĂ© d'installer systemd, pour les mĂȘmes raisons. En fait, Docker est lui-mĂȘme un superviseur. Et quand tu essaies de lancer plusieurs processus Ă l'intĂ©rieur, c'est comme lancer plusieurs applications dans un seul processus de superviseur.
Lors de la construction, tu feras une image unique, puis tu lanceras le nombre spécifique de conteneurs, de sorte que chacun exécute un seul processus.
Mais nous en reparlerons plus tard.
Q : Il semble que j'ai compris. Regardez ce que cela donne :
FROM ruby:2.5.5-stretch
WORKDIR /app
COPY Gemfile* /app
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY . /app
RUN rake assets:precompile
CMD ["bundleâ, âexecâ, âpassengerâ, âstart"]Et la gestion des dĂ©mons, allons-nous la redĂ©finir lors du lancement du conteneur ?
AI : Oui, c'est exact. D'ailleurs, tu peux utiliser à la fois CMD et ENTRYPOINT. Et comprendre la différence, c'est ton devoir. Il y a un bon article à ce sujet sur Habr. .
Alors, continuons. Tu télécharges un fichier d'installation de node, mais il n'y a aucune garantie qu'il contienne ce dont tu as besoin. Il faut ajouter une validation. Par exemple, comme ça :
RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x
&& echo "958c9a95c4974c918dca773edf6d18b1d1a41434 setup_9.x" | sha1sum -c -
&& bash setup_9.x
&& rm -rf setup_9.x
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Avec le hash, vous pouvez vérifier que vous avez téléchargé le bon fichier.
Q : Mais si le fichier change, la construction échouera.
IA : Oui, et c'est aussi un avantage. Vous saurez que le fichier a changĂ© et pourrez voir ce qui a Ă©tĂ© modifiĂ©. AprĂšs tout, peut-ĂȘtre qu'ils ont ajoutĂ© un script qui supprime tout ce qu'il touche ou crĂ©e une porte dĂ©robĂ©e.
Q : Merci. Donc, le Dockerfile final ressemblera Ă ceci :
FROM ruby:2.5.5-stretch
WORKDIR /app
COPY Gemfile* /app
RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x
&& echo "958c9a95c4974c918dca773edf6d18b1d1a41434 setup_9.x" | sha1sum -c -
&& bash setup_9.x
&& rm -rf setup_9.x
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY . /app
RUN rake assets:precompile
CMD ["bundle", "exec", "passenger", "start"]Q : Igor Ivanovich, merci pour votre aide. Je dois y aller, il me reste encore 10 commits Ă faire aujourd'hui.
Igor Ivanovich, en arrĂȘtant son collĂšgue pressĂ© d'un coup d'Ćil, prend une gorgĂ©e de cafĂ© noir. AprĂšs avoir rĂ©flĂ©chi quelques secondes au SLA de 99,9 % et au code sans bugs, il pose une question.
IA : OĂč conservez-vous les journaux ?
Q : Bien sûr, dans production.log. Au fait, comment allons-nous y accéder sans ssh ?
IA : Si vous les gardez dans des fichiers, une solution a déjà été trouvée pour vous. La commande docker exec permet d'exécuter n'importe quelle commande dans le conteneur. Par exemple, vous pouvez faire cat pour les journaux. Et en utilisant la clé -it et en lançant bash (s'il est installé dans le conteneur), vous obtiendrez un accÚs interactif au conteneur.
Mais il ne faut pas conserver les journaux dans des fichiers. Au minimum, cela conduit à une croissance incontrÎlée du conteneur, car personne ne les fait tourner. Tous les journaux doivent aller dans stdout. Là , vous pouvez les consulter avec la commande docker logs.
Q : Igor Ivanovich, et si nous sortions les journaux dans un rĂ©pertoire montĂ©, sur la nĆud physique, comme les donnĂ©es des utilisateurs ?
IA : C'est bien que vous n'ayez pas oubliĂ© de sortir les donnĂ©es Ă©crites sur le disque de la nĆud. Avec les journaux, c'est possible aussi, mais n'oubliez pas de configurer la rotation.
VoilĂ , tu peux y aller.
P: Igor Ivanovitch, pouvez-vous me conseiller sur quoi lire ?
II: Pour commencer, lis , il est peu probable que quelqu'un connaisse Docker mieux qu'eux.
Et si tu veux faire un stage, va à . Car la théorie sans pratique est morte.
Source : habr.com
