Suche
Docker in production

Von Linux zu Docker: Die Grundlagen der Container-Technologie

Gianluca Arbezzano
© Shutterstock / MicroOne

© Shutterstock / MicroOne

 

Keine andere Technologie hat die IT in den letzten Jahren so geprägt wie Docker. Doch warum ist das so? Was macht Docker so besonders, wie funktioniert die Technologie unter der Haube und wie können Sie vom Trend profitieren?

In diesem mehrteiligen Tutorial gibt Docker Captain Gianluca Arbezzano eine Einführung in die Container-Technologie Docker. Behandelt werden die grundlegenden Mechanismen der Linux-Container, die Funktionsweise der Docker Engine und die Container-Orchestrierung in verteilten Systemen. Erfahren Sie alles, was Sie zum Einstieg in Docker wissen müssen.

Die Reihe ist Teil des Buchprojektes „Docker in Production“, das aktuell unter scaledocker.com entsteht. Im ersten Kapitel schauen wir uns an,  welche technologischen Grundlagen aus dem Linux-Ökosystem jeder kennen sollte, der mit Docker arbeiten möchte.

Es ist die Veränderung, die fortwährende, unausweichliche Veränderung, die den dominanten Faktor in der heutigen Gesellschaft darstellt. Keine vernünftige Entscheidung kann noch getroffen werden, ohne dabei nicht nur die Welt, wie sie ist, sondern auch die Welt, wie sie sein wird, in Betracht zu ziehen.

Isaac Asimov 1981

Von Linux zu Docker – die Grundlagen

Es gibt zwei Arten von Erfindungen: zum einen solche, die es uns erlauben, Dinge zu tun, die wir zuvor nicht tun konnten. Und zum anderen solche, die uns helfen, Dinge besser zu tun. Die Entdeckung des Feuers etwa ermöglichte dem Menschen, Nahrung zu kochen, wilde Tiere zu vertreiben und sich in kalten Nächten aufzuwärmen. Doch erst jetzt, viele Jahrtausende später, können wir dasselbe per Knopfdruck erledigen und unsere Häuser mittels Elektrizität heizen.

Genauso schuf die Erfindung des Rades die Voraussetzungen für Reisen und den Aufbau von Handel. Doch erst durch die Erfindung des Automobils wurde das Reisen und der Fernhandel in dem globalen Ausmaß möglich, wie wir es heute kennen.

Auf eine ähnliche Weise stellt das Web ein großes Netzwerk dar, das das Potential hat, Menschen auf der ganzen Welt miteinander zu verbinden. Doch erst durch die Entwicklung von Web-Anwendungen wurde es möglich, dieses komplexe System wirklich nutzbar zu machen und zu individualisieren.

In dieser Perspektive können Container als die größte Revolution der letzten Jahre angesehen werden: Durch Container haben wir ein einzigartiges Werkzeug zur Hand, das uns beim Verwalten und Entwickeln von Anwendungen behilflich ist. Doch verweilen wir noch ein wenig bei der Entstehungsgeschichte der Container-Technologie, denn bekanntlich ist die Kenntnis der Vergangenheit wichtig, um eine solide Zukunft zu erschaffen.

Dossier Docker
Im Themen-Dossier Docker erfahren Sie alles zum Durchstarten mit der Container-Technologie. Bisher erschienen:

Wie alles begann

Es gibt keine genauen Berichte darüber, warum Bill Joy am 18. März 1982 chroot in das Unix-System BSD (Berkeley Software Distribution) einbaute. Möglicherweise einfach, um Lösungen und Programme in einem isolierten Root-Verzeichnis zu emulieren. Das allein war schon bahnbrechend, doch dem nicht genug: 1991 erweiterte Bill Cheswick chroot um einige Sicherheits-Features, die von der FreeBSD-Distribution zur Verfügung gestellt wurden. Und er implementierte die “jails”. Erneut neun Jahre später, im Jahr 2000, führte er ein, was wir heute als das “jails-Kommando” kennen.

