Der Steuermann fürs Containerschiff

Kubernetes Grundkurs: So baut man (Container-)Anwendungen heute

Timo Derstappen

© Shutterstock.com / Aun Photographer

Ein System wie Kubernetes lässt sich aus vielen Blickwinkeln betrachten. Die einen sehen es in Sachen Infrastruktur als Nachfolger von OpenStack, bei dem die Infrastruktur allerdings Cloud-agnostisch ist. Für andere ist es eine Plattform, auf der man Microservices-Architekturen einfacher orchestrieren kann – oder wie man heute sagt, Cloud-native Architekturen –, um seine Anwendungen einfacher zu deployen und ausfallsicherer sowie skalierbarer zu machen. Wieder andere begreifen es als Ablösung von Automatisierungs- und Configuration-Management-Tools – weg von komplexen imperativen Deploymenttools hin zu deklarativen Deployments, die vieles vereinfachen, jedoch den Entwicklern immer noch volle Flexibilität gewähren.

Kubernetes stellt aber nicht nur eine große Projektionsfläche dar, es ist eines der aktuell aktivsten Open-Source-Projekte, und viele große und kleine Firmen arbeiten daran mit. Unter dem Mantel der Cloud Native Computing Foundation, die zur Linux Foundation gehört, organisiert sich eine große Community. Im Mittelpunkt steht natürlich Kubernetes selbst, aber mittlerweile gehören auch andere Projekte wie Prometheus, OpenTracing, CoreDNS und Fluentd zur CNCF. Das Kubernetes-Projekt selbst organisiert sich in Special Interest Groups (SIGs). Die SIGs kommunizieren über Slack, GitHub und wöchentliche Meetings, an denen jeder Teilnehmen kann.
Innerhalb dieses Artikels liegt der Fokus weniger auf dem Betrieb und den Interna von Kubernetes als auf der Benutzerschnittstelle. Wir erklären, welche Bausteine Kubernetes mitbringt, um die eigene Anwendung oder eine Build-Pipeline auf einem Kubernetes-Cluster einzurichten.

Orchestrierung

Das Verteilen von Ressourcen auf einem Computer ist größtenteils dem Betriebssystem vorbehalten. In einem Kubernetes-Cluster übernimmt Kubernetes eine ähnliche Funktion. Kubernetes verwaltet Ressourcen wie Memory, CPU und Storage und verteilt Anwendungen und Services in Containern auf den Nodes im Cluster. Container selbst haben den Workflow der Entwickler stark vereinfacht und ihnen zu mehr Produktivität verholfen. Kubernetes bringt die Container in Produktion. Aus einem globalen Ressourcenmanagement resultieren verschiedene Vorteile. Dazu gehören eine effizientere Auslastung von Ressourcen, nahtlose Skalierung von Anwendungen und Services, hohe Verfügbarkeit und weniger Aufwand im Betrieb. Kubernetes bringt für die Orchestrierung sein eigenes API mit. Das API wird aber meist über das CLI kubectl angesprochen.

Die wichtigsten Funktionen von Kubernetes sind:

  • Container werden in sogenannten Pods (Erbsenschoten) gestartet.
  • Der Kubernetes Scheduler stellt sicher, dass alle Anforderungen an Ressourcen auf dem Cluster jederzeit erfüllt werden.
  • Container können via Services gefunden werden. Die Service Discovery sorgt dafür, dass auf dem Cluster verteilte Container über einen Namen angesprochen werden können.
  • Über Liveness und Readiness Probes wird kontinuierlich überprüft, in welchem Zustand sich die Anwendungen auf dem Cluster befinden.
  • Der Horizontal Pod Scaler kann automatisch basierend auf verschiedenen Metriken (z. B. CPU) die Anzahl der Replicas anpassen.
  • Neue Versionen können über ein Rolling-Update ausgerollt werden.

Basiskonzepte

Die hier im Folgenden sehr rudimentär beschriebenen Konzepte werden typischerweise benötigt, um eine einfache Anwendung auf Kubernetes zu starten.

Namespace: Hinter einem Namespace verbirgt sich ein virtueller Namensraum innerhalb des Clusters. Über Namespaces lässt sich ein Cluster in mehrere logische Einheiten unterteilen. Standardmäßig sind Namespaces nicht wirklich voneinander isoliert. Es gibt jedoch Möglichkeiten, Benutzer und Anwendungen auf bestimmte Namespaces einzuschränken.

