Was ist Docker: ein kurzer Überblick ĂŒber die Geschichte und die wichtigsten Abstraktionen

Am 10. August startete ein Videokurs zu Docker, in dem wir ihn vollstĂ€ndig behandeln – von den grundlegenden Abstraktionen bis zu den Netzwerkkonfigurationen.

In diesem Artikel sprechen wir ĂŒber die Geschichte von Docker und seine grundlegenden Abstraktionen: Image, Cli, Dockerfile. Die Lektion richtet sich an AnfĂ€nger, weshalb sie wahrscheinlich fĂŒr erfahrene Benutzer nicht interessant sein wird. Hier wird es keine Blutbad, keinen Blinddarm und kein tiefes Eintauchen geben. Die Grundlagen.

Was ist Docker: ein kurzer Überblick ĂŒber die Geschichte und die wichtigsten Abstraktionen

Was ist Docker

Schauen wir uns die Definition von Docker auf Wikipedia an.

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

Aus dieser Definition wird nichts klar. Besonders unklar ist, was „in containerisierten Umgebungen“ bedeutet. Um dies zu klĂ€ren, gehen wir zurĂŒck in die Vergangenheit. Beginnen wir in der Zeit, die ich bedingt als „monolithische Ära“ bezeichne.

Monolithische Ära

Die monolithische Ära ist der Anfang der 2000er Jahre, als alle Anwendungen monolithisch waren und viele AbhĂ€ngigkeiten hatten. Die Entwicklung dauerte lange. Zu dieser Zeit gab es nicht viele Server, wir kannten sie alle beim Namen und ĂŒberwachten sie. Es gibt einen amĂŒsanten Vergleich:

Video abspielen

Haustiere – das sind Haustiere. In der monolithischen Ära behandelten wir unsere Server wie Haustiere, pflegten und hegen sie und bliesen den Staub ab. Um die Ressourcen besser zu verwalten, nutzten wir Virtualisierung: Wir nahmen einen Server und teilten ihn in mehrere virtuelle Maschinen, um damit Isolierung der Umgebung zu gewĂ€hrleisten.

Virtualisierungssysteme auf Hypervisor-Basis

Über Virtualisierungssysteme hat sicherlich jeder schon gehört: VMware, VirtualBox, Hyper-V, Qemu KVM usw. Sie bieten Isolierung fĂŒr Anwendungen und Ressourcenmanagement, haben jedoch auch Nachteile. Um Virtualisierung zu ermöglichen, wird ein Hypervisor benötigt. Und ein Hypervisor bedeutet RessourcenĂŒberkopf. Außerdem ist die virtuelle Maschine normalerweise ziemlich groß – ein schweres Image, mit einem Betriebssystem, Nginx, Apache und möglicherweise MySQL. Das Image ist groß, und es ist unpraktisch, mit einer virtuellen Maschine zu arbeiten. Infolgedessen kann die Arbeit mit virtuellen Maschinen langsam sein. Um dieses Problem zu lösen, wurden Virtualisierungssysteme auf Kernel-Ebene entwickelt.

Virtualisierungssysteme auf Kernel-Ebene

Die Virtualisierung auf Kernel-Ebene wird von Systemen wie OpenVZ, Systemd-nspawn, LXC unterstĂŒtzt. Ein bekanntes Beispiel fĂŒr eine solche Virtualisierung ist LXC (Linux-Container).

LXC ist ein Betriebssystem-Virtualisierungssystem, das es ermöglicht, mehrere isolierte Instanzen des Betriebssystems Linux auf einem einzigen Knoten auszufĂŒhren. LXC verwendet keine virtuellen Maschinen, sondern erstellt eine virtuelle Umgebung mit eigenem Prozess- und Netzwerk-Stack.

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

Was ist Docker: ein kurzer Überblick ĂŒber die Geschichte und die wichtigsten Abstraktionen

Container sind nicht geeignet, um Prozesse zu isolieren: In Virtualisierungssystemen auf Kernel-Ebene werden Schwachstellen gefunden, die es erlauben, 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 in dem Diagramm zu sehen.
Es gibt hardwarebasierte Hypervisoren, Hypervisoren ĂŒber Betriebssystemen und Container.

Was ist Docker: ein kurzer Überblick ĂŒber die Geschichte und die wichtigsten Abstraktionen

Hardware-Hypervisoren sind eine großartige Lösung, wenn Sie tatsĂ€chlich etwas isolieren möchten. Denn dort gibt es die Möglichkeit, auf Seitenebene und Prozessoren zu isolieren.

