Kata Containers versuchen die Leichtgewichtigkeit von Containern mit der strengen Isolation echter Server zu kombinieren. Dafür muss sich der Docker-User nicht einmal an neue Kommandos gewöhnen.
Wenn Devops-Beschäftigte Anwendungen getrennt voneinander paketieren und betreiben wollen, so wie es das Paradigma der Continous Delivery vorgibt, stoßen sie auf ein Problem: Entweder stecken sie ihre Workloads nach alter Väter Sitte in pyhsikalische oder virtualisierte Server oder sie vertrauen Daten und Programme Containern an, die besonders unter dem Label Docker jüngst populär wurden. Beide Ansätze haben Vor- und Nachteile.
Der Vorteil echter Server ist der hohe Grad an Isolation. Für ein Gerät aus Blech ist das unmittelbar einsichtig, bei einer virtuellen Maschine (VM) erledigt der Hypervisor diese Trennung mit Hilfe seines Prozessors. Dabei ist es zwar ein Treppenwitz der Geschichte, dass die Meltdown- und Spectre-Schwachstellen aufgezeigt haben, dass selbst Hardware nicht unfehlbar ist. Doch dieses unerquickliche Kapitel begrenzter Ingenieurskunst einmal außer Acht gelassen, sorgen sowohl Server wie auch VMs für eine wirkungsvolle Trennung der Ressourcen, denn sie teilen sich nur die CPU, aber keine weiteren Betriebsmittel.
Isolation von Ressourcen
Im Gegensatz dazu nutzen alle Container, die auf einem Host laufen, denselben Kernel. Gerade wenn die einzelnen Workloads klein sind und es viele davon gibt, ergibt sich ein erhebliches Einsparpotenzial. Das ist besonders bei modernen Cloud-nativen Anwendungen der Fall, die als Microservices laufen.
Die Trennung der Daten erledigt hier ein relativ junger Mechanismus im Linux-Kernel. Die so genannten Namespaces führen innerhalb des Kernels Buch darüber, welcher Prozess auf welche Ressourcen wie Prozesse, Netzwerkschnittstellen oder Festplattenverzeichnisse zugreifen darf. Docker instrumentalisiert diese Namespaces und steuert sie und ein paar weitere Mechanismen mit einem bequemen Kommandozeilen-Werkzeug.
Devops haben also effektiv bislang die Wahl zwischen schwergewichtigen VMs, die lange booten, und leichtgewichtigen, aber nur begrenzt isolierten Containern, die sich auf die trickreich umgesetzte Trennung im Kernel verlassen.
Von dieser Situation ausgehend haben mehrere Initiativen versucht, beide Ansätze miteinander zu verbinden. So hat Microsoft als Nebenprodukt seines Virtualisierers Hyper-V das Projekt Runv entwickelt, das als ein sehr leichtgewichtiger Hypervisor zu verstehen ist [1]. Eine ähnliche Richtung hat Intel mit seinem Projekt Clear Containers eingeschlagen [2]. Es nutzt die VT-x-Technik, die in den meisten modernen CPUs aus diesem Hause eingebaut ist.
Unter dem Dach der Open Stack Foundation, die sich seit einiger Zeit jedoch nicht mehr ausschließlich mit der verbreiteten Cloudsoftware, sondern auch mit anderen Infrastrukturprojekten befasst, haben Entwickler die Ingredienzen von Microsoft und Intel zusammengeführt und das unter der Apache-Lizenz stehende Projekt Kata Containers geschaffen ([3], Abbildung 1).

