Im ersten Teil dieser zweiteiligen Artikelserie ging es um die Frage, welche Möglichkeiten es gibt das für ein Projekt benötigte Tool Set, wie Compiler, Laufzeitumgebungen oder IDEs von einem Entwicklungsteam effizient gemanagt und installiert werden kann.
In diesem zweiten Teil befassen wir uns mit der Bereitstellung von Services wie Datenbanken oder Message Brokern. Diese werden häufig zum Testen von Anwendungen während der Entwicklung benötigt und deshalb ebenfalls allen Mitgliedern eines Entwicklungsteams zur Verfügung stehen und sich unkompliziert einrichten lassen.

Anders als bei den Tools erscheint Docker für die Services als das Mittel der Wahl. Da sie im Hintergrund laufen, entfällt nach dem einmaligen Starten das wiederholte Hantieren mit der Docker Kommandozeile. Üblicherweise modifizieren die Services auch keine Dateien auf dem Host System, was die Handhabung ebenfalls einfacher macht. Graphische Oberflächen, die mit Containern schwierig zu handhaben sind, kommen hier üblicherweise nicht zum Einsatz und die erstellten Container lassen sich beliebig starten und stoppen und müssen nicht bei jedem Aufruf neu erstellt werden. Nicht zuletzt kommen Container auch in vielen Produktivumgebungen und so sind an diese angelehnte Entwicklungsumgebungen möglich.

Wir betrachten zwei unterschiedliche Möglichkeiten: Zum einen die Installation der Dienste auf dem lokalen Entwicklerrechner, zum anderen die Nutzung eines Kubernetes Clusters in einer Cloud Umgebung und dessen Einbindung in die lokale Entwicklungsumgebung. Ziel ist es auch im letzteren Fall, dass jeder Entwickler eine eigene Umgebung mit allen benötigten Services zur Verfügung hat in der er ohne zeitraubenden Abstimmungs-Overhead neue Services starten oder Testdatenstände einspielen kann.

Überblick lokaler Rechner vs. Cloud
Überblick lokaler Rechner vs. Cloud

In beiden Setup Varianten wird man in vielen Fällen aus Kosten- und Ressourcengründen darauf verzichten mehrere zur Produktivumgebung komplett identische Umgebungen bereitzustellen, sondern sich auf die notwendigen Services beschränken, beispielsweise eine einzelne Datenbankinstanz anstatt eines Clusters.

Services auf dem lokalen Rechner

Das gute alte Docker Compose ist immer noch eine gute Wahl um mehrere Container gemeinsam zu verwalten. Die Konfiguration erfolgt über ein YAML-Skript. Damit können auch Abhängigkeiten zwischen Containern, Umgebungsvariablen und Volumes unkompliziert deklariert werden. Volumes können beispielsweise für Testdaten genutzt werden, die beim Anlegen des Containers automatisch eingespielt werden. Die Container können gemeinsam gestartet und gestoppt werden und automatisch neugestartet werden. Der Status der Container kann mit den üblichen Tools, wie docker ps überprüft werden. Bei der Desktopversion von Docker für Windows und macOS wird Compose bereits mitinstalliert, für Linux kann es separat installiert werden, z. B. mit dem in Teil 1 vorgestellten Packagemanagern nix oder conda.

Beispiel:

version: '3.1'
services:
  timeseries-db:
    image: influxdb:1.5
    restart: always
    ports:
      - "8086:8086"
    volumes:
      - ./docker-entrypoint-initdb.d/sdata.txt:/docker-entrypoint-initdb.d/sdata.txt:ro
    environment:
      - INFLUXDB_DB=zeitreihen
      - INFLUXDB_ADMIN_ENABLED=true
      - INFLUXDB_ADMIN_USER=admin
      - INFLUXDB_ADMIN_PASSWORD=admin
      - INFLUXDB_READ_USER=readuser
      - INFLUXDB_READ_USER_PASSWORD=readuse

  postgres-db-virtus:
    image: postgres:10.4
    restart: always
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=lds
      - POSTGRES_PASSWORD=postgres

MicroK8s ist eine leichtgewichtige Möglichkeit einen einzelnen Kubernetes Knoten zu betreiben – allerdings ist es derzeit nur für Linux verfügbar. Installiert werden kann es mittels des in Teil 1 vorgestellten Snap.

