von Marc Zimmermann | Mai 26, 2023 | Kubernetes
Introduction
You have the desire that your application needs to scale across multiple nodes for load balancing, but needs access to a common PVC? For this purpose, you need a PVC that is RWX-enabled. As part of our Managed Kubernetes Cluster, it is possible to create a CSI Cinder block storage. However, due to technical limitations, this is only ReadWriteOnce (RWO) capable. We will show you here how we can still create a RWX-capable PVC using a workaround!
PVCs are explained in the tutorial Creating Persistent Volumes in Kubernetes, on which this tutorial builds.
Take, for example, a web server that has HTML pages, images, JS etc. stored on its PVC. These files are to be uploaded once and made available to all instances of the web server simultaneously. To better distribute the load, the deployment is scaled accordingly. Thus, several pods of the same type run on different servers.
To ensure that all pods – across host boundaries – have access to the files, we create a RWX Storageclass with the nfs-ganesha-server-and-external-provisioner.
The basis for this is an NWS Managed Kubernetes Cluster. After completing the workaround, we have the option of creating PVCs that can be read and written to simultaneously by multiple pods.
A note beforehand: the setup described is not HA capable!
Prerequisites
The tutorial assumes the following:
- NWS Managed Kubernetes Cluster
- kubectl
- helm
You can find more information on these topics in our NWS Docs
Setup
With the following command we add the Helm Repository, which provides us with the nfs-ganesha-server-and-external-provisioner, called NFS Provisioner in the following.
$ helm repo add nfs-ganesha-server-and-external-provisioner https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner/
After that we can immediately start with the installation. Note the settings that follow the –set parameter. These result in:
-
persistence.enabled=true
This parameter ensures that our data is stored on a persistent volume and persists even after the pod restarts.
-
persistence.storageClass=default
Here it is specified that the storage class "standard" should be used for the persistent data.
-
persistence.size=200Gi
This parameter specifies the size of the PVC that the NFS Provisioner fetches for the files.
Note that the PVC size specified with persistence.size is shared for all NFS PVCs that are obtained from the NFS Provisioner. There are many other configuration options with which the NFS Provisioner can be adapted to your own needs. You can find these here
$ helm install nfs-server nfs-ganesha-server-and-external-provisioner/nfs-server-provisioner --set persistence.enabled=true,persistence.storageClass=standard,persistence.size=200Gi
If the NFS Provisioner could be installed successfully, an output like the following appears:
NAME: nfs-server
LAST DEPLOYED: Mon May 22 14:41:58 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The NFS Provisioner service has now been installed.
A storage class named 'nfs' has now been created and is available to provision dynamic volumes.
You can use this storageclass by creating a `PersistentVolumeClaim` with the correct storageClassName attribute. For example:
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-dynamic-volume-claim
spec:
storageClassName: "nfs"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
Now let’s take a look at the NFS Provisioner Pod we just created and see if it’s running:
kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-server-nfs-server-provisioner-0 1/1 Running 0 36m
And of course the associated storage class for the automatic provision of NFS RWX PVCs. We can now use this to dynamically create and use PVCs of the RWX type.
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
encrypted cinder.csi.openstack.org Delete Immediate true 3h22m
encrypted-high-iops cinder.csi.openstack.org Delete Immediate true 3h22m
high-iops cinder.csi.openstack.org Delete Immediate true 3h22m
nfs cluster.local/nfs-server-nfs-server-provisioner Delete Immediate true 39m
nws-storage cinder.csi.openstack.org Delete Immediate true 3h22m
standard (default) cinder.csi.openstack.org Delete Immediate true 3h22m
Web server example
We now have a PVC with the property RWX which we can provision dynamically. Now let’s take a look at how this is integrated into a deployment.
We create two files; one for our web server deployment and one for the RWX PVC:
nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webserver
name: webserver
spec:
replicas: 1
selector:
matchLabels:
app: webserver
template:
metadata:
creationTimestamp: null
labels:
app: webserver
spec:
containers:
- image: nginx:latest
name: nginx
volumeMounts:
- mountPath: /files
name: files
volumes:
- name: files
persistentVolumeClaim:
claimName: nfs-pvc
nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: nfs
Our deployment specifies that we want to run exactly one replica of the NGiNX Container. This integrates the dynamically created NFS PVC under /files. Now we have to feed the definitions to Kubernetes using kubectl:
$ kubectl apply -f nfs-pvc.yaml nginx-deployment.yaml
persistentvolumeclaim/nfs-pvc created
deployment.apps/webserver created
As we can see a Webserver pod is running:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-server-nfs-server-provisioner-0 1/1 Running 0 54m
webserver-5486dd9cf5-hfhnd 1/1 Running 0 114s
But since this is far from sufficient for our load, we will expand the deployment to 4 replications:
$ kubectl scale deployment webserver --replicas=4
deployment.apps/webserver scaled
A small check is recommended and lo and behold, all 4 pods are running happily:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-server-nfs-server-provisioner-0 1/1 Running 0 55m
webserver-5486dd9cf5-hfhnd 1/1 Running 0 3m9s
webserver-5486dd9cf5-nh9fl 1/1 Running 0 18s
webserver-5486dd9cf5-ss27f 1/1 Running 0 18s
webserver-5486dd9cf5-xl2lj 1/1 Running 0 18s
Now let’s check if the NFS is also working properly between the pods.
A first command shows us that /files is empty. Using a second command, we create the nfs-is-rwx file. From the third issue we can see that we have been successful. We created in one pod and the file immediately existed in another pod.
$ kubectl exec webserver-5486dd9cf5-hfhnd -- ls /files
$ kubectl exec webserver-5486dd9cf5-nh9fl -- touch /files/nfs-is-rwx
$ kubectl exec webserver-5486dd9cf5-xl2lj -- ls /files
nfs-is-rwx
Summary
You have now set up an NFS server, which uses a CSI Cinder block storage in the background. The NFS server uses the block storage in the background to provide an RWX PVC for your pods via NFS. As a result, we have circumvented the technical limitation of a CSI Cinder block device and you can now use your NWS Managed Kubernetes Cluster for more use cases.
von Dominik Seidel | Jul 14, 2022 | Kubernetes
Du willst ein PersistentVolume (PV) in Kubernetes vergrößern? In diesem Blogeintrag erfährst du wie das funktioniert. Was PVs sind und wie man diese anlegt wird im Tutorial Persistente Volumes in Kubernetes erstellen erklärt, auf welchem das vorliegende Tutorial aufbaut.
PV in K8s vergrößern: Los Geht’s
Das Vergrößern eines PVs in Kubernetes ist keine schwere Übung. Zu beachten gilt es, dass das PersistentVolumeClaim– und nicht das PersistentVolume-Objekt editiert werden muss. Das PVC-Objekt kann z.B. mit dem Befehl kubectl edit angepasst werden. Falls du das oben angesprochene Tutorial absolviert hast, sollte in deinem Cluster ein PVC-Objekt mit dem Namen nginx-documentroot
existieren. Dieses wollen wir von 1 GiB auf 5 GiB vergrößern.
$ kubectl edit pvc nginx-documentroot
Im nun geöffneten Editor kann man das Yaml-Manifest des PVC-Objekts bearbeiten. Zum Vergrößern passt man den Wert unter .spec.resources.requests.storage an.