Es gibt Hypervisoren als Programme und es gibt Container, ĂŒber die wir nun sprechen werden. In Containerisierungssystemen gibt es keinen Hypervisor, aber es gibt eine Container-Engine, die Container erstellt und verwaltet. Diese Lösung ist leichter, weshalb der Overhead durch die Arbeit mit dem Kernel geringer ist oder gar nicht vorhanden.

Was wird auf Kernel-Ebene zur Containerisierung verwendet?

Die Haupttechnologien, die es ermöglichen, einen von anderen Prozessen isolierten Container zu erstellen, sind Namespaces und Control Groups.

Namespaces: PID, Networking, Mount und User. Es gibt noch weitere, aber der Einfachheit halber konzentrieren wir uns auf diese.

Der PID-Namespace beschrÀnkt Prozesse. Wenn wir beispielsweise einen PID-Namespace erstellen und einen Prozess dort platzieren, erhÀlt dieser die PID 1. Normalerweise ist PID 1 in Systemen systemd oder init. Wenn wir also einen Prozess in einen neuen Namespace verschieben, erhÀlt er ebenfalls die PID 1.

Der Networking-Namespace ermöglicht es, das Netzwerk zu beschrÀnken/zu isolieren und darin eigene Schnittstellen zu platzieren. Mount ist die BeschrÀnkung des Dateisystems. User ist die BeschrÀnkung der Benutzer.

Control Groups: Memory, CPU, IOPS, Network – insgesamt etwa 12 Einstellungen. Sie werden auch Cgroups („C-Gruppen“) genannt.

Control Groups verwalten die Ressourcen fĂŒr den Container. Über Control Groups können wir festlegen, dass der Container nicht mehr als eine bestimmte Menge an Ressourcen verbrauchen darf.

Damit die Containerisierung voll funktionsfÀhig ist, werden zusÀtzliche Technologien verwendet: Capabilities, Copy-on-Write und andere.

Capabilities sind die Vorgaben, was ein Prozess machen darf und was nicht. Auf Kernel-Ebene sind das einfach Bitmaps mit vielen Parametern. Zum Beispiel hat der Benutzer root volle Berechtigungen und kann alles tun. Der Zeitserver kann die Systemzeit Ă€ndern: Er hat Capabilities fĂŒr die Time Capsule, und das war's. Mit Privilegien können EinschrĂ€nkungen fĂŒr Prozesse flexibel eingestellt und somit die Sicherheit erhöht werden.

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

Zurzeit hat Docker KompatibilitÀtsprobleme mit Cgroups v2, daher behandelt der Artikel speziell Cgroups v1.

Aber kehren wir zur Geschichte zurĂŒck.

Mit dem Aufkommen von Virtualisierungssystemen auf Kernel-Ebene wurden diese aktiv eingesetzt. Der Overhead auf den Hypervisor entfiel, aber einige Probleme blieben bestehen:

  • große Images: In die gleiche OpenVZ werden das Betriebssystem, Bibliotheken und eine Menge anderer Software gedrĂ€ngt, und am Ende ist das Image immer noch recht groß;
  • es gibt keinen ordentlichen Standard fĂŒr Verpackung und Lieferung, was das AbhĂ€ngigkeitsproblem verursacht. Es gibt Situationen, in denen zwei Code-StĂŒcke die gleiche Bibliothek, aber mit unterschiedlichen Versionen verwenden. Zwischen ihnen kann es zu Konflikten kommen.

Um all diese Probleme zu lösen, kam die nĂ€chste Ära.

Die Ära der Container

Als die Ära der Container begann, Ă€nderte sich die Philosophie im Umgang mit ihnen:

  • Ein Prozess — ein Container.
  • Alle fĂŒr den Prozess notwendigen AbhĂ€ngigkeiten bringen wir in seinen Container. Das erfordert das Aufteilen von Monolithen in Mikrodienste.
  • Je kleiner das Image, desto besser — weniger mögliche Schwachstellen, schnellere Bereitstellung und so weiter.
  • Instanzen werden flĂŒchtig.

Erinnert ihr euch, ich habe von Haustieren vs. Vieh gesprochen? FrĂŒher waren Instanzen wie Haustiere, jetzt sind sie wie Vieh. FrĂŒher gab es einen Monolithen — eine Anwendung. Jetzt sind es 100 Mikrodienste, 100 Container. Einige Container können 2-3 Replikate haben. Es ist uns nicht mehr so wichtig, jeden Container zu kontrollieren. Vielmehr ist uns die VerfĂŒgbarkeit des Dienstes selbst wichtig: das, was diese Gruppe von Containern tut. Das verĂ€ndert die AnsĂ€tze im Monitoring.

