Ein Angebot von

Echtzeitfähigkeit von Container-Lösungen am Beispiel Docker

| Autor / Redakteur: Michael Schnelle* / Sebastian Gerstl

Container-Lösungen wie Docker haben sich für parallele Anwendungen mehrerer getrennter Systeme auf einem Prozessor bewährt. Aber für Applikationen, die harte Echtzeit erfordern, kommen sie eher nicht in Frage. Oder etwa doch?
Container-Lösungen wie Docker haben sich für parallele Anwendungen mehrerer getrennter Systeme auf einem Prozessor bewährt. Aber für Applikationen, die harte Echtzeit erfordern, kommen sie eher nicht in Frage. Oder etwa doch? (Bild: Clipdealer)

Weitläufig gilt die Annahme, dass sich Hardware-Virtualisierung und Container-Lösungen nicht mit harten Echtzeit-Ansprüchen vertragen. Tatsächlich sind Linux-Container, die Echtzeitanforderungen gerecht werden, durchaus möglich – aber sind sie auch sicher?

Definition Echtzeitbetriebssysteme: Als Echtzeitsystem bezeichnet man im Allgemeinen ein System, das auf ein Ereignis innerhalb einer endlichen und vorhersagbaren Zeitspanne reagieren muss. Solche Systeme stellen folglich nicht nur logische, sondern auch zeitliche Anforderungen an ein Ergebnis. Echtzeit bedeutet dabei nicht unbedingt schnelles Handeln, sondern das Einhalten der gesetzten Zeitschranken und deterministisches Verhalten. Echtzeitsysteme werden dabei in zwei Kategorien eingeteilt: weiche und harte Echtzeitsysteme.

Bild 1: Nutzenfunktion bei Echtzeitsystemen.
Bild 1: Nutzenfunktion bei Echtzeitsystemen. (Bild: Mixed Mode)

Bei einem weichen Echtzeitsystem dürfen die gesetzten Zeitschranken verletzt werden. Dennoch hat das gelieferte Ergebnis eine Bedeutung für das System. Hierbei hängt es vom Anwendungsfall ab, inwieweit der Nutzen des Ergebnisses unter dem verspäteten Auftreten leidet. Bei harter Echtzeit wiederum müssen die gesetzten Zeitschranken zu jedem Zeitpunkt eingehalten werden. Wird diese Regel verletzt, hat das Ergebnis keinen Wert für das System.

Linux als Echtzeitbetriebssystem

Linux wurde ursprünglich nicht für den Einsatz als Echtzeit- oder Embedded Betriebssystem entwickelt, doch machte die breite Hardwareunterstützung Linux sehr bald für beide Bereiche interessant. Die ersten Ansätze Linux echtzeitfähig zu machen, sahen dabei eine Dual-Kernel-Architektur vor. Bei diesen Lösungen wird zusätzlich ein kleiner Echtzeitkernel auf dem System betrieben. Dieser Kernel führt Linux als einen niedrig priorisierten Prozess aus. Bekannte Lösungen solcher Ansätze sind RTAI und Xenomai.

Darüber hinaus ist Linux mittlerweile sogar in der Standardausführung bereits für weiche Echtzeitsysteme geeignet. Diese Tatsache geht auf die Entwicklung des Preempt-RT-Patches zurück, der den Linux-Kernel selbst hart echtzeitfähig macht. Aktuell arbeiten die Entwickler daran, den Patch vollständig in den Mainline-Kernel zu integrieren. Der Patch konzentriert sich vor allem darauf, den Linux-Kernel vollständig unterbrechbar zu gestalten, weil dies ein wichtiges Konzept für Echtzeitbetriebssysteme ist.

Der Standard-Kernel unterstützt aktuell drei Unterbrechungs-Modelle: No Forced Preemption (Server), bei dem ein Task nur bei Rückkehr aus einem Systemaufruf oder bei einem Interrupt unterbrochen werden kann. Voluntary Kernel Preemption (Desktop), bei dem noch zusätzliche Unterbrechungspunkte eingeführt wurden. Oder Preemptible kernel (Low-Latency-Desktop), bei dem der Kernel jederzeit unterbrochen werden kann, außer er befindet sich in einem kritischen Bereich.

Der Preempt-RT-Patch führt zwei weitere Unterbrechungspunkte ein: Preemptible Kernel (Basic-RT) und Fully Preemptible (RT). Letzterer gestaltet den Linux-Kernel schließlich bis auf einige wenige kritische Abschnitte vollständig unterbrechbar. Hierzu werden Mechanismen wie sleeping Spinlocks und rt_mutexes eingesetzt. Außerdem werden Interrupt Routinen als Threaded Interrupts ausgeführt, die es ermöglichen, dass ein höher priorisierter Userspace-Prozess eine ISR unterbrechen kann.

