1. Description

In diesem Tutorial werden wir eine Postgresql Datenbank, Quarkus Backend und ein Angular Client deployen

2. Requirements

  • kubectl muss installiert und konfiguriert sein

  • Kind oder Minikube

  • Linux oder WSL2 Terminal

  • Docker muss installiert sein

3. Allgemeine Regeln

3.1. Wichtige Begriffe

3.1.1. Pod


Ein Pod besteht aus einem oder mehreren (Docker, Podman, usw.) Containern

3.1.2. Deployment


Mit einem Deployment kann man ein Pod erstellen und dabei festlegen, welche und wie viele Secrets, Volumes, CPUs, RAM und Replikationen für diesen benötigt werden.

3.1.3. Node


Eine physikalische oder eine virtuelle Maschine, wo mehrere Pods darauf rennen

3.1.4. Service


Um einen Zugriff auf die Pods mittels REST, TCP oder andere Protokolle zu erhalten, werden sogenannte Services verwendet

3.1.5. ConfigMaps


Damit ein Pod einen Zugriff auf Umgebungsvariablen erhält, wird dafür eine ConfigMap verwendet

3.1.6. Secrets


Ähnlich wie bei einer ConfigMap. Allerdings werden Secrets für sensible Daten wie zum Beispiel Credentials (Login Daten) verwendet.

3.1.7. Persistence Volume


Damit die Daten im Falle eines Ausfalls eines Servers nicht verloren gehen, werden sogenannte Volumes erstellt. Diese sind vergleichbar mit docker volumes.

3.1.8. Persistence Volume Claim (PVC)


Damit ein Pod auf einer Persistence Volume zugreifen kann, werden Persistence Volume Claim (PVC) verwendet.

Schüler haben keinen Zugriff auf die Persistence Volumes von der Leocloud.

Grundsätzlich braucht man für jede Komponente in unserem System ein Deployment und eine Service

4. Deployment von einer Postresql Datenbank

kubectl create deployment postgres-demo --image=postgres:12.16-bullseye --port=5432

Folgender Befehl wurde verwendet, um ein Deployment für unsere Datenbank zu erstellen.

4.1. Warum rennt die Datenbank nicht

Wenn man jetzt kubectl get pods oder kubectl get deployments im Terminal eingibt, dann könnte man sehen, dass bei der Spalten Ready 0/1 dabei steht. Um das Problem zu finden, können wir folgendes machen. Wir kopieren uns den Namen des Pods in der Zwischenablage und geben dann folgendes im Terminal ein

kubectl log <Name des Pods> -c <Name des Images, welches von dem Pod verwendet wird>

In meinem Fall habe ich folgendes eingeben müssen

kubectl logs postgres-demo-7cd674d4b5-9msfr -c postgres

Die Fehlermeldung schaut wie folgends aus:

Error: Database is uninitialized and superuser password is not specified.
       You must specify POSTGRES_PASSWORD to a non-empty value for the
       superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".

       You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
       connections without a password. This is *not* recommended.

       See PostgreSQL documentation about "trust":
       https://www.postgresql.org/docs/current/auth-trust.html

Für das Lösen dieses Problems müssen wir jetzt eine ConfigMap oder ein Secret erstellen, wo wir die benötigten Umgebungsvariablen definieren können.

kubectl get deployments/postgres-demo -o yaml > deployment.yaml im Terminal eingeben, damit man die ganze Konfiguration von dem Deployment in einem YAML File speichert. (Das könnte man auch bei Services, Secrets usw. auch machen ). Es lohnt sich auch, diesen Befehl nach jeder Änderung zu machen und dann die Files STÄNDIG zu kontrollieren.

4.2. Erstellung von des Secrets

Für die Erstellung des Secrets kann man diesen Befehl verwenden

