Kubernetes Grundlagen¶
Für den weitere Verlauf des Tutorials werde ich einen Control-Plane starten. Siehe ~/kubernetes-tutorial/src/opentofu/k3s-installation/k3s-installation-single/main.tf.
Danach werden alle Teilnehmer die Möglichkeit haben, sich mit dem Cluster zu verbinden und die folgenden Schritte durchzuführen. Es ist wichtig, dass alle Teilnehmer Zugriff auf den Cluster haben, damit sie die Übungen durchführen können.
$ export IP_CONTROL_PLANE=hier_ip_addresse_des_control_planes_eintragen
$ export K3S_TOKEN=$(ssh -i ../opentofu/schulung root@$IP_CONTROL_PLANE 'cat /var/lib/rancher/k3s/server/agent-token')
$ curl -sfL https://get.k3s.io | K3S_URL=https://$IP_CONTROL_PLANE:6443 K3S_TOKEN=$K3S_TOKEN sh -
$ scp -i ../opentofu/schulung root@$IP_CONTROL_PLANE:/etc/rancher/k3s/k3s.yaml ~/.kube/config
$ vim ~/.kube/config # hier die IP-Adresse des Control-Planes eintragen, damit kubectl mit dem Cluster kommunizieren kann
$ kubectl get nodes
Deployments¶
Wir fangen mit den Kubernetes Grundlagen an und zwar mit einem PgAdmin4 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgadmin4
spec:
replicas: 1
selector:
matchLabels:
app: pgadmin4
template:
metadata:
labels:
app: pgadmin4
spec:
containers:
- name: pgadmin4-container
image: dpage/pgadmin4:9.14.0
env:
- name: PGADMIN_DEFAULT_EMAIL
value: admin@admin.com
- name: PGADMIN_DEFAULT_PASSWORD
value: admin
ports:
- containerPort: 80
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 256Mi
cpu: 250m
$ cd ~/kubernetes-tutorial/src/deployments
$ kubectl apply -f pgadmin4-deployment.yaml
$ kubectl get pod -o wide
$ kubectl get rs
$ kubectl get deploy
$ kubectl get all
Wie kann man nun die Applikation erreichen? Für Debugging:
$ kubectl port-forward pod/pgadmin4-[id] 8080:80 --address=0.0.0.0
Warnung
kubectl port-forward ist nicht für die Produktion gedacht, sondern nur für Entwicklungszwecke. In einer Produktionsumgebung sollte man NIE kubectl port-forward verwenden!
Nun wird unser Deployment skaliert:
$ kubectl scale rs/pgadmin4-[id] --replicas=2 # funktioniert nicht, da die Replikas von einem Deployment verwaltet werden
$ kubectl get all
$ kubectl scale deploy/pgadmin4 --replicas=2 # funktioniert, da das Deployment die Replikas verwaltet
$ kubectl get pod -o wide
Nun können wir über Port-Forwarding beide Pods erreichen.
Services¶
Services ermöglichen es, eine Gruppe von Pods als einen einzigen Dienst zu exponieren. Sie ermöglichen es, den Zugriff auf die Pods zu load-balancen.
apiVersion: v1
kind: Service
metadata:
name: pgadmin4
spec:
type: ClusterIP
selector:
app: pgadmin4
ports:
- protocol: TCP
port: 9090
targetPort: 80
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 hours
$ kubectl apply -f pgadmin4-service.yaml
$ kubectl get svc
$ kubectl port-forward svc/pgadmin4 8080:9090 --address=0.0.0.0 # und im Browser aufrufen
$ kubectl describe svc/pgadmin4
$ kubectl get endpoints pgadmin4
$ kubectl get endpointslices
StatefulSets¶
StatefulSets sind sehr ähnlich zu Deployments, aber sie sind für Anwendungen gedacht, die einen stabilen Netzwerk-Identität und persistenten Speicher benötigen. Sie werden oft für Datenbanken verwendet. Wir wollen nun eine PostgreSQL-Datenbank mit einem StatefulSet deployen:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
replicas: 1
serviceName: postgres
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: "postgres-container"
image: "postgres:18.3-alpine3.23"
env:
- name: POSTGRES_PASSWORD
value: "secret"
ports:
- containerPort: 5432
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
$ kubectl apply -f postgres-statefulset.yaml
$ kubectl get statefulsets
$ kubectl get pods -o wide
$ kubectl describe statefulset/postgres
Wir wollen einen Service erstellen, um die Datenbank zu erreichen:
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 5432
targetPort: 5432
selector:
app: postgres
$ kubectl apply -f postgres-service.yaml
$ kubectl get svc
$ kubectl describe svc/postgres
$ kubectl port-forward svc/postgres 5432:5432 --address=0.0.0.0 # und aus einem PG Client heraus auf die Datenbank zugreifen
Nun kann man in der PgAdmin4 UI die Postgres-Datenbank einrichten und sich mit ihr verbinden.
$ kubectl port-forward svc/pgadmin4 8080:9090 --address=0.0.0.0
und im Browser eine DB Verbindung in PgAdmin4 einrichten, um die Postgres-Datenbank zu erreichen. Der Hostname ist postgres (der Name des Services), der Port ist 5432, der Benutzername ist postgres und das Passwort ist secret.
Nun kann man sich erneut aus Visual Studio Code mit der Postgres-Datenbank verbinden, um zu sehen, dass die Daten persistent sind:
$ kubectl port-forward svc/postgres 5432:5432 --address=0.0.0.0
Was passiert aber, wenn wir der postgres-Pod aus dem StatefulSet gelöscht wird?
$ kubectl delete pod postgres-0
$ kubectl get pods -o wide
Der Pod wird automatisch neu erstellt, da er von einem StatefulSet verwaltet wird. Und da der Pod eine stabile Netzwerk-Identität hat, wird er immer den Namen postgres-0 haben. Ist die Datenbanktabelle, die wir vorhin erstellt haben, immer noch da?
Persistent Volumes Claims¶
Persistent Volumes Claims (PVCs) sind eine Möglichkeit, persistenten Speicher für Pods bereitzustellen. Sie ermöglichen es, Speicher von einem Storage-Provider zu reservieren und diesen Speicher dann in einem Pod zu verwenden.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres
spec:
accessModes:
- ReadWriteOncePod # siehe auch https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
storageClassName: local-path
resources:
requests:
storage: 1Gi
$ kubectl apply -f postgres-pvc.yaml
$ kubectl get pvc
$ kubectl describe pvc/postgres
Nun können wir den PVC in unserem StatefulSet verwenden:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
replicas: 1
serviceName: postgres
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: "postgres-container"
image: "postgres:18.3-alpine3.23"
env:
- name: POSTGRES_PASSWORD
value: "secret"
#envFrom:
#- configMapRef:
# name: postgres
#- secretRef:
# name: postgres
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres
$ kubectl get statefulsets
$ kubectl delete statefulset postgres
$ kubectl apply -f postgres-statefulset-with-pvc.yaml
$ kubectl describe pvc/postgres
$ kubectl get statefulsets
$ kubectl get pods -o wide
$ kubectl describe statefulset/postgres
$ kubectl exec -it pod/postgres-0 -- bash
$ psql -U postgres
$ CREATE TABLE person (name VARCHAR(255));
$ INSERT INTO person (name) VALUES ('Christian Trutz');
$ SELECT * FROM person;
$ \\q
$ exit
$ kubectl delete pod postgres-0
$ kubectl get pods -o wide
$ kubectl exec -it pod/postgres-0 -- bash
$ psql -U postgres
$ SELECT * FROM person;
Zu einem PVC gehört immer ein Persistent Volume (PV), das den tatsächlichen Speicher repräsentiert. In unserem Fall wird der PV automatisch von Kubernetes erstellt, da wir einen StorageClass mit dem Namen local-path verwenden, der standardmäßig in k3s enthalten ist. Der PV wird auf dem Node erstellt, auf dem der Pod läuft, und der Speicher wird auf dem lokalen Dateisystem des Nodes bereitgestellt.
$ kubectl get pv
$ kubectl describe pv [id pv]
$ kubectl get pv [id pv] -o json | jq .spec.local.path
$ ls -lah [path_aus_obigem_befehl]
ConfigMaps und Secrets¶
ConfigMaps und Secrets sind Möglichkeiten, Konfigurationsdaten und sensible Daten in Kubernetes zu speichern. ConfigMaps werden für Konfigurationsdaten verwendet, Secrets für sensible Daten wie Passwörter oder API-Schlüssel verwendet werden. ConfigMaps und Secrets können in Pods als Umgebungsvariablen übergeben oder als Dateien gemountet werden.
Für unser PostgreSQL-Deployment könnten wir zum Beispiel die Konfigurationsdaten wie den Benutzernamen, die Datenbank in einer ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres
data:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
und das Passwort in einem Secret speichern:
apiVersion: v1
kind: Secret
metadata:
name: postgres
type: Opaque
data:
POSTGRES_PASSWORD: c2VjcmV0 # base64 encoded 'secret'
$ kubectl apply -f postgres-configmap.yaml
$ kubectl apply -f postgres-secret.yaml
$ kubectl get configmaps
$ kubectl get secret postgres -o json | jq .data
$ kubectl get secret postgres -o json | jq .data.POSTGRES_PASSWORD | tr -d "\"" | base64 -d
$ vim postgres-statefulset-with-pvc.yaml # hier die ConfigMap und das Secret in das StatefulSet einbinden
$ kubectl apply -f postgres-statefulset-with-pvc.yaml
$ kubectl describe statefulset/postgres
$ kubectl describe pod/postgres-0
$ kubectl exec pod/postgres-0 -it -- bash
$ env
$ env | grep POSTGRES_PASSWORD
Wichtig
Es ist wichtig zu beachten, dass Secrets in Kubernetes nicht wirklich sicher sind, da sie Base64-kodiert und nicht verschlüsselt sind. In einer Produktionsumgebung sollten zusätzliche Sicherheitsmaßnahmen ergriffen werden, um Secrets zu schützen, wie zum Beispiel Rollenbasierte Zugriffskontrolle (RBAC) und die Verwendung von externen Secret-Management-Lösungen.
RBAC¶
RBAC (Role-Based Access Control) ist ein Mechanismus, um den Zugriff auf Ressourcen in Kubernetes zu steuern. Es ermöglicht es, Rollen zu definieren, die bestimmte Berechtigungen haben, und diese Rollen dann Benutzern oder Service Accounts zuzuweisen.
$ kubectl get roles
$ kubectl get rolebindings
$ openssl req -new -key chris.key -out chris.csr -subj "/CN=chris/O=developers"
$ vim developer-csr.yaml # hier den Namen des CSR und den Pfad zur CSR Datei anpassen
$ kubectl apply -f developer-csr.yaml
$ kubectl get csr
$ kubectl certificate approve [csr name]
$ kubectl get csr
$ kubectl get csr chris -o jsonpath='{.status}' | jq
$ kubectl get csr [csr name] -o jsonpath='{.status.certificate}' | base64 -d > [name des developers].crt
$ openssl x509 -noout -text -in [name des developers].crt
$ kubectl config get-contexts
$ kubectl config get-clusters
$ kubectl config get-users
$ kubectl config set-credentials [name des developers] --client-certificate=[name des developers].crt --client-key=[name des developers].key
$ kubectl config get-users
$ kubectl config set-context developer --cluster=default --user=[name des developers]
$ kubectl config use-context developer
$ kubectl get nodes # funktioniert nicht, da der Entwickler keinen Zugriff auf die Nodes hat
$ kubectl get pods # funktioniert nicht, da der Entwickler keinen Zugriff auf die Pods hat
$ kubectl get secrets # funktioniert nicht, da der Entwickler keinen Zugriff auf die Secrets hat
$ kubectl config use-context default
$ kubectl get secrets # funktioniert
$ kubectl apply -f developer-role.yaml
$ kubectl describe roles developer
$ vim developer-rolebinding.yaml # hier die Namen der Entwickler anpassen und mehrere Entwickler hinzufügen, wenn nötig
$ kubectl apply -f developer-rolebinding.yaml
$ kubectl describe rolebindings developer
$ kubectl config use-context developer
$ kubectl get pods # funktioniert, da der Entwickler nun Zugriff auf die Pods hat
$ kubectl get secrets # funktioniert nicht, da der Entwickler keinen Zugriff auf die Secrets hat
$ kubectl exec pod/postgres-0 -- env | grep POSTGRES_PASSWORD # funktioniert nicht, da der Entwickler keinen exec-Zugriff auf die Pods hat
$ kubectl config use-context default
$ kubectl exec pod/postgres-0 -- env | grep POSTGRES_PASSWORD # funktioniert, da der default=Admin user Zugriff auf die Pods hat
Ingress¶
Ingress ist eine Möglichkeit, HTTP- und HTTPS-Verkehr zu einem Service in Kubernetes zu routen. Es ermöglicht es, mehrere Services unter derselben IP-Adresse und demselben Port zu exponieren.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pgadmin4
spec:
ingressClassName: traefik
rules:
- host: pgadmin4.trutz.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: pgadmin4
port:
number: 9090
Die Ingress-Ressource definiert, dass Anfragen an http://pgadmin4.trutz.cloud/ an den Service pgadmin4 weitergeleitet werden sollen.
Ingress alleine reicht nicht aus, um den Verkehr zu routen. Es wird ein Ingress-Controller benötigt, der die Ingress-Ressourcen überwacht und die entsprechenden Regeln konfiguriert. In unserem Fall verwenden wir den Traefik-Ingress-Controller, der standardmäßig in k3s enthalten ist.
$ kubectl apply -f pgadmin4-ingress.yaml
$ kubectl get ingress
$ kubectl describe ingress/pgadmin4
$ http://pgadmin4.trutz.cloud/ # im Browser aufrufen
Tipp
Der Einstiegspunkt von aussen in das Cluster ist der Ingress-Controller, dessen Aufgabe ist den Verkehr zu den Services im Cluster zu routen. Das Mapping URL zu Service wird typischerweise auf Domain-Ebene gemacht, also zum Beispiel http://pgadmin4.trutz.cloud/ zum Service pgadmin4, aber es ist auch möglich, das Routing auf Context-Pfad-Ebene zu machen, zum Beispiel http://trutz.cloud/pgadmin4/ zum Service pgadmin4, aber hier ist zu beachten, dass die Applikation dann so konfiguriert sein muss, dass sie den obigen Context-Pfad unterstützt.
Kubernetes Controller und CRDs¶
Kubernetes Controller sind Prozesse, die den aktuellen Zustand des Clusters überwachen und sicherstellen, dass er dem gewünschten Zustand entspricht. Sie reagieren auf Änderungen im Cluster und führen die notwendigen Aktionen aus, um den gewünschten Zustand zu erreichen. Custom Resource Definitions (CRDs) ermöglichen es, benutzerdefinierte Ressourcen in Kubernetes zu erstellen, die von Controllern verwaltet und überwacht werden können.
$ kubectl get crds
$ kubectl describe crd [name_der_crd]
$ kubectl api-resources