#JAVAPRO #Container #Kubernates

Kubernetes ist eine von Google entwickelte und derzeit am schnellsten wachsende Open-Source-Lösung für das automatisierte Deployen, Skalieren und Managen containerisierter Anwendungen über unterschiedliche Cluster und Nodes hinweg.

 

Seit Docker die Linux-Container-Technologie populär gemacht hat, ist das Ökosystem um Container explodiert. Hat vor einem Jahr auf Konferenzen noch die Frage gestanden, ob Container in Produktion eingesetzt werden können, ist das „Ob“ heute kein Thema mehr. Es wird in diesem Jahr nur noch diskutiert, auf welche Weise Container in die bestehenden Infrastrukturen integriert werden und wie die existierenden Prozesse auf die kommende Containerisierung ausgerichtet werden können. Dabei ist Kubernetes eines der sich am schnellsten entwickelnden Projekte auf GitHub. Es bringt unter der Governance
von Google alle drei Monate einen neuen Release hervor und überzeugt trotz der hohen Entwicklungsgeschwindigkeit bereits bei Alpha- und Beta-Feature mit hoher Stabilität. Auch konservative Branchen, wie Banken und Versicherungen starten Projekte, um aus Angst vor der disruptiven Energie der Fintechs
endlich ihre Prozesse zu modernisieren und die Release-Zyklen von teilweise immer noch zwölf Monaten endlich in das agile Zeitalter zu führen, damit sie wenigstens tages- oder wochenaktuelle Updates liefern können.

Die Frage der Orchestrierung von Containern ist derzeit im Fokus der Debatte. Docker Swarm, Kubernetes, Mesosphere DC/OS oder Exoten wie RancherOS sind die Kandidaten für das verteilte Betriebssystem der Zukunft.

Der primäre Fokus für Container waren zunächst zustandslose Applikationen ohne eigenen Datenbestand, Webserver oder Middleware, die eine Datenhaltung außerhalb von Kubernetes realisieren, Applikationen die schnell angepasst, aber deren Daten nicht kritisch waren.

Bei Kunden konnten wir erleben wie selbst Grenzfälle, etwa Datenbanken in Kubernetes oder SAP R/2 (sic!) Systeme auf Windows in virtuellen Maschinen, sich trotz der fehlenden Konformität zu den 12-Factor-Apps (eine Entwicklungsmethode für skalierbare Web-Applikationen in der Cloud) leichter betreiben lassen als auf herkömmliche Weise. Aus der Sicht von Projekten ist dies das einzig relevante Kriterium.

Einer der aktuell spannendsten Aspekte ist die Integration von GPUs (Global Processing Unit) und für neuronale Netze spezialisierte Hardware in Kubernetes. Mehrere Rechner eines Clusters bilden durch das GPU ein Netzwerk, bei dem auf jedem Rechner ein Client läuft, der für Rechenoperationen herangezogen werden kann. Neuronale Netze benötigen für die Trainingsphase eine deutlich höhere Rechenleistung als für das Abrufen des Gelernten. Bei einigen tausend GPUs ist es also dringend geboten, die Rechenleistung auf verschiedene Probleme zu verteilen, um die Balance zwischen Lernzeit und Clustergröße an die aktuelle Last anzupassen. Das ist der Ausgangspunkt für Kubernetes und hier kann es alle operativen Stärken jetzt auf GPUs ausspielen.

Operations

Kubernetes ist ein Google-Projekt, der Nachfolger des internen Borg-Projekts, das zu tief in die Infrastruktur integriert ist, um als Open-Source-Projekt in die Freiheit entlassen zu werden. Bei Google werden damit zuerst Operationsaspekte abgedeckt.

  • Entkopplung von Entwicklung- und Hardware-Management: Fokus ist die Bereitstellung von Applikationen ohne Umweg über System- Management.
  • Spezialisierung innerhalb Operations auf Cluster, Maschine und Application Management: Jedes Team kann sich relativ autonom um seine Problemstellung kümmern.
  • Rollouts, Monitoring und Health-Management werden in das Management Framework integriert: Damit brauchen Applikationen nur noch Checkpoints zur Verfügung stellen, das Framework kümmert sich um die Integration in den Gesamtprozess.
  • Übergang zur Immutable-Infrastruktur: Nur wenn Rollouts vollständig automatisiert sind und ein Eingriff durch Admins nicht nur unnötig, sondern auch unerwünscht ist, lässt sich jederzeit die Integrität der Plattform garantieren.

