Aus Linux-Magazin 11/2016

Container sichern und skalieren

© Elena Shchipkova, 123RF

Core OS und Kubernetes helfen Admins dabei, Container-basierte Webanwendungen in großen Landschaften zu orchestrieren. Als Herausforderungen erweisen sich in der Praxis die Sicherheit, Deployments sowie die Updates für Tausende von Nodes. Wie Admins dabei die Zügel in der Hand behalten, erklärt der Artikel.

Seit dem Erscheinen von Docker [1] vor drei Jahren sind Container nicht nur im Linux-Universum ein Dauerbrenner. Auch die nativen Ports für Windows und OS X stoßen auf reges Interesse. Waren Entwickler zuerst nur daran interessiert, ihre Applikationen als Microservices [2] in Containern zu testen, gibt es inzwischen erste produktive Erfahrungen mit dem Einsatz von Containern in großen Setups – jenseits von Google und anderen großen Portalen.

Der Artikel beleuchtet, wie sich Container in großen Herden verhalten, welche Vorteile sich daraus ergeben und worauf Admins achten sollten.

Herdentiere

Klar ist: Ein Admin muss den Betrieb von Docker-Containern in der Masse orchestrieren. Kubernetes ([3], Abbildung 1) ist ein erst zwei Jahre altes System, das genau diesen Zweck erfüllt. Als Teil von Googles Infrastructure for Everybody Else (Gifee) ist es in Go geschrieben, steht unter der Apache-2.0-Lizenz, die stabile Version beim Redaktionsschluss war die 1.3.

Abbildung 1: Kubernetes bringt eine eigene Weboberfläche mit, um Pods, Nodes und Container zu verwalten.

Abbildung 1: Kubernetes bringt eine eigene Weboberfläche mit, um Pods, Nodes und Container zu verwalten.

Der Sourcecode wartet auf Github [4], ein »git clone« liefert den aktuellen Master-Branch. Es empfiehlt sich, mit »git checkout v1.3.0« die neueste stabile Version zu holen. Wer Erfahrung oder Wagemut mitbringt, kann auch eine Beta- oder Alpha-Version probieren.

Ein »make quick-release« baut eine schnelle Version, was aber voraussetzt, dass bereits Docker und Go auf dem Host laufen. Mit Go 1.6 und Docker 1.11 ließ sich Kubernetes im Test innerhalb von ein paar Minuten installieren. Allerdings gibt es seit Version 1.2.4 die kleine Unschönheit, dass der Admin die »$CDPATH« -Environment-Variable mit »unset CDPATH« löschen muss, andernfalls drohen Fehlermeldungen.

Schwerer wiegt aus Open-Source-Sicht, dass Teile des Build von externen Containern abhängen. Zwar ist davon auszugehen, dass alle heruntergeladenen Container aus sicheren Quellen stammen – zählt man Googles Registry als solche. Doch allein die schiere Anzahl hinterlässt ein gemischtes Gefühl, vor allem in hochsicheren Umgebungen.

Ein Build ohne vorinstallierte Container zeigt zwar, dass es möglich ist, alle Komponenten auch ohne Netzwerkverbindung zu bauen. Doch scheitert der Make-Prozess beim Paketieren der Komponenten für »kubi-apiserver« [5] und »kubelet« [6]. Für eine sichere Umgebung greifen Admins besser zu einer Release, die nur ein auditierbares, auf Dockerfiles aufbauendes Repository verwendet.

Cluster to go

Nach dem Installieren setzt der Admin auch eine Testumgebung im Handumdrehen auf. Im ersten Schritt wählt er einen Kubernetes-Provider aus, im zweiten feuert er den Cluster ab:

export KUBERNETES_PROVIDER=libvirt-coreos
cluster/kube-up.sh

Nach ein paar Minuten steht er dann mit einem laufenden Cluster da, der aus einem Master und drei Worker Nodes besteht. Alternativ lässt der »vagrant« -Provider eine Installation unter Virtualbox auch auf Macs zu [10].

Alles an Bord