Es bringt eine vorkonfigurierte kubectl Installation mit, die mittels microk8s.kubectl aufgerufen werden kann. Services und Anwendungen können wie gewohnt mittels Helm oder Manifesten installiert werden. Außerdem beinhaltet es eine Reihe von Add-ons, die bei Bedarf per Kommandozeile aktiviert werden können. Beispielsweise ist es möglich eine lokale Docker Registry einzurichten, das Service Mesh Istio zu starten oder GPUs einzubinden. Damit ist MicroK8s nicht nur eine gute Wahl um für die Entwicklung benötigte Services wie Message Broker bereitzustellen, sondern auch für einen schnellen Check von Deployment Skripten und Docker Images.

MicroK8s erstellt im Netzwerksetup des Hostrechners eine Bridge. Die innerhalb von MicroK8s laufenden Services werden auf IP-Adressen aus dem Adressbereich der Bridge gemappt und können mittels kubectl get services abgefragt werden. Wem das Hantieren mit den Adressen zu umständlich ist und lieber mit den Service Namen arbeiten möchte, kann auch das weiter unten vorgestellte kubefwd verwenden.

Minikube ist eine weitere Möglichkeit einen einzelnen Kubernetes Knoten zu betreiben. Es bringt wie MicroK8s eine Reihe von Add-ons mit, die bei Bedarf aktiviert werden können, beispielsweise Dashboards, allerdings ist das Angebot deutlich knapper als bei MicroK8s, so wird unter anderem istio nicht als Add-on angeboten. Dafür unterstützt Minikube anders als MicroK8s Profile, mit denen es bei mehreren Projekten möglich ist jeweils spezifische Umgebungen in Form eines eigenen Clusters zu deklarieren. Anwendungen oder komplette Entwicklungssetups können hier ebenfalls auf dem gewohnten Kubernetes Weg mittels Helm Charts oder Manifesten konfiguriert und installiert werden. Minikube kann unter Linux, macOS und Windows in einer virtuellen Maschine installiert werden. Unter Linux ist auch eine Installation nur mit Docker möglich, das wird aufgrund von Risiken für die Systemstabilität allerdings nicht empfohlen.

Für Windows 10 Pro und macOS gibt es außerdem noch die Möglichkeit den in Docker Desktop eingebauten Kubernetes Knoten zu nutzen. Dafür muss er in den Settings zunächst aktiviert werden. Ein vorkonfiguriertes kubectl wird mitgeliefert.

Während diese vier Tools dem Entwickler ein Arbeiten unabhängig von externen Ressourcen ermöglichen, sind die Setups begrenzt durch die Ressourcen des lokalen Rechners. Das ist nicht immer ausreichend, sodass es in einigen Fällen sinnvoll ist, über die Nutzung eines Kubernetes Clusters nachzudenken.

Services im Kubernetes Cluster

Der Kommandozeilenclient von Kubernetes, kubectl, beinhaltet das Kommando kubectl port-forward. Diese ermöglicht das Forwarden eines oder mehrerer lokaler Ports zu einem Pod eines Services im Kubernetes Cluster. Beispiel Postgres Datenbank: Das Kommando
kubectl port-forward svc/my-postgres-postgresql 5432:5432
ermöglicht den Aufruf der Datenbank unter localhost:5432. Allerdings erfordert das pro Service einen Aufruf von kubectl.

Eine komfortablere Möglichkeit Portforwarding zu nutzen, bietet das Kommandozeilentool kubefwd. Anders als mit kubectl kann mit einem Aufruf nicht nur ein einzelner Service geforwarded werden, sondern mehrere oder alle Services eines Kubernetes Namespaces. Dabei werden auch die Namen der Services mitgemappt.

Funktionsweise kubefwd
Funktionsweise kubefwd

kubefwd fügt dafür temporär, solange das Forwarding aktiv ist, neue Einträge zur Datei /etc/hosts hinzu um den Namen auf eine lokale Loopback IP-Adresse zu mappen, dafür werden Superuser Rechte benötigt. Für nix und conda existieren derzeit leider noch keine Packages in den offiziellen Repositories, auf der kubewd Webseite werden aber mehrere Paketformate für Linux, MacOS und Windows bereitgestellt.

Beispiel (gekürzt):