Architektur

Kubernetes hat eine klassische Master-Worker-Architektur (Abb. 1). Auf dem Master laufen alle zentralen Dienste, auf den Workern die ausführenden Pods, Netzwerk-Services, Proxies und Prozesse zur Erfassung der Ressourcen und zum Monitoring.

Kubernetes Architektur. (Abb. 1)

Kubernetes Architektur. (Abb. 1)

 

Pods, Services, Deployments, ConfigurationMaps und Secrets

Mit diesen Instrumenten eignet sich Kubernetes hervorragend zur Integration in Continuous-Integration-Pipelines. Das ermöglicht DevOps-Umgebungen und löst das Problem der Integration der in Images abgelegten Artefakte und der Verbreitung von Passwörtern nur auf den unbedingt notwendigen Kreis von Applikationen. Pods können als kleinste Einheit definiert, konfiguriert und in Deployment-Pipelines propagiert werden. Üblicherweise werden Pods in Deployments als Templates verpackt und mit einem impliziten ReplicaSet skalierbar und resilient gemacht.

(Listing 1)
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
    metadata:
        labels:
            app: nginx
    spec:
        containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
          - containerPort: 80

Services referenzieren die Pods über das Label mit dem Wert Key (hier app)und dem Wert (hier nginx). Zusätzlich wird der interne targetPort 80 auf einen externen Port 8000 und einen nicht spezifizierten Nodeport abgebildet. Über type: LoadBalancer kann ein externer Service, etwa ein F5 oder HaProxy die Verbindung ins Internet herstellen.

(Listing 2)
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
ports:
- port: 8000
  targetPort: 80
  protocol: TCP
selector:
  app: nginx
type: LoadBalancer

Konfigurationen lassen sich in Config-Maps ablegen, Passwörter, Certificates und Tokens in Secrets.

(Listing 3)
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWTRtaW4=
  password: MWYyZDFlMmU2N2Rm

Damit lassen sich in Abhängigkeit von der Umgebung die Secrets als separate Instanzen in Container-Dateien oder Environment-Variablen zusteuern.

Resilience und Skalierbarkeit

Ein weiterer wichtiger Aspekt bei Borg, der in Kubernetes Eingang gefunden hat, ist die Widerstandsfähigkeit, die „Resilienz” gegen Ausfälle von Hardware. Grundsätzlich ist Resilienz mit Skalierbarkeit eng verbunden und beruht auf der Fähigkeit ohne Verzögerung weitere Instanzen einer Software zur Verfügung stellen. Eine Eigenschaft, die unbedingt in die Orchestrierung integriert werden sollte. Tatsächlich ergeben sich jedoch konzeptionelle Schwierigkeiten, die bereits im CAP-Theorem (in verteilten Systemen ist es unmöglich Konsistenz, Verfügbarkeit und Ausfalltoleranz gleichzeitig zu garantieren) angelegt sind
und dazu geführt haben, dass praktisch jede Datenbank ihre eigenen synchronen und asynchronen Replikationsmethoden mitbringt. Gegenwärtig gibt es erste Bestrebungen, auch Datenbanken in Kubernetes zu integrieren. Obwohl die Bestrebungen schon weiter gediehen sind als bei anderen Projekten, sind wir
von einer halbwegs generischen Lösung noch weit entfernt.

Ein weiterer wichtiger Aspekt ist die Abbildung von URIs auf Services. Dafür gibt es in Kubernetes zwei Konzepte. Ein Service vom Typ Loadbalancer kann ausgelesen werden, um einen externen Loadbalancer zu konfigurieren. Die Aufgabe kann also über einen Controller konfiguriert und dann delegiert werden.
Ähnlich, aber in Kubernetes selbst betreibbar, ist Ingress. Ein Reverse-Proxy, der auf der Granularität der URIs eine externe Adresse mit einem Kubernetes-Service verbindet. Implementierungen durch F5 Loadbalancer oder HA-Proxy bzw. durch NGinx, Haproxy oder Varnish stehen zur Verfügung.