Kubernetes Plan ist, ab Werk alle Komponenten zu enthalten, die den Aufbau einer eigenen PaaS-Infrastruktur ermöglichen. Es platziert Container automatisch, skaliert sie, heilt selbstständig, beherrscht automatische Rollouts und sogar Rollbacks.

Um Storage und Netzwerk zu orchestrieren, verwendet Kubernetes Storage-, Netzwerk- und Firewall-Provider. Die muss der Admin für die selbst aufgebaute Cloud erst einmal einrichten. Will er Deployment Pipelines aufbauen, hilft ihm Kubernetes, indem es eine Management-Schnittstelle für Konfigurationen und Passwörter mitbringt. Das ermöglicht auch im Bereich der Sicherheit Devops ohne Bauchschmerzen, kein Passwort soll mehr als Konfiguration in einem Repository warten.

Runtimes und Images

Docker ist nur eine von mehreren existierenden Container-Runtimes, die sich teils erheblich, teils in wichtigen Details voneinander unterscheiden, etwa beim Prozesslayout unterhalb des Docker-Daemon.

Aus Sicht der Aufgabentrennung (Separations of Concern) fungiert Systemd [7] als zentrale Instanz des Ressourcenmanagements, ausführlich diskutiert wurde das Thema vom Rkt-Projekt [8]. Welche Systemfunktionen Container-Runtimes übernehmen sollen und dürfen, ist umstritten. Fragt man Lennart Pöttering, erledigen Container-Runtimes eigentlich generische Aufgaben von Systemd. Seit Version 230 [9] verschiebt auch dies dank Nspawn Prozesse in Namespaces und begrenzt Ressourcen. Damit könnte Systemd die Runtimes eigentlich ersetzen. Was noch fehlt, sind eine Push- und Pull-Funktion, um die Images aus den Registries zu holen und sie dort abzulegen.

Kubernetes verspricht – und daran muss es sich messen lassen –, dass der Admin sich nicht mehr um die Infrastruktur, sondern nur noch um die Applikationen kümmern muss. Sogar von Zero Ops, also einer Steigerung von Devops, ist die Rede. Bis dahin ist es allerdings noch ein weiter Weg. Letztlich verhält es sich so wie mit jeder Kunst: Damit sie leicht und einfach aussieht, muss jemand viel Geld und Mühe investieren.

Pods und mehr

Die kleinste Einheit des Deployment sind die Pods [11]. Dabei handelt es sich um Container in einer Schicksalsgemeinschaft. Kubernetes erzeugt sie auf demselben Node, sie teilen sich eine IP-Adresse, greifen gemeinsam auf ihr Dateisystem zu und sterben auch zusammen, wenn ein Container im Pod sein Leben aushaucht. Ein Yaml-File beschreibt einen Pod, Listing 1 definiert einen für einen simplen Nginx-Server.

Listing 1

nginx_pod.yaml

01 apiVersion: v1
02 kind: Pod
03 metadata:
04   name: nginx
05 spec:
06   containers:
07   - name: nginx
08     image: nginx
09     ports:
10     - containerPort: 80

Ein Replication-Controller [12] garantiert hingegen, dass immer eine bestimmte Anzahl an Pods läuft. Im Yaml-Format sieht das dann aus wie in Listing 2. Mit »kubectl scale« ändert der Admin nachträglich die Anzahl der laufenden Instanzen, mit »kubectl autoscale« legt er in Abhängigkeit von der Systemlast einen Autoscaler fest. Der fährt Instanzen in einem festgelegten Intervall hoch, sollte die Systemlast steigen. Sinkt sie, schickt er die Instanzen wieder schlafen.

Listing 2

nginx_repl.yaml

01 apiVersion: v1
02 kind: ReplicationController
03 metadata:
04   name: nginx
05 spec:
06   replicas: 3
07   selector:
08     app: nginx
09   template:
10     metadata:
11       name: nginx
12       labels:
13         app: nginx
14     spec:
15       containers:
16       - name: nginx
17         image: nginx
18         ports:
19         - containerPort: 80

