Erste Schritte mit Docker: Vereinfachung von DevOps

Veröffentlicht: 2022-03-11

Wenn Sie Wale mögen oder einfach an einer schnellen und problemlosen kontinuierlichen Bereitstellung Ihrer Software für die Produktion interessiert sind, dann lade ich Sie ein, dieses einführende Docker-Tutorial zu lesen. Alles scheint darauf hinzuweisen, dass Software-Container die Zukunft der IT sind, also lassen Sie uns mit den Containerwalen Moby Dock und Molly ein kurzes Bad nehmen.

Docker, repräsentiert durch ein Logo mit einem freundlich aussehenden Wal

Docker, dargestellt durch ein Logo mit einem freundlich aussehenden Wal, ist ein Open-Source-Projekt, das die Bereitstellung von Anwendungen innerhalb von Software-Containern erleichtert. Seine grundlegende Funktionalität wird durch Ressourcenisolationsfunktionen des Linux-Kernels ermöglicht, aber es bietet darüber hinaus eine benutzerfreundliche API. Die erste Version wurde 2013 veröffentlicht und ist seitdem äußerst beliebt geworden und wird von vielen großen Playern wie eBay, Spotify, Baidu und anderen weit verbreitet. In der letzten Finanzierungsrunde hat Docker riesige 95 Millionen US-Dollar an Land gezogen und ist auf dem besten Weg, ein fester Bestandteil der DevOps-Dienste zu werden.

Transport von Gütern Analogie

Die Philosophie hinter Docker lässt sich mit einer einfachen Analogie veranschaulichen. In der internationalen Transportbranche müssen Waren mit verschiedenen Mitteln wie Gabelstaplern, Lastwagen, Zügen, Kränen und Schiffen transportiert werden. Diese Waren haben unterschiedliche Formen und Größen und haben unterschiedliche Anforderungen an die Lagerung: Zuckersäcke, Milchkannen, Pflanzen usw. In der Vergangenheit war es ein schmerzhafter Prozess, der an jedem Transitpunkt zum Be- und Entladen von manuellen Eingriffen abhängig war.

Ein Pferdewagen, ein Pickup und ein Transporter, die alle Waren transportieren

Mit der Einführung intermodaler Container änderte sich alles. Da sie in Standardgrößen erhältlich sind und unter Berücksichtigung des Transports hergestellt werden, können alle relevanten Maschinen so konstruiert werden, dass sie diese mit minimalem menschlichen Eingriff handhaben. Der zusätzliche Vorteil von versiegelten Behältern besteht darin, dass sie die innere Umgebung wie Temperatur und Feuchtigkeit für empfindliche Waren bewahren können. Infolgedessen muss sich die Transportbranche nicht mehr um die Waren selbst kümmern und sich darauf konzentrieren, sie von A nach B zu bringen.

Transport mit Schiffscontainern auf dem Land- und Seeweg

Und hier kommt Docker ins Spiel und bringt der Softwarebranche ähnliche Vorteile.

Wie unterscheidet es sich von virtuellen Maschinen?

Auf den ersten Blick scheinen virtuelle Maschinen und Docker-Container gleich zu sein. Ihre Hauptunterschiede werden jedoch deutlich, wenn Sie sich das folgende Diagramm ansehen:

Vergleichsdiagramm von virtuellen Maschinen (VMs) und Containern

Anwendungen, die in virtuellen Maschinen ausgeführt werden, erfordern neben dem Hypervisor eine vollständige Instanz des Betriebssystems und aller unterstützenden Bibliotheken. Container hingegen teilen sich das Betriebssystem mit dem Host. Hypervisor ist vergleichbar mit der Container-Engine (auf dem Image als Docker dargestellt) in dem Sinne, dass es den Lebenszyklus der Container verwaltet. Der wichtige Unterschied besteht darin, dass die Prozesse, die in den Containern ausgeführt werden, genau wie die nativen Prozesse auf dem Host sind und keinen mit der Ausführung des Hypervisors verbundenen Overhead verursachen. Darüber hinaus können Anwendungen die Bibliotheken wiederverwenden und die Daten zwischen Containern austauschen.

Da beide Technologien unterschiedliche Stärken haben, findet man häufig Systeme, die virtuelle Maschinen und Container kombinieren. Ein perfektes Beispiel ist ein Tool namens Boot2Docker, das im Abschnitt Docker-Installation beschrieben wird.

Docker-Architektur

Docker-Architektur

Am oberen Rand des Architekturdiagramms befinden sich Registrierungen. Standardmäßig ist die Hauptregistrierung der Docker Hub, der öffentliche und offizielle Images hostet. Organisationen können auch ihre privaten Register hosten, wenn sie dies wünschen.