Planetary Scale

Ein weiteres Problem, das für größere Cluster gelöst werden muss, ist die Integration von entfernten Clustern. Ohne Aussicht auf eine zeitnahe Synchronisation von Daten bleibt nur eine Integration über die Loadbalancer und eine asynchrone Replikation im Hintergrund. Diese Aufgabe wird von Kubernetes-Federation übernommen, allerdings ist dazu die Unterstützung durch ein globales DNS unerlässlich, dass das günstigste Routing im Sinne von kurzer Latenzzeit zum nächsten Kubernetes-Cluster erlaubt.

Anwendungsarten

Als Plattform hat es keine Vorgaben für Programmiersprachen, Bibliotheken oder bestimmte Versionen. Alles was sich in Images verpacken lässt, kann als Anwendung in Pods ausgeliefert werden. Pods sind Container, die sich die gleichen Linux-Namespaces teilen. Sie haben ein gemeinsames Schicksal, werden zusammen erzeugt und gelöscht, laufen auf demselben Hardware-Node und teilen sich die Netzwerkadresse sowie den gesamten Namespace. Sie können gemeinsam, aber auch einzeln angesprochen werden. Damit lassen sich alle Applikationen verwenden, die in Docker- oder Rkt-Containern laufen und die APPC-konform gemäß Standard der OCI (Open Container Initiative) sind. Konfigurationen in YAML-Files können als ConfigMaps und
Tokens, Passwörter oder Keys als Secrets in jeder Stage unabhängig zugesteuert werden. Pods können so schon in mehreren Sprachen implementiert werden. Das ist insofern wichtig, als neben der JVM-Plattform in agilen Projekten alle Programmiersprachen verwendet werden. Wir haben schon NodeJS und Ruby
für Frontends, Python für Machine-Learning, Go für systemnahe Anwendungen und .Net für Legacy Applikationen in Kubernetes verwendet. Auch Hypervisor mit vollständigen Images lassen sich erfolgreich in Containern betreiben.

Multitenancy, Networks, Staging, Namespaces, RBAC

Durch die gerade in Kubernetes von Alpha zu Beta gereifte Role-based Access-Control lassen sich verschiedene Konzepte auf einem Cluster realisieren. Zum einen können Entwicklung, Test und Produktion im selben Cluster ebenso wie verschiedene Kunden im gleichen Hardware-Cluster laufen, zum anderen
kann über eine sehr feingranulare Rollenverteilung der Zugriff durch Policies geregelt werden. Eine wichtige Rolle spielt hier die Möglichkeit, Policies zum Starten der Container zu vergeben und nur bestimmten Accounts zu erlauben, privilegierte Container zu starten. Der Standard, nur eingeschränkte Rechte zu gewähren, reicht für die meisten Anwendungen völlig aus. Eine besondere Stärke von Kubernetes wird die Einführung von Netzwerkimplementierungen
mit NetworkPolicies werden. Hier lassen sich Container alleine durch Label und Namespaces trennen. Der Grad der Isolierung entspricht einem Paketfilter. Die Konfiguration erlaubt zum Beispiel einen Namespace per Default zu isolieren und per Label einzelne Verbindungen zwischen gelabelten Pods
freizugeben.

(Listing 4)
kind: Namespace
apiVersion: v1
metadata:
    annotations:
        net.beta.kubernetes.io/network-policy: |
            {
                „ingress“: {
                    „isolation“: „DefaultDeny“
                }
            }
(Listing 5)
apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
  matchLabels:
    role: db
ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
ports:
  - protocol: tcp
    
    port: 6379

Java