Virtualisierung

Im Rahmen der Container-Virtualisierung wird fälschlicherweise oftmals von leichtgewichtigeren virtuellen Maschinen gesprochen, obgleich die grundlegenden Techniken gänzlich verschieden sind: Bei der Vollvirtualisierung kommt eine Softwarekomponente zum Einsatz, um die logische Schicht zwischen dem Gastsystem und der Hardware zu bilden. Diese Softwarekomponente wird Hypervisor oder Virtual Machine Monitor genannt. In der Praxis unterscheidet man Type-1- und Type-2 Hypervisor. (Mandl, 2014)

Ein Type-1-Hypervisor arbeitet direkt auf der Hardware des Systems und agiert als minimales Betriebssystem, das für das Erstellen und Verwalten der einzelnen virtuellen Maschinen zuständig ist. Der Hypervisor benötigt somit Treiber für den Hardwarezugriff. Der Type-2-Hypervisor wird innerhalb eines Betriebssystems als Anwendungsprogramm ausgeführt. Der Zugriff auf die Hardware erfolgt dabei über die Treiber des Hostsystems.

Bei der Container-Virtualisierung (Betriebssystem-Virtualisierung) werden Prozesse allein auf Betriebssystemebene isoliert. Dazu erstellt der Kernel des Host-Systems virtuelle Prozessräume, in denen die Prozesse ablaufen können. Die isolierten Prozesse bekommen den Eindruck, dass ihnen eine komplette Rechnerumgebung zur Verfügung steht, obwohl sie in Wirklichkeit isolierte User-Space-Instanzen eines darunterliegenden Betriebssystems sind. Der Vorteil besteht darin, dass durch die gemeinsame Nutzung eines Kernels kein großer Performance Einbruch zu erwarten ist. Gleichzeitig ergibt sich dadurch aber der Nachteil, dass alle isolierten Instanzen auf dem Kernel des Hostsystems angewiesen sind und deshalb keine unterschiedlichen Betriebssysteme betrieben werden können. (C.Arnold, 2012)

Der Linux Kernel selbst stellt mehrere Mechanismen zur Betriebssystem-Virtualisierung dar. Durch die Verwendung von Prozessbäumen können Prozesse gegenseitig abgeschottet werden. Die Container-Virtualisierung macht sich dieses Prinzip zu nutzen, um einzelne Container anzulegen. Container-Lösungen benutzen diesen Mechanismus z.B. für die folgenden System-Ressourcen:

  • Mount-Namespace: Dem Namespace kann ein eigenes Wurzelverzeichnis bzw. private Einbindungen zugewiesen werden.
  • PID-Namespace: Bei der Erstellung eines Containers legt die Container-Engine einen neuen PID-Namespace an und führt die zu isolierenden Prozesse in diesen Namespace über. Der erste im Container ausgeführte Prozess erhält hierbei die PID 1. Jeder weitere Prozess im Container ist nun diesem untergeordnet. Der Container selbst ist auf seinen PID-Namespace eingeschränkt, das Hostsystem sieht weiterhin alle Prozesse.

Mit control groups (cgroups) bietet der Linux Kernel einen Mechanismus, Prozessen oder Containern nur einen Bruchteil bestimmter Ressourcen zuzuteilen. Prozesse werden hierbei verschiedenen Kontrollgruppen zugeteilt, deren Zugriff auf Systemressourcen limitiert werden kann.

Container-Lösungen

Das Konzept von isolierten Prozessinstanzen unter Linux gibt es schon seit 2001. Praktikabel wurde es erst die die Einführung der vorgestellten Kernel Features cgroups und namespaces. Die erste Implementierung die diese Mechanismen verwendete war LXC (Linux Container). Eine weitere Implementierung stellt Docker dar.

Docker verwendet eine Server-Client-Architektur, bestehen aus dem Docker Client und dem Docker Daemon. Der Benutzer interagiert bei der Erstellung von Containern mit dem Docker Client, welcher Befehle an den Docker Daemon weiterreicht. Dieser übernimmt anschließend alle weiteren Aufgaben, wie die Verwaltung der Systemabbilder (Docker Images) und das Anlegen von Docker Containern.

Client und Daemon müssen nicht zwangsläufig auf demselben System laufen. Docker setzte anfangs auf externe Containerlösungen wie libvirt oder LXC, um mit dem Linux-Kernel zu interagieren. Ab Version 0.9 wurde diese Funktion durch eine eigene Implementierung ersetzt. Das virtualisierte Betriebssystem wird in einem Image zusammengefasst, welches entweder in einer privaten oder öffentlichen Registry gespeichert und verwaltet werden kann. Ein laufendes Image nennt man einen Container.

Testkonzept

Als Hardware-Grundlage wurde BeagleBone Black mit einem Sitara AM335x Conrtex-A der Firma Texas Instruments verwendet. Auf dieser Basis soll die Echtzeitfähigkeit der Container-Lösungen Docker und LXC überprüft werden. Hierzu wurden verschiedene Testszenarien Ausgewählt:

Bild 2: Cyclictest.
Bild 2: Cyclictest. (Bild: Mixed Mode)

Cyclictest ist ein Programm, das häufig im Zusammenhang mit dem Preempt-RT Patch genannt wird. Es wurde im Rahmen der Entwicklung des Patches geschrieben und kann dazu verwendet werden, die Echtzeitfähigkeit eines Systems einzuschätzen. Hierzu erstellt das Programm einen Thread, der die aktuelle Systemzeit misst und sich anschließend für ein definiertes Zeitintervall schlafen legt. Sobald der so gestellte Timer abgelaufen ist und der Thread wieder vom Scheduler ausgewählt wird, nimmt der Thread wieder die Systemzeit und kann aus den gemessenen Zeiten einen Wert für die Scheduling-Latenz berechnen.

Das Programm wird auch von OSADL eingesetzt, um die Echtzeitfähigkeit des Preempt-RT Patches zu testen. Das Ergebnis wird in Form eines Histogramms ausgegeben. Zusätzliche Informationen sind die minimale, durchschnittliche und auch die maximale Latenz. Unten stehend sind exemplarisch die Histogramme für zwei BeagleBone Blacks aus der „OSADL Testfarm“ dargestellt. Dabei ist das linke mit dem Preempt-RT Patch ausgestattet und das rechte mit einem Standard-Kernel.

Bild 3: Vergleich der Latenzen - Preempt RT Kernel und Standard Kernel.
Bild 3: Vergleich der Latenzen - Preempt RT Kernel und Standard Kernel. (Bild: Mixed Mode)

Bei einer zweiten Echtzeitanwendung, der Interrupt-Stoppuhr, soll die Messung für die Echtzeitfähigkeit nicht wie bei cyclictest direkt auf dem Testsystem, sondern über eine externe Messeinrichtung stattfinden. Die Grundidee dabei ist das Nachstellen einer oft anzutreffenden Situation, in der ein Echtzeitsystem in einem definierten Zeitrahmen auf ein externes Signal reagieren muss. Die Verarbeitung und Reaktion auf das externe Signal soll dabei in einer User-Space-Anwendung erfolgen, die man einmal nativ und einmal innerhalb der Container-Lösung betreibt.

Bild 4: Interrupt Stoppuhr.
Bild 4: Interrupt Stoppuhr. (Bild: Mixed Mode)

Konkreter definiert soll mit einem Mikrocontroller ein externes Triggersignal erzeugt werden, dass im Linux-Kernel eine Interrupt-Service-Routine auslöst, die einen GPIO-Pin auf high setzt und anschließend die Echtzeitanwendung im User-Space aufweckt. Diese Anwendung soll dann den entsprechenden GPIO-Pin wieder auf low setzen. Der geplante Signalverlauf von der Anregung bis zur Antwort ist nebenstehend skizziert.

Die Zeitspanne I steht dabei jeweils für die Interrupt-Latenz also der Zeit zwischen dem Auslösen des Triggersignals und dem Beginn der entsprechenden ISR auf dem Testsystem. Die Zeitspanne S wiederum repräsentiert die Zeit die zwischen der ISR und der Antwort der User-Space-Anwendung vergeht.

Der Mikrocontroller soll zu Beginn der Messung einen Zähler starten und sowohl die steigende, als auch die fallende Flanke des Antwortsignals detektieren. Dabei ist jeweils der aktuelle Wert des Zählers zu speichern. So kann gleichzeitig die Interrupt-Latenz als auch die Interrupt-zu-Prozess-Latenz ermittelt werden. Anschließend sollen die gemessenen Werte noch über die serielle Schnittstelle an einen Rechner für die spätere Verarbeitung weitergesendet und eine neue Messung gestartet werden.

Das Prinzip dieser Messung ist ein häufig anzutreffendes Testszenario bei der Bewertung von Echtzeitsystemen und wird z.B. von OSADL in Form der Latency Box durchgeführt. Für die Auswertung und den Vergleich der Echtzeitfähigkeit der Container-Lösungen wird auch hier vor allem die Interrupt-zu-Prozess-Zeit interessant sein, da es hier zu Verzögerungen durch den Betrieb der Anwendung innerhalb eines Containers kommen könnte.

An dieser Stelle sei noch angemerkt, dass dieses Testverfahren auf den ersten Blick redundant wirken mag, da der cyclictest einen ähnlichen Wert ermittelt. Allerdings werden externe Interrupts und Timer-Interrupts im Kernel anders abgearbeitet. Dies führt dazu, dass andere Stellen des Kernel-Codes beteiligt sind und das Zeitverhalten daher unterschiedlich sein kann.

Durchführung

Bild 5: Cyclictest - Vergleich.
Bild 5: Cyclictest - Vergleich. (Bild: Mixed Mode)

Bei der Testdurchführung wurden zwei verschiedene Lastszenarien betrachtet. Im ersten Fall wurden die Tests ohne zusätzliche Last auf dem BeagleBone Black durchgeführt. Anschließend wurde das System während der Durchführung der Tests zusätzlich belastet. Beide Programme wurden unter verschiedenen Lastszenarien mit 10 Millionen Messungen durchgeführt.

Bild 6: Interrupt Stoppuhr Vergleich.
Bild 6: Interrupt Stoppuhr Vergleich. (Bild: Mixed Mode)

Fazit: „Hypervisor-gestützte VM-Virtualisierung und Container-basierte Anwendungsvirtualisierung liegen vom Konzept her wie Tag und Nacht auseinander“ [Quote], so die allgemeine Einschätzung. Dem kann voll zugestimmt werden. Ein Container ist nicht einfach eine leichtgewichtige, virtuelle Maschine. Eher kapselt er Systemprozesse, deren Sichtweite durch das Betriebssystem eingegrenzt ist.

Weiter lässt sich festhalten, dass der grundsätzliche Betrieb einer Echtzeitanwendung in einem Linux-Container möglich ist. Dem Container sind dabei allerdings auch gewisse Rechte zuzuteilen, die vor allem im Hinblick auf die Gewährleistung eines sicheren Betriebs bedenklich sind. Auch wenn es aufgrund des beschränkten Umfangs der Messungen nicht gelungen ist, gänzlich unanfechtbare Ergebnisse festzuhalten, kann vermutet werden, dass auch in zukünftigen Tests ähnliche Ergebnisse erzielt werden.

Container und Virtualisierung in Embedded Systemen einsetzen

Native Cloud Applications für Embedded

Container und Virtualisierung in Embedded Systemen einsetzen

Sie müssen eine bewährte Anwendung schnell, sicher und kostengünstig auf eine andere Embedded-Plattform portieren? Container und Virtualisierung sind hilfreiche Werkzeuge - aber welche Technologie ist für Ihren Anwendungsfall die Beste? weiter...

Container, Kubernetes und Persistent Storage

Container, Kubernetes und Persistent Storage

24.07.19 - Anfangs wurde die x86-CPU nur zu zehn Prozent genutzt. Dann kam die virtuelle Maschine und trieb die Auslastung in bis dato unbekannte Dimensionen. Doch für die Scale-out Architektur der großen Webscaler wird die Parallelisierbarkeit von Anwendungen benötigt, die Google mit seinen Microservices in die Welt der Commodity-Prozessoren gebracht hat. lesen

Echtzeit mit Linux

Echtzeit mit Linux

18.04.19 - Mit Linux können Systeme mit harten Echtzeit-Anforderungen einfach umgesetzt werden. Doch welcher Ansatz ist der richtige? Und welche Latenzzeiten können damit erreicht werden? lesen

Literaturverzeichnis

C.Arnold, M. J. (2012). KVM Best Practices. dpunkt
Mandl, P. (2014). Grundkurs Betreibssysteme. Wiesbaden: Springer Verlag.

(Dieser Beitrag wurde mit freundlicher Genehmigung des Autors dem Tagungsband Embedded Software Engineering Kongress 2018 entnommen.)

Autor

Michael Schnelle arbeitet als Softwareentwickler bei Mixed Mode.
Michael Schnelle arbeitet als Softwareentwickler bei Mixed Mode. (Bild: Mixed Mode)

* Michael Schnelle verfügt über mehrere Jahre Entwicklungserfahrung mit den Schwerpunkten Applikationsentwicklung und Security in unterschiedlichen Schattierungen. Er führte Risikoanalysen durch, entwickelte Lösungen zur Abwehr schadhafter Anfragen auf einer Socialmedia-Plattform, für ein hochsicheres Signalisierungssystem für Notrufeinheiten, sowie zur Generierung einer sicheren Linux/ Debian-Distribution. Aktuellen wirkt er bei der Entwicklung von Fahrgastinformations-Systemen für Verkehrsunternehmen mit, mit den Schwerpunkten Verteilte Systeme, Microservices und Testautomatisierung.

Kommentar zu diesem Artikel abgeben
Cooler Artikel - Sehr wertvoll.  lesen
posted am 19.09.2019 um 14:55 von Unregistriert


Mitdiskutieren
copyright

Dieser Beitrag ist urheberrechtlich geschützt. Sie wollen ihn für Ihre Zwecke verwenden? Kontaktieren Sie uns über: support.vogel.de/ (ID: 46087929 / Echtzeit)