Pod: Ein Pod verkörpert das Basiskonzept, über das sich Container managen lassen. Ein Pod kann sich dabei aus mehreren Containern zusammensetzen. Diese Container werden dann zusammen in einem gemeinsamen Kontext auf einem Node gestartet. Diese Container laufen also immer gemeinsam. Skaliert man einen Pod, so werden die gleichen Container wieder gemeinsam gestartet. Ein Pod ist insofern praktisch, als dass der Anwender Prozesse gemeinsam laufen lassen kann, sie aber aus verschiedenen Container-Images kommen. Ein Beispiel dazu wäre ein separater Prozess, der Logs eines Service an einen zentralen Logging-Service versendet.

Im gemeinsamen Kontext eines Pods können sich die Containerarbeitsspeicher Netzwerk und Storage teilen. Damit lassen sich auch sehr gut Anwendungen auf Kubernetes portieren, die vorher gemeinsam innerhalb einer Maschine oder einer VM gelaufen sind. Der Vorteil hier besteht darin, dass man Release und Entwicklungszyklen der einzelnen Container getrennt halten kann. Der Entwickler sollte jedoch nicht den Fehler begehen, alle Prozesse einer Maschine eins zu eins in einen Pod zu stopfen. Dadurch verliert er die Flexibilität, im Cluster die Ressourcen gleichmäßig zu verteilen und getrennt zu skalieren.

Label: Jeder Ressource in Kubernetes lassen sich ein oder mehrere Key-/Value-Paare zuordnen. Anhand dieser Paare ist es möglich, mithilfe eines Selektors die zugehörigen Ressourcen zu finden. Es lassen sich also Ressourcen auf Basis von Labels gruppieren. Einige Konzepte wie beispielsweise Services und Replica Sets nutzen Labels, um Pods zu finden.

Service: Hinter einem Service innerhalb von Kubernetes steht nur ein virtuelles Konstrukt – eine Abstraktion, oder besser gesagt eine Gruppierung von vorhandenen Pods. Die Pods werden dabei über Labels gematcht. Mithilfe eines Service lassen sich diese Pods dann für andere Pods auffindbar machen. Da Pods selbst nur sehr volatil sind und ihre Adressen innerhalb des Clusters sich jederzeit ändern können, bekommen Services eine spezifische virtuelle IP-Adresse. Diese IP-Adresse lässt sich dann auch über DNS auflösen. Traffic, der an diese IP-Adresse gesendet wird, wird dann an die gematchten Pods weitergeleitet.

ReplicaSet: Ein ReplicaSet ist ebenfalls eine Gruppierung. Diesmal nicht, um Pods auffindbar zu machen, sondern um dafür zu sorgen, dass eine gewisse Anzahl dieser Pods im Cluster läuft. Durch ein ReplicaSet weiß der Scheduler, wie viele Instanzen eines Pods im Cluster laufen sollen. Laufen zu viele, werden so lange Pods beendet, bis die Anzahl stimmt. Laufen zu wenige, werden neue Pods gestartet.

Deployment: Deployments wiederum bauen auf Replica Sets auf. Beziehungsweise lassen sich Replica Sets über Deployments managen. Ein Deployment kümmert sich dabei darum, Replica Sets zu starten, zu aktualisieren und zu löschen. Deployments erzeugen bei einem Update ein neues ReplicaSet und skalieren die Pods nach oben. Laufen die neuen Pods, wird das alte ReplicaSet herunterskaliert und am Ende gelöscht. Ein Deployment lässt sich auch pausieren oder zurückrollen.

Ingress: Pods und Services lassen sich immer nur innerhalb eines Clusters erreichen. Wer aber einen Service von außen zugreifbar machen möchte, muss ein weiteres Konzept nutzen. Ingress-Objekte definieren, auf welchen Port und Service von außen zugegriffen werden kann. Kubernetes selbst aber verfügt über keinen Controller, der diese Objekte verwendet. Innerhalb der Community gibt es allerdings einige Implementationen, die sogenannten Ingress-Controller. Ein recht typischer Ingress-Controller ist der nginx Ingress Controller.

Config Maps and Secrets: In Kubernetes stehen zwei Konzepte zur Verfügung, um Anwendungen zu konfigurieren. Beide Konzepte ähneln sich, und typischerweise werden die Konfigurationen entweder über das Dateisystem oder Umgebungsvariablen an den Pod weitergereicht. Wie der Name bereits vermuten lässt, werden sensitive Daten in Secrets gespeichert.

Beispielanwendung