Auf der rechten Seite haben wir Bilder und Container. Bilder können explizit ( docker pull imageName ) oder implizit beim Starten eines Containers aus Registrierungen heruntergeladen werden. Sobald das Bild heruntergeladen ist, wird es lokal zwischengespeichert.

Container sind die Instanzen von Bildern – sie sind das Lebendige. Es können mehrere Container auf der Grundlage desselben Images ausgeführt werden.

Im Zentrum steht der Docker-Daemon, der für das Erstellen, Ausführen und Überwachen von Containern verantwortlich ist. Es kümmert sich auch um das Erstellen und Speichern von Bildern. Schließlich befindet sich auf der linken Seite ein Docker-Client. Es kommuniziert mit dem Daemon über HTTP. Unix-Sockets werden verwendet, wenn sie sich auf demselben Computer befinden, aber die Fernverwaltung ist über eine HTTP-basierte API möglich.

Docker installieren

Für die neuesten Anweisungen sollten Sie sich immer auf die offizielle Dokumentation beziehen.

Docker läuft nativ unter Linux, also könnte es je nach Zieldistribution so einfach sein wie sudo apt-get install docker.io . Einzelheiten finden Sie in der Dokumentation. Normalerweise stellen Sie unter Linux den Docker-Befehlen sudo , aber wir werden es in diesem Artikel aus Gründen der Übersichtlichkeit überspringen.

Da der Docker-Daemon Linux-spezifische Kernel-Funktionen verwendet, ist es nicht möglich, Docker nativ in Mac OS oder Windows auszuführen. Stattdessen sollten Sie eine Anwendung namens Boot2Docker installieren. Die Anwendung besteht aus einer VirtualBox Virtual Machine, Docker selbst und den Boot2Docker-Verwaltungsdienstprogrammen. Sie können den offiziellen Installationsanweisungen für MacOS und Windows folgen, um Docker auf diesen Plattformen zu installieren.

Verwenden von Docker

Beginnen wir diesen Abschnitt mit einem kurzen Beispiel:

 docker run phusion/baseimage echo "Hello Moby Dock. Hello Molly."

Wir sollten diese Ausgabe sehen:

 Hello Moby Dock. Hello Molly.

Hinter den Kulissen ist jedoch viel mehr passiert, als Sie vielleicht denken:

  • Das Image „phusion/baseimage“ wurde von Docker Hub heruntergeladen (sofern es sich nicht bereits im lokalen Cache befand).
  • Ein auf diesem Image basierender Container wurde gestartet
  • Der Befehl echo wurde innerhalb des Containers ausgeführt
  • Der Container wurde gestoppt, als der Befehl beendet wurde

Bei der ersten Ausführung bemerken Sie möglicherweise eine Verzögerung, bevor der Text auf dem Bildschirm gedruckt wird. Wenn das Bild lokal zwischengespeichert worden wäre, hätte alles einen Bruchteil einer Sekunde gedauert. Details zum letzten Container können durch Ausführen von docker ps -l abgerufen werden:

 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af14bec37930 phusion/baseimage:latest "echo 'Hello Moby Do 2 minutes ago Exited (0) 3 seconds ago stoic_bardeen

Den nächsten Tauchgang machen

