Kubernetes Cluster in Proxmox VE (AlmaLinux/CentOS)

Anleitung um ein Kubernetes Cluster innerhalb von Proxmox VE einzurichten.

Testumgebung
OS AlmaLinux 8.7
Kubernetes 1.26.0
K8s-Dashboard 2.7.0
Metrics-Server 0.6.2

Die Installation in einem VM Cluster wie Proxmox bietet sich aufgrund der Zeitersparnis an, dennoch sollte aber alles hier beschriebene grundsätzlich auch auf "echten" Maschinen funktionieren.


Vorbereitungen

Proxmox

Als ersten müssen zwei VMs mit AlmaLinux 8 erstellt werden.

- 1x Controller (Bsp.: k8s-ctrlr-1)

- 1x Worker (Bsp.: k8s-worker-1)

Mindestanforderungen: 2 GB RAM / 2 Cores / 32 GB Speicherplatz

Hinweis:

Einige Kubernetes Deployments nutzen MongoDB als Datenbank.

Ab MongoDB 5.0 werden keine Systeme mehr unterstützt deren CPU das AVX-Flag nicht hat.

Damit es später keine Probleme gibt, sollte als CPU daher nicht 'kvm64' sondern 'host' ausgewählt werden.

Controller und Worker

Hostname festlegen

Für den Controller .z.B.:

hostnamectl set-hostname k8s-ctrlr-1

Für die Worker-Nodes z.B.:

hostnamectl set-hostname k8s-worker-1

Zunächst prüfen wir ob alle Pakete auf dem neusten Stand sind:

dnf update

Damit Kubernetes später fleißig Container erstellen kann, muss zuerst Containerd installiert werden:

dnf install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install containerd

Den Service auf Autostart setzen:

systemctl enable containerd

Die ggf. automatisch erzeugte config.toml ist sehr minimalistisch und enthält nicht alle nötigen Optionen.

Daher löschen wir diese generieren sie neu:

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

Die neu erzeugte config.toml bearbeiten:

vi /etc/containerd/config.toml

Damit Kubernetes die Ressourcen(RAM/CPU) des Hosts verwalten kann, muss in der config.toml eine Option angepasst werden.

Folgenden Absatz suchen und SystemdCgroup auf true setzen (Suchen in vi mit /<Suchbegriff>)[1]:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            BinaryName = ""
            CriuImagePath = ""
            CriuPath = ""
            CriuWorkPath = ""
            IoGid = 0
            IoUid = 0
            NoNewKeyring = false
            NoPivotRoot = false
            Root = ""
            ShimCgroup = ""
            SystemdCgroup = false

Da die Container innerhalb von Kubernetes in einem eigenen Netzwerk laufen muss IP-Forwarding aktiviert werden:

vi /etc/sysctl.conf

Folgende Zeile am Ende einfügen, bzw. auskommentieren, falls schon vorhanden:

net.ipv4.ip_forward=1

Damit Kubernetes die internen VxLAN erstellen kann und die Pods(Container) untereinander kommunizieren können, muss das br_netfilter Modul aktiviert werden.[2]

Hierfür wird eine neue Config-Datei erstellt:

vi /etc/modules-load.d/k8s.conf

Dort einfach den Namen des zu ladenden Moduls einfügen:

br_netfilter

Wenn man versucht den Kubernetes Cluster zu initialisieren kommt eine Warnung, dass der Swap noch aktiv ist und dass das für eine Produktivumgebung nicht geeignet ist.

Daher deaktivieren wir ihn kurzerhand in der /etc/fstab:

vi /etc/fstab

Den Eintrag mit 'swap' suchen und mit # auskommentieren:

/dev/mapper/almalinux-root /                       xfs     defaults        0 0
UUID=3174f8e6-0465-4aa2-8e7b-77230bfee9c6 /boot                   xfs     defaults        0 0
#/dev/mapper/almalinux-swap none                    swap    defaults        0 0

Anschließend noch ein Neustart um alle Änderungen zu übernehmen:

reboot

Controller

Feste IP einrichten

Für erste geben wir nur dem Controller eine feste IP, damit aus der Worker-VM später ein Template erstellt werden kann.

Grafische Oberfläche:

nmtui

Oder per Konsole (Name des Interfaces kann abweichen):

vi /etc/sysconfig/network-scripts/ifcfg-ens18

Beispiel:

BOOTPROTO=none
IPADDR=192.168.2.65
PREFIX=24
GATEWAY=192.16.2.1
DNS1=192.168.2.1
DNS2=1.1.1.1

Hosts-Datei anpassen:

vi /etc/hosts

Die festgelegte IP mit dazugehörigem Hostnamen einfügen:

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4

192.168.2.65   k8s-ctrlr-1.my.domain k8s-ctrlr-1

Damit die Änderungen wirksam werden starten wir den Netzwerkadapter neu (Interfacename muss ggf. angepasst werden):

ifdown ens18 && ifup ens18

Falls ihr per SSH verbunden seid, wird die Sitzung getrennt und ihr müsst euch danach mit der neuen IP verbinden.

Kubernetes installieren

Controller und Worker

Zuerst das Kubernetes Repository hinzufügen:

cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF

Da kubelet nicht für SELinux optimimert ist und sich eine individuelle Konfiguration sehr aufwändig gestalten kann, sollte es am besten dauerhaft deaktiviert werden.

Nur so können die Container z.B. auf das Dateisystem des Hosts zugreifen.

setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

Jetzt können die Tools für den Cluster installiert werden:

dnf update
dnf install -y kubeadm kubectl kubelet nfs-utils --disableexcludes=kubernetes

- kubeadm / Zum initialisieren eines neuen Clusters, hinzufügen weitere Nodes und zum Updaten des Clusters

- kubectl / Kommandozeilen-Tool zum Verwalten des Clusters

- kubelet / Steuert die Kommunikation zwischen den Nodes und der API

- nfs-utils / Um NFS Exports als "Persistent Volume" in den Cluster einzubinden


Kubelet beim Neustart automatisch starten:

systemctl enable kubelet

Firewall konfigurieren

Controller

firewall-cmd --permanent --add-port={6443,10250,10256,2379-2380,30000-32767}/tcp
firewall-cmd --permanent --add-port=8472/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload

Worker

firewall-cmd --permanent --add-port={10250,10256,30000-32767}/tcp
firewall-cmd --permanent --add-port=8472/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload


Jetzt ist der perfekte Zeitpunkt ein Template aus dem Worker zu erstellen, welches dann für jeden weiteren Node verwendet werden kann.


Nachdem das Template erstellt wurde und wir neue Worker erzeugt haben, können wir nun auch dort eine feste IP vergeben.

Feste IP einrichten

Grafische Oberfläche:

nmtui

Oder per Konsole (Name des Interfaces kann abweichen):

vi /etc/sysconfig/network-scripts/ifcfg-ens18

Beispiel:

BOOTPROTO=none
IPADDR=192.168.2.67
PREFIX=24
GATEWAY=192.16.2.1
DNS1=192.168.2.1
DNS2=1.1.1.1

Den Hostnamen für die neuen Worker anpassen:

hostnamectl set-hostname k8s-worker-2

Hosts-Datei anpassen:

vi /etc/hosts

Die festgelegte IP mit dazugehörigem Hostnamen einfügen:

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4

192.168.2.67   k8s-worker-2.my.domain k8s-worker-2

Damit die Änderungen wirksam werden starten wir den Netzwerkadapter neu (Interfacename muss ggf. angepasst werden):

ifdown ens18 && ifup ens18

Falls ihr per SSH verbunden seid, wird die Sitzung getrennt und ihr müsst euch danach mit der neuen IP verbinden.


Wenn alle VMs aus dem selben Template erstellt wurden, muss auf den Nodes die 'machine-id' neu generiert werden damit sie nicht identisch sind.

 
VM UUID in Proxmox
rm /etc/machine-id
systemd-machine-id-setup

Als Rückmeldung sollte folgendes kommen:

Initializing machine ID from KVM UUID

Das bedeutet, dass die machine-id aus der UUID der Proxmox VM generiert wurde.

Controller

Nun endlich kann der Cluster initialisiert werden:

kubeadm init --control-plane-endpoint=<Controller-IP> --node-name k8s-ctrlr-1 --pod-network-cidr=10.244.0.0/16

--control-plane-endpoint / Die IP des Controllers (In diesem Beispiel 192.168.2.65)

--node-name / Der Hostname des Controllers (In diesem Beispiel k8s-ctrlr-1)

--pod-network-cidr / Der interne IP Adressraum für die Pods. (Sollte nicht geändert werden)


Damit ein normaler Linux User auf dem Controller Administrativen Zugriff auf den Cluster hat müssen folgende Befehle ausgeführt werden:

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

Wenn ihr als root angemeldet seid muss nur folgender Befehl ausgeführt werden:

export KUBECONFIG=/etc/kubernetes/admin.conf

Damit das nicht bei jeder neuen Sitzung wiederholt werden muss, kann die Zeile am Ende von .bash_profile eingefügt werden:

vi ~/.bash_profile

Die Join Kommandos braucht man sich nicht zu speichern, da der Token jederzeit mit kubeadm token create --print-join-command neu generiert werden kann.


Wenn man jetzt mit kubectl get nodes den Status des einen Nodes abfragt wird man feststellen, dass dieser auf "Not Ready" bleibt...

Mit kubectl get pods -n kube-system sieht man, dass die Pods "coredns" im Status "Pending" sind.

NAME                                  READY   STATUS    RESTARTS        AGE
coredns-787d4945fb-k4q67              1/1     Pending   0               5m16s
coredns-787d4945fb-klgw4              1/1     Pending   0               5m16s
etcd-k8s-ctrlr-1                      1/1     Running   0               6m31s
kube-apiserver-k8s-ctrlr-1            1/1     Running   0               6m30s
kube-controller-manager-k8s-ctrlr-1   1/1     Running   0               6m29s
kube-proxy-8gstv                      1/1     Running   0               5m13s
kube-scheduler-k8s-ctrlr-1            1/1     Running   0               6m31s

Das liegt daran, dass der "coredns" auf eine interne IP im Bereich 10.244.0.0/16 wartet.

Damit der Node eine IP zugewiesen bekommt, müssen wir als erstes den flannel-Agent installieren.

Dieser ist so eine Art interner Router der den Nodes eigene Subnetze zuweist, aus welchen sich die Pods dann ihre IP-Adressen beziehen.

Z.B.:

  • k8s-ctrlr-1 / 10.244.0.1/24
  • k8s-worker-1 / 10.244.1.1/24
  • k8s-worker-2 / 10.244.2.1/24
  • usw.
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

Anschließend kann mit kubectl get nodes überprüft werden, ob der Controller läuft:

NAME          STATUS   ROLES           AGE    VERSION
k8s-ctrlr-1   Ready    control-plane   8m7s   v1.26.0

Es kann ein, zwei Minuten dauern bis der Status READY ist.


 
Kubernetes Schema

Jetzt können die Worker zu dem Cluster hinzugefügt werden.


Dazu auf dem Controller folgenden Befehl ausführen um ein neues Join-Kommando zu bekommen:

kubeadm token create --print-join-command

Worker

Den neu generierten Join-Befehl auf jedem Worker ausführen:

 kubeadm join 192.168.2.65:6443 --token b1j6iu.s91gq99vytd2x096 --discovery-token-ca-cert-hash sha256:244dcacceb61419bc00d6dff2bea8ec694732be1d03b289308a58436da5e17d0

Auf dem Controller kann mit kubectl get nodes überprüft werden ob der Worker registriert wurde:

kubectl get nodes

Ausgabe:

k8s-ctrlr   Ready   control-plane   19m     v1.26.0
k8s-node-1  Ready   <none>          3m1s    v1.26.0

Es kann ein, zwei Minuten dauern bis der Status READY ist.


Glückwunsch zu eurem neuen Kubernetes Cluster!

Dashboard

 
Kubernetes Dashboard mit Metrics

Installation

Das Kubernetes Dashboard ist standardmäßig nicht installiert.

Zum Betrieb ist es nicht notwendig, aber es eignet sich hervorragend zur Fehlersuche oder um YAML-Skripte auszuführen bzw. zu bearbeiten.


Mit folgendem Befehl kann das Dashboard installiert werden:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml


Nach ein paar Minuten sollten die Pods laufen und das Dashboard über https://localhost:<NodePort> erreichbar sein.

Damit man auch über das LAN Zugriff hat, muss der Dashboard Service angepasst werden.

Den Dashboard Service editieren:

kubectl edit service kubernetes-dashboard -n kubernetes-dashboard

-n kubernetes-dashboard / Das Dashboard erzeugt standardmäßig einen neuen Namespace. Um auf den Service zugreifen zu können, muss dieser im Befehl mit angegeben werden.


Den Parameter type: ClusterIP in NodePort ändern:

apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"k8s-app":"kubernetes-dashboard"},"name":"kubernetes-dashboard","namespace":"kubernetes-dashboard"},"spec":{"ports":[{"port":443,"targetPort":8443}],"selector":{"k8s-app":"kubernetes-dashboard"}}}
  creationTimestamp: "2022-12-14T23:09:44Z"
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
  resourceVersion: "23315"
  uid: 5011d061-dce0-4e4e-a245-3de3eac0a77c
spec:
  clusterIP: 10.109.124.10
  clusterIPs:
  - 10.109.124.10
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - nodePort: 31293
    port: 443
    protocol: TCP
    targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard
  sessionAffinity: None
  type: ClusterIP # Ändern in NodePort
status:
  loadBalancer: {}

Mit NodePort wird der Service auf einen Host-Port zwischen 30000 und 32767 weitergeleitet.


Mit folgendem Befehl kann überprüft werden auf welchem Port der Service läuft:

kubectl get service -n kubernetes-dashboard
NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE
dashboard-metrics-scraper   ClusterIP   10.101.128.128   <none>        8000/TCP        10m
kubernetes-dashboard        NodePort    10.96.102.224    <none>        443:31447/TCP   10m

In diesem Fall ist der Port 31447...

Das Dashboard sollte jetzt über https://<Node-IP>:31447 erreichbar sein.

Als Node-IP kann jede IP von einem der Hosts genommen werden.


Benutzer Anmeldung

Service Account

Nur für Testzwecke, da ein Service Account mit Admin-Rechten ein Sicherheitsrisiko darstellt.[3]

Auf dem Controller in einem beliebigen Ordner folgende Dateien anlegen:

admin-user.yaml / Manifest um einen Service Account anzulegen.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard

cluster-admin.yaml / Manifest um dem Service Account Adminrechte zuzuweisen.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard

Anschließend die Konfiguration übernehmen:

kubectl apply -f admin-user.yaml
kubectl apply -f cluster-admin.yaml

Jetzt haben wir einen Service Account mit dem Namen 'admin-user' und Administrator Berechtigung angelegt.


Um einen Token für die Anmeldung im Dashboard zu generieren reicht folgender Befehl:

kubectl -n kubernetes-dashboard create token admin-user

Keycloak (OIDC)

Folgt...


Metrics Server installieren

Um die Auslastung der einzelnen Pods, bzw der Nodes im Dashboard zu sehen, muss der Metrics-Server installiert werden.

Darüber hinaus kann der Metrics-Server verwendet werden um die Pods gleichmäßig auf die Nodes aufzuteilen (Horizontal Autoscaling) oder

um die zugeteilten Ressourcen (CPU, RAM) automatisch an der Verbrauch anzupassen (Vertical Autoscaling).


Zuerst das Installations Manifest für den Metrics-Server herunterladen:

curl -LO https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

- --kubelet-insecure-tls in deployment.spec.template.spec.containers.args[4]

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        - --kubelet-insecure-tls # Hinzufügen
kubectl apply -f components.yaml

Metrics-Server testen:

kubectl top nodes

Ausgabe:

NAME           CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
k8s-ctrlr-1    105m         5%     1045Mi          28%
k8s-worker-1   27m          1%     471Mi           12%
k8s-worker-2   42m          2%     481Mi           13%

Weiterführende Links

Quellen

Einzelnachweise

Kommentare

Loading comments...