Ab diesem Zeitpunkt waren unsere chroots isoliert. Beim Start eines Prozesses in einer chroot ist der PID einzigartig, es gibt nur diesen einen Prozess. Lediglich von außen kann man alle Prozesse einsehen, die in einer chroot laufen.

Doch gab es ein Problem: Auf lange Sicht konnten unsere Anwendungen nicht in diesem völlig isolierten Gefängnis bleiben. Anwendungen müssen mit der Außenwelt kommunizieren, Informationen austauschen usw. Um dieses Problem zu lösen, implementierten Entwickler wie Eric W. Biderman und Pavel Emelyanov im Jahr 2002 das Namespace-Feature in der Kernel-Version 2.4.19. Dieses wird genutzt, um Systemressourcen wie das Netzwerk, die Prozesse und das Dateisystem zu verwalten.

All diese wunderbaren Features sind in den letzten Jahren im Zuge der “Container”-Bewegung populär geworden. Doch sind sie alles andere als neu! Vielleicht ist es aber auch genau das, was das Ganze so spannend macht: Alles Nötige existierte bereits seit langer Zeit unter der Haube. Es sind solide und getestete Features, die erst jetzt effektiv zusammengefügt und nutzbar gemacht wurden.

Dies ist nur ein kleiner Einblick in die Entstehungsgeschichte des Container-Ökosystems. Am Ende dieser Reihe werden wir versuchen nachzuvollziehen, warum schließlich Docker die Bühne betreten hat.

Isolation und Virtualisierung

Die Wichtigkeit der Isolation liegt eigentlich auf der Hand: Sie hilft uns, möglichst effizient Ressourcen und Sicherheitsaspekte zu verwalten. Sie erleichtert das Monitoring, um spezifische Fehler im System zu finden, die oftmals nichts mit unserer Anwendung zu tun haben.

Eine herkömmliche Lösung für diese Aufgaben ist die Virtualisierung: Man kann einen Hypervisor nutzen, um virtuelle Server auf einer einzelnen Maschine zu erstellen. Dabei gibt es verschiedene Arten der Virtualisierung:

  1. Vollständige Virtualisierung
  2. Teilweise Virtualisierung (virtuelle Maschinen, Xen oder VMware)
  3. Betriebssystemvirtualisierung (Container)
  4. Anwendungsvirtualisierung (JVM)

Der Hauptunterschied zwischen diesen Virtualisierungsmethoden liegt darin, wie sie die Layer, die Anwendungen, das Processing, das Netzwerk und den Speicher abstrahieren und wie die oberen Ebenen mit den darunterliegenden interagieren. Bei der vollständigen Virtualisierung wird beispielsweise die Hardware virtualisiert, bei der teilweisen Virtualisierung nicht.

Container sind eine Virtualisierung auf Ebene des Betriebssystems. Der Hauptunterschied zwischen Containern und einer virtuellen Maschine liegt in der Virtualisierungsschicht: Container arbeiten auf der Ebene des Betriebssystems, virtuelle Maschinen auf der Ebene der Hardware.

Sprechen wir von Containern, liegt unser Fokus auf der Anwendungsvirtualisierung und einem speziellen Feature, das vom Kernel zur Verfügung gestellt wird und Linux Containers (LXC) heißt. Erstellen wir Container, tun wir nichts anderes, als isolierte Linux-Systeme auf dem gleichen Host zu erzeugen. Das bedeutet, dass wir beispielsweise das Betriebssystem nicht ändern können, da unsere Virtualisierungsebene es nicht erlaubt, Linux Container außerhalb von Linux laufen zu lassen.

Container – warum überhaupt?

Revolutionen basieren nicht einfach nur auf einem bestimmten Ereignis. Sie sind das Resultat ganz unterschiedlicher Bewegungen und Veränderungen. Genauso sind Container nur ein kleines Puzzlestück einer größeren Geschichte.