Services (Abbildung 2, [13]) machen aus einem logischen Set von Pods, die die gleiche Funktion anbieten, eine Einheit. Sie entkoppeln laufende Pods und die Dienste, indem sie ein transparentes Netz über alle Hosts aufspannen. Jeder Node kann dabei Services abrufen, auch wenn kein Pod auf dem Node läuft. [14] zeigt einen Nginx-Service.

Abbildung 2: Container, Pods, Nodes und Services: Letztere fassen verschiedene Pods für den Load Balancer zusammen.

Abbildung 2: Container, Pods, Nodes und Services: Letztere fassen verschiedene Pods für den Load Balancer zusammen.

Haustiere und Viehzeug

Um die Kubernetes-Architektur sinnvoll mit Leben zu füllen, sollte der Admin einige fundamentale Dinge beachten. Dazu gehört es, zwischen Cattle (Vieh) und Pets (Haustiere) zu unterscheiden, was im Devops-Bereich seit Längerem eine Rolle spielt. Während er seine Haustiere liebevoll pflegt, verspeist er das Vieh zum Frühstück. Beim Vieh handelt es sich tatsächlich um alle zustandslosen Prozesse, bei den Haustieren um die Daten in den Datenbanken.

Der Pod-Ansatz hilft aber nur beim Umgang mit den Haustieren. Ohne weiteres Zutun des Admin bleiben Container nicht von Dauer. Pods sollte er als zustandslose Prozesse entwerfen. Datenbanken und alle persistenten Daten benötigen jedoch ein Storage-Backend (Abbildung 3), das die Daten aufbewahrt, damit sie die Lebensspanne des Pod überdauern.

Abbildung 3: Multi-Tier-Architektur von Webapplikationen. Nur die unterste Schicht sollte persistente Daten enthalten.

Abbildung 3: Multi-Tier-Architektur von Webapplikationen. Nur die unterste Schicht sollte persistente Daten enthalten.

Daten auf Dauer

Stellt sich die Frage, wie Admins am besten mit Daten umgehen. Die einfachste Lösung dürfte sein, sich auf den Cloudprovider zu verlassen, also die angebotenen Storage- oder Datenbank-Services zu verwenden. Das erzeugt jedoch einen Provider-Lock-in, der gegen den eingesparten Aufwand abzuwägen ist.

Ein Admin kann Datenbanken auch auf vorhandenem Storage mit einem »ReplicationController« und einer »Replicas« -Anzahl von »1« installieren. In diesem Fall liegen die Daten im Storage-Backend. Startet der Container auf einem anderen Host neu, greift er dennoch auf die vorhandenen Daten zu. Das setzt einen Host-übergreifenden Storage Provider voraus. Im einfachsten Fall kommt NFS zum Einsatz, Gluster FS und Ceph eignen sich auch – Kubernetes unterstützt eine Vielzahl an Providern.

Will er auf eigener Hardware arbeiten, kann er auch lokale Platten einrichten. Mit Hilfe von Labels ordnet er Pods dabei den Nodes eindeutig zu. Als Nachteil bei diesem Vorgehen erweist sich die Replikation der Datenbanken. Die vorhandenen SQL-, No-SQL-, aber auch die Messaging-Systeme verlangen vom Admin jeweils eigene Lösungen.

Ressourcen und Netzwerk

Docker beherrscht fast alle Spielarten von Ressourcen-Management unter Linux. In der Praxis reicht Kubernetes die Kontrolle allerdings nicht vollständig durch [15], Version 1.3 zeigt lediglich CPU- und Arbeitsspeicher-Ressourcen an. Wer damit beispielsweise seine Hadoop-Distributionen zähmen will, erlebt dank einer fehlenden Kontrolle von Netzwerk- und Block-IO-Performance vermutlich eine Enttäuschung.

Apropos Netzwerk: Hier stehen verschiedene Netzwerkmodelle bereit. Im eigenen Rechenzentrum ergibt es sicherlich Sinn, das Management-Netz von dem Netzwerk zu trennen, mit dem sich die Kubernetes-Container untereinander verbinden. Wer im virtuellen Netz einen Cluster mit dem »libvirt-coreos« -Provider startet, kann so eine Isolierung sehr schön nachbauen.