Abbildung 1: Die besten Stücke kombiniert: Kata Containers versucht mit einem Mix aus allen Welten zu punkten. Quelle: Levente Gyori, 123rf
Im Mai 2018 hat das Projektteam die Version 1.0 veröffentlicht, mittlerweile steht Version 1.3 zum Download bereit und bewirbt die Software mit dem Slogan “Die Geschwindigkeit von Containern mit der Sicherheit von VMs”.
Kompatible Runtime-Umgebung
Dabei vermeiden Kata Containers ein neues Anwendungsmodell und springen auf den Zug von Docker auf: Auf Druck von Wettbewerbern und der Community hatte Docker Inc., das Unternehmen hinter der gleichnamigen Software, schon vor einiger Zeit die eigentliche Runtime aus Docker herausgelöst und die Schnittstelle OCI eingezogen. Das Open Container Interface steht unter der Aufsicht der Linux Foundation. Im normalen Docker verrichtet die OCI-Referenzimplementation Runc ihre Arbeit.
Neben Runc gibt es eine Reihe von Alternativen, etwa das von Red Hat entwickelte CRI-O oder das ursprünglich von Core OS vorangetriebene Rkt. Nicht alle Runtimes erfüllen die OCI-Spezifikation vollständig, sie nutzen aber konzeptionell ähnliche Techniken. Der eigentliche Kniff ist nun, dass mit den Kata Containers eine Hypervisor-basierte Runtime bereitsteht, die sich nur unter der Motorhaube vom Klassiker Runc in Docker unterscheidet. Die Docker-Kommandos, ihre Bedeutung und selbst die Image-Formate und Kommandozeilen-Parameter bleiben gleich.
Damit das funktioniert, müssen ein paar Voraussetzungen erfüllt sein: Kata Containers arbeiten nur auf der x86-Plattform und erfordern das Vorhandensein der VT-x-Funktion. Da die Kata Containers ihren eigenen Hypervisor starten, muss dies das Gastsystem erlauben. Das kann durchaus ein Thema auf ihrerseits virtualisierten Hosts sein, etwa wenn sie als VM in einer Public Cloud laufen und Nested Virtualization nicht aktiviert ist.
Verschachtelte Virtualisierung
Wer Zugriff auf das eigentliche Hostsystem seiner Virtualisierung hat, kann mit »cat /sys/module/kvm_*/parameters/nested« prüfen, ob diese Funktion aktiviert ist. Dann zeigt das Kommando »Y« an, sonst »N«. Die Shell-Wildcard ist nötig, weil entweder das Kernelmodul »kvm_intel« oder »kvm_amd« geladen sein muss, um die Funktion anzubieten. Der eigentliche Hypervisor muss zusätzlich in der XML-Konfiguration im Tag »<cpu>« das Attribut »mode=host-model« gesetzt haben.
Wer keinen Zugriff auf das Hostsystem hat und dort die verschachtelte Virtualisierung deaktiviert ist, kann sich vielleicht mit einem Bare-Metal-Dienst behelfen, den manche Clouds im Angebot führen. Aus Sicherheitsgesichtspunkten ist das vielleicht ohnehin ein guter Gedanke, denn so kann sich der Cloudkunde sicher sein, dass selbst bei Vorfällen wie Meltdown oder Spectre kein anderer Nachbar in der Wolke Zugriff auf die eigenen Daten erhält. Gleichwohl eignet sich dieser Ansatz nur für wirklich sensible Daten, denn die Shared-Economy-Vorteile der Cloud sind damit für diesen Server natürlich dahin.
Docker tunen
Nachdem die Voraussetzungen geschaffen sind, ist nun das Container-Framework mit der neuen Runtime auszustatten. Dazu muss natürlich erst einmal das klassische Docker installiert sein. Listing 1 zeigt, wie sich die Software auf einem Ubuntu 18.04 LTS aus den Hersteller-Repositories installieren lässt. Das Skript sollte ein normaler Benutzer aufrufen, es erlangt die Installationsrechte später durch »sudo«.
Listing 1
Docker installieren
01 #!/bin/sh
02 echo "Configure Docker repo ..."
03 sudo -E apt-get -y install \
04 apt-transport-https ca-certificates wget software-properties-common
05 curl -sL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
06 arch=$(dpkg --print-architecture)
07 sudo -E add-apt-repository \
08 "deb [arch=${arch}] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
09 sudo -E apt-get update
10 echo "Install Docker ... "; sudo -E apt-get -y install docker-ce
11 echo "Enable user 'ubuntu' to use Docker ... "; sudo usermod -aG docker ubuntu
Gewissenhafte Admins sollten die GPG-Prüfsumme, die das erste »curl«-Kommando ausgibt, mit der Referenz von der Docker-Website vergleichen. Das Skript fügt dem Benutzer Ubuntu das Recht hinzu, fortan Docker zu nutzen, was nebenbei indirekt der Zuteilung von Rootrechten entspricht. Sollen andere Benutzer ebenfalls Docker verwenden dürfen, so sind sie ebenfalls in die Gruppe »docker« aufzunehmen. Danach müssen sich die Benutzer einmal neu anmelden oder mittels »newgrp -« ihre Gruppenzugehörigkeiten aktualisieren.
Sofern »docker info« keine Fehler meldet, ist die Containersoftware mit der klassischen Runc-Runtime installiert. Nach einem ähnlichen Muster lassen sich die Kata Containers aufspielen. Es gibt Installationspakete für die wichtigsten Distributionen, die jeweils bei Suses Buildservice abrufbar sind.
Listing 2 zeigt die Schritte der Installation. Auch hier gilt es, sicherheitshalber die GPG-Signatur mit der Angabe auf der Kata-Website zu vergleichen.
Listing 2
Kata Containers installieren
01 #!/bin/sh 02 echo "Configure Kata repo ..." 03 sudo sh -c "echo 'deb http://download.opensuse.org/repositories/home:/katacontainers:\ 04 /release/xUbuntu_$(lsb_release -rs)/ /'\ 05 >/etc/apt/sources.list.d/kata-containers.list" 06 curl -sL http://download.opensuse.org/repositories/home:/katacontainers:\ 07 /release/xUbuntu_$(lsb_release -rs)/Release.key | sudo apt-key add - 08 sudo -E apt-get update 09 echo "Install Kata components ..." 10 sudo -E apt-get -y install kata-runtime kata-proxy kata-shim 11 echo "Create new Systemd unit ..." 12 sudo mkdir -p /etc/systemd/system/docker.service.d/ 13 cat <<EOF | sudo tee /etc/systemd/system/docker.service.d/kata-containers.conf 14 [Service] 15 ExecStart=/usr/bin/dockerd -D --add-runtime kata-runtime=/usr/bin/kata-runtime --default-runtime=kata-runtime 16 EOF 17 echo "Restart Docker with new OCI driver ..." 18 sudo systemctl daemon-reload 19 sudo systemctl restart docker
Die Kata Containers kommen in drei Paketen, »kata-runtime«, »kata-proxy« und »kata-shim« (Abbildung 2). Dabei übernimmt das erste die Kommunikation mit der Containerverwaltung, also hier der Docker-Kommandozeile, »kata-proxy« übersetzt Kommandos so, dass sie auch eine VM versteht, und »kata-shim« ist eine Zwischenschicht, die für die Vielzahl an Komfortfunktionen verantwortlich zeichnet, die in Docker eingebaut sind. Dazu gehören zum Beispiel die Verwaltung der Standardausgabe als Logging-Mechanismus oder das Hineinreichen von Signalen, etwa bei »docker stop« oder »docker kill«.
Damit ist alles Notwendige für den Einsatz der Kata Containers erledigt. Nun ist nur noch das Docker-Subsystem neu zu starten. Das könnte ganz rabiat durch einen Reboot geschehen oder ganz gezielt durch den Systemdienst. Dafür kommt bei fast allen aktuellen Distributionen Systemd zum Einsatz. Die Zeilen 14 (ab »[Service]«) bis 16 installieren daher noch eine benutzerdefinierte Systemd-Unit, die den Default der Runtime mit »kata-runtime« überschreibt. Anschließend startet das Skript in den Zeilen 17 bis 19 die Docker-Unit neu.
Um zu prüfen, ob die neue Runtime eingerichtet ist, gibt der Anwender ein:
# docker info | grep Runtime Runtimes: kata-runtime runc Default Runtime: kata-runtime
Startet er nun einen neuen Container, etwa mit »docker run -it ubuntu«, läuft der ab sofort in einem Kata Container. Soll wieder ein Container mit klassischer Runtime starten, reicht dazu »docker run -it –runtime kata-runtime ubuntu«.
Wenn der Runc-Treiber einen neuen Container startet, bereitet er ein Verzeichnis aus dem Image vor, legt ein Overlay-Dateisystem an, erzeugt einen neuen Prozess und wendet neue Namespaces auf ihn an. Dann läuft der neue Container.
Kata hingegen bootet einen eigenen Kernel für jeden Container. Zu diesem Zweck sind in den Installationspaketen ein Kernel und ein VM-Image enthalten, die beide maximal reduziert sind und letztlich den Prozess »kata-agent« in der neuen VM starten, der seinerseits dann das gewünschte Kommando startet, das via »CMD« oder »ENTRYPOINT« in dem Image definiert ist.
Leistungsvergleich
Das geht bemerkenswert schnell. Wer zum Zeitpunkt, als dieser Artikel entstand, in einem Kata Container einmal »dmesg« aufgerufen hat, sieht, dass der Kernel mit der Version 4.14.51 in deutlich unter einer Sekunde startet.
Getestet wurde auf dem Bare-Metal-Flavor »physical.o2.medium« auf der Open Telekom Cloud, der eine 2-x-8-Core-CPU Broadwell EP Xeon E5-2667v4 mit 3,2 GHz und 256 GByte RAM bereitstellt. Das ist schon ein ganz ordentlicher Server, der viele Container verwalten kann. Auf diese Weise lassen sich Rundungseffekte bei der Messung reduzieren.
Wichtige Systemparameter sind Speicherbedarf und CPU-Zeit. Um zu vergleichen, wie hier die Kata Containers gegenüber einer Runc-Runtime abschneiden, startet das Skript aus Listing 3 zunächst nacheinander 100 Container vom Image »nginx« und legt in jedem eine kurze, individuelle und statische Datei ab, die die Nummer des jeweiligen Containers enthält. Die dafür nötige Zeit misst das Skript. Dazu sollte im Vorfeld das Image »nginx« schon im Cache geladen sein, sodass sich ein Probedurchlauf vor der eigentlichen Messung empfiehlt, um sowohl den Image- als auch die Linux-Cache einmal vorzuwärmen.
Listing 3
Benchmark
01 #!/bin/bash
02 N=100
03 time for i in {1..$N}; do
04 CID=$(docker run --name server-$i -d nginx)
05 docker exec server-$i /bin/sh -c \
06 "echo I am number $i > /usr/share/nginx/html/index.html"
07 done
08 # Jeden Container einmal abfragen:
09 time for i in {1..$N}; do
10 IP=$(docker inspect --format '{{.NetworkSettings.IPAddress}}' server-$i)
11 curl http://${IP}/ &
12 done
Nach dem Start der Container lässt sich das Skript die abgelegte Datei von jeder Instanz einmal per HTTP ausliefern, um zu belegen, dass die Dienste in den Containern auch wirklich bereitstehen. Auch diese Zeit misst das Skript. Wer beide Messwerte addiert und durch die Zahl der Container teilt, erhält eine mittlere Bereitstellungszeit pro Webserver in einem Container.
Diese betrug bei den Kata Containers 1,6 Sekunden. Die klassische Runc-Runtime benötigt für die gleiche Aufgabe 0,6 Sekunden. Dieser Wert ist auch bei einer Variation der Anzahl der Durchläufe stabil, die sich im Skript über die Variable »N« konfigurieren lässt.
Grenzen nach oben für »N« sind rund 1000 Container, denn mehr als 1024 IP-Adressen passen nicht an ein virtuelles Bridge-Interface, das Docker verwendet. Bei den Kata Containers ist außerdem schon bei etwa 700 Instanzen Schluss, da ihre leichtgewichtigen VMs mit 160 MByte RAM einen deutlich größeren Hauptspeicherbedarf haben als ein normaler Linux-Prozess. Das ist ja auch kein Wunder, da jeder Kata Container seinen eigenen Kernel startet.
Wer einen solchen Test selbst nachstellen will, sollte dies auf einem dedizierten System tun, da er doch erhebliche Ressourcen erfordert. Die Load und das Antwortverhalten verhielten sich jedoch unauffällig. Hinterher sind die Container am besten mit einem ähnlichen Skript wieder zu verwerfen.
Ernsthafte Option für besondere Fälle
Die Installation der Kata Containers geht leicht von der Hand. Auch wer bereits eine Docker-Umgebung in Betrieb hat, kann diese einfach mit der zusätzlichen Runtime erweitern und zunächst selektiv mit der Option »–runtime« auswählen. Wer gar den Default dauerhaft umstellt, muss sich um überhaupt nichts mehr kümmern, alle neuen Container laufen dann automatisch in VMs.
Ob das für den Regelbetrieb sinnvoll ist, steht auf einem anderen Blatt: Der Mehrbedarf an Speicher ist schon spürbar, die etwas längere Startzeit dürfte sich hingegen nur in sehr hochfrequentierten Microservice-Architekturen niederschlagen. Aus der Sicherheitsperspektive heraus bietet die VM-Isolation natürlich einen ganz anderen Schutz als den, den Namespace & Co. mitbringen. Ehrlicherweise sollte man aber auch berücksichtigen, dass die letzte bekannt gewordene Container-Schwachstelle aus dem Juni 2014 datiert [4].
So empfehlen sich die Kata Containers im Docker-Framework als gute Alternative für die Fälle, in denen es darum geht, eine zusätzliche Isolationsschicht einzuführen. Das kann zum Beispiel dort sinnvoll sein, wo es um die Verarbeitung besonders sensibler personenbezogener oder Geschäftsdaten geht.
Infos
-
Microsoft Hyper Runv: https://github.com/hyperhq/runv
-
Intel Clear Containers: https://github.com/clearcontainers/runtime
-
Kata Containers: https://katacontainers.io/
-
Mark Vogelsberger, “Ausbrecher”: Linux-Magazin 09/14, S. 80