Durch das Aufkommen des Cloud Computing hat sich unser Denken über Infrastruktur verändert. Heute verstehen wir darunter eine variable Anzahl an Servern, die sich bei Bedarf, mit einem vertretbaren Zeitaufwand und immer kostengünstiger hoch- bzw. herunterskalieren lassen. Anwendungen, die bislang vielleicht irgendwo in einem Keller auf eigenen Servern liefen, liegen nun bspw. auf AWS und verfügen über einen Load Balancer und verschiedene Verfügbarkeitszonen. Kleine Teams und mittelgroße Unternehmen ohne eigene Datenzentren und Infrastrukturen profitieren von dieser Entwicklung in besonderem Maße, da sie nun ebenfalls über Dinge wie Distribution, hohe Verfügbarkeit und Redundanz nachdenken können.

Wenn unsere Anwendungen in virtuellen Maschinen laufen, ist es einfacher, in einem wachsenden Unternehmen die Server entsprechend zu skalieren, um den Bedürfnissen der Kunden besser gerecht zu werden. Wir haben diese Vorteile zu schätzen gelernt, aber es gibt auch etliche neue Herausforderungen, etwa das Zeitmanagement, um dieser Dynamik nachzukommen. Die Skalierung großer Anwendungen ist in der Regel teurer: Typischerweise werden unsere Anwendungen immer größer, sodass das Deployment hohe Kosten verursachen kann.

Wir haben außerdem festgestellt, dass das Verhalten einer Anwendung nicht in allen Services und Entrypoints das gleiche ist: Manche erhalten mehr Traffic als andere. Also haben wir damit begonnen, unsere großen Anwendungen in mehrere Teile zu splitten, um sie leichter skalieren und überwachen zu können. Um unseren Anforderungen gerecht zu werden, musste allerdings ein Weg gefunden werden, die Anwendungsteile sicher und isoliert voneinander zu betreiben, ohne dass sie die Möglichkeit verlieren, miteinander zu kommunizieren.

Lesen Sie auch: Docker für Java-Entwickler

So entstand der Architekturansatz der Microservices, den sich große Unternehmen wie Netflix, Amazon und Google zu nutze machen. Heute bestehen deren Anwendungen aus hunderten von Microservices, die alle zu einem großen und profitablen Produkt zusammengefasst sind.

Netflix war eines der ersten Unternehmen, das der Öffentlichkeit einen Blick unter die Motorhaube gewährte, indem sie offen erklärten, wie die Seite Netflix.com aufgebaut wurde: Mehr als 400 Microservices sind dort im Einsatz, um Features wie die Registrierung, das Streaming, Rankings und viele weitere Teile der Anwendung zur Verfügung zu stellen.

Aktuell scheinen Container die beste Lösung zu sein, um eine dichte und dynamische Umgebung zu verwalten, in der hohe Kontroll- und Sicherheitsstandards herrschen und die Möglichkeit gegeben ist, Anwendungen zwischen verschiedenen Servern hin- und herzuschieben.

Linux Container im praktischen Einsatz

Wikipedia nennt das Jahr 2008 als Startpunkt für das LXC-Projekt – das Ganze ist also nicht wirklich neu. Joe Beda, Senior Software Engineer bei Google, spricht sogar davon, dass Container seit über zehn Jahren erfolgreich in der Produktion eingesetzt werden und betont, dass heutzutage alles innerhalb eines Containers laufen kann. Was Docker gemacht hat, ist, die Nutzung von Containern einfach zu gestalten. Dazu bietet Docker ein großes Ökosystem von Tools an, mit denen sich Container erstellen und betreiben lassen. Bevor wir aber auf Docker zu sprechen kommen, müssen wir erst einmal verstehen, was ein Linux Container im herkömmlichen Sinne ist und wie er eingesetzt wird.