Per Netzwerkprovider schaltet der Admin Netze ein und aus. Dabei hilft ein Plugin [16], das Support für das Container Network Interface (CNI) aufweisen muss. Mit Flannel [17], Calico [18], Canal [19], Open Vswitch [20] steht eine Reihe weiterer Schnittstellen bereit, die ein Admin in sein Netz integrieren kann.

Der API-Server erlaubt es, die Details der Dienste über den Befehl »kubectl get service« auszulesen. Das Kommando verbindet die Dienste mit einem Load Balancer [21]. Mit Kubernetes Ingress wartet zudem ein Proxy, mit dem der Admin die gängigen Pfade für die Web-Anwendersicht in Kubernetes direkt konfiguriert [22].

Absicherung

Jenseits der Testbeispiele sichert Kubernetes alle Kommunikationsprozesse mit Server- und Client-Zertifikaten ab. Das betrifft die von »kubectl« oder »kubelet« zum API-Server, die zu und zwischen den »etcd« -Instanzen sowie die zu den Registries wie dem Docker Hub.

Soll eine Applikation sauber als Microservice in Kubernetes laufen, muss sie ein paar Bedingungen einhalten, damit Kubernetes erkennt, dass sie noch lebt. Zudem muss das Loggen auf »stdout« erfolgen, damit »kubectl log« die Daten korrekt anzeigt.

Wer einen eigenen Microservice entwerfen möchte, sollte noch einmal einen Blick auf die Architektur von Kubernetes werfen [23]. Vom Load Balancer über den Weblayer und einen eventuellen Cache bis hin zur Businesslogik sind alle Applikationen zustandslos.

Über die Container Design Pattern von Brendan Burns [24], dem Lead-Entwickler von Kubernetes, kann ein Admin die Architektur vereinheitlichen. Betreibt er etwa eine klassische Legacy-Applikation, lässt der Admin Logging, Monitoring oder auch Messaging in einem zweiten Container des Pod mitfahren, quasi als Sidecar. Auf diese Weise klappt auch die Kommunikation mit der Außenwelt.

Außerdem darf in jedem Container eines Pod nur ein Prozess laufen. Zu den häufig auftretenden Problemen gehört, dass der Admin das Loggen in den Hintergrund verlegt oder er ein »tail -f« auf »stdout« loslässt. Beides verhindert jedoch, das Kubernetes den Prozess als tot erkennt. In der Folge kann es den Container und damit den Pod nicht abräumen und ersetzen.

Alle Cattle-Applikationen müssen tatsächlich zustandslos sein, die Datenbanken sich auf ihre eigene Weise replizieren und nach Unterbrechungen selbstständig synchronisieren. Fehlerszenarien, die auf den Ausfall eines Node und des Netzes reagieren, muss der Admin inklusive Recovery ebenso testen wie Applikationen. Das ist gerade bei klassischen SQL-Datenbanken eine Herausforderung, auf die wiederum No-SQL-Datenbanken wie etwa Mongo DB eine Reaktion sind.

Sprachbegabt

Ganz offensichtlich beeinflussen auch die Imagegrößen den Deployment-Prozess. Es gibt sehr schlanke Programmiersprachen wie Go, und es gibt Sprachen, die in der Minimalversion bereits ein 150-MByte-Image erfordern. Indem er die Container-Caches aufräumt, steht dem Admin eine einfache Möglichkeit zur Seite, die Größen drastisch zu reduzieren. Ebenfalls aus Größen- und Sicherheitserwägungen empfiehlt sich zudem eine Installation aus Containern »FROM scratch« . Einen statisch gelinkten Go-Webserver finden Container-Betreiber bereits ab einer Imagegröße von 6,7 MByte.

Muss er Ruby Gems oder Python Eggs kompilieren, empfiehlt es sich, den Compiler und alle nicht verwendeten Dateien anschließend wieder in die Wüste zu schicken. Für Ruby präsentiert sich Traveling Ruby [25] als vielversprechende Alternative. Es linkt alle Gems und das Ruby-Executable statisch in eine Datei, was das Aufsetzen minimaler Container auch mit Ruby erlaubt.