Um eine einfache Anwendung auf einem Kubernetes-Cluster zu deployen, benötigt der Entwickler ein Deployment, einen Service und ein Ingress-Objekt. In diesem Beispiel veröffentlichen wir einen einfachen Webserver, der mit einer Hello-World-Website antwortet. Das Deployment definiert zwei Replicas eines Pods mit jeweils einem Container giantswarm/helloworld. Sowohl das Deployment als auch die Pods bekommen das Label helloworld. Das Deployment wird zudem im Default-Namespace gestartet (Listing 1).

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: helloworld
  labels:
    app: helloworld
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: helloworld
        image: giantswarm/helloworld:latest
        ports:
        - containerPort: 8080

Um die Pods im Cluster erreichbar zu machen, muss ein Service definiert werden (Listing 2). Der Service wird ebenfalls im Default-Namespace platziert und besitzt einen Selektor auf das Label helloworld.

apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
  namespace: default
spec:
  selector:
    app: helloworld

Jetzt fehlt nur noch, dass der Service auch von außerhalb des Clusters erreichbar sein soll. Hier bekommt der Service einen externen DNS-Eintrag und der im Cluster befindliche Ingress-Controller leitet schließlich den Traffic, der diesen DNS-Eintrag im Hostheader trägt, an die helloworld Pods weiter (Listing 3).

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  labels:
    app: helloworld 
  name: helloworld
  namespace: default
spec:
  rules:
  - host: helloworld.clusterid.gigantic.io
    http:
      paths:
      - path: /
        backend:
          serviceName: helloworld
          servicePort: 8080

Achtung: Kubernetes selbst enthält keinen eigenen Ingress-Controller. Es gibt allerdings einige Implementationen: nginx, HAProxy, Træfik.
Profitipp: Befindet sich ein Load Balancer vor dem Kubernetes-Cluster, richtet man diesen normalerweise so ein, dass der Traffic an den Ingress-Controller weitergeleitet wird. Den Ingress-Controller-Service sollte man dann über einen NodePort auf allen Nodes verfügbar machen. Auf Cloudprovidern verwendet man dazu typischerweise den Typ LoadBalancer. Dieser Typ sorgt dafür, dass die Cloud-Provider-Erweiterung von Kubernetes automatisch einen neuen Load Balancer erzeugt und entsprechend konfiguriert.
Diese YAML-Definitionen lassen sich nun wahlweise in einzelnen Dateien oder gemeinsam in einer Datei speichern und mit kubectl auf einen Cluster laden.

kubectl create -f helloworld-manifest.yaml

Der Beispielcode befindet sich auf GitHub.

Helm

Um nicht ständig nur mit einzelnen YAML-Dateien jonglieren zu müssen, lassen sich diese in Helm Charts zusammenfassen. Helm zielt mehr darauf ab, ganze Anwendungen zu installieren und zu managen. Darüber hinaus werden die YAML-Dateien als Templates in die Charts eingebunden, sodass sich sehr einfach unterschiedliche Konfigurationen erzeugen lassen. So kann der Entwickler seine Anwendung mit demselben Chart in einer Testumgebung mit einer anderen Konfiguration starten als in der Produktivumgebung. Wenn also das Betriebssystem des Clusters Kubernetes lautet, dann ist Helm die Paketverwaltung. Helm benötigt einen Service namens Tiller. Dieser lässt sich ganz einfach via helm init auf dem Cluster installieren. Wer nun beispielsweise Jenkins auf dem Server installieren möchte, braucht nur die folgenden Befehle einzugeben:

helm repo update
helm install stable/Jenkins

Das Chart für Jenkins wird dabei von GitHub geladen. Es gibt aber auch bereits sogenannte Application Registries, über die sich die eigenen Charts ähnlich verwalten lassen wie die Container-Images (z. B. über quay.io). Den installierten Jenkins kann der Entwickler jetzt ganz einfach nutzen, um seine eigenen Helm Charts zu deployen. Dies erfordert es, das Kubernetes-CI-Plug-in für Jenkins [4] zu installieren. Darüber erhält man einen neuen Build Step, der Helm Charts deployen kann. Das Plug-in erzeugt automatisch eine Cloud-Konfiguration in Jenkins, und auch die Zugangsdaten zur Kubernetes API werden automatisch konfiguriert.

DevOpsCon Whitepaper 2018

Free: BRAND NEW DevOps Whitepaper 2018

Learn about Containers,Continuous Delivery, DevOps Culture, Cloud Platforms & Security with articles by experts like Michiel Rook, Christoph Engelbert, Scott Sanders and many more.