kubectl create secret generic  postgres-secret \
--from-literal=POSTGRES_USER=<Name des Users> \
--from-literal=POSTGRES_PASSWORD=<Password> \
--from-literal=POSTGRES_DB=<Name der DBs>
Die Argumente,welche nach --from-literal kommen, sollen mit den Namen der benötigten Umgebenungsvariablen übereinstimmen. Das zweite Argument ist einfach der Wert dieser Umgebungsvariable.

Jetzt kann man die Konfigurationen von unserem Secret mit der Verwendung von diesem Befehl in einem YAML File speichern

kubectl get secrets postgres-secret -o yaml > secret.yaml

Wir sind aber noch immer nicht fertig mit dem Secret. Das Deployment soll sich die Werte von diesem Secret für seine Umgebungsvariablen holen. Dafür muss man folgendes im Terminal eingeben:

kubectl set env deployment/postgres-demo --from=secret/postgres-secret

Unser Deployment soll jetzt problemlos funktionieren. Wenn das nicht der Fall ist, könnte man folgendes machen (Nicht vergessen, YAML Files mit Hilfe der kubectl CLI zu generieren):

kubectl delete deployments/postgres-demo
kubectl apply -f postgres.yaml

4.3. Erstellung einer Service

Wir haben die Datenbank jetzt zum Laufen gebracht. Damit wir auf diese zugreifen können, müssen wir ein Service erstellen:

kubectl expose deployments/postgres-demo --type=LoadBalancer --port=5432
kubectl get services

Connection String von postgres schaut dann wie folgendes aus:

jdbc:postgresql://<Minikube IP>:<NodePort>/db

die Minikube IP kann man mit dem Befehl minikube ip anzeigen lassen. beim NodePort muss mann kubectl get services/postgres-demo eingeben und dann den Port, welcher nach dem Doppelpunkt steht nehmen

5. Persistence Volume/Persistence Volume Claim

Bei der Leocloud sind diese schon vordefiniert. Auf dem lokalen Minikube könnte man folgende Konfiguration verwenden

apiVersion: v1
kind: PersistentVolume
metadata:
  finalizers:
  - kubernetes.io/pv-protection
  labels:
    type: local
  name: task-pv-volume
  resourceVersion: "33077"
  uid: ae6d772a-0090-4074-b3ac-1edb929daf29
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 10Gi
  hostPath:
    path: /mnt/data
    type: ""
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  volumeMode: Filesystem
status:
  phase: Available

Wir brauchen noch ein PVC, damit unsere Anwendung auf die Datenbank zugreifen kann.

Folgende Konfiguration kann dafür verwendet werden

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  finalizers:
  - kubernetes.io/pvc-protection
  name: franklyn-pvc
  namespace: default
spec:
    accessModes:
      - ReadWriteMany
    resources:
      requests:
        storage: 10Mi
    storageClassName: standard

Jetzt müssen wir unser Deployment umkonfigurieren, sodass es dieses Volume verwendet. Leider können wir diese Konfigurationen nicht mit der Verwendung von der kubectl CLI hinzufügen. Deshalb habe ich sie in diesem Code-Snippet markiert. (Ich rate Ihnen, den Code nicht zu kopieren, sondern nur die Zeilen, die einen Plus enthalten, in ihrem Config Files händisch hinzufügen. Ohne die Plus-Symbole natürlich)

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "2"
  generation: 2
  labels:
    app: postgres-demo
  name: postgres-demo
  namespace: default
  resourceVersion: "40443"
  uid: 5e97ba55-98ff-454d-9247-946157c8a5ec
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: postgres-demo
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: postgres-demo
    spec:
      containers:
      - env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              key: POSTGRES_PASSWORD
              name: postgres-secret
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              key: POSTGRES_USER
              name: postgres-secret
        - name: POSTGRES_DB
          valueFrom:
            secretKeyRef:
              key: POSTGRES_DB
              name: postgres-secret
        image: postgres:12.16-bullseye
        imagePullPolicy: IfNotPresent
        name: postgres