Für Java empfiehlt sich nicht der Einsatz von J2EE, sondern von Spring Boot [26]. Das erlaubt es, Anwendungen auf der Kommandozeile zu konfigurieren. Einen Webserver startet der Nutzer dann aus der Applikation heraus. Java lässt sich mit seinem eigenen Paketsystem und seinen verzweigten Properties leider nur sehr schwer verschlanken.

Ansonsten ergibt es Sinn, für jede Programmiersprache sowie für jede Development-, Test- und Produktionsumgebung einen Debug-fähigen beziehungsweise einen gehärteten und abgespeckten Container anzulegen. Zusätzlich sollte sich der Admin auf eine Distribution für die eigene Applikationen beschränken, damit möglichst viele Container das zugehörige Basisimage wiederverwerten.

Deployment Pipelines

Kubernetes eignet sich ausgesprochen gut für Deployment Pipelines. Nicht nur die Container, auch die »Dockerfiles« lassen sich einfach auditieren und transportieren. Auf Basis der auditierten »Dockerfiles« bauen Admins in sicherheitskritischen Bereichen ohne Einfluss der Entwickler aus den Dateien die Container für Test und Produktion. Ein Git-Repository macht den Audit nachvollziehbar, den gesamten Prozess kann der Clusterbetreiber anschließend voll automatisieren. Grundsätzlich ist es aber auch möglich, erfahrenen Entwicklern den Rollout von Images zu erlauben, um in Devops-Teams die Infrastruktur zu vereinfachen.

Core OS

Folgt man dem Container-Paradigma, besteht die einzige Aufgabe eines Betriebssystems darin, sicher und zuverlässig Container zu starten und deren Lebenszyklus zu garantieren. Das macht selbst die wenigen Programme schlanker Linux-Distributionen überflüssig. Core OS [27] greift diese Idee auf und zieht sich auf den Kern eines Betriebssystems zurück. Die »/usr« -Partition umfasst gerade einmal 1 GByte, wovon Core OS weniger als 700 MByte belegt. Kubernetes selbst benötigt mit 1,5 GByte mehr als das Doppelte an Plattenplatz.

Aufgrund der geringen Größe von Core OS ist es möglich, eine zweite Platte als Alternative für ein Update zu verwenden. Die Distribution bringt zwei »/usr« -Partitionen (»USR-A« und »USR-B« ) mit. Bei Updates wechseln sie sich ab und werden nur-lesbar eingebunden. Geht etwas schief, stellt der Admin das alte System jederzeit als Fallback wieder her. Solche Immutable Operating Systems kannte man zuvor nur von eher wenig verbreiteten Embedded-Linux-Distributionen, die im Sicherheitsbereich zum Einsatz kommen. Sie machen zugleich ein Paket- und eigentlich auch ein Konfigurations-Management überflüssig.

Um das Mission Statement von Core OS zu erfüllen, kann der Admin in Richtung Hardware noch ein Trusted-Plattform-Modul [28] einschalten und mit Rkt [29] eine Container-Runtime alternativ zu Docker einsetzen, die nur signierte Container ausführt.

Neubau

Eine Lücke gilt es aber noch zu schließen: Die meisten (je nach Zählweise zwei Drittel oder sogar fünf Sechstel der Images auf Docker Hub) enthalten Sicherheitslücken, ein Drittel davon gilt als kritisch. In abgesicherten Umgebungen sollten Admins daher nur selbst gebaute Images »FROM scratch« aus einer eigenen Registry einsetzen [30]. Es hilft ihnen außerdem, Container regelmäßig zu erneuern, sie also maximal eine Woche zu nutzen oder sie gar täglich auf einem isolierten Buildsystem neu zu bauen.

Konfigurationen fasst der Admin in »config_maps« [31] zusammen oder bringt sie in Environment-Variablen unter. Passwörter legt er in Secrets [32] ab, die den Containern als RAM-Disks erscheinen und nicht auf der Platte des Node landen. Die entsprechenden Schritte kann eine automatische Build-Pipeline übernehmen, Jenkins lässt sich mit passenden Plugins für diesen Zweck problemlos erweitern.

