In den Kommentaren zu meinem Artikel gab es viele Anfragen, zu erklÀren, was so schrecklich an dem darin beschriebenen Dockerfile ist.
Zusammenfassung der vorherigen Episode: zwei Entwickler haben eine straffe Deadline und erstellen ein Dockerfile. WĂ€hrenddessen kommt Ops Igor Ivanovich dazu. Das Ergebnis-Dockerfile ist so schlecht, dass der IO an einem Herzinfarkt kratzt.

Jetzt klÀren wir, was mit diesem Dockerfile nicht stimmt.
Also, eine Woche ist vergangen.
Entwickler Petja trifft sich in der Kantine auf einen Kaffee mit Ops Igor Ivanovich.
P: Igor Ivanovich, sind Sie sehr beschĂ€ftigt? Ich wĂŒrde gerne herausfinden, wo wir einen Fehler gemacht haben.
IO: Das ist gut, man trifft nicht oft Entwickler, die sich fĂŒr den Betrieb interessieren.
ZunĂ€chst lass uns ĂŒber einige Dinge klar werden:
- Docker-Philosophie: ein Container â ein Prozess.
- Je kleiner der Container, desto besser.
- Je mehr aus dem Cache verwendet wird, desto besser.
P: Warum sollte in einem Container nur ein Prozess sein?
IO: Docker ĂŒberwacht beim Starten des Containers den Zustand des Prozesses mit pid 1. Wenn der Prozess stirbt, versucht Docker, den Container neu zu starten. Angenommen, in Ihrem Container laufen mehrere Anwendungen oder die Hauptanwendung wird nicht mit pid 1 gestartet. Wenn der Prozess stirbt, erfĂ€hrt Docker nichts davon.
Wenn es keine weiteren Fragen gibt, zeige mir dein Dockerfile.
Und Petja zeigte:
FROM ubuntu:latest
# Kopiere den Quellcode
COPY .\/ \/app
WORKDIR \/app
# Aktualisiere die Paketliste
RUN apt-get update
# Aktualisiere die Pakete
RUN apt-get upgrade
# Installiere die benötigten Pakete
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor
# Installiere bundler
RUN gem install bundler
# Installiere nodejs, das fĂŒr den Bau statischer Dateien verwendet wird
RUN curl -sL https:\/\/deb.nodesource.com\/setup_9.x | sudo bash -
RUN apt-get install -y nodejs
# Installiere die AbhÀngigkeiten
RUN bundle install --without development test --path vendor\/bundle
# Bereinige die 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
# FĂŒhre das Skript beim Start des Containers aus, das alles andere startet.
CMD ["\/app\/init.sh"]IO: Oh, lass uns der Reihe nach vorgehen. Fangen wir mit der ersten Zeile an:
FROM ubuntu:latestSie verwenden das Tag latest. Die Verwendung des Tags latest fĂŒhrt zu unvorhersehbaren Konsequenzen. Stellen Sie sich vor, der Maintainer des Images erstellt eine neue Version des Images mit einer anderen Liste von Software, dieses Image erhĂ€lt das Tag latest. Und Ihr Container hört im besten Fall auf, sich zu bauen, oder im schlimmsten Fall laufen Sie in Bugs, die vorher nicht da waren.
Sie verwenden ein Image mit einem vollstĂ€ndigen Betriebssystem und viel unnötiger Software, die das Volumen des Containers aufblĂ€ht. Je mehr Software vorhanden ist, desto mehr SicherheitslĂŒcken und Schwachstellen gibt es.
ZusĂ€tzlich belegt ein gröĂeres Image mehr Speicherplatz auf dem Host und im Registry (wo speicherst du die Images?).
P: Ja, natĂŒrlich haben wir eine Registry, die hast du doch eingerichtet.
KI: Also, wo war ich stehen geblieben?.. Ach ja, Volumina⊠Auch die Netzwerklast steigt. Bei einem einzelnen Image merkt man das nicht, aber bei kontinuierlichem Bauen, Tests und Deployments ist das spĂŒrbar. Und wenn du keinen Godâs Mode auf AWS hast, bekommst du am Ende auch noch eine schockierend hohe Rechnung.
Deshalb ist es wichtig, das am besten geeignete Image auszuwÀhlen, mit der genauen Version und minimaler Software. Zum Beispiel nimm: FROM ruby:2.5.5-stretch
P: Oh, verstehe. Wo und wie kann ich die vorhandenen Images ansehen? Wie erkenne ich, welches ich brauche?
KI: Normalerweise werden Images von , nicht zu verwechseln mit Pornhub :). FĂŒr ein Image gibt es normalerweise mehrere Builds:
Alpine: Die Images sind auf einem minimalistischen Linux-Image gebaut, nur 5 MB groĂ. Sein Nachteil: Es wurde mit einer eigenen Implementierung von libc erstellt, Standardpakete funktionieren darin nicht. Die Suche und Installation des benötigten Pakets könnte viel Zeit in Anspruch nehmen.
Scratch: Basis-Image, wird nicht zum Erstellen anderer Images verwendet. Es ist ausschlieĂlich dafĂŒr gedacht, binares, vorbereitetes Material auszufĂŒhren. Es eignet sich ideal zum AusfĂŒhren von binĂ€ren Anwendungen, die alles Notwendige beinhalten, z.B. Go-Anwendungen.
Basierend auf einem bestimmten Betriebssystem, z.B. Ubuntu oder Debian. Ich denke, das muss ich hier nicht weiter erklÀren.
KI: Jetzt mĂŒssen wir alle zusĂ€tzlichen Pakete installieren und den Cache aufrĂ€umen. Und gleich können wir auch apt-get upgraderausschmeiĂen. Andernfalls werden bei jedem Build, trotz des festen Tags des Basisimages, unterschiedliche Images erstellt. Die Aktualisierung der Pakete im Image ist eine Aufgabe des Maintainers und geht mit einer Ănderung des Tags einher.
P: Ja, ich habe versucht, das zu tun, und so hat es funktioniert:
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/*KI: Nicht schlecht, aber hier gibt es auch noch Verbesserungsbedarf. Schau, dieser Befehl:
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ⊠entfernt keine Daten aus dem endgĂŒltigen Bild, sondern erstellt lediglich eine zusĂ€tzliche Schicht ohne diese Daten. So ist es richtig:
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/* Aber das ist noch nicht alles. Was hast du da, Ruby? Dann musst du zu Beginn nicht das gesamte Projekt kopieren. Es reicht, Gemfile und Gemfile.lock zu kopieren.
Mit diesem Ansatz wird bundle install nicht bei jeder Ănderung des Quellcodes ausgefĂŒhrt, sondern nur, wenn sich Gemfile oder Gemfile.lock geĂ€ndert haben.
Die gleichen Methoden gelten auch fĂŒr andere Sprachen mit Paketmanagern wie npm, pip, composer und anderen, die auf einer Liste von AbhĂ€ngigkeiten basieren.
Und schlieĂlich, erinnerst du dich, dass ich am Anfang ĂŒber die Docker-Ideologie âein Container - ein Prozessâ gesprochen habe? Das bedeutet, dass ein Supervisor nicht erforderlich ist. Auch systemd sollte aus denselben GrĂŒnden nicht installiert werden. Im Grunde ist Docker selbst der Supervisor. Und wenn du versuchst, mehrere Prozesse darin zu starten, ist das so, als wĂŒrde man in einem Prozess des Supervisors mehrere Anwendungen starten.
Beim Bauen erstellst du ein einziges Bild und startest dann die benötigte Anzahl an Containern, sodass in jedem ein Prozess lÀuft.
Aber dazu spÀter mehr.
P: Ich glaube, ich habe es verstanden. Schau, was herauskommt:
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"]Und wir werden die Daemon-Starts beim Start des Containers ĂŒberschreiben?
KI: Ja, genau. Ăbrigens kann man sowohl CMD als auch ENTRYPOINT verwenden. Und herauszufinden, was der Unterschied ist, ist deine Hausaufgabe. Zu diesem Thema gibt es einen guten Artikel auf HabrĂ©. .
Also, lass uns weitermachen. Du lĂ€dst eine Datei fĂŒr die Installation von node herunter, aber es gibt keine Garantie, dass darin das enthalten ist, was du benötigst. Du musst eine Validierung hinzufĂŒgen. Zum Beispiel so:
FĂŒhre 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/* Mit der PrĂŒfziffer kannst du ĂŒberprĂŒfen, ob die heruntergeladene Datei korrekt ist.
P: Aber wenn die Datei geÀndert wird, schlÀgt der Build fehl.
II: Ja, und das ist, merkwĂŒrdigerweise, auch ein Vorteil. Du wirst wissen, dass die Datei sich geĂ€ndert hat, und kannst nachsehen, was geĂ€ndert wurde. Man weiĂ ja nie, vielleicht wurde ein Skript hinzugefĂŒgt, das alles löscht, was es erreicht, oder einen Backdoor-Installiert.
P: Danke. Das heiĂt, die endgĂŒltige Dockerfile sieht so aus:
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, danke fĂŒr die Hilfe. Ich muss jetzt los, ich muss heute noch 10 Commits machen.
Igor Ivanovich, der den hastigen Kollegen mit seinem Blick stoppt, nimmt einen Schluck starken Kaffees. Er denkt ein paar Sekunden ĂŒber das SLA von 99,9 % und fehlerfreien Code nach, und stellt eine Frage.
II: Wo speichert ihr die Logs?
P: NatĂŒrlich in production.log. Ăbrigens, wie bekommen wir ohne SSH Zugriff darauf?
II: Wenn ihr sie in Dateien lasst, gibt es dafĂŒr schon eine Lösung. Der Befehl docker exec ermöglicht es euch, jeden Befehl im Container auszufĂŒhren. Zum Beispiel könnt ihr cat fĂŒr die Logs verwenden. Und wenn ihr den SchlĂŒssel -it verwendet und bash startet (falls es im Container installiert ist), erhaltet ihr interaktiven Zugriff auf den Container.
Aber es ist nicht ratsam, Logs in Dateien zu speichern. Mindestens fĂŒhrt das zu unkontrolliertem Wachstum des Containers, niemand rotiert die Logs. Alle Logs sollten in stdout geworfen werden. Dort können sie dann mit dem Befehl docker logs.
betrachtet werden.
P: Igor Ivanovich, vielleicht sollten wir die Logs in ein gemountetes Verzeichnis auf den physischen Knoten auslagern, wie die Nutzerdaten?
II: Gut, dass du vergessen hast, die auf die Knotenfestplatte geladenen Daten auszulagern. Mit Logs geht das auch, vergiss nur nicht, die Rotation einzustellen.Alles, du kannst loslaufen.
P: Igor Ivanowitsch, was wĂŒrden Sie empfehlen, zu lesen?
II: ZunÀchst lese , es gibt kaum jemanden, der Docker besser kennt als sie.
Wenn du ein Praktikum machen möchtest, schau dir das an. Theorie ohne Praxis ist tote Theorie.
Quelle: habr.com
