Was ist Docker: eine kurze Geschichte und grundlegende Abstraktionen

Begonnen am 10. August in Slurm Docker-Videokurs, in dem wir es vollständig analysieren – von grundlegenden Abstraktionen bis hin zu Netzwerkparametern.

In diesem Artikel werden wir über die Geschichte von Docker und seine wichtigsten Abstraktionen sprechen: Image, Cli, Dockerfile. Da sich die Vorlesung an Einsteiger richtet, dürfte sie für erfahrene Anwender kaum von Interesse sein. Es wird kein Blut, Blinddarm oder tiefes Eintauchen geben. Die Grundlagen.

Was ist Docker: eine kurze Geschichte und grundlegende Abstraktionen

Was ist Docker?

Schauen wir uns die Definition von Docker aus Wikipedia an.

Docker ist eine Software zur Automatisierung der Bereitstellung und Verwaltung von Anwendungen in Containerumgebungen.

Aus dieser Definition geht nichts klar hervor. Es ist insbesondere unklar, was „in Umgebungen, die die Containerisierung unterstützen“ bedeutet. Um das herauszufinden, gehen wir in die Vergangenheit zurück. Beginnen wir mit der Ära, die ich üblicherweise die „monolithische Ära“ nenne.

Monolithische Ära

Die monolithische Ära beginnt in den frühen 2000er Jahren, als alle Anwendungen monolithisch waren und eine Reihe von Abhängigkeiten aufwiesen. Die Entwicklung dauerte lange. Gleichzeitig gab es nicht viele Server; wir alle kannten sie mit Namen und überwachten sie. Es gibt so einen lustigen Vergleich:

Haustiere sind Haustiere. Im monolithischen Zeitalter behandelten wir unsere Kellner wie Haustiere, pflegten sie und bliesen Staubkörnchen weg. Und für ein besseres Ressourcenmanagement haben wir Virtualisierung eingesetzt: Wir haben einen Server genommen und ihn in mehrere virtuelle Maschinen aufgeteilt, um so die Isolierung der Umgebung sicherzustellen.

Hypervisorbasierte Virtualisierungssysteme

Jeder hat wahrscheinlich von Virtualisierungssystemen gehört: VMware, VirtualBox, Hyper-V, Qemu KVM usw. Sie bieten Anwendungsisolation und Ressourcenverwaltung, haben aber auch Nachteile. Für die Virtualisierung benötigen Sie einen Hypervisor. Und der Hypervisor ist ein Ressourcenaufwand. Und die virtuelle Maschine selbst ist normalerweise ein ganzer Koloss – ein umfangreiches Image, das ein Betriebssystem, Nginx, Apache und möglicherweise MySQL enthält. Das Bild ist groß und die Bedienung der virtuellen Maschine ist unpraktisch. Daher kann die Arbeit mit virtuellen Maschinen langsam sein. Um dieses Problem zu lösen, wurden Virtualisierungssysteme auf Kernel-Ebene erstellt.

Virtualisierungssysteme auf Kernel-Ebene

Virtualisierung auf Kernel-Ebene wird von OpenVZ-, Systemd-nspawn- und LXC-Systemen unterstützt. Ein markantes Beispiel für eine solche Virtualisierung ist LXC (Linux Containers).

LXC ist ein Virtualisierungssystem auf Betriebssystemebene zum Ausführen mehrerer isolierter Instanzen des Linux-Betriebssystems auf einem einzelnen Knoten. LXC verwendet keine virtuellen Maschinen, sondern erstellt eine virtuelle Umgebung mit eigenem Prozessraum und Netzwerkstapel.

Im Wesentlichen erstellt LXC Container. Was ist der Unterschied zwischen virtuellen Maschinen und Containern?

Was ist Docker: eine kurze Geschichte und grundlegende Abstraktionen

Der Container eignet sich nicht zur Isolierung von Prozessen: In Virtualisierungssystemen finden sich auf Kernel-Ebene Schwachstellen, die es ihnen ermöglichen, aus dem Container auf den Host zu gelangen. Wenn Sie also etwas isolieren müssen, ist es besser, eine virtuelle Maschine zu verwenden.

Die Unterschiede zwischen Virtualisierung und Containerisierung sind im Diagramm zu sehen.
Es gibt Hardware-Hypervisoren, Hypervisoren auf dem Betriebssystem und Container.

Was ist Docker: eine kurze Geschichte und grundlegende Abstraktionen

Hardware-Hypervisoren sind cool, wenn Sie etwas wirklich isolieren möchten. Weil es möglich ist, auf der Ebene von Speicherseiten und Prozessoren zu isolieren.

