Dans les commentaires de mon article Docker: mauvais conseil , il y avait de nombreuses demandes pour expliquer pourquoi le Dockerfile décrit dans ce document est si terrible.
Résumé de la série précédente : deux développeurs dans un délai serré composent le Dockerfile. Dans le processus, Ops Igor Ivanovich vient à eux. Le Dockerfile résultant est si mauvais que l'IA est au bord d'une crise cardiaque.

Voyons maintenant ce qui ne va pas avec ce Dockerfile.
Donc, une semaine s'est écoulée.
Dev Petya se réunit dans la salle à manger avec une tasse de café avec Ops Igor Ivanovich.
P: Igor Ivanovich, êtes-vous très occupé? Je voudrais savoir où nous avons foiré.
AI: C'est bien, vous ne rencontrez pas souvent des développeurs intéressés par le fonctionnement.
Pour commencer, accordons-nous sur certaines choses:
- Idéologie Docker: un conteneur - un processus.
- Plus le contenant est petit, mieux c'est.
- Plus il y a de cache, mieux c'est.
P: Et pourquoi devrait-il y avoir un processus dans un conteneur?
AI: Docker, lorsque le conteneur démarre, surveille l'état du processus avec pid 1. Si le processus meurt, Docker essaie de redémarrer le conteneur. Supposons que plusieurs applications soient exécutées dans votre conteneur ou que l'application principale ne soit pas lancée avec le pid 1. Si le processus s'arrête, Docker ne le saura pas.
S'il n'y a plus de questions, montrez votre Dockerfile.
Et Petya a montré:
FROM ubuntu:latest # COPY ./ /app WORKDIR /app # RUN apt-get update # RUN apt-get upgrade # RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor # bundler RUN gem install bundler # nodejs RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash - RUN apt-get install -y nodejs # RUN bundle install --without development test --path vendor/bundle # 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 # , , . CMD ["/app/init.sh"]
AI: Oh, trions-le dans l'ordre. Commençons par la première ligne:
FROM ubuntu:latest
Vous prenez la latest
balise. L'utilisation de la latest
balise entraîne des conséquences imprévisibles. Imaginez que le responsable de l'image crée une nouvelle version de l'image avec une liste de logiciels différente, cette image obtient la dernière balise. Et au mieux, votre conteneur cesse de s'accumuler, et au pire, vous attrapez des bogues qui n'existaient pas auparavant.
Vous prenez une image avec un système d'exploitation complet avec beaucoup de logiciels inutiles, ce qui gonfle le conteneur. Et plus il y a de logiciels, plus il y a de trous et de vulnérabilités.
De plus, plus l'image est grande, plus elle prend de la place sur l'hĂ´te et dans le registre (stockez-vous les images quelque part)?
P: Oui, bien sûr, nous avons un registre et vous l'avez configuré.
II: Alors, de quoi je parle? .. Oh oui, les volumes ... La charge du réseau augmente également. Pour une seule image, cela est invisible, mais lorsqu'il y a un assemblage, des tests et un déploiement continus, c'est palpable. Et si vous n'avez pas le mode Dieu sur AWS, vous recevrez également un compte spatial.
Par conséquent, vous devez choisir l'image la plus appropriée, avec la version exacte et le logiciel minimum. Par exemple, prenez: FROM ruby:2.5.5-stretch
P: Oh, je vois. Et comment et oĂą voir les images disponibles? Comment comprendre lequel j'ai besoin?
AI: Habituellement, les images sont prises à partir d'un dockhub , ne les confondez pas avec un pornhub :). Il existe généralement plusieurs assemblages pour une image:
Alpine : les images sont compilées sur une image Linux minimaliste, seulement 5 Mo. Son inconvénient: il est construit avec sa propre implémentation de libc, les packages standard ne fonctionnent pas. Trouver et installer le bon package prendra beaucoup de temps.
Scratch : image de base, non utilisée pour construire d'autres images. Il est uniquement destiné à exécuter des données préparées binaires. Idéal pour exécuter des applications binaires, qui incluent tout ce dont vous avez besoin, comme les applications go.
Basé sur n'importe quel système d'exploitation tel que Ubuntu ou Debian. Eh bien, ici, je pense, pas besoin d'expliquer.
II: Maintenant, nous devons mettre tous les extras. Emballez et nettoyez les caches. Et immédiatement, vous pouvez lancer une mise à niveau apt-get . Sinon, avec chaque assemblage, malgré la balise fixe de l'image de base, différentes images seront obtenues. La mise à jour des packages dans une image est la tâche du responsable, elle s'accompagne d'un changement de tag.
P: Oui, j'ai essayé de le faire, ça s'est avéré comme 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/*
II: Pas mal, mais il y a aussi quelque chose Ă travailler. Regardez, voici 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 uniquement un calque supplémentaire sans ces données. À juste titre:
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? Ensuite, il n'est pas nécessaire de copier l'intégralité du projet au début. Copiez simplement Gemfile et Gemfile.lock.
Avec cette approche, l'installation du bundle ne sera pas exécutée pour chaque modification de la source, mais uniquement si Gemfile ou Gemfile.lock a changé.
Les mêmes méthodes fonctionnent pour d'autres langues avec le gestionnaire de dépendances, telles que npm, pip, composer et autres basées sur le fichier avec la liste des dépendances.
Et enfin, rappelez-vous, au début, je parlais de l'idéologie Docker «un conteneur - un processus»? Cela signifie qu'un superviseur n'est pas nécessaire. N'installez pas non plus systemd, pour les mêmes raisons. En fait, Docker lui-même est un superviseur. Et lorsque vous essayez d'y exécuter plusieurs processus, c'est comme lancer plusieurs applications dans un processus de superviseur.
Lors de l'assemblage, vous allez créer une seule image, puis exécuter le nombre de conteneurs souhaité de sorte que chaque processus ait un processus.
Mais plus Ă ce sujet plus tard.
P: Il semble comprendre. Voyez ce qui se passe:
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"]
Les démons seront-ils redéfinis au démarrage du conteneur?
AI: Oui, c'est vrai. Soit dit en passant, vous pouvez utiliser à la fois CMD et ENTRYPOINT. Et pour comprendre quelle est la différence, voici vos devoirs. Il y a un bon article sur ce sujet dans Habré.
Alors viens. Vous téléchargez le fichier pour installer le nœud, mais rien ne garantit qu'il aura ce dont vous avez besoin. Il est nécessaire d'ajouter une validation. Par exemple, comme ceci:
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/*
À l'aide de la somme de contrôle, vous pouvez vérifier que vous avez téléchargé le fichier correct.
P: Mais si le fichier change, l'assemblage ne fonctionnera pas.
II: Oui, et curieusement, c'est aussi un plus. Vous découvrirez que le fichier a changé et vous pouvez voir ce qui y a été modifié. On ne sait jamais, ont-ils ajouté, disons, un script qui supprime tout ce qu'il atteint ou fait une porte dérobée.
P: Merci. Il s'avère que 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"]
P: Igor Ivanovich, merci pour l'aide. Il est temps pour moi de courir, je dois faire 10 commits de plus pour aujourd'hui.
Igor Ivanovich, arrêtant son collègue précipité d'un regard, prend une gorgée de café fort. Après avoir réfléchi quelques secondes au SLA à 99,9% et au code sans bugs, il pose une question.
AI: Et oĂą gardez-vous les journaux?
P: Bien sûr, dans production.log. Soit dit en passant, oui, comment pouvons-nous y accéder sans ssh?
II: Si vous les laissez dans les fichiers, une solution a déjà été inventée pour vous. La commande docker exec vous permet d'exécuter n'importe quelle commande dans un conteneur. Par exemple, vous pouvez créer un chat pour les journaux. Et en utilisant le commutateur -it et en exécutant bash (s'il est installé dans le conteneur), vous obtiendrez un accès interactif au conteneur.
Mais vous ne devez pas stocker les journaux dans des fichiers. Au minimum, cela conduit à une croissance incontrôlée du conteneur, mais personne ne fait tourner les billes. Tous les journaux doivent être jetés dans stdout. Là , vous pouvez déjà les voir en utilisant la commande docker logs .
P: Igor Ivanovich, mais peut mettre les journaux dans le répertoire monté, sur le nœud physique, en tant que données utilisateur?
AI: C'est bien que vous n'ayez pas oublié de supprimer les données téléchargées sur le disque du nœud. Avec les journaux, cela est également possible, n'oubliez pas de configurer la rotation.
Tout ce que vous pouvez exécuter.
P: Igor Ivanovich, mais conseiller quoi lire?
AI: Tout d'abord, lisez les recommandations des développeurs de Docker , presque personne ne connaît mieux Docker qu'eux.
Et si vous voulez passer par la pratique, allez intensivement . Après tout, une théorie sans pratique est morte.