$ sudo -E ./kubefwd/kubefwd svc -n default
2019/06/09 23:34:50  _          _           __             _
2019/06/09 23:34:50 | | ___   _| |__   ___ / _|_      ____| |
2019/06/09 23:34:50 | |/ / | | | '_ \ / _ \ |_\ \ /\ / / _  |
2019/06/09 23:34:50 |   <| |_| | |_) |  __/  _|\ V  V / (_| |
2019/06/09 23:34:50 |_|\_\\__,_|_.__/ \___|_|   \_/\_/ \__,_|
2019/06/09 23:34:50
2019/06/09 23:34:50 Press [Ctrl-C] to stop forwarding.
2019/06/09 23:34:50 Hostfile management: Backing up your original hosts file /etc/hosts
to /home/ck/hosts.original
2019/06/09 23:34:51 Forwarding: my-postgres-postgresql:5432 to pod my-postgres-postgres
ql-0:5432
2019/06/09 23:34:51 Forwarding: my-postgres-postgresql-0.my-postgres-postgresql-headles
s:5432 to pod my-postgres-postgresql-0:5432
2019/06/09 23:34:51 Forwarding: my-influx-influxdb:8086 to pod my-influx-influxdb-847fb
9ffd5-4wd8x:8086
2019/06/09 23:34:51 Forwarding: my-influx-influxdb:8088 to pod my-influx-influxdb-847fb
9ffd5-4wd8x:8088

Danach lassen sich die Datenbanken lokal mittels my-postgres-postgresql:5432 bzw. my-influx-influxdb:8086 aufrufen – den gleichen Hostnamen und Ports unter denen sie auch innerhalb des Kubernetes Clusters erreichbar sind.

Gelegentlich gibt es Situationen, bei denen man nicht nur auf im Kubernetes Cluster laufenden Services zugreifen möchte, sondern kurzzeitig auch Anfragen vom Cluster auf den lokalen Rechner umleiten möchte um Probleme in einer Anwendung zu debuggen oder Sonderfälle zu testen. An dieser Stelle kommt Telepresence ins Spiel, ein Kommandozeilentool das eine Art Zwei-Wege Netzwerk Proxy anlegt: Er erlaubt sowohl den Zugriff auf im Cluster laufende Dienste wie Datenbanken, wie auch umgekehrt den Aufruf auf lokale Anwendungen aus dem Cluster heraus. Dafür wird je Anwendung im Cluster ein Pod mit einem Proxy Container angelegt sowie lokal ein Proxy Container gestartet. Dies geschieht automatisch, sobald Telepresence lokal gestartet wird. Mittels Parameter lassen sich die Ports mitgeben, die von außen erreichbar sein sollen. Beim Entwickeln wird die Anwendung in den lokalen Container gemountet. Telepresence ist momentan unter Linux und macOS verfügbar.

Funktionsweise Telepresence
Funktionsweise Telepresence

Conclusion

Fazit

Es gibt eine ganze Reihe unterschiedlicher Möglichkeiten um mit Docker Containern oder Kubernetes für Entwickler Dienste wie Datenbanken, Message Broker oder Sidecars bereitzustellen. Dabei gibt es natürlich Abwägungen zu treffen: Bevorzugt man geringe Latenzen und ein autonomeres Arbeiten, aber eben auch durch den lokalen Rechner beschränkten Rechenressourcen, welches einem die lokalen Setups mit Docker Compose, MicroK8s, Minikube oder Docker Desktop bieten? Oder doch lieber ein evtl. produktionsnäheres Setup mit einem Kubernetes Cluster in der Cloud und das Arbeiten mit kubectl port-forward, kubefwd oder Telepresence? Und welche Sicherheitsrisiken ist man bereit in Kauf zu nehmen, wenn die Anwendungen Superuser Rechte auf dem lokalen Rechnern einfordern oder man von ausserhalb des Kubernetes Clusters Zugriff auf Datenbanken erlaubt? kubefwd editiert beispielsweise die /etc/hosts Datei, MicroK8s verbiegt iptables Regeln.
Ziemlich sicher werden wir in diesem Bereich in den nächsten Jahren noch viele Verbesserungen sehen werden. Vielleicht machen leistungsstarke Web IDEs wie VS Code Server auch die komplette Verlagerung der Entwicklung in einen Kubernetes Cluster populär.