Weitere Konzepte

Verteilte Software bringt so manche Herausforderung mit sich. Kubernetes bietet deshalb noch viele weitere Konzepte, um den Aufbau solcher Architekturen zu vereinfachen. In den meisten Fällen sind diese Bausteine spezielle Abwandlungen der zuvor vorgestellten Ressourcen. Oder sie dienen dazu, die Ressourcen zu konfigurieren, zu isolieren oder zu erweitern.

  • Job: Startet ein oder mehrere Pods und sorgt dafür, dass sie erfolgreich beendet werden.
  • Cron Job: Startet Jobs zu einer bestimmten Zeit oder wiederkehrend.
  • DaemonSet: Sorgt dafür, dass Pods auf alle (oder einige bestimmte) Nodes verteilt werden.
  • PersistentVolume, PersistentVolumeClaim: Definition von Datenträgern im Cluster und die Zuordnung zu Pods.
  • StorageClass: Definiert die im Cluster zur Verfügung stehenden Speichermöglichkeiten.
  • StatefulSet: Sorgt ähnlich wie Replica Sets dafür, dass eine bestimmt Anzahl an Pods gestartet wird. Diese Pods besitzen aber jeweils eindeutige IDs, die auch nach einem Neustart oder Verschieben des Pods bestehen bleiben. Dies eignet sich z. B. besser für Datenbanken.
  • NetworkPolicy: Ermöglicht die Definition eines Regelwerks, welche Netzwerkverbindungen innerhalb eines Clusters aufgebaut werden dürfen.
  • RBAC: Rollenbasierter Zugriff auf die Funktionen im Cluster.
  • PodSecurityPolicy: Definiert den Funktionsumfang, der bestimmten Pods zur Verfügung steht, beispielsweise auf welche Ressourcen des Hosts ein Container zugreifen darf.
  • ResourceQuota: Schränkt die Ressourcennutzung innerhalb eines Namespaces ein.
  • HorizontalPodAutoscaler: Skaliert Pods auf Basis von Metriken im Cluster.
  • CustomResourceDefinition: Erweitert das Kubernetes-API um eigene Objekte. Mit einem CustomerController, können diese Objekte dann auch innerhalb der Clusters verwaltet werden (siehe auch Operators).

In diesem Zusammenhang sollte man nicht vergessen, dass die Community viele Tools und Erweiterungen rund um Kubernetes entwickelt. Der Kubernetes-Inkubator enthält derzeit 27 weitere Repositories, und viele andere Softwareprojekte bieten Schnittstellen zum Kubernetes-API oder kommen bereits mit Kubernetes-Manifesten daher.

Fazit

Kubernetes ist ein mächtiges Werkzeug. Die Detailtiefe der einzelnen Konzepte ist beeidruckend, und man braucht sicherlich etwas Zeit, um einen wirklich guten Überblick über alle Möglichkeiten zu bekommen. Wichtig ist aber, dass die Konzepte aufeinander aufbauen und damit Bausteine bilden, die sich je nach Bedarf kombinieren lassen. Im Gegenteil zu einem Framework, das Prozesse und Abläufe abstrahiert, werden die eigenen Anwendungen nicht in eine bestimmte Form gepresst, sondern lassen sich flexibel entwerfen. Kubernetes bietet eine gesunde Mischung aus IaaS und PaaS. Viele Jahre Erfahrung seitens Google über den Betrieb von verteilter Software stecken in Kubernetes. Außerdem haben viele der am Projekt beteiligten Personen aus den Problemen und Fehlern der OpenStack-, CloudFoundry- und Mesos-Projekte gelernt. Zudem ist Kubernetes bei einer großen Anzahl von Unternehmen von GitHub über OpenAI bis hin zu Disney im Einsatz.

Verwandte Themen:

Geschrieben von
Timo Derstappen
Timo Derstappen
Timo Derstappen ist Mitgründer von Giant Swarm in Köln. Er hat langjährige Erfahrung mit dem Aufbau von skalierbaren und automatisierten Cloud-Architekturen und sein Interesse wird meist von leichtgewichtigen Konzepten bzgl. Produkt-, Prozess- und Softwareentwicklung geweckt. Freie Software ist für ihn ein Grundprinzip.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Kubernetes Grundkurs: So baut man (Container-)Anwendungen heute"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Daniel D. Clemens
Gast

IST: „[…] an denen jeder Teilnehmen kann.“
SOLL: „[…] an denen Jeder teilnehmen kann.“

Gruß,
Daniel