In den Jahren 2014-2015 erlebte Docker einen Aufschwung — diese Technologie, ĂŒber die wir jetzt sprechen werden.

Docker hat die Philosophie verÀndert und die Verpackung von Anwendungen standardisiert. Mit Docker können wir eine Anwendung verpacken, sie in ein Repository senden, von dort herunterladen und bereitstellen.

In einem Docker-Container packen wir alles Notwendige, weshalb das Problem der AbhÀngigkeiten gelöst ist. Docker garantiert Reproduzierbarkeit. Ich denke, viele haben schon einmal das Problem der Nicht-Reproduzierbarkeit erlebt: Es funktioniert alles bei dir, aber sobald du es ins Produktionssystem lÀdst, funktioniert es dort nicht mehr. Mit Docker gehört dieses Problem der Vergangenheit an. Wenn dein Docker-Container startet und das tut, was er tun soll, dann wird er mit hoher Wahrscheinlichkeit auch in der Produktion starten und das Gleiche tun.

Ein Exkurs ĂŒber Overhead

Es gibt stĂ€ndig Diskussionen ĂŒber den Overhead. Einige glauben, dass Docker keine zusĂ€tzliche Belastung darstellt, da es den Linux-Kernel und alle seine Prozesse nutzt, die fĂŒr die Containerisierung erforderlich sind. Man sagt, "wenn du sagst, dass Docker Overhead ist, dann ist auch der Linux-Kernel Overhead".

Auf der anderen Seite gibt es tatsÀchlich einige Dinge in Docker, die man mit einem gewissen Vorbehalt als Overhead bezeichnen kann.

Erstens – der PID-Namespace. Wenn wir einen Prozess in einen Namespace setzen, erhĂ€lt er die PID 1. Gleichzeitig hat dieser Prozess eine weitere PID, die sich im Host-Namespace außerhalb des Containers befindet. Zum Beispiel, wenn wir Nginx in einem Container starten, wird es PID 1 (Hauptprozess). Auf dem Host hat es jedoch die PID 12623. Es ist schwer zu sagen, wie viel Overhead das verursacht.

Das zweite Thema sind die Cgroups. Nehmen wir Cgroups fĂŒr den Speicher, also die Möglichkeit, die SpeicherkapazitĂ€t des Containers zu begrenzen. Wenn diese aktiviert werden, aktivieren sich ZĂ€hler, die memory accounting ermöglichen: Der Kernel muss verstehen, wie viele Seiten zugewiesen wurden und wie viele noch frei sind fĂŒr diesen Container. Das könnte ein Overhead sein, aber ich habe keine genauen Studien gesehen, die zeigen, wie sich das auf die Leistung auswirkt. Ich habe selbst nicht bemerkt, dass Anwendungen, die in Docker ausgefĂŒhrt werden, plötzlich erheblich an Leistung verlieren.

Und noch eine Bemerkung zur Leistung. Einige Kernel-Parameter werden vom Host in den Container ĂŒbertragen. Insbesondere einige Netzwerkeinstellungen. Wenn du also etwas HochleistungsfĂ€higes in Docker starten möchtest, zum Beispiel etwas, das das Netzwerk intensiv nutzen wird, musst du diese Parameter zumindest anpassen. Beispielsweise nf_conntrack.

Über das Konzept von Docker

Docker besteht aus mehreren Komponenten:

  1. Docker Daemon – das ist die Container Engine; sie startet Container.
  2. Docker CII – ist ein Tool zur Verwaltung von Docker.
  3. Dockerfile – eine Anleitung, wie man ein Image erstellt.
  4. Image – das Image, aus dem der Container erstellt wird.
  5. Container.
  6. Docker-Registry – ein Speicher fĂŒr Images.

Schematically sieht das ungefÀhr so aus:

Was ist Docker: ein kurzer Überblick ĂŒber die Geschichte und die wichtigsten Abstraktionen

Auf dem Docker_host lĂ€uft der Docker-Daemon, der Container startet. Es gibt einen Client, der Befehle ĂŒbertrĂ€gt: erstelle ein Image, lade ein Image herunter, starte einen Container. Der Docker-Daemon greift auf die Registry zu und fĂŒhrt diese aus. Der Docker-Client kann sowohl lokal (ĂŒber einen Unix-Socket) als auch ĂŒber TCP von einem Remote-Host aus zugreifen.

Lass uns jeden Bestandteil durchgehen.

Docker Daemon (Daemon) – ist der Serverteil, der auf der Host-Maschine lĂ€uft: er lĂ€dt Images herunter und startet Container daraus, erstellt Netzwerke zwischen Containern und sammelt Protokolle. Wenn wir sagen „Erstelle ein Image“, kĂŒmmert sich auch der Daemon darum.