In Java-Umgebungen, wie auch in anderen Programmiersprachen, geht der Trend dazu, auch Aufgaben, die über die eigentliche Programmierung hinausgehen, in das Framework hineinzuziehen. Je weiter vom Kern der Programmierung, also von der Entwicklung entfernt, desto schwächer ist die Ausführung. Während Webserver meistens noch gut implementiert werden, ist Application-Lifecycle-Management schlecht ausgeführt. Wir müssen hier nicht auf IoT (Internet-of-Things) zeigen, um Beispiele für wirklich verheerende Sicherheitsprobleme zu finden, wie die Operation Rosehub zeigt. Dieser Philosophie von Separation-
of-Concerns folgend sind Service Broker, Updates und Lifecycle-Management nicht Aufgabe von Java-Tools sondern des Container-Managements. Sobald die JVM-only-Welt verlassen wird, weil zum Beispiel neue Herausforderungen für Machine-Learning angegangen werden sollen, stellt sich diese Frage ernsthaft neu. Spring Boot erlaubt die Verbindungen zwischen diesen Welten, weil direkt auf der Kommandozeile der Java-Applikation auch tief in Archiven vergrabene Parameter auf einfache Weise überschrieben werden können. Aus

(Listing 6)
java -jar $ARTEFACT $JAVA_RUNTIME_OPTIONS“

wird durch Konfiguration der Environment Variablen

(Listing 7)
java -jar myApplication.jar --rabbitmq.server-host=172.17.0.100
--spring.data.mongodb.host=172.17.0.99

eine laufende, fertig konfigurierte Applikation.

Deployment Pipelines mit Fabric8

Fabric8 ist eine Microservice-Plattform, die Docker, Kubernetes und Jenkins zu einem Continuous-Deployment-System integriert. Es enthält eine Web-Developer-Console, die den gesamten Lebenszyklus eines Microservice abdeckt. Neben Create, Edit, Build auch Deploy und Test, Logging, Metrics, ChatOps und selbst einen Chaos-Monkey. Hawtio und Jolokia erlauben eine tiefe Analyse von Java in Containern. Eine Integration von Messaging-Services wie ActiveMQ und Camel, ein Maven-Plugin zum direkten Deployment in Kubernetes und Test-Integration in Arquillian runden das Build-System ab.

Die Verbindungen zwischen Java und Kubernetes werden Java-konform über Annotationen gestiftet. Es gibt bereits Annotationen für Delivery und Management sowie für Secrets, sodass bereits im Java-Code Hinweise auf die Deployment-Pipeline, der Anschluss an das Monitoring und die zu verbindenden Secrets designt werden können. Damit kann bereits bei der Architektur einer Applikation der Anschluss an Operations gelegt und die DevOps-Kultur in Kubernetes gelebt werden.

(Listing 8)
@Produces
public BuildSignallerService createBuildSignallerService(
@Protocol(„http“) @ServiceName(„fabric8“) String consoleLink,
@ConfigProperty(name = „BUILD_NAMESPACE“, defaultValue = „“)
String namespace,
KieSession ksession)
{
    BuildSignallerService service =
    new BuildSignallerService(ksession, namespace);
    service.setConsoleLink(consoleLink);
    service.start();
    return service;
}

Pakete und Charts

Helm als Kubernetes-Projekt erlaubt die Definition von Paketen, also Pods die in Abhängigkeit zueinanderstehen, und die Definitionen von Anwendungen über Pattern. Client-Server ist in diesem Sinne auch ein Pattern, genau wie Replikationsmuster in Datenbanken.

Fazit:

Das die Zukunft containerisiert wird, steht nicht mehr in Frage. Die Frage die nunmehr besteht ist, wie schnell die Koffer für den Umzug gepackt werden können.

 

Thomas Fricke

Thomas Fricke ist CTO der Endocode AG, beschäftigt sich seit vier Jahren mit Containern und hat sich davor 10 Jahre mit Virtualisierung und 25 Jahre mit Linux auseinandergesetzt. Mit mehreren gefühlt hunderten Jahren Operations und Entwicklungserfahrung hat er diese Themen in den letzten Jahren intensiv begleitet. Dabei sieht er so alt gar nicht aus.


(Visited 175 times, 1 visits today)

Leave a Reply