Es gibt Hypervisoren als Programm und es gibt Container, über die wir weiter unten sprechen werden. Containerisierungssysteme haben keinen Hypervisor, aber es gibt eine Container Engine, die Container erstellt und verwaltet. Dieses Ding ist leichter, sodass durch die Arbeit mit dem Kern weniger oder gar kein Overhead entsteht.

Was wird für die Containerisierung auf Kernel-Ebene verwendet?

Die wichtigsten Technologien, mit denen Sie einen von anderen Prozessen isolierten Container erstellen können, sind Namespaces und Kontrollgruppen.

Namespaces: PID, Netzwerk, Mount und Benutzer. Es gibt noch mehr, aber zum besseren Verständnis konzentrieren wir uns auf diese.

Der PID-Namespace begrenzt Prozesse. Wenn wir beispielsweise einen PID-Namespace erstellen und dort einen Prozess platzieren, erhält dieser die PID 1. Normalerweise ist PID 1 in Systemen systemd oder init. Wenn wir einen Prozess in einem neuen Namensraum platzieren, erhält er dementsprechend auch PID 1.

Mit dem Netzwerk-Namespace können Sie das Netzwerk einschränken/isolieren und Ihre eigenen Schnittstellen darin platzieren. Mount ist eine Einschränkung des Dateisystems. Benutzer – Einschränkung der Benutzer.

Kontrollgruppen: Speicher, CPU, IOPS, Netzwerk – insgesamt etwa 12 Einstellungen. Ansonsten werden sie auch Cgroups („C-Gruppen“) genannt.

Kontrollgruppen verwalten Ressourcen für einen Container. Durch Kontrollgruppen können wir sagen, dass der Container nicht mehr als eine bestimmte Menge an Ressourcen verbrauchen sollte.

Damit die Containerisierung vollständig funktioniert, werden zusätzliche Technologien verwendet: Capabilities, Copy-on-Write und andere.

Bei Fähigkeiten sagen wir einem Prozess, was er kann und was nicht. Auf Kernel-Ebene handelt es sich lediglich um Bitmaps mit vielen Parametern. Beispielsweise hat der Root-Benutzer alle Rechte und kann alles tun. Der Zeitserver kann die Systemzeit ändern: Er verfügt über Funktionen auf der Time Capsule, und das war's. Mithilfe von Privilegien können Sie Einschränkungen für Prozesse flexibel konfigurieren und sich so schützen.

Das Copy-on-Write-System ermöglicht es uns, mit Docker-Images zu arbeiten und diese effizienter zu nutzen.

Docker hat derzeit Kompatibilitätsprobleme mit Cgroups v2, daher konzentriert sich dieser Artikel speziell auf Cgroups v1.

Aber kommen wir zurück zur Geschichte.

Als Virtualisierungssysteme auf Kernelebene auftauchten, wurden sie aktiv genutzt. Der Overhead für den Hypervisor verschwand, einige Probleme blieben jedoch bestehen:

  • große Bilder: Sie schieben ein Betriebssystem, Bibliotheken und eine Reihe verschiedener Software in dasselbe OpenVZ, und am Ende ist das Bild immer noch ziemlich groß.
  • Es gibt keinen normalen Standard für Verpackung und Lieferung, daher bleibt das Problem der Abhängigkeiten bestehen. Es gibt Situationen, in denen zwei Codeteile dieselbe Bibliothek verwenden, jedoch unterschiedliche Versionen. Möglicherweise besteht ein Konflikt zwischen ihnen.

Um all diese Probleme zu lösen, ist die nächste Ära gekommen.

Container-Ära

Als die Ära der Container anbrach, änderte sich die Philosophie der Arbeit mit ihnen:

  • Ein Prozess – ein Container.
  • Wir liefern alle Abhängigkeiten, die der Prozess benötigt, zu seinem Container. Dies erfordert das Zerlegen von Monolithen in Microservices.
  • Je kleiner das Image, desto besser – es gibt weniger mögliche Schwachstellen, es wird schneller bereitgestellt und so weiter.
  • Instanzen werden vergänglich.

Erinnern Sie sich, was ich über Haustiere vs. Rinder gesagt habe? Früher waren Instanzen wie Haustiere, aber jetzt sind sie wie Rinder geworden. Zuvor gab es einen Monolithen – eine Anwendung. Jetzt sind es 100 Microservices, 100 Container. Einige Container enthalten möglicherweise 2–3 Replikate. Es wird für uns immer weniger wichtig, jeden Container zu kontrollieren. Was für uns wichtiger ist, ist die Verfügbarkeit des Dienstes selbst: was dieser Containersatz leistet. Dies verändert die Ansätze zur Überwachung.

In den Jahren 2014-2015 blühte Docker auf – die Technologie, über die wir jetzt sprechen werden.

Docker hat die Philosophie geändert und die Anwendungsverpackung standardisiert. Mit Docker können wir eine Anwendung verpacken, an ein Repository senden, von dort herunterladen und bereitstellen.

Wir legen alles, was wir brauchen, in den Docker-Container, damit das Abhängigkeitsproblem gelöst ist. Docker garantiert Reproduzierbarkeit. Ich denke, viele Menschen sind auf Unreproduzierbarkeit gestoßen: Alles funktioniert für Sie, Sie bringen es in die Produktion und dort funktioniert es nicht mehr. Mit Docker verschwindet dieses Problem. Wenn Ihr Docker-Container startet und tut, was er tun muss, dann wird er mit hoher Wahrscheinlichkeit in der Produktion starten und dort dasselbe tun.

Exkurs zum Overhead

Es gibt immer wieder Streitigkeiten über Gemeinkosten. Manche Leute glauben, dass Docker keine zusätzliche Last mit sich bringt, da es den Linux-Kernel und alle seine für die Containerisierung notwendigen Prozesse nutzt. Zum Beispiel: „Wenn Sie sagen, dass Docker Overhead ist, dann ist der Linux-Kernel Overhead.“

Wenn man jedoch genauer hinschaut, gibt es in Docker tatsächlich mehrere Dinge, die man mit Abstand als Overhead bezeichnen kann.

Der erste ist der PID-Namespace. Wenn wir einen Prozess in einem Namespace platzieren, wird ihm PID 1 zugewiesen. Gleichzeitig verfügt dieser Prozess über eine andere PID, die sich auf dem Host-Namespace außerhalb des Containers befindet. Wir haben beispielsweise Nginx in einem Container gestartet, es wurde PID 1 (Master-Prozess). Und auf dem Host hat es PID 12623. Und es ist schwer zu sagen, wie hoch der Overhead ist.

Die zweite Sache sind Cgroups. Betrachten wir Cgroups nach Speicher, also nach der Möglichkeit, den Speicher eines Containers zu begrenzen. Wenn es aktiviert ist, werden Zähler und Speicherabrechnung aktiviert: Der Kernel muss verstehen, wie viele Seiten zugewiesen wurden und wie viele noch für diesen Container frei sind. Dies ist möglicherweise ein Overhead, aber ich habe keine genauen Studien darüber gesehen, wie sich dies auf die Leistung auswirkt. Und ich selbst habe nicht bemerkt, dass die in Docker laufende Anwendung plötzlich einen starken Leistungsverlust erfuhr.

Und noch eine Anmerkung zur Leistung. Einige Kernel-Parameter werden vom Host an den Container übergeben. Insbesondere einige Netzwerkparameter. Wenn Sie also beispielsweise etwas Hochleistungsfähiges in Docker ausführen möchten, das das Netzwerk aktiv nutzt, müssen Sie zumindest diese Parameter anpassen. Etwas nf_conntrack zum Beispiel.

Über das Docker-Konzept

Docker besteht aus mehreren Komponenten:

  1. Docker Daemon ist die gleiche Container Engine; startet Container.
  2. Docker CII ist ein Docker-Verwaltungsdienstprogramm.
  3. Dockerfile – Anweisungen zum Erstellen eines Images.
  4. Bild – das Bild, aus dem der Container ausgerollt wird.
  5. Container.
  6. Die Docker-Registrierung ist ein Image-Repository.

Schematisch sieht es etwa so aus:

Was ist Docker: eine kurze Geschichte und grundlegende Abstraktionen

Der Docker-Daemon läuft auf Docker_host und startet Container. Es gibt einen Client, der Befehle sendet: Image erstellen, Image herunterladen, Container starten. Der Docker-Daemon geht zur Registrierung und führt sie aus. Der Docker-Client kann sowohl lokal (auf einen Unix-Socket) als auch über TCP von einem Remote-Host aus zugreifen.

Lassen Sie uns jede Komponente durchgehen.

Docker-Daemon - Dies ist der Serverteil, er funktioniert auf dem Host-Computer: lädt Bilder herunter und startet Container von ihnen, erstellt ein Netzwerk zwischen Containern, sammelt Protokolle. Wenn wir sagen „Erschaffe ein Bild“, tut der Dämon das auch.

Docker-CLI – Docker-Client-Teil, Konsolen-Dienstprogramm für die Arbeit mit dem Daemon. Ich wiederhole, es kann nicht nur lokal, sondern auch über das Netzwerk funktionieren.

Grundbefehle:

docker ps – Container anzeigen, die derzeit auf dem Docker-Host ausgeführt werden.
Docker-Bilder – lokal heruntergeladene Bilder anzeigen.
Docker-Suche <> – Suche nach einem Bild in der Registrierung.
Docker Pull <> – Laden Sie ein Image aus der Registrierung auf den Computer herunter.
Docker-Build < > - Sammeln Sie das Bild.
docker run <> – Container starten.
docker rm <> – Container entfernen.
Docker-Protokolle <> – Containerprotokolle
Docker start/stop/restart <> – Arbeiten mit dem Container

Wenn Sie diese Befehle beherrschen und sicher damit umgehen können, gehen Sie davon aus, dass Sie Docker auf Benutzerebene zu 70 % beherrschen.

Dockerfile - Anweisungen zum Erstellen eines Bildes. Fast jeder Befehl ist eine neue Ebene. Schauen wir uns ein Beispiel an.

Was ist Docker: eine kurze Geschichte und grundlegende Abstraktionen

So sieht die Docker-Datei aus: links Befehle, rechts Argumente. Jeder Befehl, der hier steht (und im Allgemeinen in der Docker-Datei geschrieben ist), erstellt eine neue Ebene in Image.

Selbst wenn Sie auf die linke Seite schauen, können Sie ungefähr verstehen, was passiert. Wir sagen: „Erstelle einen Ordner für uns“ – das ist eine Ebene. „Den Ordner zum Laufen bringen“ ist eine weitere Ebene und so weiter. Schichtkuchen macht das Leben einfacher. Wenn ich eine weitere Docker-Datei erstelle und etwas in der letzten Zeile ändere – ich führe etwas anderes als „python“, „main.py“ aus oder installiere Abhängigkeiten von einer anderen Datei – dann werden die vorherigen Ebenen als Cache wiederverwendet.

Bild - Dies ist eine Containerverpackung; Container werden vom Bild aus gestartet. Wenn wir Docker aus der Sicht eines Paketmanagers betrachten (als würden wir mit Deb- oder RPM-Paketen arbeiten), dann ist Image im Wesentlichen ein RPM-Paket. Mit yum install können wir die Anwendung installieren, löschen, im Repository finden und herunterladen. Hier ist es ungefähr das Gleiche: Container werden vom Image aus gestartet, sie werden in der Docker-Registrierung gespeichert (ähnlich wie bei yum in einem Repository) und jedes Image hat einen SHA-256-Hash, einen Namen und ein Tag.

Das Image wird gemäß den Anweisungen aus der Docker-Datei erstellt. Jede Anweisung aus der Docker-Datei erstellt eine neue Ebene. Ebenen können wiederverwendet werden.

Docker-Registrierung ist ein Docker-Image-Repository. Ähnlich wie das Betriebssystem verfügt Docker über eine öffentliche Standardregistrierung – Dockerhub. Sie können jedoch Ihr eigenes Repository, Ihre eigene Docker-Registrierung, erstellen.

Container - was aus dem Bild gestartet wird. Wir haben ein Image gemäß den Anweisungen aus der Docker-Datei erstellt und es dann von diesem Image aus gestartet. Dieser Container ist von anderen Containern isoliert und muss alles enthalten, was für die Funktion der Anwendung erforderlich ist. In diesem Fall ein Container – ein Prozess. Es kommt vor, dass man zwei Prozesse durchführen muss, aber das widerspricht etwas der Docker-Ideologie.

Die Anforderung „ein Container, ein Prozess“ bezieht sich auf den PID-Namespace. Wenn ein Prozess mit PID 1 im Namespace startet und plötzlich stirbt, stirbt auch der gesamte Container. Wenn dort zwei Prozesse laufen: einer ist lebendig und der andere tot, dann lebt der Container trotzdem weiter. Aber das ist eine Frage der Best Practices, wir werden in anderen Materialien darüber sprechen.

Um die Funktionen und das vollständige Programm des Kurses genauer zu studieren, folgen Sie bitte dem Link: „Docker-Videokurs".

Autor: Marcel Ibraev, zertifizierter Kubernetes-Administrator, praktizierender Ingenieur bei Southbridge, Referent und Entwickler von Slurm-Kursen.

Source: habr.com

Kommentar hinzufügen