Wie Sie sehen, ist das Ausführen eines einfachen Befehls in Docker so einfach wie das direkte Ausführen auf einem Standard-Terminal. Um einen praktischeren Anwendungsfall zu veranschaulichen, werden wir im weiteren Verlauf dieses Artikels sehen, wie wir Docker verwenden können, um eine einfache Webserveranwendung bereitzustellen. Um die Dinge einfach zu halten, schreiben wir ein Java-Programm, das HTTP-GET-Anforderungen an „/ping“ verarbeitet und mit der Zeichenfolge „pong\n“ antwortet.

 import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class PingPong { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/ping", new MyHandler()); server.setExecutor(null); server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "pong\n"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }

Dockerfile

Bevor Sie einsteigen und Ihr eigenes Docker-Image erstellen, sollten Sie zunächst prüfen, ob im Docker-Hub oder in privaten Registrierungen, auf die Sie Zugriff haben, bereits eines vorhanden ist. Anstatt Java beispielsweise selbst zu installieren, verwenden wir ein offizielles Image: java:8 .

Um ein Image zu erstellen, müssen wir uns zuerst für ein Basis-Image entscheiden, das wir verwenden werden. Sie wird durch die FROM- Anweisung bezeichnet. Hier ist es ein offizielles Image für Java 8 vom Docker Hub. Wir werden es in unsere Java-Datei kopieren, indem wir eine COPY -Anweisung ausgeben. Als nächstes werden wir es mit RUN kompilieren. Die EXPOSE -Anweisung gibt an, dass das Image einen Dienst an einem bestimmten Port bereitstellt. ENTRYPOINT ist eine Anweisung, die wir ausführen möchten, wenn ein auf diesem Image basierender Container gestartet wird und CMD die Standardparameter angibt, die wir ihm übergeben werden.

 FROM java:8 COPY PingPong.java / RUN javac PingPong.java EXPOSE 8080 ENTRYPOINT ["java"] CMD ["PingPong"]

Nachdem wir diese Anweisungen in einer Datei namens „Dockerfile“ gespeichert haben, können wir das entsprechende Docker-Image erstellen, indem wir Folgendes ausführen:

 docker build -t toptal/pingpong .

Die offizielle Dokumentation für Docker enthält einen Abschnitt, der Best Practices zum Schreiben von Dockerfiles gewidmet ist.

Laufende Container

Wenn das Image erstellt wurde, können wir es als Container zum Leben erwecken. Es gibt mehrere Möglichkeiten, Container auszuführen, aber fangen wir mit einer einfachen an:

 docker run -d -p 8080:8080 toptal/pingpong

wobei -p [port-on-the-host]:[port-in-the-container] die Portzuordnung auf dem Host bzw. dem Container bezeichnet. Außerdem weisen wir Docker an, den Container als Daemon-Prozess im Hintergrund auszuführen, indem wir -d angeben . Sie können testen, ob die Webserveranwendung ausgeführt wird, indem Sie versuchen, auf „http://localhost:8080/ping“ zuzugreifen. Beachten Sie, dass Sie auf Plattformen, auf denen Boot2docker verwendet wird, „localhost“ durch die IP-Adresse der virtuellen Maschine ersetzen müssen, auf der Docker ausgeführt wird.

Unter Linux:

 curl http://localhost:8080/ping

Auf Plattformen, die Boot2Docker erfordern:

 curl $(boot2docker ip):8080/ping

Wenn alles gut geht, sollten Sie die Antwort sehen:

 pong

Hurra, unser erster benutzerdefinierter Docker-Container lebt und schwimmt! Wir könnten den Container auch im interaktiven Modus starten -i -t . In unserem Fall überschreiben wir den Entrypoint- Befehl, sodass uns ein Bash-Terminal angezeigt wird. Jetzt können wir beliebige Befehle ausführen, aber das Verlassen des Containers stoppt ihn:

 docker run -i -t --entrypoint="bash" toptal/pingpong

Für die Inbetriebnahme der Container stehen Ihnen noch viele weitere Möglichkeiten zur Verfügung. Lassen Sie uns ein paar mehr behandeln. Wenn wir beispielsweise Daten außerhalb des Containers beibehalten möchten, können wir das Host-Dateisystem mit dem Container teilen, indem wir -v verwenden. Standardmäßig ist der Zugriffsmodus „Lesen/Schreiben“, kann aber in den Nur-Lesen-Modus geändert werden, indem „ :ro “ an den Volume-Pfad innerhalb des Containers angehängt wird. Volumes sind besonders wichtig, wenn wir Sicherheitsinformationen wie Anmeldeinformationen und private Schlüssel innerhalb der Container verwenden müssen, die nicht auf dem Image gespeichert werden sollten. Darüber hinaus könnte es auch die Duplizierung von Daten verhindern, indem es beispielsweise Ihr lokales Maven-Repository dem Container zuordnet, um Ihnen das zweimalige Herunterladen aus dem Internet zu ersparen.

Docker hat auch die Fähigkeit, Container miteinander zu verknüpfen. Verknüpfte Container können miteinander kommunizieren, auch wenn keiner der Ports verfügbar ist. Dies kann mit –link other-container-name erreicht werden. Unten ist ein Beispiel für die Kombination der oben genannten Parameter:

 docker run -p 9999:8080 --link otherContainerA --link otherContainerB -v /Users/$USER/.m2/repository:/home/user/.m2/repository toptal/pingpong

Andere Container- und Image-Operationen

Es überrascht nicht, dass die Liste der Operationen, die man auf die Container und Images anwenden könnte, ziemlich lang ist. Lassen Sie uns der Kürze halber nur einige davon betrachten:

  • stop – Stoppt einen laufenden Container.
  • start – Startet einen angehaltenen Container.
  • commit – Erstellt ein neues Image aus den Änderungen eines Containers.
  • rm - Entfernt einen oder mehrere Container.
  • rmi - Entfernt ein oder mehrere Bilder.
  • ps - Listet Container auf.
  • images - Listet Bilder auf.
  • exec – Führt einen Befehl in einem laufenden Container aus.

Der letzte Befehl könnte besonders nützlich für Debugging-Zwecke sein, da Sie damit eine Verbindung zu einem Terminal eines laufenden Containers herstellen können:

 docker exec -i -t <container-id> bash

Docker Compose für die Microservice-Welt

Wenn Sie mehr als nur ein paar miteinander verbundene Container haben, ist es sinnvoll, ein Tool wie docker-compose zu verwenden. In einer Konfigurationsdatei beschreiben Sie, wie die Container gestartet und wie sie miteinander verknüpft werden sollen. Unabhängig von der Anzahl der beteiligten Container und ihrer Abhängigkeiten könnten Sie alle mit einem Befehl zum Laufen bringen: docker-compose up .

Docker in freier Wildbahn

Lassen Sie uns einen Blick auf drei Phasen des Projektlebenszyklus werfen und sehen, wie unser freundlicher Wal helfen könnte.

Entwicklung

Docker hilft Ihnen, Ihre lokale Entwicklungsumgebung sauber zu halten. Anstatt mehrere Versionen verschiedener Dienste wie Java, Kafka, Spark, Cassandra usw. installiert zu haben, können Sie bei Bedarf einfach einen erforderlichen Container starten und stoppen. Sie können noch einen Schritt weiter gehen und mehrere Software-Stacks nebeneinander ausführen, um die Verwechslung von Abhängigkeitsversionen zu vermeiden.

Mit Docker können Sie Zeit, Aufwand und Geld sparen. Wenn die Einrichtung Ihres Projekts sehr komplex ist, „docken“ Sie es an. Machen Sie sich einmal die Mühe, ein Docker-Image zu erstellen, und von diesem Punkt an kann jeder im Handumdrehen einen Container starten.

Sie können auch eine „Integrationsumgebung“ lokal (oder auf CI) ausführen und Stubs durch echte Dienste ersetzen, die in Docker-Containern ausgeführt werden.

Testen / Kontinuierliche Integration

Mit Dockerfile ist es einfach, reproduzierbare Builds zu erreichen. Jenkins oder andere CI-Lösungen können so konfiguriert werden, dass für jeden Build ein Docker-Image erstellt wird. Sie können einige oder alle Images zur späteren Bezugnahme in einer privaten Docker-Registrierung speichern.

Mit Docker testen Sie nur, was getestet werden muss, und nehmen die Umgebung aus der Gleichung heraus. Das Durchführen von Tests an einem laufenden Container kann dazu beitragen, die Dinge viel vorhersehbarer zu machen.

Ein weiteres interessantes Merkmal von Software-Containern ist, dass es einfach ist, Slave-Maschinen mit identischem Entwicklungs-Setup auszugliedern. Dies kann besonders nützlich für Lasttests von geclusterten Bereitstellungen sein.

Produktion

Docker kann eine gemeinsame Schnittstelle zwischen Entwicklern und Betriebspersonal sein, wodurch Reibungspunkte beseitigt werden. Es ermutigt auch dazu, bei jedem Schritt der Pipeline dieselben Bilder/Binärdateien zu verwenden. Darüber hinaus hilft die Möglichkeit, vollständig getestete Container ohne Umgebungsunterschiede bereitzustellen, um sicherzustellen, dass keine Fehler in den Build-Prozess eingeführt werden.

Sie können Anwendungen nahtlos in die Produktion migrieren. Etwas, das einst ein mühsamer und flockiger Prozess war, kann jetzt so einfach sein wie:

 docker stop container-id; docker run new-image

Und wenn bei der Bereitstellung einer neuen Version etwas schief geht, können Sie jederzeit schnell ein Rollback durchführen oder zu einem anderen Container wechseln:

 docker stop container-id; docker start other-container-id

… hinterlässt garantiert keinen Dreck oder einen inkonsistenten Zustand.

Zusammenfassung

Eine gute Zusammenfassung dessen, was Docker tut, ist in seinem eigenen Motto enthalten: Build, Ship, Run.

  • Build – Mit Docker können Sie Ihre Anwendung aus Microservices zusammenstellen, ohne sich Gedanken über Inkonsistenzen zwischen Entwicklungs- und Produktionsumgebungen machen zu müssen und ohne sich auf eine Plattform oder Sprache festzulegen.
  • Mit Ship - Docker können Sie den gesamten Zyklus der Anwendungsentwicklung, des Testens und der Verteilung entwerfen und mit einer konsistenten Benutzeroberfläche verwalten.
  • Run - Docker bietet Ihnen die Möglichkeit, skalierbare Dienste sicher und zuverlässig auf einer Vielzahl von Plattformen bereitzustellen.

Viel Spaß beim Schwimmen mit den Walen!

Ein Teil dieser Arbeit ist von einem ausgezeichneten Buch Using Docker von Adrian Mouat inspiriert.