Nach dem Speichern und Schließen des Editors erfolgt die Vergrößerung des Volumes. Die standardmäßig in NWS Managed Kubernetes Clustern verwendete Cinder-StorageClass unterstützt Online Expansion, d.h. das Filesystem auf dem Volume wird auch bei laufendem Pod vergrößert und der zusätzliche Speicherplatz ist direkt im Container verfügbar. Es ist also kein Neustart des Pods notwendig. Eine Verkleinerung eines PVs ist nicht möglich. Die Kubernetes-API lässt dies gar nicht erst zu.
Um zu überprüfen, ob der Resize erfolgreich vollzogen wurde, kannst du dir zum Schluss noch das zum PVC gehörige PV anzeigen lassen:

Das PV-Objekt wurde aktualisiert und spiegelt die neue Größe von 5 GiB des zugrundeliegenden Volumes wider. Um sich vollends davon zu überzeugen, dass der zusätzliche Speicherplatz zur Verwendung im Container bereitsteht, öffnet man eine Shell auf dem Container mit kubectl exec:

Weitere Hilfreiche Informationen
Du hast nun gesehen, wie sich ein bestehendes PV vergrößern lässt. Damit du den Füllstand der Volumes in deinem Cluster immer im Blick hast, lohnt es sich zum Zweck des Monitorings den Kube Prometheus Stack einzusetzen. Wenn du benachrichtigt werden willst, wenn ein Volume eine kritische Speicherbelegung erreicht, könnte das Tutorial Kubernetes Alerting mit Prometheus Alertmanager interessant für dich sein.
von Gabriel Hartmann | Sep 22, 2021 | Kubernetes
Du würdest gerne ein eigenes Limit für eingehende Verbindungen an deinem Load Balancer festlegen?
In diesem Tutorial lernst du wie das geht.
Über das Verbindungslimit
Das Verbindungslimit (engl. „connection limit“) beschreibt die maximal zugelassene Anzahl an Verbindungen pro Sekunde, die für einen Load Balancer Listener (offenen Frontend-Port) erlaubt sind. Du fragst dich vielleicht, wieso sollte man überhaupt ein Verbindungslimit festlegen? Der naheliegendste Grund ist, um eine Überflutung von HTTP-Anfragen an die Kubernetes Apps vorzubeugen. Mit einer stärkeren Limitierung werden die überschüssigen Verbindungen abgelehnt, wodurch verhindert wird, dass die Kapazitäten des Clusters überlastet werden. Wenn du in deinem Cluster bereits Vorkehrungen für solche Fälle getroffen hast und eine Flut an Anfragen z.B. mittels Autoscaling ausgleichen kannst, so ist es natürlich auch möglich, das Verbindungslimit am Load Balancer weiter zu erhöhen.
Probleme bei hohen Verbindungslimits
In früheren Versionen unseres Load Balancer Dienstes „Octavia“ war die Anzahl an erlaubten Verbindungen nicht limitiert. Allerdings führte das unter bestimmten Umständen zu Problemen bei den Load Balancern. Wenn mehr als drei Listener am Load Balancer konfiguriert werden und kein Verbindungslimit gesetzt ist, dann führt dies dazu, dass die HAProxy-Prozesse des Load Balancers abstürzen. Das Problem wird in diesem Bug-Report näher beschrieben. Um zu vermeiden, dass unsere Kunden in dieses Problem laufen, haben wir uns entschieden, das standardmäßige Verbindungslimit auf 50000 Verbindungen festzulegen. Dieses Tutorial zeigt dir, wie du das Limit selber anpassen kannst.
Service Annotations für Load Balancer
Da die Kubernetes Cluster von NWS in unserer OpenStack Cloud betrieben werden, wird die Interaktion zwischen Kubernetes und OpenStack über den „Cloud Provider OpenStack“ realisiert. In der Dokumentation des Providers findet man eine Sektion, die alle verfügbaren Kubernetes Service Annotations für Load Balancers beinhaltet. Die Annotation, die wir benötigen, um ein benutzerdefiniertes Verbindungslimit setzen zu können ist „loadbalancer.openstack.org/connection-limit“.
Setzen der Annotation
Die Annotation wird im Kubernetes Cluster an einem Service Objekt des Load Balancers gesetzt. Du solltest in deiner Serviceliste einen Service vom Typ „LoadBalancer“ finden.
~ $ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.254.194.107 193.111.222.333 80:30919/TCP,443:32016/TCP 2d
ingress-nginx-controller-admission ClusterIP 10.254.36.121 <none> 443/TCP 2d
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 2d17h
my-k8s-app ClusterIP 10.254.192.193 <none> 80/TCP 47h
In meinem Beispiel wurde der Service automatisch eingerichtet, als ich einen Nginx Ingress Controller per Helm installiert habe. Ich kann den Service einfach editieren und die gewünschte Annotation hinzufügen:
~ $ kubectl edit service ingress-nginx-controller
Nun setzt man die Annotation für das Verbindungslimit auf den gewünschten Wert (in dem Beispiel „100000“):
apiVersion: v1
kind: Service
metadata:
annotations:
loadbalancer.openstack.org/connection-limit: "100000"
meta.helm.sh/release-name: ingress-nginx
meta.helm.sh/release-namespace: default
creationTimestamp: "2021-09-08T08:40:59Z"
...
Die Änderungen sichert man und verlässt anschließend den Editormodus. Man erhält als Bestätigung die folgende Meldung:
service/ingress-nginx-controller edited
Wenn nicht, dann sollte man prüfen, ob man einen Tippfehler bei der Annotation hat und ob der gewünschte Wert auch in doppelten Anführungszeichen gesetzt ist. Sobald die Anpassung des Services erfolgreich durchgeführt wurde, dauert es für gewöhnlich 10 bis 15 Sekunden, bis die Änderungen an dem OpenStack Load Balancer durchgeführt wurden. Du kannst den Status deines Load Balancers im Webinterface von NWS prüfen. Der „Provisioning Status“ steht auf „PENDING_UPDATE“ während die Änderungen ausgeführt werden und wechselt zurück auf „ACTIVE“ sobald diese abgeschlossen wurden.
Fazit
In bestimmten Fällen kann es nützlich sein, ein benutzerdefiniertes Verbindungslimit an einem Load Balancer zu setzen und wie wir eben sehen konnten, ist die Umsetzung ziemlich einfach zu bewerkstelligen. Man sollte allerdings beachten, dass sehr hohe Limits in Kombination mit einer großen Anzahl an Listenern nach wie vor zu Problemen führen kann.
von Achim Ledermüller | Mai 19, 2021 | Kubernetes
Du willst automatische Fedora CoresOS Updates für Dein Kubernetes? Und was haben Zincati und libostree damit zu tun? Hier bekommst Du schnell einen Überblick!
Als Betriebssystem vieler Kubernetes Cluster kommt Fedora CoreOS zum Einsatz. Dieses auf Container spezialisierte Betriebssystem punktet vor allem mit einfachen, automatischen Updates. Anders als gewohnt wird hier nicht Paket für Paket aktualisiert. Fedora CoreOS erstellt zuerst ein neues, aktualisiertes Image des Systems und finalisiert das Update mit einem Reboot. Für einen reibungslosen Ablauf sorgt rpm-ostree in Kombination mit Cincinnati und Zincati.
Bevor wir einen genaueren Blick auf die Komponenten werfen, klären wir zuerst wie Du automatische Updates für Dein NWS Kubernetes Cluster aktivieren kannst.
Wie aktivierst Du automatische Updates für Dein NWS Kubernetes Cluster?
Im NWS Portal kannst Du einfach zwischen verschiedenen Update-Mechanismen wählen. Klicke dazu auf “Update Fedora CoreOS” im Kontextmenü Deines Kubernetes Clusters und wähle zwischen immediate, periodic und lock-based.
Immediate spielt Updates sofort auf all Deinen Kubernetes Knoten ein und finalisiert das Update mit einem Reboot.
Periodic aktualisiert Deine Nodes nur während eines frei wählbaren Wartungsfensters. Neben der Wochentage kannst Du den Startzeitpunkt und die Länge des Wartungsfensters festlegen.
Lock-based verwendet zur Koordinierung der Updates das FleetLock-Protokoll. Hier wird über einen Lockmanager das Finalisieren der Updates koordiniert. Damit wird sichergestellt, dass Nodes nicht gleichzeitig Updates finalisieren und rebooten. Zudem wird bei Problemen der Update Prozess gestoppt und weitere Nodes führen kein Update durch.
Disable deaktiviert automatische Updates.
So weit, so gut! Aber was ist jetzt rpm-ostree und Zincati?
Updates mal anders!
Durch die Einführung Container-basierter Anwendungen konnte auch eine Vereinheitlichung und Vereinfachung der darunter liegenden Betriebssysteme stattfinden. Zuverlässige, automatische Updates und die Steuerung dieser – durch den Betreiber der Anwendung – verringern zusätzlich den Aufwand für Wartung und Koordination.
rpm-ostree erstellt die Images
rpm-ostree ist ein Hybrid aus libostree und libdnf und somit eine Mischung aus Image- und Paketsystem. libostree bezeichnet sich selbst als git for operating system binaries, wobei jeder Commit einen bootfähigen Dateibaum enthält. Eine neue Version von Fedora CoreOS entspricht somit einem rpm-ostree Commit, gepflegt und bereitgestellt durch das CoreOS-Team. libdnf bietet die bekannten Features zur Paketverwaltung, wodurch die von libostree zur Verfügung gestellte Basis durch die Benutzer erweiterbar ist.
Taints und Tolerations
Nodes, auf denen keine Container gestartet werden können bzw. nicht erreichbar sind, bekommen von Kubernetes einen sogenannten Taint (z.B. not-ready oder unreachable). Als Gegenstück werden Pods auf solchen Nodes mit einer Toleration versehen. Das passiert auch bei einem Fedora CoreOS Update. Pods werden bei einem Reboot automatisch mit
tolerationSeconds=300 markiert, wodurch nach 5 Minuten Deine Pods auf anderen Nodes neu gestartet werden. Mehr zu Taints und Tolerations findest Du natürlich in der
Kubernetes Dokumentation.
Cincinnati und Zincati verteilen die Updates
Zum Verteilen der rpm-ostree Commits kommt Cincinnati und Zincati zum Einsatz. Letzteres ist ein Client, der regelmäßig den Fedora CoreOS Cincinnati Server nach Updates fragt. Sobald ein passendes Update vorliegt, bereitet rpm-ostree einen neuen, bootfähigen Dateibaum vor. Je nach gewählter Strategie finalisiert Zincati das Update durch einen Reboot des Nodes.
Was sind die Vorteile?
Einfacher Rollback
Mit libostree ist es einfach, den alten Zustand wieder herzustellen. Hierfür muss man nur in den vorherigen rpm-ostree Commit booten. Dieser ist auch als Eintrag im Grub Bootloader-Menü zu finden.
Geringer Aufwand
Fedora CoreOS kann sich ohne manuelles Eingreifen aktualisieren. In Kombination mit Kubernetes werden auch die Anwendungen automatisch auf die aktuell verfügbaren Nodes verschoben.
Flexible Konfiguration
Zincati bietet eine einfache und flexible Konfiguration, mit der hoffentlich jeder Anwender eine passende Update Strategie findet.
Bessere Qualität
Durch den schlanken Image-basierten Ansatz kann jede Version als Ganzes leichter und genauer getestet werden.
Ob sich dieser Hybrid aus Image- und Paket-basiertem Betriebssystem durchsetzt, wird die Zukunft zeigen. Fedora CoreOS – als Basis für unser NMS Managed Kubernetes – vereinfacht den Update-Prozess erheblich und ermöglicht unseren Kunden trotzdem eine einfache Steuerung.
von Achim Ledermüller | Okt 14, 2020 | Kubernetes
Du willst wissen wie Du in Deinem Kubernetes Cluster an die IP-Adressen Deiner Clients kommst? In fünf Minuten hast Du den Überblick!
Vom HTTP-Client zur Anwendung
Im Tutorial zum nginx-Ingress-Controller zeigen wir wie man eine Anwendung öffentlich erreichbar macht. Im Fall der NETWAYS Cloud bedient sich dein Kubernetes Cluster an einem Openstack-Loadbalancer, welcher die Client-Anfragen an einen nginx-Ingress-Controller im Kubernetes Cluster weitersendet. Dieser verteilt dann alle Anfragen an die entsprechenden Pods.
Bei all dem Herumschieben und Weiterleiten der Anfragen gehen ohne weitere Konfiguration die Verbindungsdetails der Clients verloren. Da das Problem nicht erst seit Kubernetes auftritt, wird auf die alt bewährten Lösungen X-Forwarded-For oder Proxy-Protocol zurückgegriffen.
Um im Buzzword-Bingo zwischen Service, Loadbalancer, Ingress, Proxy, Client und Anwendung nicht die Übersicht zu verlieren, kannst Du Dir im folgenden Beispiel den Weg einer HTTP-Anfrage vom Client bis zur Anwendung durch die Komponenten eines Kubernetes Clusters ansehen.