Wenn wir Container nutzen wollen und dabei einige Features der Kernel Control Groups (cgroups) ansprechen, so sind Namespaces besonders wichtig. Die Control Group begrenzt und isoliert Ressourcen von Prozessen wie Memory, Swap, CPU, I/O und Netzwerk. Es stehen außerdem Funktionalitäten für die Verwaltung, das Monitoring und die Priorisierung sowie die Bearbeitung des Inhalts einer Kontrollgruppe zur Verfügung. Daher ist es sinnvoll, jeden Container mit genau einem Prozess zu verbinden, damit man die vollständige Kontrolle darüber hat, was dieser Prozess tut. Ein Namespace wrapt eine Ressource und macht diese für einen Prozess oder eine Gruppe von Prozessen verfügbar, die diesen Namespace verwenden. Es gibt verschiedene Arten von Namespaces: PID, Netzwerk, IPC, Mount, Users und andere.

Docker-Container nutzen cgroup, um Container zu isolieren. Namespaces dienen der Zugriffskontrolle, um die Container abzusichern. cgroups verbinden Tasks mit einem Set von Ressourcen:

  • memory, um ein Limit für die Speichernutzung zu setzen
  • cpu, um den Scheduler zu nutzen und der CPU Zugang zu einer cgroup zu gewähren
  • cpuset, um individuelle CPUs und Speicher-Nodes zuzuweisen
  • cpuacct, um automatische Berichte über die CPU-Nutzung in einer cgroup zu erhalten
  • devices, um einem spezifischen Gerät Zugang zu gewähren bzw. zu verweigern
  • blkio, um I/O-Limits von Block-Geräten wie Festplatten oder USB festzulegen
  • freezer, um Tasks in einer cgroup zu stoppen oder fortzuführen
  • ns, um Namespaces innerhalb einer cgroup zu verwenden
  • net_cls, um Linux Traffic Control zu ermöglichen, Pakete einer cgroup zu identifizieren
  • net_prio, um den Netzwerk-Traffic zu priorisieren

Bekanntlich ist alles in Linux eine Datei. Daher ist auch eine cgroup nur eine Ansammlung von Dateien, die in /sys/fs/cgroup zu finden ist. Doch ist es nicht einfach, cgroups als Dateien zu verwalten. In der Praxis ist es schier unmöglich, das ganze aktuell zu halten. Deshalb gibt es verschiedene Tools, die uns das Managen einer cgroup erleichtern:

  • libcgroup: eine Bibliothek, die die Control Group des Kernels abstrahiert und Tools wie cgreate und cgclassify enthält
  • cgmnager: eine Kombination aus einem Daemon und einem Clients (cgm) für die Verwaltung von cgroups

libcgroup lässt sich unter Ubuntu 16.10 mit der folgenden Anweisung installieren:

$ sudo apt-get install cgroup-bin
$ ls -lsa /sys/fs/cgroup
total 0
0 drwxr-xr-x 14 root root 360 gen 14 20:47 .
0 drwxr-xr-x 10 root root 0 gen 21 15:18 ..
0 dr-xr-xr-x 6 root root 0 gen 21 15:18 blkio
0 drwxr-xr-x 2 root root 60 gen 14 20:47 cgmanager
0 lrwxrwxrwx 1 root root 11 gen 14 20:47 cpu -> cpu,cpuacct
0 lrwxrwxrwx 1 root root 11 gen 14 20:47 cpuacct -> cpu,cpuacct
0 dr-xr-xr-x 6 root root 0 gen 21 15:18 cpu,cpuacct
0 dr-xr-xr-x 3 root root 0 gen 21 15:18 cpuset
0 dr-xr-xr-x 6 root root 0 gen 21 15:18 devices
0 dr-xr-xr-x 4 root root 0 gen 21 15:18 freezer
0 dr-xr-xr-x 3 root root 0 gen 21 15:18 hugetlb
0 dr-xr-xr-x 7 root root 0 gen 21 15:18 memory
0 lrwxrwxrwx 1 root root 16 gen 14 20:47 net_cls -> net_cls,net_prio
0 dr-xr-xr-x 3 root root 0 gen 21 15:18 net_cls,net_prio
0 lrwxrwxrwx 1 root root 16 gen 14 20:47 net_prio -> net_cls,net_prio
0 dr-xr-xr-x 3 root root 0 gen 21 15:18 perf_event
0 dr-xr-xr-x 6 root root 0 gen 21 15:18 pids
0 dr-xr-xr-x 7 root root 0 gen 21 15:18 systemd