Docker CLI – ist der Clientteil von Docker, ein Konsolen-Tool zur Arbeit mit dem Daemon. Ich möchte betonen, dass es nicht nur lokal, sondern auch ĂŒber das Netzwerk arbeiten kann.

Grundbefehle:

docker ps – zeigt die Container an, die momentan auf dem Docker-Host laufen.
docker images – zeigt die lokal heruntergeladenen Images an.
docker search – Suche nach einem Image in der Registry.
docker pull – Lade ein Image aus der Registry auf die Maschine.
docker build <> – erstelle ein Image.
docker run – starte den Container.
docker rm – lösche den Container.
docker logs – Protokolle des Containers
docker start/stop/restart – Arbeit mit dem Container

Wenn du diese Befehle beherrschst und sicher damit umgehst, kannst du davon ausgehen, dass du Docker als Benutzer zu 70 % gemeistert hast.

Dockerfile – Anleitung zur Erstellung eines Images. Fast jeder Befehl in der Anleitung ist eine neue Schicht. Schauen wir uns ein Beispiel an.

Was ist Docker: ein kurzer Überblick ĂŒber die Geschichte und die wichtigsten Abstraktionen

So sieht ein Dockerfile ungefÀhr aus: links die Befehle, rechts die Argumente. Jeder Befehl, der hier steht (und generell im Dockerfile steht), schafft eine neue Schicht im Image.

Selbst auf der linken Seite kann man ungefĂ€hr verstehen, was passiert. Wir sagen: „Erstelle uns einen Ordner“ – das ist eine Schicht. „Mach den Ordner zur Arbeitsumgebung“ – das ist eine weitere Schicht, und so weiter. Ein geschichteter Kuchen erleichtert das Leben. Wenn ich ein weiteres Dockerfile erstelle und in der letzten Zeile etwas Ă€ndere – nicht "python" "main.py" starte, sondern etwas anderes, oder AbhĂ€ngigkeiten aus einer anderen Datei installiere – dann werden die vorherigen Schichten wie Cache wiederverwendet.

Bild — das ist das Container-Image, aus dem Container gestartet werden. Wenn man Docker als Paketmanager betrachtet (als ob wir mit deb- oder rpm-Paketen arbeiten), dann ist das Image im Grunde ein rpm-Paket. Mit yum install können wir eine Anwendung installieren, sie entfernen, im Repository suchen und herunterladen. Hier ist es Ă€hnlich: Aus dem Image werden Container gestartet, sie werden im Docker-Registry (analog zu yum, im Repository) gespeichert, und jedes Image hat einen SHA-256-Hash, einen Namen und ein Tag.

Das Image wird nach den Anweisungen im Dockerfile erstellt. Jede Anweisung im Dockerfile erzeugt eine neue Schicht. Schichten können wiederverwendet werden.

Docker-Registry — das ist ein Repository fĂŒr Docker-Images. Analog zu Betriebssystemen hat Docker ein öffentliches Standard-Registry — dockerhub. Aber man kann auch sein eigenes Repository, seine eigene Docker-Registry erstellen.

Container — das, was aus dem Image gestartet wird. Nach der Anleitung im Dockerfile haben wir das Image erstellt, dann starten wir es aus diesem Image. Dieser Container ist von anderen Containern isoliert, er muss alles Notwendige fĂŒr den Betrieb der Anwendung enthalten. Dabei gilt: Ein Container — ein Prozess. Manchmal muss man zwei Prozesse erstellen, aber das widerspricht etwas der Ideologie von Docker.

Die Anforderung „ein Container — ein Prozess“ hĂ€ngt mit dem PID-Namespace zusammen. Wenn in einem Namespace ein Prozess mit PID 1 gestartet wird und dieser plötzlich stirbt, stirbt auch der gesamte Container. Wenn jedoch zwei Prozesse gestartet sind: einer lebt, der andere ist gestorben, bleibt der Container trotzdem am Leben. Aber das ist eine Frage der Best Practices, ĂŒber die wir in anderen Materialien sprechen werden.

Eine detailliertere Untersuchung der Besonderheiten und des vollstĂ€ndigen Kursprogramms finden Sie unter dem Link: „Videokurs ĂŒber Docker».

Autor: Marsel Ibraev, zertifizierter Kubernetes-Administrator, praktizierender Ingenieur bei Southbridge, Sprecher und Kursentwickler bei Sljorm.

Quelle: habr.com

60GB SSD 8Gb DDR4