Docker: keine schÀdlichen RatschlÀge

In den Kommentaren zu meinem Artikel Docker: schÀdliche Tipps gab es viele Anfragen, zu erklÀren, warum der beschriebene Dockerfile so schrecklich ist.

Kurze Zusammenfassung der vorherigen Folge: zwei Entwickler stehen unter starkem Zeitdruck und erstellen einen Dockerfile. WĂ€hrenddessen kommt Ops Igor Ivanovich vorbei. Der endgĂŒltige Dockerfile ist so schlecht, dass der I.I. kurz vor einem Herzinfarkt steht.

Docker: keine schÀdlichen RatschlÀge

Jetzt klÀren wir, was mit diesem Dockerfile nicht stimmt.

Also, eine Woche ist vergangen.

Dev Petya trifft sich in der Cafeteria mit Ops Igor Ivanovich bei einer Tasse Kaffee.

P: Igor Ivanovich, sind Sie stark beschĂ€ftigt? Ich wĂŒrde gerne herausfinden, wo wir einen Fehler gemacht haben.

I.I.: Das ist gut, man trifft nicht oft auf Entwickler, die sich fĂŒr den Betrieb interessieren.
Lass uns zuerst ein paar Dinge klÀren:

  1. Die Ideologie von Docker: 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?

I.I.: Docker ĂŒberwacht beim Start eines Containers den Zustand des Prozesses mit der PID 1. Wenn dieser Prozess stirbt, versucht Docker, den Container neu zu starten. Angenommen, in Ihrem Container laufen mehrere Anwendungen oder die Hauptanwendung wird nicht mit der PID 1 gestartet. Wenn der Prozess stirbt, erfĂ€hrt Docker nichts davon.

Wenn es keine weiteren Fragen gibt, zeigen Sie bitte Ihre Dockerfile.

Und Petya zeigte:

FROM ubuntu:latest

# Quellcode kopieren
COPY ./ /app
WORKDIR /app

# Paketliste aktualisieren
RUN apt-get update 

# Pakete aktualisieren
RUN apt-get upgrade

# Notwendige Pakete installieren
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Bundler installieren
RUN gem install bundler

# Node.js installieren, wird zum Bauen von statischen Inhalten verwendet
RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# AbhÀngigkeiten installieren
RUN bundle install --without development test --path vendor/bundle

# Caches aufrÀumen
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
# Skript beim Start des Containers ausfĂŒhren, das alles andere anstoßt.
CMD ["/app/init.sh"]

KI: 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 Softwareliste, dieses Image erhĂ€lt das Tag latest. Und Ihr Container hört bestenfalls auf zu bauen, schlimmstenfalls fangen Sie Bugs, die zuvor nicht auftraten, ein.

Sie verwenden ein Image mit einem vollwertigen Betriebssystem, das mit viel unnötiger Software ĂŒberladen ist, was das Volumen des Containers aufblĂ€ht. Je mehr Software vorhanden ist, desto mehr SicherheitslĂŒcken und Schwachstellen ergeben sich.

ZusĂ€tzlich benötigt ein grĂ¶ĂŸeres Image mehr Speicherplatz auf dem Host und im Registry (wo speichern Sie die Images?).

P: Ja, natĂŒrlich, wir haben ein Registry, das haben Sie selbst eingerichtet.

AI: Also, worĂŒber habe ich gerade gesprochen?... Ach ja, die Volumen... Die Belastung des Netzwerks wĂ€chst ebenfalls. FĂŒr ein einzelnes Image ist das nicht spĂŒrbar, aber bei kontinuierlichen Builds, Tests und Deployments wird es deutlich. Und wenn Sie keinen God’s Mode auf AWS haben, kann die Rechnung astronomisch ausfallen.

Daher ist es wichtig, das am besten geeignete Image auszuwÀhlen, mit einer genauen Version und minimaler Software. Zum Beispiel: FROM ruby:2.5.5-stretch

P: Oh, ich verstehe. Wo und wie kann ich die verfĂŒgbaren Images einsehen? Wie erkenne ich, welches ich brauche?

AI: In der Regel werden Images von Docker Hub, nicht zu verwechseln mit Pornhub :). FĂŒr ein Image gibt es normalerweise mehrere Builds:
Alpine: Diese Images basieren auf einem minimalistischen Linux-Image, nur 5 MB groß. Der Nachteil ist, dass es mit einer eigenen Implementierung von libc erstellt wurde, weshalb standardmĂ€ĂŸige Pakete darin nicht funktionieren. Die Suche und Installation des benötigten Pakets kann viel Zeit in Anspruch nehmen.
Scratch: Basis-Image, das nicht fĂŒr den Bau anderer Images verwendet wird. Es ist ausschließlich dafĂŒr bestimmt, binĂ€re, vorbereitete Daten auszufĂŒhren. Ideal fĂŒr die AusfĂŒhrung binĂ€rer Anwendungen, die alles Notwendige enthalten, wie z. B. Go-Anwendungen.
Auf Basis eines Betriebssystems, wie beispielsweise Ubuntu oder Debian. Hierbei ist, denke ich, keine weitere ErklÀrung nötig.

