Kubernetes

HowTos rund um das Thema Kubernetes

Unterabschnitte von Kubernetes

ClusterIssuer

Für die Verwaltung von Zertifikaten wird in der Regel der cert-manager eingesetzt. dieser kann eine lokale CA benutzen um eigene Zertifikate zu erstellen, aber auch per ACME - Protokoll erreichbare Zertifizierer wie z. B. Let’s Encrypt.

Werden die Zertifizierer als Issuer für einzelne Namespaces erstellt, können sie auch nur in den Namespaces benutzt werden.

Für die globale Nutzung gibt es den ClusterIssuer, der im gesamten Cluster verfügbar ist.

---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: acme-production
spec:
  acme:
    email: michael+kubernetes@gisbers.de
    privateKeySecretRef:
      name: acme-production
    server: https://acme-v1.o.mylinuxtime.de/acme/directory
    solvers:
      - http01:
          ingress: {}

Einbauen lässt sich der ClusterIssuer in einen Ingress durch eine Annotation.

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: acme-production
[...]

Example Pods

Create OOM

Dieser pod erzeugt durch das Überschreiten der verfügbaren Resourcen einen OOM.

apiVersion: v1
kind: Pod
metadata:
  name: memory-oom
spec:
  containers:
    - name: memory-oom
      image: "polinux/stress"
      resources:
        requests:
          memory: "50Mi"
        limits:
          memory: "100Mi"
      command: ["stress"]
      args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

Flux

Installation

Für die Installation muss zunächst der flux Client installiert sein.

curl -s https://fluxcd.io/install.sh | sudo bash

Bei Benutzung eines GitLab wird ein Access Token mit Schreibrechten auf das Repository oder die Gruppe benötigt. Sinnvollerweise kann dieser in der Datei $HOME/.gitlab hinterlegt sein.

Danach kann flux mit den benötigen Umgebungswerten gestartet werden.

export GITLAB_TOKEN="$(<.gitlab)"
export GITLAB_HOSTNAME="git.mylinuxtime.de"
export GITLAB_OWNER="kubernetes/flux" # GitLab Gruppe für das Repository
export GITLAB_REPOSITORY="prod-kube"  # Repository in der Gruppe
export GITLAB_BRANCH="main"           # zu benutzender Branch
export GITLAB_PATH="prod"             # Pfad innerhalb des Repositories

flux bootstrap gitlab --owner $GITLAB_OWNER \
                      --repository $GITLAB_REPOSITORY \
                      --branch $GITLAB_BRANCH \
                      --path $GITLAB_PATH \
                      --token-auth \
                      --hostname $GITLAB_HOSTNAME

Es wird automatisch das benötigte Repository angelegt und die Dateien für flux abgelegt.

Im Cluster werden die benötigten CRDs und Deployments angelegt und gestartet.

Update

Ob eine neuere Version für ein Update zur Verfügung steht kann über das Subkommando check geprüft werden.

$ flux check
► checking prerequisites
✗ flux 2.4.0 <2.5.0 (new CLI version is available, please upgrade)
✔ Kubernetes 1.30.4+rke2r1 >=1.28.0-0
► checking version in cluster
✔ distribution: flux-v2.4.0
✔ bootstrapped: true
► checking controllers
✔ helm-controller: deployment ready
► ghcr.io/fluxcd/helm-controller:v1.1.0
✔ kustomize-controller: deployment ready
► ghcr.io/fluxcd/kustomize-controller:v1.4.0
✔ notification-controller: deployment ready
► ghcr.io/fluxcd/notification-controller:v1.4.0
✔ source-controller: deployment ready
► ghcr.io/fluxcd/source-controller:v1.4.1
► checking crds
✔ alerts.notification.toolkit.fluxcd.io/v1beta3
✔ buckets.source.toolkit.fluxcd.io/v1
✔ gitrepositories.source.toolkit.fluxcd.io/v1
✔ helmcharts.source.toolkit.fluxcd.io/v1
✔ helmreleases.helm.toolkit.fluxcd.io/v2
✔ helmrepositories.source.toolkit.fluxcd.io/v1
✔ kustomizations.kustomize.toolkit.fluxcd.io/v1
✔ ocirepositories.source.toolkit.fluxcd.io/v1beta2
✔ providers.notification.toolkit.fluxcd.io/v1beta3
✔ receivers.notification.toolkit.fluxcd.io/v1
✔ all checks passed

Die Komponenten können dann innerhalb des Repositories aktualisiert werden.

$ flux install --export > flux-system/gotk-components.yaml
$ git add flux-system/gotk-components.yaml
$ git commit -m "Update gotk components"
$ git push

Install Cert-Manager

Der Cert-Manager ist eine Verwaltungskomponente für Zertifikate innerhalb eines Kubernetes Clusters.

Erst mit dem Cert-Manager ist es möglich Zertifikate zu erstellen, verwalten und löschen. Der Cert-Manager bringt über CRDs eigene Objekte für die Verwaltung mit

Installation

Die einfachste Installation kann direkt über Helm erfolgen.

Typischerweise wird für den Cert-Manager ein eigener Namespace (cert-manager) angelegt.

helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager --namespace cert-manager \
  --create-namespace --set installCRDs=true

Beispiel für die Anwendung

Bei einem extern erreichbaren Webserver soll neben der Konfiguration eines Ingress mit nginx auf Port 80 auch ein Zugang über https (Port 443) ermöglicht werden.

Dazu bedarf es eines Zertifikats von einer Trusted-CA. Bei extern erreichbaren Webseiten bietet sich dafür z. B. Let’s Encrypt an.

Issuer

Um bei Let’s Encrypt Zertifikate abholen zu können bedarf es zunächst eines Issuers über diese Objekt wir die Anmeldung an Let’s Encrypt durchgeführt. Die gespeicherte Anmeldung dient später auch als Berechtigung die Zertifikate zu beantragen.

Der Issuer wird im gleichen Namespace wie der spätere Ingress angelegt.

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: acme
  namespace: example
spec:
  acme:
    email: user@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: acme-issuer
    solvers:
      - http01:
          ingress:
            class: nginx

Sobald das Objekt erstellt ist, wird die Anmeldung durchgeführt. Der Status ist im status des Ingress Objekts sichtbar.

Ingress

Um einen Webserver per https erreichbar zu machen, wird zusätzlich zu der normalen Konfiguration ein Abschnitt spec.tls benötigt in dem der SNI spec.tls.hosts[] und der Name eines Secrets für das Zertifikat angegeben wird.

Damit der Cert-Manager sich um das Zertifikat kümmert muss eine Annotation cert-manager.io/issuer: angelegt werden, in die der Name des zu benutztenden Issuers angegeben wird.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/issuer: "acme"
  name: nginx
  namespace: example
spec:
  ingressClassName: nginx
  rules:
    - host: example.com
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: nginx
                port:
                  number: 80
  tls:
    - hosts:
        - example.com
      secretName: example.com-tls

Nach Anlegen des Objekts sorgt der Cert-Manager für die Erzeugung des Privaten Schlüssels und eines Certificate Sign Requests (CSR). Den CSR sendet er zur Validierung an den über den Issuer angegeben Dienst und sorgt sich um die Kommunikation mit diesem um das Zertifikat zu holen. Sobald es vorliegt wird es in dem unter secretName angegebenen Secret zusammen mit dem Key abgelegt und steht für den Ingress zu Verfügung. Dieser baut das Zertifikat - sobald es vorhanden ist - ein und stellt die Domain per https zur Verfügung.

ClusterIssuer

Der Issuer steht immer nur in dem Namespace in dem er angelegt wurde zur Verfügung. Um einen Issuer auch über die Namespaces hinweg benutzen zu können gibt es den ClusterIssuer.

Dieser wird - in der Regel - Im Namespace des Cert-Manager abgelegt, kann aber - solang es keine RBAC Einschränkungen gibt - aus allen Namespaces benutzt werden.

Cluster-Issuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: acme
  namespace: cert-manager
spec:
  acme:
    email: user@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: acme-issuer
    solvers:
      - http01:
          ingress:
            class: nginx

Tatsächlich unterscheidet sich die Konfiguration des ClusterIssuer nur durch den unterschiedlichen kind von dem Issuer (ClusterIssuer statt Issuer)

Ingress

Auch beim Ingress ist nur die Annotation zu ändern.

Hier wird dann cert-manager.io/cluster-issuer: benutzt.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: "acme"
  name: nginx
  namespace: example
spec:
  ingressClassName: nginx
  rules:
    - host: example.com
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: nginx
                port:
                  number: 80
  tls:
    - hosts:
        - example.com
      secretName: example.com-tls

Install GitLab on Kubernetes

Install

