Docker: nĂŒtzliche Tipps

In den Kommentaren zu meinem Artikel Docker: schÀdliche RatschlÀge 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.

Docker: nĂŒtzliche Tipps

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:

  1. Docker-Philosophie: ein Container – ein Prozess.
  2. Je kleiner der Container, desto besser.
  3. 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:latest

Sie 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 Docker Hub, 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Ă©. Artikel.

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 die Empfehlungen der Docker-Entwickler, es gibt kaum jemanden, der Docker besser kennt als sie.

Wenn du ein Praktikum machen möchtest, schau dir das Intensivtrainingan. Theorie ohne Praxis ist tote Theorie.

Quelle: habr.com

60GB SSD 8Gb DDR4