+        volumeMounts:
+          - name: postgres-data
+            mountPath: /var/lib/postgresql/data
        ports:
        - containerPort: 5432
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

+      volumes:
+        - name: postgres-data
+          persistentVolumeClaim:
+            claimName: my-pvc

Es ist jetzt völlig egal, ob die Pods aus irgendeinem Grund gestoppt oder gekillt werden. Die Daten existieren noch immer.

Gratuliere! Sie haben jetzt die Datenbank erfolgreich deployed :)

Wenn Sie das Program auf ihren Namespace deployen wollen, dann müssen Sie bei jedem YAML File das Attribut namespace ändern

6. Deployment einer Java Applikation

Vorgehensweise:

  1. Hochladen des Image auf ghcr.io oder andere Conatiner Registries

  2. Erstellung eines Deployments

  3. Erstellung eines Services

  4. Zugriffsroute in der Ingress.yml Datei definieren

6.1. Hochladen des Images auf ghcr.io

6.2. Erstellung eines Deployments

Für die Erstellung des Services kann man diesen Befehl verwenden

kubectl create deployment <beliebiger Namen> --image=<Name des Images, was sie bereits auf ghcr hochgeladen haben> --port=<beliebiger Port>

6.3. Erstellung eines Services

Gleich wie Erstellung einer Service allerdings müssen sie das richtige Deployment auswählen

6.4. Zugriffsroute in der Ingress.yml Datei definieren

7. Deployment einer Angular Applikation

Vorgehensweise: gleich wie Deployment einer Java Applikation

8. Erstellung von Ingress

Damit man auf die deployten Komponenten mittels http und https von außen zugreifen kann, wird ein Ingress gebraucht.

Wir müssen zuerst ein Reverse Proxy mittels nginx erstellen und dann die Routen für unsere Komopnenten definieiren.

8.1. Erstellung von nginx Reverse Proxy

Es gibt 100 unterschiedliche Lösungen und Konfigurationen dafür, aber wir können uns an der Lösung von Prof. Christian Aberger orientieren.

server {
    listen 80;
    root /usr/share/nginx/html;
    rewrite_log on;
    error_log /dev/stdout debug;

    location /api/ {
        proxy_pass <Name von dem Service>:8080;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host:$server_port;
    }
}

Danach sollen wir ein Dockerfile erstellen, in dem wir diese Konfigurationen kopieren

FROM nginx:stable

# COPY frontend/* /usr/share/nginx/html/
COPY ./default.conf /etc/nginx/conf.d/default.conf

Letztendlich sollen wir dieses Image auf ghcr.io publishen how to upload an image to ghcr - 2023/24

9. Erstellung von Deployment und Service für unser Reverse Proxy

Sie sollen mittlerweile wissen, wie man ein Deployment und eine Service erstellt.

# nginx Web Server
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx

spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: <Name von dem nginx Package, den Sie bereits auf einem Container Registery hochladen haben>
          # remove imagePullPolicy when stable. Currently we do not take care of version numbers
          ports:
            - containerPort: 80
#          livenessProbe:
#            httpGet:
#              path: /index.html
#              port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx

spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    app: nginx

9.1. Konfiguration von Routen

Dafür muss man auch eine fertige Konfiguration nehmen und diese umändern

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
  name: nginx-ingress
  namespace: <Namespace von Ihnen>
spec:
  rules:
    - host: student.cloud.htl-leonding.ac.at
      http:
        paths:
          - path: <Ihr Namen in diesem Format m.mustermann >
            pathType: Prefix
            backend:
              service:
                name: nginx
                port:
                  number: 80

          - path: <Name von Ihnen in diesem Format m.mustermann>/<belieger Namen für die Route ihrer Anwendung>
            pathType: Prefix
            backend:
              service:
                name: <Name von dem Service, was sie deployen möchten>
                port:
                  number: <Port von dem Service, was sie deployen möchten>