Wer diesen Aufwand scheut, kann Clair [33] einsetzen, eine von Core OS entwickelte Docker Registry. Sie scannt die enthaltenen Container gegen bekannte CVEs (Common Vulnerabilities and Exposures) und schlägt Alarm, wenn sie eine unsichere Library in einem Container findet. Als gehostete Alternative lässt sich auch http://Quay.io verwenden, das zum Core-OS-Projekt gehört.

Übrigens sollten Admins daran denken, eine Registry und auch einen Repository-Server für die klassischen Pakete groß genug zu dimensionieren. Der Grund: Ein Massen-Rollout von Containern nach einem Patch an einer systemnahen Bibliothek zieht in der Regel einen vollständigen Rollout aller Images nach sich. Die an sich sehr schlanke Technik, nur Deltas auszurollen, lässt sich in einem solchen Fall nicht anwenden.

Infos

  1. Docker: http://docker.io
  2. Martin Fowlers Artikel über Microservices: http://martinfowler.com/articles/microservices.html
  3. Kubernetes: http://kubernetes.io
  4. Kubernetes auf Github: https://github.com/kubernetes/kubernetes
  5. Kubernetes API Server: http://kubernetes.io/docs/admin/kube-apiserver/
  6. Kubelet: http://kubernetes.io/docs/admin/kubelet/
  7. Systemd: https://www.freedesktop.org/wiki/Software/systemd/
  8. Rkt: https://coreos.com/rkt/docs/latest/rkt-vs-other-projects.html
  9. Systemd 230: https://lists.freedesktop.org/archives/systemd-devel/2016-May/036583.html
  10. Docker-Provider Vagrant: https://www.vagrantup.com/docs/docker/basics.html
  11. Pods: http://kubernetes.io/docs/user-guide/pods/
  12. Replication Controller: http://kubernetes.io/docs/user-guide/replication-controller/
  13. Services: http://kubernetes.io/docs/user-guide/services/
  14. Nginx-Service: https://github.com/k8sp/kubernetes/blob/master/service_nginx.yaml
  15. Kubernetes Ressource Management: http://kubernetes.io/docs/user-guide/compute-resources/
  16. Network-Plugins: http://kubernetes.io/docs/admin/network-plugins/
  17. Flannel: https://coreos.com/flannel/docs/latest/
  18. Calico: https://www.projectcalico.org
  19. Canal: https://www.projectcalico.org/canal-tigera/
  20. Open Vswitch: https://kubernetes.io/docs/admin/ovs-networking/
  21. Load Balancer für Services: https://github.com/kubernetes/kubernetes/wiki/Services-FAQ
  22. Ingress: http://kubernetes.io/docs/user-guide/ingress/
  23. Kubernetes-Architektur: https://github.com/kubernetes/kubernetes/blob/release-1.4/docs/design/architecture.md
  24. Brendan Burn, David Oppenheimer, “Design patterns for container-based distributed systems”: https://www.usenix.org/system/files/conference/hotcloud16/hotcloud16_burns.pdf
  25. Traveling Ruby: http://github.com/phusion/traveling-ruby
  26. Spring Boot: http://projects.spring.io/spring-boot/
  27. Core OS: https://coreos.com
  28. Trusted Computing Vision: https://coreos.com/blog/coreos-trusted-computing.html
  29. Docker-Alternative Rkt: https://github.com/coreos/rkt
  30. Dockers Base-Images: https://docs.docker.com/engine/userguide/eng-image/baseimages/
  31. Config Maps: http://kubernetes.io/docs/user-guide/configmap/
  32. Secrets: http://kubernetes.io/docs/user-guide/secrets/
  33. Clair: https://coreos.com/blog/vulnerability-analysis-for-containers/

Der Autor

Thomas Fricke ist CTO der Endocode AG und beschäftigt sich als Cloud-, Database- und Software-Architekt mit Systemautomation und Devops. In Berlin bewegt er sich, entgegen allen Vorurteilen über die Hauptstadt, hauptsächlich mit dem Fahrrad durch die Stadt und den Wald.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben