Containerplattform: Lego für DevOps
Inzwischen setzen immer mehr Unternehmen auf Containervirtualisierung. Häufig zunächst in Proof-of-Concepts oder der Entwicklung, aber die Überzeugung wächst, dass Container in der Zukunft eine immer wichtigere Rolle spielen werden. Viele Eigenschaften dieser Containervirtualisierung sprechen dafür: Container sind portabel und lassen sich auf jedem Linux-Server betreiben; sie lassen sich schnell starten und lassen sich über mehrere Server schnell und leicht skalieren. Dadurch wird der Betrieb von Webservices plattformunabhängiger und vorhandene Hardware kann effizienter genutzt werden.
Der Begriff Container wird meist direkt mit Docker assoziiert und häufig auch mit Docker gleichgesetzt. Dabei gibt es theoretisch auch andere Container-Runtimes wie rkt (rocket) und Cloudfoundry. In diesem Artikel geht es auch um Docker-Container, jedoch sollte ein großer Teil auch auf andere Runtimes zutreffen. Die Idee von Containern bleibt dabei aber gleich: der Container enthält nur die nötigen Abhängigkeiten mit einem Minimum an Betriebssystem und sie teilen sich den Kernel mit dem Host auf dem sie laufen. Durch die Minimierung der installierten Pakete bleiben Containerimages meistens sehr schlank und lassen sich dadurch schnell starten. Das Prinzip, welches mit Containern bezwecken, ist die Auslagerung von einer Anwendung bzw. einem Dienst pro Container. Einzelne Container beanspruchen daher die Hardware oft nicht sehr intensiv, wodurch auf einem Host eine größere Anzahl von Containern betrieben werden kann. Daher ist der Vergleich pets vs cattle (Haustiere vs Vieh) für klassische Server gegenüber Containern sehr treffend. Um die Haustiere kümmert man sich, diese Server werden gepflegt und mit Upgrades und Patches versorgt während Container wie Vieh in großer Menge vorhanden sein können; wenn ein Container nicht mehr funktioniert, wird er beseitigt und gegebenenfalls durch einen neuen Container des gleichen Typs ersetzt.
Containerplattformen und Werkzeuge
Setzt man nun Containerplattform mit Docker gleich und verwendet keine zusätzlichen Tools, kommt man allerdings nicht sonderlich weit. Wenn die Containeranzahl stark ansteigt und es sich um einen produktiven Containerbetrieb handelt und nicht nur um Testversionen - beispielsweise um zu prüfen, ob ein Dienst tatsächlich in einem Container laufen kann - reicht ein einzelner Host einfach nicht mehr aus. Um eine große Menge an Containern zu betreiben, wird eine Containerplattform mit vielen Hosts benötigt. Eine solche Plattform besteht aus einigen Bausteinen, bei deren Auswahl viele Optionen bestehen. Ähnlich wie bei Lego gibt es die einzelnen Komponenten in verschiedenen Farben und in verschiedenen Größen. Deshalb sollen im Folgenden einige dieser Bausteine mit Beispielen vorgestellt werden, denn einige Überlegungen gilt es bereits in der Planungsphase eines Containerclusters zu berücksichtigen.
Mit Docker alleine kann man Container starten und dabei einige Parameter mitgeben (wie die Mountpoints oder das Portforwarding), damit diese dann auch von außerhalb des internen Docker-Netzwerks erreichbar sind. Wenn man aber viele Container auf einmal starten muss, ist dieser Weg sehr umständlich und Automatisierung ist wünschenswert. Außerdem verwaltet das klassische Docker nur den Host, auf dem es läuft, wodurch die Anzahl der Container schnell begrenzt ist. Daher werden Container-Cluster verwendet, die aus vielen Servern bestehen. Für die Umsetzung eines solchen Clusters gibt es bereits die ersten Wahlmöglichkeiten, z. B. Docker Swarm Mode, Kubernetes und Rancher.
Container-Cluster: Docker Swarm Mode, Kubernetes und Ranche
Docker Swarm Mode ist eine Lösung, die in Docker bereits integriert ist, aber auch extra konfiguriert und installiert werden muss, da sich die Verwaltung der Hosts dadurch ändert. Kubernetes ist die Orchestrierungs- und Cluster-Managementvariante, die größtenteils von Google entwickelt wurde. Rancher sieht sich als Orchestrierungstool, das momentan sowohl Kubernetes als auch Docker Swarm und andere unterstützt, bietet aber mit Cattle auch eine eigene Variante, die direkt auf Docker aufsetzt. Allerdings stehen bereits die ersten Alphaversionen von Rancher 2.0 zur Verfügung, bei dem Cattle grundlegend verändert wird und nicht mehr direkt auf Docker aufsetzt, sondern stattdessen Kubernetes als Unterbau benutzt. Kubernetes ist auch die Wahl von größeren Linux-Distributoren wie RedHat, SUSE und Canonical.
Management eines Container-Clusters: Design und Skalierbarkeit
Alle diese Cluster-Managementtools kümmern sich um Aufgaben, die erst in Clustern eine Rolle spielen, wie z. B. Loadbalancing, Scheduling und Skalierung der Container. Skalierung bedeutet bei Containern in der Regel, dass ein bestimmter Container mehrfach über verschiedene Server erstellt wird und die Anfragen über einen Loadbalancer auf diese Container verteilt werden, damit nicht ein einzelner Container überlastet wird. Diese Skalierung kann manuell oder dynamisch abhängig von Parametern wieder CPU-, RAM- oder Netzwerkauslastung geschehen. Dadurch können Lasten von Frontend-Diensten verteilt werden, die unter Umständen an einem gemeinsamen Backend wieder auf die gleiche Datenbank zugreifen. Eine solche Skalierbarkeit muss allerdings schon im Design der Anwendung berücksichtigt sein, denn nicht alle Dienste lassen sich problemlos skalieren.
Generell unterschieden sich alle Varianten der Container-Cluster in zwei verschiedene Servertypen: Manager-Nodes und Worker-Nodes (bei Kubernetes: Master und Nodes). Dabei sind Worker-Nodes Server, auf denen die Container laufen, während die Manager-Nodes sich um die Verwaltung und Verteilung der Container kümmern (s. Abb.1). Diese sammeln auch die Rückmeldungen der Nodes und Container, damit ausgefallene Container und Server sofort ersetzt oder neugestartet werden können. In den weiteren Details der Umsetzung unterscheiden sich die Tools wieder.
Aufbau am Beispiel von Kubernetes
Da Kubernetes aktuell oft als das Werkzeug der Wahl gilt, soll der Aufbau an diesem Beispiel etwas genauer analysiert werden. Die kleinste Einheit bei Kubernetes ist ein Pod. Ein Pod besteht aus mindestens einem Container, kann aber auch aus mehreren bestehen, die eng miteinander verknüpft sind.
Auf den Nodes für die Container laufen drei Dienste von Kubernetes: Docker, kubelet und kube-proxy.
- Unter Docker laufen die Container.
- Der Dienst kubelet ist der Agent, der die Container verwaltet (u. a. auch die Volumes, Health-Checks und Secrets der Container). Dieser Dienst wird vom Master (oder über lokale Konfiguration) gesteuert.
- Um das Portforwarding bzw. das Netzwerk der Container zu verwalten, wird kube-proxy verwendet.
Auf dem Master sind die Kern-Aufgaben aufgeteilt auf: kube-apiserver, etcd, kube-scheduler und kube-controller-manager.
- Als Schnittstelle zum User und zu den Nodes wird der kube-apiserver betrieben.
- Die Konfigurationen der Nodes und der Container werden in etcd, einem Key-Value-Store für Cluster abgelegt.
- Der kube-scheduler wählt für neue Pods aus, auf welchem der Nodes diese gestartet werden.
- Den restlichen Teil der Verwaltung übernimmt überwiegend der kube-controller-manager. Darunter fällt die Kontrolle, ob alle Nodes korrekt funktionieren, ob von allen Pods die gewünschte Anzahl läuft, welche Pods welchem Service zugeordnet sind und welche Ports auf welchen Service weitergeleitet werden.
Eine Service-Ebene verwaltet alle zugehörigen Pods in einem Cluster. Mit einer Routing-Ebene wird der Zugang über einen bestimmten Port mit einem Service verknüpft und somit nach außen verfügbar gemacht. Auf der anderen Seite werden neue Pods über den Api-Server mit dem Commandline-Tool kubectl gestartet und verwaltet.
Betrieb einer Containerplattform
Mit Orchestrierungstools lassen sich auch Hochverfügbarkeits-Container-Cluster erstellen. Dafür werden – statt nur eines Masters – drei oder fünf Master aufgesetzt, von denen nur einer aktiv die Nodes verwaltet. Aber auch die Master im Standby-Betrieb haben Zugriff auf die Konfiguration der Nodes und Container (schematischer Aufbau s. Abb.2). Wenn nun einer der Master ausfällt (bei 3 Mastern, für 5 Master dürfen auch 2 ausfallen), übernimmt einer der anderen beiden Master die Rolle des Masters. Es übernimmt immer der Master im größeren übrigen Netz den Betrieb, damit nicht bei einem Split des Netzwerkes beide Teile versuchen, den Betrieb aufrecht zu erhalten, was zu zusätzlichen Störungen führen kann. Geringere Probleme entstehen, wenn ein einzelner Node oder Container ausfällt. Wenn diese nicht antworten, wird einfach eine neue Version des Containers gestartet bzw. alle verlorenen Container auf einem anderen Node neu erstellt.
Persistierung der Daten im Container
Container werden üblicherweise nicht gepatcht und gepflegt, sondern im Falle eines Problems oder neueren Version einfach durch einen neuen Container ersetzt. Da alles, was im Container enthalten ist, verloren geht, muss man sich vor der Erstellung eines Containers Gedanken machen, ob in der darin betriebenen Applikation Daten anfallen, die nach einem Fehler/Neustart/Upgrade weiterhin bestehen bleiben sollen. Für diese Daten gibt es verschiedene Speichermöglichkeiten: So können Sie für Docker-Container vom Hostsystem Ordner mounten oder aber Docker-Volumes erstellen.
Docker-Volumes sind Daten-Container, die einem Container zugewiesen werden, aber bestehen bleiben, wenn der Container neu erstellt wird. Dann wird das Docker-Volume an den neuen Container gebunden, so dass die im Docker-Volume gespeicherten Daten auch Neustarts und Upgrades überstehen. Da in einem Container-Cluster auch Skalierung möglich sein soll, werden hier Volumes benötigt, auf die alle Nodes bzw. alle Container zugreifen können. Auch dafür bestehen Varianten der Docker-Volumes in den entsprechenden Cluster-Managementtools.
Damit diese Volumes auch beständig bleiben, gibt es persistenten Speicher, der in der Regel über ein Cluster-Filesystem, auf das alle Nodes zugreifen können, verfügbar gemacht wird. Dort werden Volumes in bestimmten Größen bereitgestellt, die an Container gebunden werden können und bestehen bleiben, auch wenn die zugehörigen Container gelöscht werden (s. Abb.3).
Bei der Planung einer Container-Infrastruktur sollte berücksichtigt werden, wie viel Speicher zur Verfügung stehen muss, wie dieser am besten erreichbar und erweiterbar ist. Es gibt Szenarien, in denen das Cluster-Filesystem auf den Nodes läuft, auf denen auch die Container liegen, oder andere mit eigenen Storage-Cluster. Auch beim Typ des Filesystems gibt es Wahlmöglichkeiten von NFS über GlusterFS bis hin zu Ceph – je nachdem, wie viel und wie schnell der Speicher verfügbar sein muss.
Container Images bereitstellen
Nicht nur die Container benötigen Speicher für ihre Anwendungen – es wird auch auch ein Ort benötigt, um die Containerimages zu sammeln. Zum einen existiert Docker Hub als zentrale Anlaufstelle, um Containerimages zu beziehen. Dort findet man von Softwareanbietern zum Teil offizielle vorgefertigte Containerimages, aber der Open Source-Gedanke ermöglicht es jedem, die eigenen Container dort öffentlich zur Verfügung zu stellen. Gerade für Unternehmen kann dies ein potentielles Sicherheitsrisiko bedeuten, wenn nicht ausführlich geprüft wird, welche Prozesse und Dienste in einem solchen Container laufen.
Neben Docker Hub existieren auch andere Anbieter, die ihre eigenen zertifizierten Container anbieten und zur Verfügung stellen, wie beispielsweise der Red Hat Container Catalog. In den meisten Fällen entwickeln Unternehmen jedoch ihre eigenen Microservices und möchten diese oft nicht öffentlich freigeben bzw. auch die verschiedenen Versionen ihrer Container in Entwicklung zentral in der eigenen Plattform unterbringen. Für diesen Zweck gibt es Registries wie die Docker Registry, die auf einem eigenen Server gehostet werden, in denen die Images abgelegt und somit im eigenen Netz verfügbar gemacht werden können. Dabei spielen die Tags der Containerimages die Rolle der Versionskontrolle, so dass auch gezielt Upgrades und Rollbacks von Containern möglich sind.
Templates für die Container-Konfiguration
Oft werden Container im Betrieb mit ähnlichen Konfigurationsparametern eingesetzt oder bestimmte Anwendungen bestehen aus mehreren Containern. Diese benötigen eine Standardkonfiguration, damit ihr Zusammenspiel reibungslos funktioniert. Um diesen Betrieb zu vereinfachen, können Vorlagen erstellt werden, die anschließend je nach Plattform und der entsprechenden Oberfläche auch in Form eines Katalogs verfügbar gemacht werden können. In diesen Fällen bleiben nur bestimmte Konfigurationsparameter offen und die meisten Einstellungen sind vorher schon definiert, so dass man sich keine Gedanken machen muss, wie das Web-Frontend mit dem Backend verbunden werden muss. Solche Vorlagen sparen Zeit und vereinfachen Nutzern mit weniger Erfahrung den Einstieg, um eine funktionierende Anwendung aus mehreren Komponenten zu starten, die auf mehrere Container verteilt ist.
Bei großen Plattformen gibt es oft auch sehr unterschiedliche Benutzergruppen. Daher bringen die meisten Orchestrierungstools bereits eine Form von rollenbasierten Zugriffsrechten mit. Meistens lassen sich diese an bereits bestehende Login-Dienste wie Github oder LDAP koppeln. Dadurch können Umgebungen oder Projekte geschaffen werden, auf die nur bestimmte Nutzer oder Nutzergruppen Zugriff haben. Die Art des Zugriffs kann meist auch weiter eingeschränkt werden (s. Abb.4), so dass bestimmte Nutzer beispielsweise nur lesenden Zugriff erhalten oder nur bestimmte Container starten und stoppen dürfen. In manchen Fällen lassen sich für diese Umgebungen bzw. Projekte auch Grenzen für die Auslastung der Server einstellen, so dass in einer Entwicklungsumgebung eine maximale CPU- und RAM-Auslastung definiert ist oder eine Höchstanzahl von laufenden Containern besteht. Damit kann sichergestellt werden, dass z. B. die Entwicklungsumgebung einer Gruppe nicht den Betrieb von anderen Containern einschränkt bzw. stört, indem dort die Hardware zu stark ausgelastet wird.
Monitoring von Containern und Clustern
Damit im Betrieb einer Plattform sichergestellt werden kann, dass rechtzeitig mehr Nodes zur Verfügung gestellt werden bzw. alle Dienste auch planmäßig laufen, ist es auch wichtig, ein Monitoring für die Clusterumgebung und die Container zu betreiben. Dabei stehen grundsätzlich zwei Wege zur Verfügung, die normalerweise beide genutzt werden sollten.
So können in den Containern sogenannte Healthchecks implementiert werden: Es wird in regelmäßigen Intervallen darüber überprüft, ob ein Container noch planmäßig läuft. Das kann darüber geschehen, dass eine Anfrage an einen bestimmten Port gesendet wird oder eine Anfrage an einen Webservice eine bestimmte Antwort geben muss. Dazu können Optionen definiert werden, was bei "ungesunden" Containern geschehen soll, also diesen, die nicht mehr korrekt oder gar nicht antworten. Diese können automatisch neu gestartet werden, was unter Umständen dann auch auf einem anderen Node passieren kann, wenn dieser geringer ausgelastet ist. Alternativ kann aber auch definiert sein, dass von einem bestimmten Container immer eine Mindestanzahl laufen soll. Wenn dann ein Container ausfällt, diese Mindestanzahl aber weiterhin erfüllt wird, wird der defekte Container nur entsorgt und Ersatz wird erst bei neuem Bedarf wieder zur Verfügung gestellt.
Zusätzlich dazu können die Metrics ins Monitoring aufgenommen werden. Damit sind üblicherweise die Netzwerk-, CPU- und RAM-Auslastungen gemeint. Diese können für den gesamten Host, den gesamten Cluster oder auch nur für einzelne Container von Interesse sein. Da diese Umgebungen dynamisch sind und sich durch Skalierungen häufig in der Anzahl der Nodes sowie Container ändern können, sind nicht alle Monitoringtools dafür geeignet. Es existieren aber inzwischen viele unterschiedliche Tools, um das Monitoring und das zugehörige Alerting von Containerclustern zu realisieren. Von der Open Source-Seite ist vermutlich eines der populärsten Tools Prometheus in Verbindung mit Grafana. Ansonsten sind sysdig und dataDog auch häufiger anzutreffen. Aber gerade in diesem Bereich ist die Auswahl groß und stetig wachsend.
Um bei Störungen möglichst zügig die Ursachen zu finden, spielt auch das Logging eine große Rolle. In vielen Orchestrierungstools ist bereits Unterstützung dafür enthalten. In Kubernetes gibt es zum Beispiel Support für Fluentd, mit dem ein EFK-Stack (Elasticsearch-Fluentd-Kibana) betrieben werden kann. Damit werden auf jedem Node die Logs von den Containern gesammelt und in eine Elasticsearch-Datenbank übertragen. Diese kann dann mit Kibana graphisch aufbereitet und durchsucht werden.
Continuous Integration (CI) und Continuous Delivery (CD): Entwicklung und automatisiertes Deployment
Neben dem Betrieb spielt in den meisten Unternehmen aber auch die Entwicklung von Containern eine Rolle. Um diese zu vereinfachen und Updates möglichst schnell ausrollen zu können, ist auch hier Automatisierung wünschenswert. Über Webhooks lässt sich zwischen dem CI/CD-Tool der Wahl und dem Git-Server eine Verbindung erstellen, bei der auf Änderungen automatisch reagiert werden kann. Wenn somit am Code etwas geändert wird, kann automatisch ein neues Containerimage damit gebaut werden oder ein vordefinierter Container gestartet werden, in dem der entsprechende Code heruntergeladen wird. Somit werden Änderungen automatisch in neue Containerimages integriert. Anschließend können automatisch oder mit Freigabe eines Nutzers zum Beispiel automatische Testroutinen mit dem entsprechenden Containerimage durchgeführt werden.
Wenn diese erfolgreich durchgeführt werden, kann das Containerimage für den Produktivbetrieb freigegeben werden. Auch das kann automatisch erfolgen, wird aber in der Realität oft nochmal durch eine Bestätigung eines Nutzers ausgelöst. Dabei kümmert sich die entsprechende Pipeline zunächst um die Erstellung eines Containerimages. Für die Tests wird dieses dann mit einem entsprechenden Label versehen, damit die Test-Pipeline ausgelöst wird und anschließend mit der Freigabe für den Produktivbetrieb ein weiteres Label erstellt. Spätestens für den Produktivbetrieb wird das Containerimage dann auch in die Registry geladen, damit es überall zur Verfügung steht.
Wenn die Änderungen an einem Container im Produktivbetrieb auf die laufenden Container angewendet werden sollen, gibt es eine Reihe von Upgrademöglichkeiten. Je nach Strategie werden die Container schrittweise durch neue Container in der neuen Version ersetzt oder mit neuen Containern die gewünschte Anzahl erstellt, um dann den Loadbalancer auf die neue Version umzuschwenken. Es sind somit verschiedene Ansätze möglich, in denen es zu keinen Ausfallzeiten kommt. Dadurch, dass die alte Version in der Registry vorhanden ist oder unter Umständen noch läuft, ohne dass der Loadbalancer auf diese Container verweist, sind auch Rollbacks kein Problem, wenn es doch zu Zwischenfällen mit der neuen Version kommt. Auch ein Mischbetrieb von Containern in unterschiedlicher Version ist möglich: In dieser Form unterstützen einige Orchestrierungstools, dass die Anfragen in einem gewünschten Verhältnis auf die alte sowie neue Version verteilt weitergeleitet werden. Gleichzeitig wird der größte Teil der alten Version verwendet, um auch anschließend sicher zu gehen, dass die neue Version problemlos funktioniert. Das kann in manchen Anwendungen der Fall sein, wenn die automatischen Tests nicht alle komplexen Szenarien abbilden können.
Bei CI/CD-Tools zur Realisierung von Pipelines kommt in sehr vielen Fällen Jenkins zum Einsatz, aber auch andere Tools wie Drone, Semaphore und viele weitere können integriert werden. Drone ist gezielt als Continuous Delivery-Plattform für Docker-Container entwickelt worden. Viele dieser Tools können ebenfalls in einem Container betrieben werden bzw. setzen die Pipeline innerhalb von Containern um.
Komplettpaket: OpenShift
Wem es zu viel Arbeit ist, alle Bausteine für eine Containerplattform selbst auszusuchen, der kann auf Komplettpakete wie OpenShift zurückzugreifen. Diese Containerplattform wird von Red Hat entwickelt und ist wie für Red Hat üblich sowohl mit Enterprise-Subscription als auch als Open Source-Variante (OpenShift origin) verfügbar. Dabei sind die meisten erwähnten Bausteine bereits enthalten oder leicht integrierbar. Dabei setzt OpenShift auf Kubernetes als Orchestrierungstool und liefert eine integrierte Version von Jenkins im Container mit. Aber auch ein bestehender Jenkins-Server lässt sich problemlos einbinden. Zudem lassen sich zahlreiche Erweiterungen mit Red Hat-Produkten unterstützen bzw. integrieren. So ist eine Cloudforms-Subscription bei der Enterprise-Version enthalten, um eine Containerplattform zu erstellen, in der sowohl eigene Server als auch Cloudinfrastruktur hybrid benutzt werden können. Auch für verschiedene Speicheranbindungsmöglichkeiten ist Unterstützung bereits vorhanden, aber welche Form genutzt wird, muss weiterhin entschieden und konfiguriert werden.
Zusätzlich zu den bisher beschriebenen Bausteinen legt OpenShift viel Wert auf die Integration von Entwickler-Tools. Um diesen die Arbeit zu erleichtern, wurde beispielsweise die Möglichkeit von Source-to-Image-Builds (S2I-Builds) integriert. Dabei wird beim Anlegen eines Containers nur ein Git-Verzeichnis angegeben und OpenShift erkennt an den Dateiendungen, welches Framework verwendet wird. Es wählt dann einen von Red Hat vorgefertigten Container aus, der die entsprechende Umgebung bereitstellt, kopiert das Git-Verzeichnis hinein und versucht den Code entsprechend auszuführen. Dadurch muss der Entwickler keine Zeit mehr investieren, wie man den entsprechenden Container baut, in dem der gewünschte Code ausgeführt werden soll.
Gerade für Unternehmen spielt der Support eine große Rolle. Bei einer kompletten Containerplattform wie OpenShift bekommt man vom Hersteller den entsprechenden Support, während eine selbst erstellte Plattform aus vielen unterschiedlichen Tools den Nachteil hat, dass jedes Tool einzeln gepflegt werden muss und unter Umständen nicht alle Versionen miteinander einwandfrei kompatibel sind.
Fazit
Eine Containerplattform (Beispiel in Abb.5) aufzubauen, bedarf mehr als nur die Installation und den Betrieb von Docker. Dabei kommen eine Vielzahl unterschiedlicher Bausteine zum Einsatz, bei denen man in Komplexität und Features eine große Auswahl hat. Um die richtige Wahl zu treffen, sollte man rechtzeitig überlegen, was für eine Art von Containerplattform man benötigt und welche Tools von Nutzen sind. Komplettpakete wie OpenShift nehmen viel Arbeit ab und liefern eine Plattform mit vielen Features – einige Details zum Aufbau der Infrastruktur müssen aber weiterhin vorher getroffen werden, bevor man damit den Betrieb aufnehmen kann.
Je nach Einsatzzweck und Größenordnung kann eine solche Containerplattform auch sehr komplex werden. Dafür können nach sorgfältiger Planung später sehr viele Prozesse automatisiert und damit Zeit und Hardware effizient genutzt werden. Aber nicht jede Umgebung benötigt alle Features und es gibt durchaus Szenarien, in denen auch schon das Komplettpaket OpenShift komplexer als nötig ist.
Nachdem Containerimages aber einen Standard haben, ist es auch möglich, die Plattform zu wechseln und die bestehenden Konfigurationen und Container auf einer anderen Plattform zu betreiben. Dafür können Konfigurationsschemas ein anderes Layout bekommen. Container behalten aber ihren Vorteil, dass man weitestgehend uneingeschränkt bei der Wahl der Plattform ist.