Client IP-Adressen mit X-Forwarded-For
Verwendest Du HTTP, kann die Client IP-Adresse im X-Forwarded-For (XFF) gespeichert und weiter transportiert werden. XFF ist ein Eintrag im HTTP-Header und wird von den meisten Proxy-Servern unterstützt. Im Beispiel setzt der Loadbalancer dazu die Client-IP-Adresse in den XFF-Eintrag und schickt die Anfrage weiter. Alle weiteren Proxy-Server und die Anwendungen können dadurch im XFF-Eintrag erkennen, von welcher Adresse die Anfrage ursprünglich gesendet wurde.
In Kubernetes konfiguriert man den Loadbalancer über Annotations im Service Objekt. Setzt man dort loadbalancer.openstack.org/x-forwarded-for: true wird der Loadbalancer entsprechend konfiguriert. Wichtig ist jetzt natürlich auch noch, dass der nächste Proxy den X-Forwarded-For Header nicht wieder überschreibt. Im Fall eines nginx kann man dazu die Option use-forwarded-headers in dessen ConfigMap setzen.
Service
---
kind: Service
apiVersion: v1
metadata:
name: loadbalanced-service
annotations:
loadbalancer.openstack.org/x-forwarded-for: "true"
spec:
selector:
app: echoserver
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
nginx ConfigMap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx
data:
use-forwarded-headers: "true"
Da der HTTP-Header verwendet wird, ist es nicht möglich HTTPS-Verbindungen mit der Client-IP-Adresse anzureichern. Hier muss man entweder das TLS/SSL-Protokoll am Loadbalancer terminieren oder auf das Proxy-Protocol zurückgreifen.
Client-Informationen mit Proxy-Protocol
Verwendet man X-Forwarded-For ist man offensichtlich auf HTTP beschränkt. Um auch HTTPS und anderen Anwendungen hinter Loadbalancern und Proxys den Zugriff auf die Verbindungsoption der Clients zu ermöglichen, wurde das sogenannte Proxy-Protocol erfunden. Technisch wird dazu vom Loadbalancer ein kleiner Header mit den Verbindungsinformationen des Clients hinzugefügt. Der nächste Hop (hier nginx) muss natürlich ebenfalls das Protokoll verstehen und entsprechend behandeln. Neben klassischen Proxys unterstützten auch andere Anwendungen wie MariaDB oder postfix das Proxy-Protocol.
Um das Proxy-Protocol zu aktivieren, musst Du das Service Objekt mit der Annotation loadbalancer.openstack.org/proxy-protocol versehen. Für den annehmenden Proxy muss das Protocol ebenfalls aktiviert werden.
Service Loadbalancer
---
kind: Service
apiVersion: v1
metadata:
name: loadbalanced-service
annotations:
loadbalancer.openstack.org/proxy-protocol: "true"
spec:
selector:
app: echoserver
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
Nginx ConfigMap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx
data:
use-proxy-protocol: "true"
In den meisten Fällen wirst Du aber auf das Helm-Chart des nginx-Ingress-Controller zurückgreifen. Dort ist eine entsprechende Konfiguration sogar noch einfacher.
nginx-Ingress-Controller und Helm
Benutzt man Helm, um den nginx-ingress-controller zu installieren, ist die Konfiguration sehr übersichtlich. Das Proxy-Protocol wird über die eine Helm-Values-Datei sowohl für den nginx als auch für den Loadbalancer aktiviert:
nginx-ingress.values:
---
controller:
config:
use-proxy-protocol: "true"
service:
annotations:
loadbalancer.openstack.org/proxy-protocol: true
type: LoadBalancer
$ helm install my-ingress stable/nginx-ingress -f nginx-ingress.values
Ob alles so funktioniert wie erwartet, kannst Du am einfachsten mit Hilfe des Google Echoserver testen. Dabei handelt sich um eine kleine Anwendung, die den HTTP-Request einfach an den Client zurück gibt. Wie im Tutorial zum nginx-Ingress-Controller beschrieben, benötigen wir dazu ein Deployment mit Service und Ingress. Ersteres startet den Echoserver, der Service macht diesen im Cluster erreichbar und der Ingress konfiguriert den nginx so, dass die Requests an das Deployment weitergeleitet werden.
Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
spec:
selector:
matchLabels:
app: echoserver
replicas: 1
template:
metadata:
labels:
app: echoserver
spec:
containers:
- name: echoserver
image: gcr.io/google-containers/echoserver:1.8
ports:
- containerPort: 8080
Service
---
apiVersion: v1
kind: Service
metadata:
name: echoserver-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: echoserver
Ingress
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: echoserver-ingress
spec:
rules:
- host: echoserver.nws.netways.de
http:
paths:
- backend:
serviceName: echoserver-svc
servicePort: 80
Zum Testen fakest Du am besten noch deine /etc/hosts damit echoserver.nws.netways.de auf die öffentlichen IP-Adresse deines nginx-Ingress-Controller deutet. curl echoserver.nws.netways.de zeigt dir dann alles an, was der Echoserver von Deinem Client weiß, inklusive der IP-Adresse im X-Forwarded-For Header.
Fazit
Im Kubernetes Cluster ist das Proxy-Protocol für die meisten Anwendungsfälle wohl die bessere Wahl. Die bekannten Ingress-Controller unterstützen das Proxy-Protocol und TLS/SSL-Verbindungen können im K8s-Cluster konfiguriert und terminiert werden. Welche Informationen bei Deiner Anwendung ankommen, findest Du am schnellsten mit Googles Echoserver raus.
von Gabriel Hartmann | Okt 7, 2020 | Kubernetes
In einem vorangegangenen Post erläuterte Sebastian, wie man mit dem Prometheus Operator sein Kubernetes Cluster monitoren kann. Dieser Beitrag baut darauf auf und zeigt, wie man Benachrichtigungen per E-Mail und als Push Notifications mit dem Alertmanager einrichten kann.
Mit Helm den Monitoring Stack installieren
Neben der von Sebastian gezeigten Methode zum deployen des Prometheus Operators gibt es noch eine weitere Variante, mit der sich ein kompletter Prometheus-Stack im Kubernetes Cluster aufsetzen lässt. Und zwar mit Hilfe des Package Managers Helm.
Die Sourcen des Helm Charts für den Prometheus Stack kann man sich auf Github ansehen.
Dort findet man auch eine Datei die alle Konfigurationsoptionen und Standardwerte des Monitoring-Stacks enthält. Zu den Bestandteilen zählt neben dem Prometheus-Operator und Grafana auch der Prometheus Node Exporter, Kube State Metrics und der Alertmanager. Neu ist an dieser Stelle Kube State Metrics und der Alertmanager. Kube State Metrics bindet sich an die Kubernetes API und kann somit Metriken über sämtliche Ressourcen im Cluster abgrasen. Der Alertmanager kann anhand eines Regelwerks für ausgewählte Metriken alarmieren. Bei dem Setup per Helm kann jede Komponente über die Optionen in der Values Datei konfiguriert werden. Es macht Sinn für Prometheus und Alertmanager jeweils ein PVC zu verwenden um somit die Metriken und History von Alarmen, sowie die Alarmunterdrückungen persistent zu haben.
Mit folgenden Values wird in meinem Fall für beides ein Persistent Volume Claim (PVC) angelegt. Der Name der Storage-Class kann natürlich variieren.
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: nws-storage
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
prometheus:
prometheusSpec:
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: nws-storage
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
Ich speichere mir die Konfiguration im Verzeichnis „prom-config“ als Datei „stack.values“ ab. Nun kann man auch schon den Stack deployen. Zuerst muss man die passenden Repos einbinden:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
Anschließend wird der Stack erstellt:
helm install nws-prometheus-stack prometheus-community/kube-prometheus-stack -f prom-config/stack.values
Mittels Port-Forwarding kann man per Browser das Grafana Webinterface aufrufen und sich mit den Credentials admin/prom-operator anmelden.
kubectl port-forward service/nws-prometheus-stack-grafana 3002:80
Im Browser anschließend http://localhost:3002/ aufrufen.
Es sind bereits einige vordefinierte Dashboards mit dabei. Um für alle Dashboards (Scheduler, Etcd, etc.) auch Metriken angezeigt zu bekommen, sind allerdings weitere Optionen in den Values von Prometheus zu setzen. In diesem Post werde ich allerdings nicht darauf eingehen, wie sich diese konfigurieren lassen.
Stattdessen möchte ich wie Eingangs erwähnt das Einrichten von Alarmen demonstrieren. Hier kommt der Alertmanager ins Spiel. In Grafana sucht man jedoch vergebens danach. Ein Blick ins Prometheus Web UI macht da mehr Sinn.
Also erstmal wieder einen Port-Forward dafür einrichten:
kubectl port-forward service/nws-prometheus-stack-kube-prometheus 3003:9090
Dann im Browser http://localhost:3003/ aufrufen.
Unter Alerts sieht man sämtliche Alarmregeln, die bei dem Stack bereits vorkonfiguriert dabei sind. Per Klick auf eine beliebige Regel kann man deren Definition sehen. Konfigurieren lässt sich jedoch auch hier nichts.
Über die Buttons „Inactive“, „Pending“ und „Firing“ können die Regeln mit dem jeweiligen Status ein-, bzw. ausgeblendet werden.
Wir gehen zur nächsten Weboberfläche – Alertmanager. Auch hierfür benötigen wir einen Port-Forward.
kubectl port-forward service/nws-prometheus-stack-kube-alertmanager 3004:9093
Das Webinterface (unter http://localhost:3004/ erreichbar) ähnelt dem von Prometheus. Unter Alerts hat man hingegen eine History der bereits abgefeuerten Alarme. Silences zeigt eine Übersicht der unterdrückten Alerts und Status enthält neben Versionsinfos und dem Betriebsstatus auch die aktuelle Config des Alertmanagers. Leider lässt sich diese Config hier jedoch auch nicht ändern.
Wie konfigurieren wir nun also den Alertmanager?
Den Alertmanager konfigurieren
Manche werden es schon erahnt haben: indem man die Values des Helm-Charts anpasst. Wir schreiben also eine neue Values-Datei, die ausschließlich die Alertmanager-Config enthält. Die Doku zeigt uns welche Optionen es gibt und nach welchem Schema sich diese setzen lassen. Wir fangen mit einem grundlegenden Beispiel an und fügen erstmal globale SMTP-Settings hinzu:
alertmanager-v1.values
alertmanager:
config:
global:
resolve_timeout: 5m
smtp_from: k8s-alertmanager@example.com
smtp_smarthost: mail.example.com:587
smtp_require_tls: true
smtp_auth_username: k8s-alertmanager@example.com
smtp_auth_password: xxxxxxxxx
route:
receiver: 'k8s-admin'
routes: []
receivers:
- name: 'k8s-admin'
email_configs:
- to: k8s-admin@example.com
Dadurch kann der Alertmanager dann schon mal E-Mails herausschicken. Zudem ist es natürlich auch notwendig mindestens einen Empfänger zu definieren. Unter „receivers“ kann man wie in meinem Beispiel einfach Namen und E-Mailadresse hinterlegen. Bitte darauf achten, dass die Texteinrückung stimmt, ansonsten kann es beim Ausrollen der Config zu Startproblemen des Alertmanagers kommen.
Damit nun die Zustellung der Alerts auch wirklich stattfindet muss man unter „route“ die Routen festlegen. Wenn man erstmal alle feuernden Alarme an einen Kontakt rausschicken möchte, dann kann man das wie in meinem Beispiel machen. Was man noch so alles mit Routen anstellen kann, das sehen wir uns in einem späteren Beispiel genauer an.
Mit einem Helm Upgrade können wir unsere neue Konfiguration für den Alertmanager deployen. Wir übernehmen dabei alle bereits gesetzten Werte mittels „–reuse-values“:
helm upgrade --reuse-values -f prom-config/alertmanager-v1.values nws-prometheus-stack prometheus-community/kube-prometheus-stack
Und wie testen wir das jetzt?
Alerts testen
Wer nicht gleich einen Node abschießen möchte kann einfach mal den Alertmanager neu starten. Beim Start feuert nämlich die Alert-Rule „Watchdog“. Diese ist nur dazu da, um die ordnungsgemäße Funktion der Alertingpipeline zu testen.
So kannst du den Neustart durchführen:
kubectl rollout restart statefulset.apps/alertmanager-nws-prometheus-stack-kube-alertmanager
Kurz nach dem Neustart sollte auch schon die E-Mail ankommen. Falls nicht, dann erst einmal prüfen ob der Alertmanager noch startet. Wenn er in einem Crash-Loop hängt, dann kann man über die Logs des Pods herausfinden, was hier schief läuft. Ist eine Konfig-Option falsch gesetzt, kannst du diese in den Values nochmal anpassen und erneut mit einem Helm Upgrade deployen. Wer sich bei einem Optionskey vertippt hat, kann per Helm Rollback auch auf einen vorherigen Stand zurück rollen:
helm rollback nws-prometheus-stack 1
Damit haben wir schon mal ein rudimentäres Monitoring für den Kubernetes Cluster selbst. Natürlich kann man noch weitere Receiver hinterlegen und somit auch mehrere Kontakte benachrichtigen.
Eigene Metriken und Alert Rules hinzufügen
Jetzt sehen wir uns noch an, für was die Routen nützlich sein können. Allerdings werden wir uns dazu erst noch ein paar Pods zum Testen hochfahren und verschiedene Namespaces anlegen. Zudem sehen wir uns auch noch kurz den Prometheus Blackbox Exporter an.
Szenario: In einem K8s-Cluster werden verschiedene Environments per Namespaces betrieben – z.B. development und production.Wenn die sample-app des Namespaces development ausfällt, dann soll nicht die Bereitschaft alarmiert werden.Die Bereitschaft ist nur für Ausfälle der sample-app des Production-Namespaces zuständig und bekommt neben E-Mails auch Benachrichtigungen aufs Mobiltelefon. Die Entwickler werden gesondert per Mail über Probleme bei der sample-app aus dem development Namespace informiert.
Wir definieren die beiden Namespaces „nws-production“ und „nws-development“ in namespaces-nws.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: nws-development
labels:
name: nws-development
---
apiVersion: v1
kind: Namespace
metadata:
name: nws-production
labels:
name: nws-production
kubectl apply -f ./prom-config/namespaces-nws.yaml
Nun starten wir uns zwei sample-apps, die uns abwechselnd in einem 60 Sekunden Intervall HTTP 200 und HTTP 500 zurück liefern. Ich verwendet hier ein einfaches Image, das ich mir für diesen Zweck erstellt habe (Sourcen auf GitHub).
sample-app.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nws-sample-app
spec:
replicas: 1
selector:
matchLabels:
app: nws-sample-app
template:
metadata:
labels:
app: nws-sample-app
spec:
containers:
- name: nws-sample-app
image: gagaha/alternating-http-response
ports:
- containerPort: 80
kubectl apply -f prom-config/sample-app.yaml -n nws-production
kubectl apply -f prom-config/sample-app.yaml -n nws-development
Anschließend exposen wir die App im Cluster:
kubectl expose deployment nws-sample-app -n nws-production
kubectl expose deployment nws-sample-app -n nws-development
Nun brauchen wir allerdings noch einen Komponente, die die Verfügbarkeit dieser Apps per HTTP Request abfragen kann und als Metriken für Prometheus bereitstellt. Es bietet sich hierfür der Prometheus Blackbox Exporter an. Damit kann man neben HTTP/HTTPS Anfragen auch Verbindungen mit den Protokollen DNS, TCP und ICMP prüfen.
Zuerst müssen wir den Blackbox Exporter im Cluster deployen. Ich bediene mich auch hier wieder dem offiziellen Helm Chart.
helm install nws-blackbox-exporter prometheus-community/prometheus-blackbox-exporter
Nun müssen wir Prometheus noch mitteilen, wie er an den Blackbox Exporter kommt. Interessant sind hier die Targets. Hier tragen wir die HTTP Endpoints ein, die vom Blackbox Exporter abgefragt werden sollen. Wir schreiben die zusätzliche Konfiguration in eine Datei und deployen sie per Helm Upgrade:
prom-blackbox-scrape.yaml
prometheus:
prometheusSpec:
additionalScrapeConfigs:
- job_name: 'nws-blackbox-exporter'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- http://nws-sample-app.nws-production.svc
- http://nws-sample-app.nws-development.svc
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: nws-blackbox-exporter-prometheus-blackbox-exporter:9115
helm upgrade --reuse-values -f prom-config/prom-blackbox-scrape.yaml nws-prometheus-stack prometheus-community/kube-prometheus-stack
Wenn wir dann nochmal den Port-Forward für prometheus starten, können wir unter http://localhost:3003/targets die beiden neuen Targets des nws-blackbox-exporters sehen. Somit kommen schon mal Metriken zu Prometheus rein. Wir müssen allerdings auch neue Alert Rules definieren, damit für diese Metriken alarmiert werden kann.
Wir editieren die Rules direkt per Kubectl:
kubectl edit prometheusrules nws-prometheus-stack-kube-k8s.rules
Vor den k8s.rules fügen wir unsere neue Regel hinzu:
...
spec:
groups:
- name: blackbox-exporter
rules:
- alert: HttpStatusCode
annotations:
description: |-
HTTP status code is not 200-399
VALUE = {{ $value }}
LABELS: {{ $labels }}
summary: HTTP Status Code (instance {{ $labels.instance }})
expr: probe_http_status_code <= 199 OR probe_http_status_code >= 400
for: 30s
labels:
severity: error
- name: k8s.rules
rules:
...
Jetzt müssen wir nur noch die Kontaktdaten der verschiedenen Empfänger und Routen definieren.
Unter route kann man in routes verschiedene Empfänger angeben. Diese Empfänger müssen natürlich weiter unten auch vorhanden sein. Es kann dann für eine Route auch Bedingungen definiert werden. Nur wenn die Bedingungen auch zutreffen, wird die Benachrichtigung an den angegebenen Empfänger verschickt.
Hier nun die Config für das Szenario:
alertmanager-v2.values
alertmanager:
config:
global:
resolve_timeout: 5m
smtp_from: k8s-alertmanager@example.com
smtp_smarthost: mail.example.com:587
smtp_require_tls: true
smtp_auth_username: k8s-alertmanager@example.com
smtp_auth_password: xxxxxxxxx
route:
receiver: 'k8s-admin'
repeat_interval: 5m
routes:
- receiver: 'dev_mail'
match:
instance: http://nws-sample-app.nws-development.svc
- receiver: 'bereitschaft'
match:
instance: http://nws-sample-app.nws-production.svc
receivers:
- name: 'k8s-admin'
email_configs:
- to: k8s-admin@example.com
- name: 'dev_mail'
email_configs:
- to: devs@example.com
- name: 'bereitschaft'
email_configs:
- to: bereitschaft@example.com
pushover_configs:
- user_key: xxxxxxxxxxxxxxxxxxxxxxxxxxx
token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
helm upgrade --reuse-values -f prom-config/alertmanager-v2.values nws-prometheus-stack prometheus-community/kube-prometheus-stack
Am besten man löst danach wieder einen Neustart des Alertmanagers aus:
kubectl rollout restart statefulset.apps/alertmanager-nws-prometheus-stack-kube-alertmanager
Sofern die Konfiguration passt, sollten dann schon bald Alerts ankommen.
Fazit
Das Einrichten des Alertmanagers kann aufwändig sein. Man hat jedoch auch viele Konfigurationsmöglichkeiten und kann sich seine Benachrichtigungen über Regeln so zurechtlegen wie man es benötigt. Wer möchte kann auch noch die Templates der Nachrichten bearbeiten und somit das Format und die enthalten Informationen anpassen.
Recent Comments