Im Verzeichnis können wir alle Control Groups einsehen, die zuvor erstellt wurden. Jetzt können wir versuchen, unsere erste eigene cgroup zu erstellen:

$ sudo cgcreate -a user -g memory,cpu:groupname

Diese Anweisung erstellt eine cgroup mit dem Namen foo, die zwei Control Groups beinhaltet: memory und cpu. Wir können nun prüfen, ob es das Verzeichnis foo in den Verzeichnissen cpu und memory gibt:

$ ls -lsa /sys/fs/cgroup/cpu/foo/
total 0
0 drwxr-xr-x 2 gianarb root 0 gen 21 15:53 .
0 dr-xr-xr-x 7 root  root 0 gen 21 15:53 ..
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 cgroup.clone_children
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 cgroup.procs
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.stat
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage_all
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage_percpu
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage_percpu_sys
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage_percpu_user
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage_sys
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpuacct.usage_user
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 cpu.cfs_period_us
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 cpu.cfs_quota_us
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 cpu.shares
0 -r--r--r-- 1 gianarb root 0 gen 21 15:53 cpu.stat
0 -rw-r--r-- 1 gianarb root 0 gen 21 15:53 notify_on_release
0 -rw-r--r-- 1 root  root 0 gen 21 15:53 tasks

In der cgroup können wir nun Kommandos ausführen, zum Beispiel:

$ cgexec -g memory,cpu:groupname/foo sleep 5

Dadurch ist es nun möglich, die Limits der Ressourcen, also in unserem Fall memory und cpu, einzustellen. Das ist ganz einfach: Liest man sich den Inhalt von /sys/fs/cgroup/memory/foo/memory.limit_in_bytes durch, sieht man das aktuelle Speichermaximum, das ein Task in der cgroup foo nutzen kann. Dieses Limit kann wie folgt geändert werden:

echo 20000000 > /sys/fs/cgroup/memory/groupname/foo/memory.limit_in_bytes

Um die cgroup zu entfernen, kann man einfach eingeben:

sudo cgdelete -g memory,cpu:foo

Nach dieser Anweisung sollten die Verzeichnisse von foo nicht mehr existieren.

Das vorangegangene Beispiel war natürlich wenig sinnvoll und diente nur einer vereinfachten Darstellung der Funktionsweise von Containern. Ich habe aber festgestellt, dass diese einfachen Mechanismen auf den unteren Ebenen für viele Docker-Nutzer immer noch reine Magie sind. Doch ist es keine gute Idee, etwas in Produktionsbetrieb zu nehmen, das man nicht wirklich versteht.

Mit diesem Hintergrundwissen ausgestattet, können wir uns im nächsten Artikel anschauen, was Docker wirklich tut und welche Innovationen damit möglich sind.

Geschrieben von
Gianluca Arbezzano
Gianluca Arbezzano
Software Engineer at InfluxData. The main way to improve and grow is to share what you do to catch feedback to expand my points of view. I am an enthusiast open source contributor and maintainer of different projects in different languages. DevOps evangelist and Docker Captain I am happy to make my environment efficient and secure for me and my team.
Kommentare
  1. Christian Fromme2017-10-11 10:43:42

    In dem Artikel befindet sich ein kleiner Fehler beim Anlegen der cgroup "foo"...

    s/sudo cgcreate -a user -g memory,cpu:groupname/sudo cgcreate -a user -g memory,cpu:foo/

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.