Die Vorteile von Kubernetes (K8S) und wie man für Testzwecke einen eigenen Cluster mit Raspberry Pis aufbauen kann, habe ich vor einem Jahr in dem Artikel k3s – 5 weniger als k8s (Kubernetes) skizziert. Einfacher und schneller gelingt natürlich der Einstieg durch die Verwendung von gemanagten Kubernetes-Clustern (wie Googles GKE und Amazons EKS). Mit einem Managed Kubernetes umgeht man auch einige Herausforderungen, die ich mit meinem Cluster auf Pi-Basis habe:
- Pi-Lösung funktioniert momentan nur im lokalen Netzwerk, wobei manche Applikationen auch öffentlich zugänglich sein sollen
- sehr eingeschränkte Ressourcen nur verfügbar (Arbeitsspeicher, Performanz und Netzwerkdurchsatz)
- die ARM-Architektur des Pis wird von vielen Docker-Images (bzw. Helm-Charts) nicht unterstützt
In der Zwischenzeit haben sich viele neue Angebote für Managed Kubernetes entwickelt. Mir gefällt das Angebot des deutschen Anbieters Netways sehr gut, der ein Kubernetes-Cluster mit öffentlicher IP-Adresse für unter 70,- Euro ermöglicht. Folgende Kriterien sind meistens für einen detailierten Kostenvergleich relevant:
- Anzahl und Leistung der Master-Node (Control Plane Nodes)
- Anzahl und Leistung der Worker Nodes
- anfallender Netzwerkverkehr
- verwendeter Festplattenspeicher
- Anzahl öffentlicher IP-Adressen
Quickstart mit Netways-Kubernetes
Das Erstellen und das Anbinden eines Kubernetes-Clusters von Netways gelingt innerhalb von wenigen Minuten:
- Kubernetes-Cluster in der Netways-Weboberfläche erstellen
- auf lokalem Rechner kubectl installieren, z.B.: brew install kubectl
- Kubernetes-Config herunterladen und speichern: $HOME/.kube/config
- => mit kubectl den Kubernetes-Cluster analysieren, z.B: kubectl get nodes
Kubernetes-Dashboard anzeigen
Einen hervorragenden Überblick bezüglich der Konfiguration der Ressourcen bietet das Kubernetes-Dashboard:
- Admin-Token ermitteln (Befehl der Weboberfläche entnehmen)
- auf lokalem Rechner Proxy starten: kubectl proxy
- Kubernetes-Dashboard im Browser öffnen:
http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ - Admin-Token zur Authentifizierung eingeben
- => mit dem Dashboard den Kubernetes-Cluster analysieren
Beispiel-Deployment mit „kubectl apply“
Die folgende Ressourcen-Definition echoserver.yaml spezifiziert einen HTTP-Echoserver, der die Anfrage-Parameter einer HTTP-Anfrage ausgibt. Mit einem entsprechenden Apply-Befehl erstellen wir ein Deployment mit einem passenden Service, sodass das entsprechende Replicaset und die Pods erzeugt werden:
kubectl apply -f echoserver.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
spec:
replicas: 1
selector:
matchLabels:
app: echoserver
template:
metadata:
labels:
app: echoserver
spec:
containers:
- name: echoserver
image: gcr.io/google_containers/echoserver:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: echoserver
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: echoserver
type: ClusterIP
kubectl zeigt uns anschließend die erzeugten Ressourcen an:
➜ kubectl get service echoserver
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
echoserver ClusterIP 10.254.48.98 <none> 80/TCP 52m
➜ kubectl get deployment echoserver
NAME READY UP-TO-DATE AVAILABLE AGE
echoserver 1/1 1 1 52m
➜ kubectl get replicaset echoserver-68666dcc9f
NAME DESIRED CURRENT READY AGE
echoserver-68666dcc9f 1 1 1 52m
➜ kubectl get pods echoserver-68666dcc9f-2lnkg
NAME READY STATUS RESTARTS AGE
echoserver-68666dcc9f-2lnkg 1/1 Running 0 52m
Die ausgegebene IP-Adresse des Services ist nur innerhalb des Kubernetes-Cluster erreichbar, weil wir den Typ ClusterIP angegeben haben. Für das Aufrufen des Echoservers starten wir einen weiteren Pod und stellen eine entsprechende Anfrage beispielsweise mit wget. Mit dem schlanken Busybox-Container können wir wget einfach ausführen.
➜ kubectl run busybox --image=busybox --rm -it /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget -O output.txt http://10.254.48.98/bar/?foo=1
Connecting to 10.254.48.98 (10.254.48.98:80)
saving to 'output.txt'
output.txt 100% |****************| 331 0:00:00 ETA
'output.txt' saved
/ # cat output.txt
CLIENT VALUES:
client_address=('10.100.1.43', 34938) (10.100.1.43)
command=GET
path=/bar/?foo=1
real path=/bar/
query=foo=1
request_version=HTTP/1.1
SERVER VALUES:
server_version=BaseHTTP/0.6
sys_version=Python/3.5.0
protocol_version=HTTP/1.0
HEADERS RECEIVED:
Connection=close
Host=10.254.48.98
User-Agent=Wget
/ # exit
pod "busybox" deleted
Die Echoserver-Ressourcen löschen wir übrigens abschließend mit:
kubectl delete -f echoserver.yaml
Das ist insbesondere dann wichtig, wenn wir in der echoserver.yaml den Typ LoadBalancer statt ClusterIP setzen. Dann erhalten wir eine externe IP-Adresse, die wir von außerhalb des Clusters ansprechbar können, wobei dadurch auch höhere Kosten entstehen.
➜ kubectl get service echoserver
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
echoserver ClusterIP 10.254.48.98 185.233.188.130 80/TCP 52m
Beispiel-Deployment mit Helm
Der Kubernetes-Paketmanager Helm ermöglicht die einfache Installation von bereits aufeiander abgestimmten Programmpaketen (genannt Charts) in den Kubernetes-Cluster. Beispielsweise wird für das WordPress-Chart die eigentliche Worpress-Software mit der Datenbank MySQL installiert:
- auf lokalem Rechner helm installieren, z.B.: brew install helm
- Installation: helm install my-wordpress bitnami/wordpress –version 10.1.5
- MySQL- und Worpdress-Ressourcen werden erstellt, z.B.:
Service, Deployments, Pods, Persistent Volume (Claims) - der Terminal-Ausgabe die URL, Username und Passwort entnehmen
- per Browser in die WordPress-Installation anmelden
➜ helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-wordpress default 1 2021-01-23 20:55:20.632896 +0100 CET deployed wordpress-10.1.5 5.6.0
➜ kubectl get services,deployments,replicasets,pods,pvc,pv
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/my-wordpress LoadBalancer 10.254.121.35 185.233.188.217 80:30600/TCP,443:31890/TCP 15m
service/my-wordpress-mariadb ClusterIP 10.254.69.47 <none> 3306/TCP 15m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/my-wordpress 1/1 1 1 15m
NAME DESIRED CURRENT READY AGE
replicaset.apps/my-wordpress-56d96ff5c9 1 1 1 15m
NAME READY STATUS RESTARTS AGE
pod/my-wordpress-56d96ff5c9-5vxp6 1/1 Running 1 15m
pod/my-wordpress-mariadb-0 1/1 Running 0 15m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-my-wordpress-mariadb-0 Bound pvc-0006bcf1-1b14-4b44-a5dc-e00f2b3a67fa 8Gi RWO standard 15m
persistentvolumeclaim/my-wordpress Bound pvc-1ac70a0e-c755-421e-91b7-844176517389 10Gi RWO standard 15m
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-0006bcf1-1b14-4b44-a5dc-e00f2b3a67fa 8Gi RWO Delete Bound default/data-my-wordpress-mariadb-0 standard 15m
persistentvolume/pvc-1ac70a0e-c755-421e-91b7-844176517389 10Gi RWO Delete Bound default/my-wordpress standard 15m
Die WordPress-Installation hat einen Service vom Typ LoadBalancer erstellt (mit externer IP-Adresse), wodurch entsprechende Extra-Kosten entstehen. Falls wir das nicht möchten, können wir das WordPress-Chart auch nur mit einer ClusterIP starten und testen:
- WordPress löschen:
helm delete my-wordpress - evtl. PersistenceVolumeClaim löschen:
kubectl delete pvc data-xxx… - WordPress erstellen nur mit ClusterIP:
helm install my-wordpress bitnami/wordpress –version 10.1.5 –set service.type=ClusterIP - Forward Service vom lokalem Rechner erstellen:
kubectl port-forward service/my-wordpress 8888:80 - => im Browser aufrufen: http://localhost:8888
Kosten sparen mit einem Ingress-Controller
Egal wie wir unsere Ressourcen anlegen, wir sollten darauf achten, ob erstellte Services vom Typ ClusterIP oder LoadBalancer sind. Mit LoadBalancer erhalten wir eine kostenpflichtige öffentliche IP-Adresse. Falls wir für mehrere Services einen öffentlichen Zugriff benötigen, dann sollte der Einsatz eines Ingress-Controllers in Erwägung gezogen werden. Der ermöglicht es, dass mehrere Services über die selbe öffentliche IP-Adresse erreichbar sind.