helm repo add gitlab https://charts.gitlab.io/
helm repo update
helm upgrade --install gitlab gitlab/gitlab \
  --timeout 600s \
  --set global.hosts.domain=git.49-12-189-3.ip.dynlinux.io \
  --set global.hosts.externalIP=49.12.189.3 \
  --set certmanager-issuer.email=michael@gisbers.de \
  --set global.edition=ce \
  --set installCertmanager=false \
  --set nginx-ingress.enabled=false \
  --set prometheus.install=false \
  --set global.ingress.class=nginx

Kubectl Konfiguration

Das Tool kubectl wird für die direkte Kommunikation mit einer Kubernetes API benötigt.

Um mit der API kommunizieren zu können wird eine Konfigurationsdatei benötigt (KUBECONFIG) in der die Informationen zur API, wie die Adresse, Benutzer, Kennwort oder Zertifikate enthalten sind. Die Konfigurationdatei wird in der Regel in der Datei ~/.kube/config abgelegt. Alternativ kann die Umgebungsvariable KUBECONFIG gesetzt werden um eine alternative Konfigurationsdatei angeben zu können.

KUBECONFIG Dateien zusammenfassen

Statt mehrere Konfigurationsdateien zu führen ist es Sinnvoll die Konfigurationsdaten in eine Datei zusammen zu fassen.

Auf Grund des Formats können die Dateien nicht einfach zusammenkopiert werden. Die einzelnen Daten müssen in die jeweiligen Sektionen der Konfigurationdatei verteilt werden.

Mit dem folgenden Workflow ist es möglich dies teilweise zu automatisieren.

  • Erstellen einer Kopie der aktuellen Konfigurationsdatei (es wird von ~/.kube/config ausgegangen)
  • Setzen der Variable KUBECONFIG auf die kopierte Datei und die neue Datei (/tmp/kube_config). Weitere Dateien können durch Doppelpunkt getrennt angegeben werden
  • Ausgeben der Kopie und der zusätzlichen Datei, oder Dateien über kubectl in die Datei ~/.kube/config.
  • Löschen der temporären Dateien
  • Löschen der Variable KUBECONFIG
user@linux # cp ~/.kube/config ~/.kube/config_temp
user@linux # export KUBECONFIG="~/.kube/config_temp:/tmp/kube_config"
user@linux # kubectl config view --flatten >~/.kube/config
user@linux # rm ~/.kube/config_temp /tmp/kube_config
user@linux # unset KUBECONFIG

Auswählen des Context

Sobald die Konfigurationsdatei für kubectl mehr als einen Eintrag enthält, kann der Context in dem gearbeitet werden soll gewechselt werden.

Dies geschieht entweder durch das Festlegen eines Default Context:

user@linux # kubectl config use-context meincontext

Oder durch die Nutzung des Parameters --context bei kubectl oder anderen Befehlen wie z. B. flux, die dies unterstützen.

user@linux # kubectl get --context meincontext nodes

Über den Befehl kubectl config get-contexts lässt sich eine Liste der verfügbaren Umgebungen anzeigen.

Kubernetes auf podman mit kind

Über das Tool kind ist es möglich schnell einen Kubernetes Cluster innerhalb einer podman Umgebung aufzusetzen.

Vorbereitung

Dazu muss das Tool heruntergeladen oder über das Paketmanagement installiert.

podman und auch kubectl müssen ebenfalls installiert und konfiguriert sein.

Einrichtung des Clusters

Der einfachste Weg den Cluster einzurichten geht über das systemctl-run Kommando. Hierbei übernimmt der systemd die Kontrolle über die Umgebung für Umgebung in der der podman Befehl läuft.

$ systemd-run -p Delegate=yes --setenv=KIND_EXPERIMENTAL_PROVIDER=podman \
              --scope --user kind create cluster
Running as unit: run-rc08baccf7a724130a9284623ccd2ddf9.scope; invocation ID: 0fb8f6ec97a94f5782bbfc3f0f78a3ae
using podman due to KIND_EXPERIMENTAL_PROVIDER
enabling experimental podman provider
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.27.3) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Thanks for using kind! 😊

Nach dieser Installation kann direkt über das kubectl Kommando ein Zugriff auf die Kubernetes Node erfolgen.

$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:34125
CoreDNS is running at https://127.0.0.1:34125/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Dazu wurde die Datei $HOME/.kube/config um den Context kind-kind erweitert oder eine neue Datei erzeugt.

Genauere Informationen über den laufenden Cluster Node lassen sich über kubectl cluster-info dump abrufen.

Cluster Konfiguration

Statt den Cluster mit einer Default Konfiguration zu starten, kann man eine yaml Datei mit passenden Werten zur Erzeugung nutzen.

Beispiele:

# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
# six node (three workers) full cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: control-plane
- role: control-plane
- role: worker
- role: worker
- role: worker

Die erstellten yaml Dateien sind bei der Erstellung des Clusters anzugeben.

$ systemd-run -p Delegate=yes --setenv=KIND_EXPERIMENTAL_PROVIDER=podman \
              --scope --user kind create cluster --config multinode.yaml \
              --name multinode

Weitere Beispiele und Parameter sind in der kind Konfiguration zu finden.

Cluster entfernen

Der Befehl kind kann ebenfalls zum entfernen eines Clusters genutzt werden.

$ systemd-run -p Delegate=yes --setenv=KIND_EXPERIMENTAL_PROVIDER=podman \                                                                       
              --scope --user kind delete cluster --name kind
Running as unit: run-rba10793fe745483eb50981e5b0d3417b.scope; invocation ID: ce7a140694494618a843f3a01687e619
using podman due to KIND_EXPERIMENTAL_PROVIDER
enabling experimental podman provider
Deleting cluster "kind" ...
Deleted nodes: ["kind-control-plane"]

Erweitertes Beispiel

Installation eines einfachen Clusters mit einer Node. nginx-ingress über die Ports 8000 und 8443 auf dem Host zu erreichen.

$ cat >/tmp/cluster.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 8000
    protocol: TCP
  - containerPort: 443
    hostPort: 8443
    protocol: TCP
EOF
$ systemd-run -p Delegate=yes --setenv=KIND_EXPERIMENTAL_PROVIDER=podman \
              --scope --user kind create cluster --config=/tmp/cluster.yaml
$ kubectl --context kind-kind apply -f \
    https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
$ cat >/tmp/ingress.yaml <<EOF
kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  labels:
    app: foo
spec:
  containers:
  - command:
    - /agnhost
    - netexec
    - --http-port
    - "8080"
    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    name: foo-app
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
spec:
  selector:
    app: foo
  ports:
  # Default port used by the image
  - port: 8080
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  labels:
    app: bar
spec:
  containers:
  - command:
    - /agnhost
    - netexec
    - --http-port
    - "8080"
    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    name: bar-app
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
  # Default port used by the image
  - port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /\$2
spec:
  rules:
  - http:
      paths:
      - pathType: ImplementationSpecific
        path: /foo(/|$)(.*)
        backend:
          service:
            name: foo-service
            port:
              number: 8080
      - pathType: ImplementationSpecific
        path: /bar(/|$)(.*)
        backend:
          service:
            name: bar-service
            port:
              number: 8080
---
EOF
$ kubectl --context patch configmap/ingress-nginx-controller \
          -n ingress-nginx --type merge \
          -p '{"data":{"worker-processes": "2"}}'
$ kubectl --context kind-kind apply -f /tmp/ingress.yaml

Kubernetes Certificate

Kubernetes Certificate

Manuellen TLS Eintrag erstellen

Für den manuellen Eintrag wird die komplette Zertifikatskette ohne das root-Zertifikat in einer Datei und der Key in einer anderen Datei benötigt. Beide müssen im PEM - Format vorliegen.

Sollten die Intermediate Zertifikate und das eigentliche Zertifikat nicht in einer Datei sein, dann lassen sich diese per cat Befehl zusammenfügen.

cat server.ca >> server.crt
$ kubectl create secret tls --cert server.crt \
    --key server.key --namespace default
    example.com

Der zusätzliche Parameter -o yaml gibt den Eintrag aus statt ihn per API in den Cluster zu senden.

Alternativ kann der Eintrag auch selber erstellt werden:

---
apiVersion: v1
kind: Secret
metadata:
namespace: default
type: kubernetes.io/tls
  name: example.com
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURH [...]
  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUVA [...]

Der hinter tls.crt und tls.key angegebene Text entspricht dem Inhalt der Zertifikats- und Key-Datei in einer mit base64 ummantelten Version ohne Zeilenumbrüche.

$ base64 -i -w0 server.crt

Kubernetes Cookbook

Pods

List all pods on a node

kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=<node>

List all pods with custom outout

kubectl get pod -A -o=custom-columns=POD_NAME:.metadata.name,STATUS:.status.phase,NAMESPACE:.metadata.namespace,CONTAINER_NAME:.spec.containers[*].name

List all pods in a namespace

kubectl get pods -n <namespace>

List all pods not in the running state

kubectl get pods -n <namespace> --field-selector "status.phase!=Running"

Kubernetes Dashboard

Über das Dashboard lassen sich die Informationen über den Cluster visualisieren und anpassen. Sobald ein metrics-server im Cluster existiert, kann man auch die Metriken visualisieren.

Installation metrics-server

> kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Sollte der kublet-server nicht mit gültigen Zertifikaten ausgestattet sein, muss dies dem metrics-server mitgeteilt werden.

> kubectl patch deployment metrics-server -n kube-system --type=json  -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

Installation Dashboard

> helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
> helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard --set web.service.type=NodePort
> kubectl get svc -n kubernetes-dashboard -l app.kubernetes.io/component=web

Diese Installation des Dashboards ist ohne Absicherung per SSL. Diese müsste zusätzlich konfiguriert werden. Alternativ Nutzung von Rancher.

Zugriff auf das Dashboard

Anlegen eine serviceAccounts

kubectl create serviceaccount admin-user -n kubernetes-dashboard

serviceAccount ClusterAdmin Rechte zuweisen

kubectl create clusterrolebinding admin-user-binding --clusterrole=cluster-admin \
        --serviceaccount=kubernetes-dashboard:admin-user

Token generieren und ausgeben lassen

kubectl -n kubernetes-dashboard create token admin-user

SSL Port per Proxy weiterleiten

kubectl -n kubernetes-dashboard port-forward \
        svc/kubernetes-dashboard-kong-proxy 8443:443

Zugriff auf das Dashboard über https://localhost:8443

Kubernetes mit kubeadm auf Rocky Linux

Vorbereitung des Systems

Bevor du mit der Installation von Kubeadm beginnst, musst du dein Rocky Linux 9 System vorbereiten. Du musst sicherstellen, dass bestimmte Pakete und Abhängigkeiten installiert und die Systemeinstellungen korrekt sind.

Setzen des korrekten Hostnamens

Der folgenden Befehl setzt den Hostnamen des Systems auf seine eigene IP Adresse über den Dienst ip.dynlinux.io

sudo hostnamectl set-hostname $(hostname -I | cut -f 1 -d " " | tr . -).ip.dynlinux.io
sudo hostnamectl

Vollständige Aktualisierung des Systems

sudo dnf update -y

Dieser Befehl stellt sicher, dass alle Systempakete auf dem neuesten Stand sind.

Deaktivieren von SELinux

Kubernetes erfordert die Deaktivierung von SELinux, um ordnungsgemäß zu funktionieren. Du kannst dies tun, indem du die Konfigurationsdatei bearbeitest.

sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config

Nach der Deaktivierung über die Konfigurationsdatei muss das System neu gestartet werden.

Alternativ kannst du es auch temporär deaktivieren

sudo setenforce 0

Deaktivieren der Firewall

Die Firewall kann den Netzwerkverkehr zwischen den Kubernetes-Komponenten blockieren. Deaktiviere sie, um mögliche Probleme zu vermeiden:

sudo systemctl stop firewalld
sudo systemctl disable firewalld

Konfigurieren von IP-Weiterleitung und Bridge-Einstellungen

Für die Netzwerkinfrastruktur von Kubernetes ist die IP-Weiterleitung erforderlich. Erstelle die Konfigurationsdatei 99-kubernetes.conf

sudo tee /etc/sysctl.d/99-kubernetes.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

Lade die Einstellungen neu

sudo sysctl --system

Lade das Kernelmodul br_netfilter

sudo tee /etc/modules-load.d/k8s.conf <<EOF
br_netfilter
EOF
sudo modprobe br_netfilter

Installation von Containerd

Containerd ist die Container-Laufzeitumgebung (Container Runtime), die von Kubernetes benötigt wird, um Container zu starten und zu verwalten. Kubernetes verwendet die Container Runtime Interface (CRI), um mit der Container-Laufzeitumgebung zu interagieren.

Installation der erforderlichen Pakete

sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y containerd.io

Konfiguration von Containerd

Zuerst erstellst du eine Standardkonfiguration für Containerd:

sudo containerd config default | sudo tee /etc/containerd/config.toml

Anschließend bearbeitest du die Konfigurationsdatei, um das cgroupfs-System für das Systemd-Cgroup-Treiber-System zu verwenden:

sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

Aktivierung und Starten von Containerd

sudo systemctl enable containerd
sudo systemctl restart containerd

Installation von Kubeadm, Kubelet und Kubectl

Nachdem Containerd eingerichtet ist, installierst du die Kubernetes-Tools.

Hinzufügen des Kubernetes-YUM-Repositorys

sudo tee /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key
EOF

⚠️ Hinweis: Die Versionsnummer v1.32 sollte an deine gewünschte Kubernetes-Version angepasst werden.

Installation der Kubernetes-Pakete

sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

Dieser Befehl installiert die drei Hauptkomponenten:

  • kubelet: Der Agent, der auf jedem Knoten läuft und die Kommunikation mit dem Control Plane übernimmt.
  • kubeadm: Das Tool zum Bootstrapping des Clusters.
  • kubectl: Das Befehlszeilentool zur Interaktion mit dem Cluster.

Initialisierung des Control-Plane-Knotens

Jetzt kannst du den Control-Plane-Knoten (Master-Knoten) initialisieren. Dies ist der wichtigste Schritt, da er das Herzstück deines Kubernetes-Clusters bildet.

Initialisiere das Kubernetes Control Plane

sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint $IP:6443

Der Parameter –pod-network-cidr ist wichtig, da er das Netzwerk-Subnetz für die Pods festlegt. Der Wert 10.244.0.0/16 ist der Standard, der von vielen CNI-Plugins wie Flannel verwendet wird.

Starten und Aktivieren des Kubelet-Dienstes

sudo systemctl enable --now kubelet

Konfigurieren des Benutzers für kubectl

Nach der erfolgreichen Initialisierung musst du kubectl für den aktuellen Benutzer konfigurieren, um Befehle ausführen zu können:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Installieren eines Pod-Netzwerk-Addons

Ohne ein Pod-Netzwerk-Addon können die Pods nicht miteinander kommunizieren. Flannel ist eine beliebte Wahl.

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

Nach der Installation kannst du den Status der Pods überprüfen

kubectl get pods --all-namespaces

Erlaube pods auf der Control Plane

Auf der Control Plane solltest du keine Pods, die nicht zur Control Plane gehören - laufen lassen. Dies kann zu einer Instabilität der Control Plane führen.

Für Test-Setups ist es möglich auch auf der Control Plane Pods laufen zu lassen

kubectl taint nodes <node-name> node-role.kubernetes.io/control-plane:NoSchedule-

Hinzufügen von weiteren Control Plane-Knoten

Um weitere Control Plane-Knoten zu erstellen musst du zunächst die Zertifikatsdaten des Clusters ein Kubernetes Secret speichern. Dazu führst du auf einem existierenden Conntrol Plane-Knoten den folgenden Befehl aus.

sudo kubeadm init phase upload-certs --upload-certs

⚠️ Dieser Schritt ist für das Hinzufügen eines Workers nicht notwendig.

Der Certificate Key Name aus der Ausgabe des vorherigen Befehls wird für den kubeadm join benötigt. Der restliche Aufruf wird mit dem folgenden Befehl ausgegeben auf dem bestehenden Control Plane-Knoten.

sudo kubeadm token create --print-join-command

Den Ausgegebenen Befehl musst du nun um die Parameter --control-plane und --certificate-key <certificate-key-string> erweitern und auf dem neuen Control Plane-Knoten ausführen.

sudo kubeadm join <control-plane-IP>:<control-plane-port> \
     --token <token-string> --discovery-token-ca-cert-hash sha256:<hash-string> \
     --control-plane --certificate-key <certificate-key-string>

Sobald die zusätzliche Node bei kubectl get nodes auftaucht können die korrekten Label gesetzt werden.

kubectl label node <node-name> node-role.kubernetes.io/etcd=
kubectl label node <node-name> node-role.kubernetes.io/control-plane=

Hinzufügen von Worker-Knoten

Kubeadm gibt dir nach der Initialisierung einen kubeadm join-Befehl aus. Du musst diesen Befehl auf den Worker-Knoten ausführen, um sie dem Cluster hinzuzufügen.

sudo kubeadm join <control-plane-ip>:<control-plane-port> --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>

Dieser Befehl verbindet den Worker-Knoten mit dem Control Plane.

💡 Die Daten für diesen Befehl kannst du auf der Control Plane auslesen

kubeadm token create --print-join-command

Starten und Aktivieren des Kubelet-Dienstes auf dem Worker

sudo systemctl enable --now kubelet

Sobald die zusätzliche Node bei kubectl get nodes auftaucht können die korrekten Label gesetzt werden.

kubectl label node <node-name> node-role.kubernetes.io/worker=

Überprüfung des Clusters

Um sicherzustellen, dass dein Cluster ordnungsgemäß funktioniert, kannst du den Status der Knoten überprüfen:

kubectl get nodes

Die Ausgabe sollte alle Knoten (den Control-Plane-Knoten und alle hinzugefügten Worker-Knoten) als Ready anzeigen.

Testanwendung installieren

Wenn du eine Anwendung bereitstellen möchtest, kannst du dies mit einem Deployment-Befehl tun:

kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --type=NodePort --port=80

Diese Befehle erstellen ein Deployment für eine Nginx-Webserver-Anwendung und legen einen Service vom Typ NodePort an, um die Anwendung über eine externe IP-Adresse zugänglich zu machen.

Deinstallieren der kompletten Installation

Uninstall k8s

## on control-plane
kubectl drain mynodename --force --ignore-daemonsets
kubectl delete node mynodename
## on node
sudo kubeadm reset --force
sudo systemctl stop kubelet
sudo dnf -f remove kubeadm kubectl kubelet kubernetes-cni kube*
sudo dnf -f autoremove
rm -rf ~/.kube
sudo rm -rf /var/lib/kubelet/*

Uninstall containerd

sudo systemctl disable --now containerd
sudo dnf -y remove yum-utils
sudo dnf -y remove containerd.io
sudo rm -rf /var/lib/containerd
sudo rm -rf /etc/containerd

Uninstall flannel

sudo rm -rf /var/lib/cni/
sudo rm -rf /run/flannel
sudo rm -rf /etc/cni/

Kubernetes Pods

Pods sind in Kubernetes die Umgebung in der ein oder mehrere Container gestartet werden können. Container können nicht ohne einen umgebenden Pod laufen.

Alle Container innerhalb eines Pods teilen sich die Namespaces des Pods und können nur parallel auf der gleichen Node laufen. Wir einer der Container eines Pods beendet, dann werden alle Container im gleichen Pod gestoppt.

Evicted Pods

Pods können durch Signale zu Evicted Pods werden. Die Signale können z.B. durch zu wenig Speicher, Festplattenplatz, fehlende INodes oder Prozess IDs gesetzt werden. Meist starten in dem Fall neue Pods, sobald der entsprechende Engpass behoben ist.

Die verbleibenden - Evicted - Pods können dann entfernt werden.

Eine Liste der Evicted Pods zusammen mit dem Grund wird wie folgt erstellt:

$ kubectl get pods --all-namespaces -o go-template='{{range .items}}{{if eq .status.phase "Failed"}}{{if eq .status.reason "Evicted"}}{{.metadata.name}}{{" "}}{{.metadata.namespace}}{{"\n"}}{{end}}{{end}}{{end}}' | while read epod enamespace; do kubectl -n $enamespace get pod $epod -o=custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,MSG:.status.message; done 

Zum Löschen aller Evicted Pods reicht ebenfalls ein Einzeiler:

$ kubectl get pods --all-namespaces -o go-template='{{range .items}}{{if eq .status.phase "Failed"}}{{if eq .status.reason "Evicted"}}{{.metadata.name}}{{" "}}{{.metadata.namespace}}{{"\n"}}{{end}}{{end}}{{end}}' | while read epod enamespace; do kubectl -n $enamespace delete pod $epod; done

Debugging mit Ephemeral Containern

Ephemeral Container können einem Pod hinzugefügt werden um in der bestehenden Pod Umgebung debugging durchführen zu können.

$ kubectl debug $PODNAME --image=alpine --attach -ti -- sh

Nach dem Verlassen des Containers bleibt dieser im Pod erhalten und wird erst durch entfernen des Pods gelöscht.

Solange kann an den Container attached werden.

$ kubectl attach $PODNAME $CONTAINERNAME -ti

Kubernetes Service

Pods werden nicht direkt über die Pod IP angesprochen, da sich diese IP bei jedem Anlegen eines Pods ändert. Falls über das Deployment mehrere Pods angelegt werden, wird über die IP immer nur ein Pod angesprochen.

Ein Service sorgt für die Vergabe einer ClusterIP, eines NodePorts oder der Nutzung eines LoadBalancers.

Um die zusammengehörigen Pods zu finden werden über einen selector die passenden Label der Pods festgelegt. Dabei müssen die Pods und der Service im gleichen Namespace sein.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: "nginx"
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: "nginx"
  type: ClusterIP

Durch den port wird der auf der ClusterIP zu belegende Port festgelegt. Der targetPort legt den vom Pod freigegebenen Port fest.

Bei der Konfiguration eines NodePort kann ein Port von 30000 bis 32767 automatisch oder durch die Angabe des Parameters nodePort festgelegt werden.

Die dynamische Vergabe des Ports bevorzugt, da es hier nicht zu Konflikten auf Grund paralleler Deployments kommen kann.

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: "nginx"
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30000
  selector:
    app: "nginx"
  type: NodePort

Kubernetes StorageClass

Eine StorageClass wird benutzt um einen Datenspeicher für die Pods zur Verfügung zu stellen.

Es gibt verschiedene Provider über die die Speicherung durchgeführt werden kann.

Eine StorageClass als default festlegen

Um für einen Cluster einen Storage als Voreinstellung zu haben wird in der Regel die erste angelegte StorageClass als default definiert.

Wird nun Storage angefordert, dann wird diese Klasse als StorageClass benutzt.

In der StorageClass Liste erscheint die StorageClass dann mit der Kennzeichnung (default).

$ kubectl get StorageClass
NAME                 PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn (default)   driver.longhorn.io   Retain          Immediate           true                   24m
longhorn-static      driver.longhorn.io   Delete          Immediate           true                   24m

StorageClass als nicht-default setzen

Mittels eines Patches lässt sich eine StorageClass als nicht-default setzen. Ist kein default definiert und eine Resource benötigt ein Volume, dann muss eine StorageClass explizit angegeben sein.

kubectl patch StorageClass longhorn -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

StorageClass als default setzen

Sobald es keine default StorageClass gibt, kann eine StorageClass als default festgelgt werden. Dies geschieht analog zum Entfernen der default Definition.

kubectl patch StorageClass longhorn -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Kubernetes Tools

Für den Betrieb eines Kubernetes Clusters werden einige Tools benötigt.

dieses sind nicht immer direkt in den Repositories der Distributionen verfügbar. Das folgende Script installiert die wichtigsten Tools in /usr/local/bin und fügt den Pfad zur PATH Variable hinzu.

echo "install needed tools"
  sudo yum install -y epel-release
  sudo yum install -y vim tar git libiscsi-utils iscsi-initiator-utils nfs-utils
  sudo systemctl enable --now iscsid

echo "add /usr/local/bin to PATH"
  echo "PATH=\"/usr/local/bin:\$PATH\"" | sudo tee -a /root/.bashrc | tee -a $HOME/.bashrc

echo "installing kubectl"
  sudo curl -Lo /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
  sudo chmod +x /usr/local/bin/kubectl

echo "installing helm"
  curl -L https://get.helm.sh/helm-v3.15.3-linux-amd64.tar.gz|sudo tar xvz -C /usr/local/bin/ --strip-components=1 --wildcards '*/helm'

echo "installing flux"
  curl -s https://fluxcd.io/install.sh | sudo bash

echo "installing ranchercli"
  curl -L https://github.com/rancher/cli/releases/download/v2.8.0/rancher-linux-amd64-v2.8.0.tar.gz|sudo tar xvz -C /usr/local/bin --strip-components=2 --wildcards '*/rancher'

echo "installing cilium"
  CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
  CLI_ARCH=amd64
  curl -L --fail https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz | sudo tar xzvC /usr/local/bin

echo "installing hubble"
  HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
  HUBBLE_ARCH=amd64
  curl -L --fail https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-${HUBBLE_ARCH}.tar.gz | sudo tar xzvC /usr/local/bin

echo "installing kubectl krew plugin"
	curl -L https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-linux_amd64.tar.gz | sudo tar -xzv -C /usr/local/bin ./krew-linux_amd64
	sudo chmod +x /usr/local/bin/krew-linux_amd64 
	sudo /usr/local/bin/krew-linux_amd64 install krew
	/usr/local/bin/krew-linux_amd64 install krew

	sudo grep KREW_ROOT /root/.bash_profile -q || echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' | sudo tee -a /root/.bash_profile
	grep KREW_ROOT ${HOME}/.bash_profile -q || echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' | tee -a ${HOME}/.bash_profile

echo "installing krew plugins"
	for I in neat cert-manager cilium change-ns df-pv sick-pods; do
		sudo /usr/local/bin/kubectl krew install $I
		/usr/local/bin/kubectl krew install $I
	done

Kubernetes Troubleshooting

RKE2

RKE2 startet nicht “cluster-cidr: [x.x.x.x/16 201:db8::/56] and node-ip: [y.y.y.y], must share the same IP version (IPv4, IPv6 or dual-stack)”

RKE2 hat die IPv6 der Node nicht erkannt. Durch eine zusätzliche Konfigurationsdatei in /etc/rancher/rke2/config.yaml.d/ kann die bestehende Konfiguration ergänzt werden:

# node-ip.yaml
{
  "node-ip": "y.y.y.y,201:db8:1::1/643"
}

Cluster meldet “Waiting fpr probes: kube-controller-manager, kube-scheduler”

Die Meldung deutet darauf hin, dass es ein Problem mit den Zertifikaten gibt und dieses erneuter werden müssen.

Das folgende Script zeigt ob die Zertifikate aktuell sind:

$ curl  --cacert /var/lib/rancher/rke2/server/tls/kube-controller-manager/kube-controller-manager.crt \
  https://127.0.0.1:10257/healthz >/dev/null 2>&1 \
  && echo "[OK] Kube Controller probe" \
  || echo "[FAIL] Kube Controller probe"

$ curl --cacert /var/lib/rancher/rke2/server/tls/kube-scheduler/kube-scheduler.crt \
  https://127.0.0.1:10259/healthz >/dev/null 2>&1  \
  && echo "[OK] Scheduler probe" \
  || echo "[FAIL] Scheduler probe"

Falls bei diesem Test ein Fehler ausgegeben wird, können mit dem folgenden Script die Zertifikate gelöscht und erneuert werden:

export CRI_CONFIG_FILE=/var/lib/rancher/rke2/agent/etc/crictl.yaml
export CONTAINERD_ADDRESS=unix:///run/k3s/containerd/containerd.sock
export PATH=$PATH:/var/lib/rancher/rke2/bin

echo "Rotating kube-controller-manager certificate"
rm -f /var/lib/rancher/rke2/server/tls/kube-controller-manager/kube-controller-manager.{crt,key}
crictl rm -f $(crictl ps -q --name kube-controller-manager)

echo "Rotating kube-scheduler certificate"
rm -f /var/lib/rancher/rke2/server/tls/kube-scheduler/kube-scheduler.{crt,key}
crictl rm -f $(crictl ps -q --name kube-scheduler)

Longhorn Storage unter Kubernetes

Das SUSE Longhorn Projekt stellt eine Open-Source-Lösung für verteilte Block-Speicher für Kubernetes bereit.

Was ist Longhorn?

Longhorn ist eine kostenlose und Open-Source-Software-Defined-Storage-Lösung, die für Kubernetes entwickelt wurde. Sie bietet persistente Speichervolumes für zustandsbehaftete Anwendungen, die auf Kubernetes laufen.

Hauptmerkmale von Longhorn:

  • Einfache Installation und Verwaltung: Longhorn lässt sich einfach in einem Kubernetes-Cluster installieren und über die Kubernetes-API oder eine benutzerfreundliche Weboberfläche verwalten.
  • Hochverfügbarkeit: Longhorn repliziert Speicherdaten über mehrere Knoten hinweg, um Ausfallsicherheit zu gewährleisten. Wenn ein Knoten ausfällt, sind die Daten weiterhin verfügbar.
  • Snapshots und Backups: Es unterstützt das Erstellen von Snapshots von Volumes und das Sichern dieser Snapshots an externen Speicherorten (z. B. S3-kompatible Speicher, NFS).
  • Dynamische Volume-Bereitstellung: Volumes können dynamisch erstellt und an Pods angehängt werden, wenn diese gestartet werden.
  • Plattformübergreifend: Longhorn funktioniert auf verschiedenen Cloud-Providern und On-Premises-Umgebungen.
  • Block-Speicher: Longhorn stellt Block-Speicher bereit, der für eine Vielzahl von Anwendungen geeignet ist, einschließlich Datenbanken und anderer zustandsbehafteter Dienste.

Anwendungsfälle

Longhorn eignet sich hervorragend für Szenarien, in denen Sie:

  • Zustandsbehaftete Anwendungen auf Kubernetes betreiben, die persistenten Speicher benötigen.
  • Eine einfache und kostengünstige Speicherlösung für Ihren Kubernetes-Cluster suchen.
  • Hochverfügbarkeit und Datensicherheit für Ihre Speicherdaten benötigen.
  • Flexibilität bei der Verwaltung und Sicherung Ihrer Speicherdaten wünschen.

Installation

Die Installation von Longhorn erfolgt typischerweise über die Bereitstellung von YAML-Manifesten in Ihrem Kubernetes-Cluster. Sie können die neuesten Installationsanweisungen und Manifeste auf der offiziellen Longhorn-Dokumentation finden.

Installation Longhorn unter Rocky Linux

Vorbereitung

sudo dnf install -y epel-release
sudo dnf install -y vim tar git libiscsi-utils iscsi-initiator-utils nfs-utils iptables
sudo systemctl enable --now iscsid
sudo tee /etc/sysctl.d/20-inotify.conf <<HERE
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=524288
HERE
sudo sysctl --system

Installation per helm

helm repo add longhorn https://charts.longhorn.io
helm repo update
helm upgrade --install longhorn longhorn/longhorn \
 --namespace longhorn-system --create-namespace \
 --set service.ui.type=NodePort \
 --set persistence.defaultClassReplicaCount=1 \
 --set csi.attacherReplicaCount=1 \
 --set csi.provisionerReplicaCount=1 \
 --set csi.resizerReplicaCount=1 \
 --set csi.snapshotterReplicaCount=1 \
 --set defaultSettings.defaultReplicaCount=1 \
 --set longhornUI.replicas=1

Uninstall Longhorn

https://github.com/longhorn/longhorn/discussions/9167

Default storageClass anpassen

Durch das Setzen einer Annotation kann eine storageClass zur default storageClass ungestellt werden.

Dabei sollte zunächst geschaut werden ob eine storageClass als default deklariert ist und diese deaktiviert werden.

Danach kann eine andere storageClass zur default storageClass gemacht werden.

aktuelle default storageClass deaktivieren

Auflisten der storageClass Elemente um eine default storageClass zu finden

kubectl get storageClass
kubectl patch storageclass $STORAGECLASS -p \
  '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

neue default storageClass aktivieren

kubectl patch storageclass $STORAGECLASS -p \
  '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Manage PostgreSQL in Kubernetes

Verbindung zu einer PostgreSQL-Datenbank in Kubernetes herstellen

Der sinnvollste Weg um an eine PostgreSQL-Datenbank in einer Kubernetes-Cluster Umgebung zu verbinden ist die Verwendung eines passenden PostgreSQL Containers.

Dieser lässt sich über den den Befehl kubectl run erzeugen.

Dazu sollte im Vorfeld der namespace des PostgreSQL Servers sowie dessen Service-Name und Service-Port bekannt sein.

user@linux $ kubectl get svc -n postgresql
NAMESPACE      NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
postgresqlns   mypostgresql   ClusterIP   89.207.132.170  <none>        5432/TCP  13m

In dem Beispiel sind die folgenden Parameter vergeben:

  • postgresqlns ist der Namespace
  • mypostgresql ist der Name des Services
  • 5432 ist der Service-Port

Der Container für den Zugriff wird nun mit dem Befehl kubectl run erzeugt und direkt eine Verbindung zum PostgreSQL Server hergestellt.

user@linux $ kubectl run -it postgres --image=postgres --rm=true \
             --namespace=postgresqlns -- \
             psql postgresql://username:password@mypostgresql:5432/database

Nach Ende der Verbindung wird der Container gelöscht.

NGINX Ingress auf Kubernetes

Voraussetzungen

  • Ein Kubernetes Cluster ist konfiguriert und läuft
  • kubectl ist auf einer Control Plane installiert

Installation helm

helm kann über die Webseite https://get.helm.sh heruntergeladen und installiert werden.

Die folgende Kommandozeile führt dies automatisch durch. Die Versionsnummer sollte entsprechend angepasst sein.

 dnf install -y tar
 curl -L https://get.helm.sh/helm-v3.19.0-linux-amd64.tar.gz| \
      sudo tar xvz -C /usr/local/bin/ --strip-components=1 --wildcards '*/helm'

Installation ingress-nginx

Einrichtung des repositories

Über das Repository stehen die verschiedenen Charts für die Installation zur Verfügung und können auch darüber upgedated werden.

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

Erstellen eines Namespaces

Die Installation erfolgt in einem eigenen Namespace. Dieser kann vorab oder beim helm install über den Parameter --create-namespace während der Installation angelegt werden.

kubectl create namespace ingress-nginx

Installation des ingress-nginx

helm install nginx-ingress ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --set controller.service.type=NodePort \
  --set controller.kind=DaemonSet --set controller.hostNetwork=true

Prüfen des der Installation

Test des DaemonSet

kubectl get daemonset --namespace ingress-nginx

Test Installation eines nginx

Im folgenden wird ein nginx Webserver installiert und mit einem Ingress versehen.

kubectl create deployment nginx --image=docker.io/nginx:alpine
kubectl expose deployment nginx --type=ClusterIP --port=80
kubectl apply -f - <<MANIFEST
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations: {}
spec:
  ingressClassName: nginx
  rules:
  - host: <node dns name>
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
MANIFEST

Ingress mit Basic Authentication versehen

Durch die Basic Authentication wird sichergestellt, dass nur Benutzer mit entsprechenden Berechtigungen Zugriff auf die Website bekommen.

htpasswd Datei erzeugen

podman run --rm -ti docker.io/library/httpd htpasswd -bn \
  username passwort >htpasswd

htpasswd in Secret ablegen

kubectl create secret generic basic-auth --from-file=auth=htpasswd

Im Ingress müssen nun nur noch die folgenden Annotations hinterlegt werden:

# type of authentication
nginx.ingress.kubernetes.io/auth-type: basic
# name of the secret that contains the user/password definitions
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# message to display with an appropriate context why the authentication is required
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'

OCI Registry einrichten

Um eigene Images zu verwalten und zu pushen, wird ein OCI Registry benötigt.

Im folgenden Beispiel wird die Installation über yaml Dateien durchgeführt, die direkt über kubectl importiert werden. Dazu werden die im folgenden aufgeführten Beispiele in Dateien gepackt und über denn Befehl kubectl apply -f $DATEINAME importiert.

Namespace

Zunächst wird ein Namespace erstellt in dem die Registry läuft. Für das Beispiel ist das der Namespace registry.

apiVersion: v1
kind: Namespace
metadata:
  name: registry

Storage

Damit die Daten der Registry auch nach einem Neustart erhalten bleiben, wird ein Storae benötigt. Über die default Storage Klasse wird dafür ein PersistentVolumeClaim erstellt. Dieser erstellt dann ein PersistentVolume sobald dieses von dem Deployment angefordert wird.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: registry
  namespace: registry
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: "5Gi"
#  storageClassName: longhorn

Wird eine andere als die default StorageClass gewünscht kann diese mit dem Parameter storageClassName angegeben werden.

Secrets

Damit die Registry nicht ohne Authentifizierung läuft muss im Vorfeld eine htpasswd Datei erstellt werden um darüber die Benutzer zu verwalten.

Zunächst wird die htpasswd Datei erstellt. Dies lässt sich am einfachsten über den httpd OCI Container durchführen. Dieser enthält das benötigte Progamm htpasswd. Im Beispiel wird podman genutzt. Der Befehl kann gegen docker getauscht werden.

user@linux $ podman run --rm --entrypoint htpasswd docker.io/httpd -Bbn \
                testuser testpassword > htpasswd

Über die Datei htpasswd wird anschließend ein Secret im Namespace registry erstellt.

user@linux $ kubectl create secret generic registry-secret --dry-run=client \
                -o yaml --type opaque --from-file htpasswd -n registry
apiVersion: v1
data:
  htpasswd: dGVzdHVzZXI6JDJ5JDA1JGVSNXliR3hoU0gxSmVtcEIxR0dZMnVwUFVJb1lFQnFTSXhaVVlEYUpYRld6SVV0UTVReDFDCgo=
kind: Secret
metadata:
  creationTimestamp: null
  name: registry-secret
  namespace: registry
type: opaque

Deployment

Über das Deployment wird die Definition des Registry Pods durchgeführt. Dieser wird dann über ein automatisch erstelltes ReplicaSet gestartet.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  namespace: registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry
      release: registry
  template:
    metadata:
      labels:
        app: registry
        release: registry
    spec:
      containers:
        env:
        - name: REGISTRY_STORAGE_DELETE_ENABLED
          value: "true"
        - name: REGISTRY_AUTH
          value: htpasswd
        - name: REGISTRY_AUTH_HTPASSWD_REALM
          value: Registry Realm
        - name: REGISTRY_AUTH_HTPASSWD_PATH
          value: /auth/htpasswd
        image: registry:2
        imagePullPolicy: ifNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 5000
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: registry
        ports:
        - containerPort: 5000
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 5000
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        volumeMounts:
        - mountPath: /auth
          name: auth
          readOnly: true
        - mountPath: /var/lib/registry/
          name: data
      restartPolicy: Always
      securityContext:
        fsGroup: 1000
        runAsUser: 1000
      terminationGracePeriodSeconds: 30
      volumes:
      - name: auth
        secret:
          defaultMode: 420
          items:
          - key: htpasswd
            path: htpasswd
          secretName: registry-secret
      - name: data
        persistentVolumeClaim:
          claimName: registry

Service

Nach dem Einlesen des Deployments existiert der Pod registry im Namespace registry, es existiert aber noch kein Zugriff von Außen auf den Container. Dazu wird ein Service erstellt.

apiVersion: v1
kind: Service
metadata:
  name: registry
  namespace: registry
spec:
  ports:
  - name: http-5000
    port: 5000
    protocol: TCP
    targetPort: 5000
  selector:
    app: registry
    release: registry
  type: ClusterIP

Der Service ist in der Lage den passenden Pod auf Grund des selector zu finden und diesem eine ClusterIP zu geben. Dies ClusterIP mit dem Port 5000 ist dann eine Möglichkeit auf den Dienst zuzugreifen.

Allerdings nur auf einer der Cluster Nodes. Um auch von Außen auf den Dienst zugreifen zu können macht es Sinn einen Ingress zu erstellen über den dann der Zugriff erfolgt.

Ingress

Für den Ingress wird eine DNS Domain benötigt, die auf den Cluster zeigt. Für das Beispiel ist es registry.internal. Um auch per https auf den Dienst zugreifen zu können muss ein TLS Zertifikat vorhanden sein. Diese kann wie Folgt erstellt werden:

user@linux $ openssl req -x509 -newkey rsa:4096 -keyout tls.key -nodes \
                -out tls.crt -days 365 -subj '/CN=registry.internal'

Statt des selbstzertifizierten Zertifikats kann auch ein Let’s Encrypt Zertifikat über cert-manager oder ein bestehendes Zertifikat verwendet werden.

Aus dem Zertifikat wird nun wiederum ein secret erstellt.

user@linux $ kubectl create secret tls registry-tls -n registry \
                --key=tls.key --cert=tls.crt --dry-run=client -o yaml
apiVersion: v1
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZHVENDQXdHZ0F3SUJBZ0lVZC9kV1Q2TEloLzBlR2JJZXNqclA1WEQyQ0hBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0hERWFNQmdHQTFVRUF3d1JjbVZuYVhOMGNua3VhVzUwWlhKdVlXd3dIaGNOTWpVd05ERTNNVEV4TmpBMApXaGNOTWpZd05ERTNNVEV4TmpBMFdqQWNNUm93R0FZRFZRUUREQkZ5WldkcGMzUnllUzVwYm5SbGNtNWhiRENDCkFpSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTFJwdUYxcW5OTXdUUDNrdy9SWFdMSWUKU1dIVXJ0U3dGMjJML0RSL0N3bWRCU0RvbjIvRHJFcTdqcmYwOUkrS21SMGJlRWNNczJUTDQvSVpIT2RYQkVZTgpMbzNJTTR5c1ZoRUh1ajRDekFQYzc5eW95U20vYjFSRVpjSElKd0ltSS93UjRrSlFhODU3eTZZVVR5enhUbFdyCm8zUXdLcHEwcnhwdDgxN083SkI0THp1V1dyTklqdWpJVHh2YXNXR1ozd0NuSmNIczRzWlZpZk1KUGZhV2RpUysKd2hTZDR6U1J1VnZFWFZMalFmYkk2bGtJVTF4THIrR2VPL0c5WVZ2TmFJeVZsT0JUd1Jqa0hDT2pOa0dpR1dKVAp1b2JoTE43KzNWaFdmYjZ3enQwV3BHM0NJTjVFemJNOUxQems3aVljdTk2RVJ3dlhvREp6eGRSbUF3WUZRY2twCjEzd1VoSGdFdkZUZDlvZk9HZ3graDd3cm1Mdk1mZitCRUk3amRNUEhST3UwSVpqSGxOQTVMRzA3ai9rbE5xWmsKMXdhZHNyUjNDZjVpM0tVbmlpMTYxQ29WMitjU1YyRTIyUFBrVnNsVklKYmJvT0VXZGc0c2ZNTHd5Y3ZSVk8xcQovVjl6MEVsajBhMllSTUdXMWI4cXIwNkpHUkRvVEFKSFNzc1R1dm5icmJ6ZzJOdEl0NFd0OFZpVXRFVjdTMkpTCnN6WmhwSTRUUlN2bHRwbUlneXVMWDUzVmNWWUhxRXBFY2xZOC9tSkdmdXJzWGxhdm9NSmlaYk5MMWpLaUJkL1kKNkRxMTJnMmMvc3BXOXlYeGJyQnlIWEZraVZpc21pOG5tTFYxQTFsSHpGc3ZTdnIzZTRpVWpLV0t3RklsQ2o4Rgorak42c2FFdjFmZHFwUldERExXMUFnTUJBQUdqVXpCUk1CMEdBMVVkRGdRV0JCVElWdGo0bDdCNVczcUw1T0NOClBrWFlNWkdpcWpBZkJnTlZIU01FR0RBV2dCVElWdGo0bDdCNVczcUw1T0NOUGtYWU1aR2lxakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElDQVFBcUtWVjFHM2JwRHFMN04rTnpNTitGK0lIMwphUVd3ZGFRSGtuN2JXYmNBamQvUEZ1YXJPdCtMaUMvUmQ5VUhLWVA3MGdHZmQ5YTFmWjBGTlZDb3RZR2ZvYnVDCjNUV0RnanRXNy8wSi9ENXFNR3REWk1UVW1NVGVheVR5VlpsdUlxcTdGMklDYjU0R0k2WnR4aWRuNUw5bkhpQU8KRGFmQnZkMTFPQ2ViZ1VsRHBVR3FoaW50ZmJsVkY1NUhwUjdiSEpqV2U1WDAva3NyZi92a0RUMEVLYTlzZkhrUwpLTTZaNm9LTVlDUEdrL1d4dzA0YkF2dzU2U0pMUnFTU0NCL1pqQVQxazhyeW1ieENMekczN1NPeDJ4NWpueEF5CkZ5RldUcExHb2hOZmRURDlxOGxlSjJYMUh5L1gwVVgyM1Vpek5US2kyRjMxallDVXQ5QkJwbEx6ekM5bW1BeEgKbm9FMnNKS3lKbXdDdTk3VGhvOUlnSHpPMVZHV3Qyb2NPQ0lNdkxqY2ZoTUpHR2F5MFVoQ3JvYk9XWlBHS3VndAoyQU85VW9WaDVWMzloVDJVSkFVZFdDN0hsYi9RdFhLL3ZyczgybW5kZDVyUXFXajVrSjFEbXRNaldFak1DcVlvCjJPd0tSc1pqT2ZzcGhDSlpIOFR5eFoxY1V3Q0laMEsrVzFxeURSWDYwUkF4M3ZtMFBueU1YNGR4aFJsMWl6VmEKZi9jc1d3K09mSnVnVzZ6Zm1VdlV4by95WG0vSElHVExaL003OUlBY3NrbnJuM0pqWTBubGlmd0h4bFV1U0F6ZwoxN2ZIcjdGcjk3NHIzYWZLdWZnSU8rR1VkZnFsQWZhWkxmcGtqdGhqZ3FEV0VBRlNaSEhxcXJuWnZva2hhZURpClIyZUZVT0JRT1czaHhhVTdZQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRQzBhYmhkYXB6VE1FejkKNU1QMFYxaXlIa2xoMUs3VXNCZHRpL3cwZndzSm5RVWc2Sjl2dzZ4S3U0NjM5UFNQaXBrZEczaEhETE5reStQeQpHUnpuVndSR0RTNk55RE9NckZZUkI3bytBc3dEM08vY3FNa3B2MjlVUkdYQnlDY0NKaVA4RWVKQ1VHdk9lOHVtCkZFOHM4VTVWcTZOME1DcWF0SzhhYmZOZXp1eVFlQzg3bGxxelNJN295RThiMnJGaG1kOEFweVhCN09MR1ZZbnoKQ1QzMmxuWWt2c0lVbmVNMGtibGJ4RjFTNDBIMnlPcFpDRk5jUzYvaG5qdnh2V0ZieldpTWxaVGdVOEVZNUJ3agpvelpCb2hsaVU3cUc0U3plL3QxWVZuMitzTTdkRnFSdHdpRGVSTTJ6UFN6ODVPNG1ITHZlaEVjTDE2QXljOFhVClpnTUdCVUhKS2RkOEZJUjRCTHhVM2ZhSHpob01mb2U4SzVpN3pIMy9nUkNPNDNURHgwVHJ0Q0dZeDVUUU9TeHQKTzQvNUpUYW1aTmNHbmJLMGR3bitZdHlsSjRvdGV0UXFGZHZuRWxkaE50ano1RmJKVlNDVzI2RGhGbllPTEh6Qwo4TW5MMFZUdGF2MWZjOUJKWTlHdG1FVEJsdFcvS3E5T2lSa1E2RXdDUjByTEU3cjUyNjI4NE5qYlNMZUZyZkZZCmxMUkZlMHRpVXJNMllhU09FMFVyNWJhWmlJTXJpMStkMVhGV0I2aEtSSEpXUFA1aVJuN3E3RjVXcjZEQ1ltV3oKUzlZeW9nWGYyT2c2dGRvTm5QN0tWdmNsOFc2d2NoMXhaSWxZckpvdko1aTFkUU5aUjh4YkwwcjY5M3VJbEl5bAppc0JTSlFvL0Jmb3plckdoTDlYM2FxVVZnd3kxdFFJREFRQUJBb0lDQUNPVTlFRXNYVGpEdm5PV3NsVVhBdzNDCkxtdm1sQUtycGpzN1VDY1VaVnRraGhYcSswTUNQeEZRTTRJeGhDaHBSL0IzTWY0bFVaNVFIaWxwN1lyczNSRnAKMlNRcWQ0eEhrd1B4MTdnU09pV0s1aDNKaGo4L0c5aHRVdnBvbk5UdUs4dXp4VUdaOWVFNEJqNi9vNnZ6L2FTawo0T3h4OFgwb3BKNWNVQk1EVWFIZXFrWWd6Y3V0UkU1QjcyUkplaEdMVlBHZWhiSmRJNDdGWVJzM1YrcXgyeVE5CklTOEt3cnhqLzBCTGxySlowdkUyeUExK09GNnNLTWloT2laZjJQdFBwRTZSMDlaNGZrMkg4OHJOams1Y2g1QWUKZmx2S3ZseE92TjRHMU84T2xRWUN5TFJEQk5PQmsyWkY2d0VIcjdhQmxVWDZKYjcrcklBMjJDNGZYbnBLQUNLMApTY0Jjd3d6dzBmQTF4a3BaTk1zSVVpLzhwLzJvUjlyY0loaUNRSTd0WFlueERRSENWZ3dVNGJITjJhakdHMUJICitZTmZsK2t4QWk1WUJja0p0bHVjQXJoWWdHUFd0b1FmWnFJU3IrNFVkWEFTZ3VBOGp5aVdHVVlBWnFuUlVrWEMKYjBseTIyR0x5dWRoN2ZJSnFmQXR3Sm5ON25icmpDZy90dHFOc1FMZWgxWGpVYUNYbExvMmVTVmFlZHNZa0dJNwpKMXVwckJiWVlqQURDa1hLaDhHYnB0cEZBZmpRekU2TjEyRTd1dVF1eGdrRXRBZnp6QlIrWTh3ejhpWkFPR043ClpWcUYwTWlRV21UWmRhM3RVSnM5QnpKMDBPZDhNbzZsVHFYTlFxOXU0TkxoOThzTTRsc0dhU0lLYmFTS09RQ2MKTkpKdXc3M3R2V0ltZlNQNm5GbVRBb0lCQVFEOFVmSTlwTU52R2picjQ3Y00vZElYRk44NWFQZ2VDd2RXRXh2KwpWUG9VeVd2THp0a3BtdWRUdWZXY25aVzVZSTZRZUVtcmk0YjNkbUMySHgvS0tTL0c0a0tvdFZncFJhd3RjSUpNCnh4ck5YdTdZSjhadkg2bG84NUxHbmlQVDR2NDRNR2dHNjFBRVREVEorakUrSStwVmpnazdEKzNXQnd5RkRSbXAKaWVoSSs2THU0VDI1U3ZEYjdaZW1OT1RXbjY1N25BOURXUzUrRUtQY00yOFJid3VTdEJiMEZNTWptUFhJM25NcwpyeDBkd2lNc0wzNStOdU9qRk14VEpocnQ5RHFESUJBUGdtdUNKTUNuVHlwakdKR01DbzJZVXpZdko3U0Z0TjMvClBCd0dQb050cjFkUW4ydnpVeHluZVBTeDZydDZYUlhOeFQyTU1tUmg2dndoTUNtdkFvSUJBUUMzQzAzTU90NFgKRTVnQkxPZzc4L2ZWb2txNURCa0tBdGlPM3lJWnFHS1BXUHRnQ2x5dUpzZ012SVI3M3hoVkJ6YTJCWkMvUkNyMApkQWRRbnZXYXRhb1YyN3YzMFBzQ0xRaFU3Q2NpdWdxRlJUN1lnV0RIQk5tbkVWeHExOWYxd2pTcHlIdVl1bUkyCk5SczZCME90RHRleld3TndvWkRCZGN1VnR5cXFReDdWRFErcHRKZ290dnhtbWNRcTN4WnhQczNuWmhVazJ1VlUKL241SnhzNWFLOHpNZHRoUWpucGdOYzVFQmhEa1pjK1dyTmM1WjBtNUsvMnh5dlFsdXF6R1AzWnJYUlFFcm1VYwpOWkQrRVJxdVVuY3BJS3pMOU8yVHFKZHoySGlQOWptSUJKOTdjVUVrNHRQcTJZYnFlWHY1SGxRczk5TGtXTFFQCm9rVHp4RUthWkFQYkFvSUJBQk5yYXkxN1pOOUVNVDN1aE1Rbk5PZzUzd1JZSStDTUVNQjdNQWhmR2ZCSG9GaEwKVDBONGZKMWhEcHBETnFiUjI2Y2EydkN0Q0hJN1ZpNHVMeEFzSTFVM1ROSVhRdlhLT3ZvMFVwTCtMLzNtZEpPRwpYQ3RwVUd5WGNwQiswMXNYZVdGeHVFL3dCSFNRT08vaXBhK1dyQTV4cEJ3N21aNkRaRjlKSnRSNW5Mc2hRdVVICnlPeGQ3RFBCRVk3bE8wZVplcUJnM252Y2ZVdU9sRTM2VjI0TUVlUDBvRitneVhRUDdick1CQ2xJUVFGdFU2K3UKNkJsbXVCWnhIMWkyOGhPbVhHcURLVGxJWEdYajFrQUtROFRrdXk4QUVPM01XYnMvWHhGN1hpcXF3YTFwV2t3eQoxcEdKODVFQ3NJM2pMVklVTXVHMEVGMko5TGE1bWlnL0liQ2NPRmtDZ2dFQWNyalgrcitPZ0tJY0sraHNhVjhBCkh0cEh3UER2SkpJaURuR1ZHc1dwZURTSjRHaStLN3hNdDRiVDloc2VVZlJpZXZURUFzeURxNUNwSVFOdjVaWWsKVXV6VVEzNnNRM0hiL2ZYQWxZaEtuYnFIcUFSMmxtWFkwRmdXTnpTdXV1NE1PYmxZbGFHRGVud1FZMjg0SUZmawpJeHBRN3Ara2JVd1oyK3pnQmJScHQ1Z25EL01MUnNGaE1pRC9qT3NqVXJqTkxsR1J3UGFjczRlSnVVMnIrV0hvClN1SFZzNitJelpJTGhDRFZKNEVUK1ZpOHo5S0hwMUlHQUErMnM0cnJUSmxyNitCN2NtK25HV1lOU04rSmZHWmQKSHQ3R3psSGxNUjJDUnU0T3FoUGxmUFdBeTBweFRPQUpkL0Q4R1NzbzR6cjZBUEVXTVYzVEJOVnNKK2dDNG1iNgo3d0tDQVFFQXoxWEdMV1pILytyblozdmVkVzh2U0U0cks0N3RXQ0dwQlJsM0xSTTBia1ZlaGJQN3JLRHg4a3MwCmtqQUtXei8rb3Vud1BqSGR0T2dBZmhVcUR6cS85Q2IzOFBOQ1Q3emM5dmlnc2FXc3ZuU05aSWNjakhFV2hpT1QKcjg1L3h6ZG82WkorOE1WQzl0Wll5YjRudmNuWkhVV2dKSmljYm1PMTAxcnJxVG1uUi8xcFZwVnF2R3FwNG5lRgprbm5mS1oydnhwZUlsQjIyQ20rNGlLNzdLekgveW1oc2xGcE5nR3FNS0F3RFlqM0NHK21hc0RPMXVXUUhSWjZWCmxQYjB0WHNOVVNYLysyb1d5U2VYWlBTbnhMd1JvUDZPdFFGTjdSR0MyekN6b05tdndnV3l5RVVrNHFtVW9vOVYKSTBrSFBkNkhFVXFkcTF3SVJ5WjBrWWFMakNJRkNBPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=
kind: Secret
metadata:
  creationTimestamp: null
  name: registry-tls
  namespace: registry
type: kubernetes.io/tls

Dieses secret wird in dem folgenden Ingress verwendet.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    # cert-manager.io/cluster-issuer: acme-production
    # kubernetes.io/tls-acme: "true"
    ingress.kubernetes.io/proxy-body-size: 1024m
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: 1024m
  name: registry
  namespace: registry
spec:
  rules:
  - host: registry.internal
    http:
      paths:
      - backend:
          service:
            name: registry
            port:
              number: 5000
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - registry.internal
    secretName: registry-tls

Die beiden auskommentierten Einträge sind die Verwendung von cert-manager und einem ClusterIssuer gedacht. Der ClusterIssuer muss dafür zunächst angelegt sein und die Domain von extern durch den Zertifizierungsdienst - z. B. Let’s Encrypt

  • erreichbar sein.

Nach dem Erstellen des Ingress ist ist die Registry über https://registry.internal erreichbar.

Rancher Installation

Rancher ist eine von SUSE entwickelte Verwaltungsplattform für Kubernetes.

Für die Installation wird eine bereits vorhandene Kubernetes Installation und das Tools Helm benötigt.

Bei der Installation über Helm werden durch das Tool die benötigten Steuerdateien für den Cluster aus Templates erstellt und eingespielt.

Zunächst wird der cert-manager für die Zertifikatsverwaltung benötigt.

# Add Cluster Resource Definitions (CRD) for cert-manager
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.15.1/cert-manager.crds.yaml
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
# Update your local Helm chart repository cache
helm repo update
# Install the cert-manager Helm chart
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.15.1
# Create namespace for rancher
kubectl create namespace cattle-system
# Add rancher helm repository
helm repo add rancher-latest https://releases.rancher.com/server-charts/latest
# Update your local Helm chart repository cache
helm repo update

Nun folgt die eigentlich Rancher Installation

helm install rancher rancher-latest/rancher --namespace cattle-system \
                                            --set hostname=$(hostname --fqdn) \
                                            --set replicas=1 \
                                            --set global.cattle.psp.enabled=false

Während der Installation wird die URL für das Webinterface sowie der Zugang zum Anmeldetoken angezeigt.

RKE2 Installation

Die Rancher Kubernetes Engine 2 ermöglicht die Installation und Konfiguration eines Kubernetes Clusters durch wenige Befehle.

Im folgenden wird die Installation unter einer aktuellen Version von Rocky Linux 9 vorgestellt.

Da das System selber Firewall - Regeln setzt sollte eine zusätzliche Firewall Konfiguration deaktiviert sein.

In der Regel läuft auf einem neu installierten Rocky Linux der firewalld.

sudo systemctl disable --now firewalld.service
sudo systemctl mask firewalld.service

Der Hostname der Node sollte korrekt gesetzt sein, oder durch hostnamectl gesetzt werden.

sudo hostnamectl set-hostname node150.train.mylinuxtime.de

Die folgenden Tools werden von RKE2 je nach konfigurierter Umgebung benutzt und sollten installiert sein.

sudo yum install -y epel-release
sudo yum install -y vim tar git libiscsi-utils iscsi-initiator-utils nfs-utils iptables
sudo systemctl enable --now iscsid

Da das RKE2 einige der in einer Standard - Installation vorherrschenden Limit überschreitet müssen für inotify die Limits erhöht werden.

sudo tee /etc/sysctl.d/20-inotify.conf <<HERE
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=524288
HERE
sudo sysctl --system

Die Installation der RKE2 Umgebung kann durch ein Script oder die Installation des Binaries von GitHub durchgeführt werden.

Das Script installiert neben dem Binary auch einige nützliche Scripte über die alle Prozesse des RKE2 gestoppt oder eine Installation entfernt werden kann.

Bei unterstützten Plattformen - wie Rocky Linux - wird zusätzlich ein Repository für den Paketmanager installiert über den ein direktes Update per yum update möglich ist.

curl -sfL https://get.rke2.io | sudo sh -

Zusätzlich wird das Kommando kubectl für die Verwaltung des Clusters benötigt. Der folgende Aufruf installiert den Befehl direkt nach /usr/local/bin. Um direkt per bash verfügbar zu sein wird auch die Variable PATH erweitert.

sudo curl -Lo /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
sudo chmod +x /usr/local/bin/kubectl
echo "PATH=\"/usr/local/bin:\$PATH\"" | sudo tee -a /root/.bashrc | tee -a $HOME/.bashrc
source $HOME/.bashrc

Falls dem RKE2 keine weiteren Parameter angegeben werden sollen, kann der Dienst aktiviert und gestartet werden.

sudo systemctl enable --now rke2-server.service

Die für den kubectl benötigte Konfiguration kann direkt unter .kube/config im Homeverzeichnis des aktuellen Benutzers und dem von root zur Verfügung gestellt werden.

sudo mkdir /root/.kube
sudo ln -s /etc/rancher/rke2/rke2.yaml /root/.kube/config
mkdir $HOME/.kube
sudo cp /etc/rancher/rke2/rke2.yaml $HOME/.kube/config
sudo chown $USER: $HOME/.kube/config
chmod 0600 $HOME/.kube/config

Nach wenigen Minuten sollte die neue RKE2 Node zur Verfügung stehen.

user@linux $ kubectl get nodes
NAME                           STATUS   ROLES                       AGE     VERSION
node150.train.mylinuxtime.de   Ready    control-plane,etcd,master   2m29s   v1.28.11+rke2r1