Webseiten bestehen oft aus Hunderten Microservices. Während dezentrale Sidecars Dienste in Pods bündeln, verteilt Ingress als zentraler Zugang die Zugriffe auf die Microservices.
Als Stärke von Kubernetes preisen viele, dass die Containerplattform Configuration-as-Code-Konzepte konsequent umsetzt und Admins die Konfigurationen nachvollziehbar in Repositories ablegen lässt. Doch während Entwickler Quellcode heute ganz selbstverständlich in Git oder anderen Versionierungssystemen lagern, gilt das für Konfigurationen, besonders solche für das Netzwerk oder für Domains und Domainzertifikate, noch nicht überall als üblich.
Ausgehend von der Zwölf-Faktor-Philosophie für Cloud-Native-Apps [1] will dieser Artikel das Versäumnis exemplarisch nachholen. Über Yaml-Dateien definiert der Admin dabei die Struktur einer Website, die aus Pfaden zu verschiedenen Services besteht. Der Artikel erklärt, wie der Admin deklarative Yaml-Formate dazu verwendet, die Domainstruktur in Ingress [2] zu verwalten.
Bei Ingress, das seit Kubernetes 1.1 mit an Bord ist, handelt es sich um ein API-Objekt, das für die Außenwelt HTTP- und HTTPS-Routen zu Services in einem nach außen abgeschotteten Cluster anbietet. Die Routen bestimmen in der Ingress-Ressource aufgestellte Regeln.
Mit Hilfe eines Tricks, über den er die Nip.io-Namen [3] auf IP-Adressen abbildet, testet der Admin auch eigene Zertifikate mit diesen Konfigurationen. So erreicht er am Ende eine minimale Abweichung zwischen Produktions- und Testszenarien.
Konkret deckt das beschriebene Setup von den insgesamt zwölf die Faktoren eins bis drei, sieben sowie zehn ab. Das genügt, um in Devops-Umgebungen die Netzwerkkonfiguration den Entwicklern zu überlassen. Die Domain ist nur noch ein konfigurierbarer Parameter, gültige Zertifikate erzeugt der Anbieter selbst.
Cloudnetze
Es gibt in Kubernetes mehrere Möglichkeiten, Netzwerke aufzubauen. Ein früherer Artikel zeigte bereits [4], wie sich ein Service mit einem Load Balancer verbinden lässt. Um jedoch die eingebauten Features von Kubernetes für Webanwendungen gewinnbringend einzusetzen, ist es notwendig, die zentralen Konzepte im Detail und im Zusammenspiel zu verstehen. Der Artikel erklärt Gemeinsamkeiten und Unterschiede zwischen den Sidecars (Beiwagen) für die Pods und Ingress als zentralem Zugang. Beide lassen sich auf ähnliche Weise und mit Hilfe von Reverse-Proxys implementieren.
Die beschriebene Technologie steckt nativ in Kubernetes. Mit Erweiterungen wie dem Container Network Interface beschäftigt sich ein älterer Artikel im Linux-Magazin [5]. Einem fortgeschrittenen Ansatz mit Istio widmet sich ein anderer Artikel in diesem Heft.
Sidecars
Beiwagen sind aus der motorisierten Welt hinlänglich bekannt als Teil eines Motorradgespanns. Ihre Vorläufer reichen zurück bis ins 19. Jahrhundert. Deutlich weniger betagt ist der Beiwagen als Kubernetes-Konzept. Er findet aber bereits im ersten Paper von David Oppenheimer und Brendan Burns als zentrales Entwurfsmuster Erwähnung [6]. Es definiert den Pod als die wesentliche Einheit eines Microservice-Deployment in Kubernetes und beschreibt Design Pattern für verteilte Dienste.
Ein einfaches Beispiel für einen Sidecar finden Anwender beim Logging. Hier überführt ein eigener Container die Logfiles aus einem Filesystem auf dem Host in einen zentralen Logging-Dienst. Dabei schreibt die Hauptanwendung in ein Volume, das sie mit dem Sidecar teilt. Den realisiert üblicherweise eine Variante von Fluentd. Der Sidecar liest die Daten ein und schickt sie an den Logserver – wahlweise ein Elastic-Search-Cluster, ein Splunk, ein Hadoop-Filesystem oder in der Cloud ein Stackdriver-Server. Damit erfüllt der Beiwagen zugleich Regel 11 der zwölf Faktoren, indem er die Verantwortung für das Logging von der Applikation an einen zentralen Dienst überträgt.
Netzwerk-Beiwagen
Ebenso wichtig, wenn nicht gar noch wichtiger, ist der Einsatz von Sidecars im Netzwerk-Setup. Hier kontrollieren und steuern die jeweiligen Beiwagen den Zugang zu den Applikationen. Zu ihnen zählt beispielsweise der Cert-Manager, der Let’s-Encrypt-Zertifikate ausrollt und erneuert. Es gibt Sidecars für Oauth-2-Autorisierungen und solche, die sich über LDAP an einem Active-Directory-Server anmelden.
Sogar Windows-Anwendungen lassen sich über einen Sidecar betreiben. So hat der Autor an einem Sidecar für eine Windows-Anwendung mitgewirkt, welche in KVM – in einem Container – lief. Der Desktop ließ sich auf dem VNC-Port 5100 über NoVNC als eine Browser-fähige Applikation nutzen, der Reverse-Proxy kümmerte sich über einen Go-Sidecar um die LDAP-Autorisierung beim Active-Directory-Server.
Das klappt allerdings nur, wenn Kubernetes auf Bare Metal läuft und sich kein weiterer Hypervisor zwischen die Container und die Hardware schiebt. Die Abbildung 1 zeigt die Architektur eines solchen Windows-Pod.
Ein simpler Beispieldienst demonstriert, wie sich der Sidecar als Network-Pattern einsetzen lässt. Dabei soll ein Nginx-Reverse-Proxy als Sidecar in einem zweiten Container laufen und den Zugriff auf einen Tomcat-Server kontrollieren. Der Tomcat steht hier prototypisch für all jene Applikationen, die der Admin im Betrieb gern absichern möchte, aber nicht modifizieren darf.
Das ist ein legitimes und gängiges Vorgehen, um beispielsweise Legacy-Anwendungen, die sich nicht mehr verändern lassen, einzusperren und auf diese Weise zu isolieren. Das wird nötig, wenn etwa der Quellcode fehlt oder der Aufwand für eine Anpassung zu groß wäre. Am Ende dieser Aktion wartet nicht unbedingt ein Microservice, aber sie lindert die Schmerzen, wobei Windows ein zugegeben extremes Beispiel ist.
Listing 1
Nginx-Sidecar als ConfigMap
01 apiVersion: v1
02 kind: ConfigMap
03 metadata:
04 name: nginx
05 namespace: default
06 data:
07 nginx.conf: |+
08 server {
09 listen 80 default_server;
10 listen [::]:80 default_server;
11 server_name example.com;
12 location / {
13 if ($http_x_forwarded_proto = 'http') {
14 return 301 https://$server_name $request_uri;
15 }
16 proxy_pass http://localhost:8080/;
17 auth_basic "Protected by a Sidecar";
18 auth_basic_user_file /etc/nginx/auth/htpasswd;
19 }
20 }
Listing 2
Secret aus dem Passwort erzeugen
01 htpasswd -c passwd user pw123 02 kubectl create secret generic nginx-password --from-file=passwd
Das hier im Artikel vorgestellte Setup nutzt aus, dass Pods unter Linux denselben Network Namespace [7] teilen – Linux-Namespaces, nicht Kubernetes-Namespaces [8], da besteht ein Unterschied. Dieser Netzwerk-Namespace definiert neben gemeinsamen IP-Adressen für alle darin laufenden Container auch eigene IPtables-Regeln sowie eigene Routen. Im Zusammenspiel mit dem UTS-Namespace (Unix Timesharing System, 9), der den Host- und Domainnamen sowie den DNS-Server definiert, erhält ein Pod eine völlig separate Sicht auf das Netz. Daher darf die isolierte Applikation, hier also Tomcat, auf eine Autorisierung verzichten.
Listing 3
Deployments des Tomcat-Pod mit Nginx-Sidecar
01 apiVersion: extensions/v1beta1 02 kind: Deployment 03 metadata: 04 name: tomcat 05 namespace: default 06 spec: 07 replicas: 1 08 template: 09 metadata: 10 labels: 11 name: tomcat 12 spec: 13 containers: 14 - name: tomcat 15 image: tomcat 16 - image: nginx 17 name: nginx 18 ports: 19 - containerPort: 80 20 volumeMounts: 21 - mountPath: /etc/nginx/conf.d 22 name: conf 23 - mountPath: /etc/nginx/auth 24 name: auth 25 volumes: 26 - name: conf 27 configMap: 28 name: nginx 29 - name: auth 30 secret: 31 secretName: nginx-password
In dem Szenario hängt der Admin die »ConfigMap« (Listing 1) als Kubernetes-Volume in den Nginx-Container ein. Sie wird damit zu einem Teil der Konfiguration. Listing 2 generiert das Nginx-Secret als »nginx-password« aus dem mit Htpasswd erzeugten Passwortfile »passwd«. Listing 3 liefert schließlich den Tomcat-Pod mit Nginx-Sidecar und inklusive Secret aus (Abbildung 2).
Services
Ein Service integriert nun die so erzeugten Pods und erkennt anhand der Label, welche von ihnen zusammenarbeiten. Dabei führt Kubernetes per Round Robin einen elementaren Lastausgleich durch. Der Service adressiert Pods, die den Status »alive« und »ready« besitzen, und tilgt solche, die bereits das Zeitliche gesegnet haben. Hat sie ein Replicaset erzeugt, ersetzt er sie. Den Pods, die »alive«, aber nicht »ready« sind, gibt der Service Zeit, in die Gänge zu kommen.

Abbildung 2: Ein einfacher Sidecar für Tomcat lässt sich mit Nginx umsetzen. Der kümmert sich um die Traffic-Kontrolle, Autorisierung und Authentifizierung.
Im vorliegenden Beispiel (Listing 4) ist der Service vom Type »NodePort« (Abbildung 3). Nur ein Node bietet ihn an, und er ist nicht aus dem Netzwerk erreichbar. Einen Test mit Minikube zeigt Listing 5. Würde der Admin als Type »LoadBalancer« angeben, stellte Kubernetes automatisch eine öffentliche IP-Adresse bereit und richtete eine Weiterleitung ein. Hätte er eine IP-Adresse angegeben, würde Kubernetes diese automatisch mit dem Tomcat-Service verbinden.
Listing 4
Erzeugen des Tomcat-Service
01 apiVersion: v1
02 kind: Service
03 metadata:
04 labels:
05 name: tomcat
06 name: tomcat
07 namespace: default
08 spec:
09 ports:
10 - port: 80
11 protocol: TCP
12 targetPort: 80
13 selector:
14 name: tomcat
15 type: NodePort
16 status:
17 loadBalancer: {}
Ingress
Komplexere Dienste bestehen in der Regel jedoch aus mehreren, oft Dutzenden oder Hunderten Services. Diese verwalten und rufen Nutzer über ganz unterschiedliche Domainnamen, URI-Pfade und mit voneinander abweichenden HTTP-Headern auf.
Listing 5
Testen des Service in Minikube
01 PORT=$(kubectl get svc tomcat -o jsonpath='{.spec.ports[0].nodePort}')
02 curl -u user:pw123 http://$(minikube ip):$PORT
Diese Vielfalt zu ordnen übernimmt in Kubernetes der Ingress-Dienst. Er bietet eine Möglichkeit, Services unter einer gemeinsamen Adresse und unter einem gemeinsamen Zertifikat zu bündeln. Sie sehen dann wie ein Dienst aus, ohne dass es sich tatsächlich um ein monolithisches Programm handelt.
Dadurch ähnelt Ingress der zentralen Version eines Sidecar, weil es erlaubt, mit nur einem Proxy viele Services zu vereinigen. Dabei geht zwar die Möglichkeit zur feingranularen Konfiguration verloren, die ist aber, wenn der Admin sie nicht wirklich braucht, eher eine Last. Es gibt verschiedene Beispiele im Netz, wie sich in Ingress Services in Software (etwa mit HA-Proxy, [10]) oder mit realer Hardware (etwa mit F5, [11]) implementieren lassen.
Hier soll ein kleineres Beispiel das Konzept verdeutlichen. Dabei geht eine Website, die aus verschiedenen Microservices besteht, unter einer IP-Adresse an den Start. Aus dem DNS sollen Nutzer die Dienste unter verschiedenen Namen und die unterschiedlichen Anwendungen über mehrere URI-Pfade erreichen – typisches Setup für eine Webplattform. Deren zahlreiche Applikationen müssen nach außen wie eine Site erscheinen.
Das Szenario schöpft das Kubernetes-Konzept der Microservices voll aus. Es setzt voraus, dass die Dienste untereinander nur lose gekoppelt sind. Der erwähnte Ingress-Service erzeugt dank einer gemeinsamen Adresse das geschlossene Bild der Website. Listing 6 zeigt exemplarisch, wie sich dafür mit »kubectl« zwei Go-Dienste ausrollen lassen.
Listing 6
Deployment zweier Go-Dienste
01 kubectl run go --image=gcr.io/google-samples/hello-app:1.0 --port=80 02 kubectl run go-v2 --image=gcr.io/google-samples/hello-app:2.0 --port=80 03 kubectl expose deployment go --type=NodePort --port=80 04 kubectl expose deployment go-v2 --type=NodePort --port=80
Die Existenz der beiden Dienste überprüft dann Minikube:
curl $(minikube ip):$(kubectl get svc go -o jsonpath='{.spec.ports[0].nodePort}')
curl $(minikube ip):$(kubectl get svc go-v2 -o jsonpath='{.spec.ports[0].nodePort}')
Ohne Load Balancer lässt sich nur testen, ob die Nodes überhaupt erreichbar sind. Das darf in der späteren Produktionsumgebung nicht mehr möglich sein.
Zertifikate
Um eine Domain testweise mit TLS abzusichern, erzeugt der Admin ein selbst signiertes Zertifikat für eine Subdomain von Nip.io. Dabei handelt es sich um einen DNS-Service, der stets die IP zurückliefert, die er im Namen direkt vor »nip.io« findet. Für die Adressen »demo.192.168.39.5.nip.io« und »info.192.168.39.5.nip.io« liefert er zum Beispiel jeweils »192.168.39.5« zurück.
Listing 7
Wildcard-Zertifikat für die Minikube-IP (Teil 1)
01 #!/bin/bash
02
03 test "$1" == "-x" && set -x && shift
04
05 MINIKUBE_IP=$(minikube ip)
06 DOMAIN=${MINIKUBE_IP}.nip.io
07 CONF=${DOMAIN}.conf
08 SECRET=${DOMAIN}.yaml
09 KEY=${DOMAIN}.key
10 CRT=${DOMAIN}.crt
11
12 test ! -f ${CONF} && cat > ${CONF} <<EOF
13 [ req ]
14 default_bits = 2048
15 default_md = sha512
16 default_keyfile = ${KEYFILE}
17 prompt = no
18 encrypt_key = no
19 distinguished_name = req_distinguished_name
20 # distinguished_name
21 [ req_distinguished_name ]
22 countryName = "DE" # C=
23 localityName = "Berlin" # L=
24 organizationName = "nip.io" # O=
25 organizationalUnitName = "K8S Demo Department" # OU=
26 commonName = "*.${DOMAIN}" # CN=
27 emailAddress = "nowhere@${DOMAIN}" # CN/emailAddress=
28 EOF
Listing 8
Wildcard-Zertifikat für die Minikube-IP (Fortsetzung)
01 test ! -f ${KEY} && openssl req -config ${CONF} -newkey rsa:2048 nodes -keyout ${KEY} -x509 -out ${CRT}
02
03 test ! -f ${SECRET} && cat > ${SECRET}<<EOF
04 apiVersion: v1
05 kind: Secret
06 metadata:
07 name: wildcard.tls
08 namespace: default
09 type: kubernetes.io/tls
10 data:
11 tls.crt: `cat ${CRT} | base64 -w0`
12 tls.key: `cat ${KEY} | base64 -w0`
13 EOF
Weil aber der Hostname auch Teil des HTTP-Request ist und mehrere Dienste unter einer IP liegen dürfen, ist dieses Verhalten normal. Es erlaubt Ingress, mit Zertifikaten und Domains zu testen, ohne Zugriff auf einen DNS-Server zu benötigen. Listing 7 und Listing 8 zeigen ein Wildcard-Zertifikat für die Minikube-IP unter der »nip.io«-Domain mit »wildcard.tls« als Secret. Mit dem Befehl
htpasswd -b -c passwd ingress auth
erzeugt der Admin wieder eine Passwortdatei, die er anschließend als Kubernetes-Secret importiert. Das gelingt über:
kubectl create secret generic basic-auth --from-file=auth
Die Ingress-basierte Website (Abbildung 4) greift schließlich auf dieses Secret und das Wildcard-TLS-Zertifikat zurück (Listing 9 und Listing 10).
Listing 9
Ingress-Definition (Teil 1)
01 apiVersion: extensions/v1beta1 02 kind: Ingress 03 metadata: 04 name: website 05 annotations: 06 nginx.ingress.kubernetes.io/rewrite-target: / 07 # type of authentication 08 nginx.ingress.kubernetes.io/auth-type: basic 09 # name of the secret that contains the user/password definitions 10 nginx.ingress.kubernetes.io/auth-secret: basic-auth 11 # message to display with an appropiate context why the authentication is required 12 nginx.ingress.kubernetes.io/auth-realm: "Authentication Required - ingress" 13 spec: 14 backend: 15 serviceName: default-http-backend 16 servicePort: 80 17 tls: 18 - hosts: 19 - demo.192.168.39.5.nip.io 20 - info.192.168.39.5.nip.io 21 secretName: wildcard.tls
Listing 10
Ingress-Definition (Fortsetzung)
01 rules: 02 - host: info.192.168.39.5.nip.io 03 http: 04 paths: 05 - path: / 06 backend: 07 serviceName: server 08 servicePort: 8080 09 - host: demo.192.168.39.5.nip.io 10 http: 11 paths: 12 - path: /go 13 backend: 14 serviceName: go 15 servicePort: 8080 16 - path: /go/v2 17 backend: 18 serviceName: go-v2 19 servicePort: 8080 20 - path: /tomcat 21 backend: 22 serviceName: tomcat 23 servicePort: 80
Zu Testzwecken kontaktiert der Admin dann mit Curl über HTTPS und mit Autorisierung die erzeugten Dienste:
curl -k https://demo.192.168.39.5.nip.io/go/v2 -u'auth:ingress'
Die getestete und vollständige Cloud-Native-App lässt sich dann später in eine Produktivumgebung verfrachten.
Fazit
Neben allen Listings in diesem Artikel hält [12] zusätzlich einige ergänzende Links bereit. Sie verweisen auf meist englischsprachige Webseiten, die sich intensiver mit Ingress-Implementierungen beschäftigt haben.

Abbildung 4: Eine moderne Website besteht oft aus Hunderten Microservices, die auf Kubernetes laufen. Ingress kennt und sammelt die Routen zu den verstreuten Diensten an zentraler Stelle, was die mit TLS abgesicherte Website aus einem Guss erscheinen lässt.
Das hier dargestellte Konzept dient als Sprungbrett, um komplexe Microservice-basierte Sites als Configuration as Code in Git-Repositories zu entwerfen und auszurollen. Das erforderte bisher ein komplexes Konfigurationsmanagement oder teure Hardware im Millionen-Bereich. Durch die Möglichkeit, mit http://nip.io Domains zu konfigurieren und zu testen, lassen sich auch Devops mit Domainregeln anwenden. Damit fällt eine weitere Ausrede weg, um nicht zu testen oder Devops generell zu vermeiden.
Der Artikel über Istio in diesem Heft zeigt zudem, wie sich mit diesem Konzept ein komplettes Service-Grid aufbauen lässt – doch ist Istio bislang (noch) kein fester Bestandteil von Kubernetes.
Infos
-
Zwölf Faktoren für Cloud-Native-Apps: https://12factor.net/de/
-
Ingress: https://kubernetes.io/docs/concepts/services-networking/ingress/
-
Wildcard DNS mit Nip.io: http://nip.io
-
Thomas Fricke, “Die Herde hüten”: Linux-Magazin 11/17, S. 58
-
Thomas Fricke, “Redebedarf”: Linux-Magazin 08/17, S. 18
-
Brendan Burns, David Oppenheimer, “Design patterns for container-based distributed systems”: https://www.usenix.org/system/files/conference/hotcloud16/hotcloud16_burns.pdf
-
Network Namespaces in Linux: http://man7.org/linux/man-pages/man7/namespaces.7.html,https://lwn.net/Articles/531114/
-
Kubernetes-Namespaces: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
-
UTS-Namespace: https://lwn.net/Articles/179345/
-
HA-Proxy und Ingress: https://www.haproxy.com/de/blog/haproxy_ingress_controller_for_kubernetes/
-
Listings zum Artikel: https://www.linux-magazin.de/magazine/listings/#2019-03&cloud-native-apps