KI: Jetzt mĂŒssen wir alle zusĂ€tzlichen Pakete installieren und den Cache aufrĂ€umen. Und gleich kann der Befehl ausgefĂŒhrt werden: apt-get upgrade. Andernfalls entstehen bei jedem Build, trotz des festen Tags des Basis-Images, unterschiedliche Images. Die Aktualisierung von Paketen im Image ist die Aufgabe des Maintainers und geht mit einer Änderung des Tags einher.

F: Ja, ich habe versucht, das zu machen, und es hat bei mir so 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 auch hier gibt es noch Verbesserungsmöglichkeiten. Sieh mal, dieser Befehl:

RUN rm -rf /usr/local/bundle/cache/*.gem 
    && apt-get clean  
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*  


 löscht 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 dort, Ruby? Dann musst du nicht zu Beginn das gesamte Projekt kopieren. Es reicht, Gemfile und Gemfile.lock zu kopieren.

Mit diesem Ansatz wird bundle install nicht bei jeder Änderung der Quellcodes ausgefĂŒhrt, sondern nur, wenn sich Gemfile oder Gemfile.lock geĂ€ndert haben.

Die gleichen Methoden funktionieren auch fĂŒr andere Programmiersprachen mit Paketmanagern wie npm, pip, composer und anderen, die auf einer AbhĂ€ngigkeitsliste basieren.

Und schließlich, erinnerst du dich, am Anfang habe ich von der Docker-Ideologie ‚ein Container – ein Prozess‘ gesprochen? Das bedeutet, dass ein Supervisor nicht benötigt wird. Systemd sollte aus denselben GrĂŒnden ebenfalls nicht installiert werden. Im Grunde genommen ist Docker selbst der Supervisor. Und wenn du versuchst, mehrere Prozesse darin zu starten, ist das so, als wĂŒrdest du in einem einzigen Supervisor mehrere Anwendungen starten.
Beim Erstellen erstellen Sie ein konsolidiertes Image und starten dann die benötigte Anzahl von Containern, wobei in jedem Container ein Prozess lÀuft.

Aber dazu spÀter mehr.

F: Ich glaube, ich verstehe. Sehen wir uns das Ergebnis an:

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 wie sieht es mit dem Starten von Daemons beim Containerstart aus?

AI: Ja, genau. Übrigens, sowohl CMD als auch ENTRYPOINT können verwendet werden. Zu verstehen, was der Unterschied ist, ist deine Hausaufgabe. Zu diesem Thema gibt es einen guten Artikel auf Habr. Artikel.

Also, machen wir weiter. Du lĂ€dst die Datei fĂŒr die Installation von Node herunter, aber es gibt keine Garantie, dass das, was du brauchst, darin enthalten ist. Du solltest eine Validierung hinzufĂŒgen. Zum Beispiel so:

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/* 

Anhand der PrĂŒfziffer kannst du sicherstellen, dass du die richtige Datei heruntergeladen hast.

P: Aber wenn die Datei geÀndert wird, wird der Build nicht erfolgreich sein.

AI: Ja, und das ist auch ein Vorteil. Du wirst wissen, dass die Datei sich geĂ€ndert hat und kannst nachsehen, was geĂ€ndert wurde. Wer weiß, vielleicht wurde ein Skript hinzugefĂŒgt, das alles löscht, was es erreichen kann, oder einen Backdoor einfĂŒgt.

P: Danke. Das finale Dockerfile wird also so aussehen:

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 hĂ€lt hastenden Kollegen mit einem Blick auf und nimmt einen Schluck starken Kaffee. Nach einigen Sekunden Überlegungen zu SLA 99,9% und fehlerfreiem Code stellt er eine Frage.

AI: Wo speichern Sie die Logs?

P: NatĂŒrlich in production.log. Übrigens, wie bekommen wir ohne SSH Zugang zu ihnen?

AI: Wenn Sie sie in Dateien lassen, gibt es bereits eine Lösung fĂŒr Sie. Mit dem Befehl docker exec können Sie jeden Befehl im Container ausfĂŒhren. Zum Beispiel können Sie cat fĂŒr die Logs verwenden. Und wenn Sie den SchlĂŒssel -it und bash (falls im Container installiert) starten, erhalten Sie interaktiven Zugriff auf den Container.

Aber es ist nicht ratsam, Logs in Dateien zu speichern. Zumindest fĂŒhrt das zu unkontrolliertem Wachstum des Containers, da die Logs nicht rotiert werden. Alle Logs sollten in stdout geschrieben werden. Dort können Sie sie mit dem Befehl docker logs.

P: Igor Ivanovich, vielleicht sollten wir die Logs in ein gemountetes Verzeichnis auf den physischen Knoten auslagern, wie die Benutzerdaten?

AI: Gut, dass Sie nicht vergessen haben, die auf die Festplatte des Knotens geladenen Daten auszulagern. Mit den Logs geht das auch, aber vergessen Sie nicht, die Rotation einzurichten.
Okay, du kannst gehen.

P: Igor Ivanovich, können Sie mir bitte BĂŒcher empfehlen?

KI: Zuerst lies die Empfehlungen der Docker-Entwickler, denn wahrscheinlich kennt niemand Docker besser als sie.

Wenn du praktische Erfahrung sammeln möchtest, schau dir den Intensivkursan. Theorie ohne Praxis ist tot.

Quelle: habr.com

Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster