Kubernetes auf Proxmox mit Ansible und Terraform (Teil 2)

Table of Contents

Dieser Post wurde aus dem Original (https://datastrophic.io/kubernetes-homelab-with-proxmox-kubeadm-calico-openebs-and-metallb/)auf Deutsch übersetzt und leicht angepasst.

Einleitung

Nachdem wir im ersten Teil mit Terraform die VMs für unseren K8s Cluster deployed haben, werden wir nun mit Ansible die folgenden K8s Komponenten in den worker Nodes und dem Controller installieren:

  • kubeadm für das Kubernetes Cluster bootstrapping
  • containerd als container runtime
  • Calico für das Pod networking
  • MetalLB  verwenden wir als LoadBalancer service type
  • OpenEBS für das Volume provisioning
  • Istio für ingress und traffic management

Die Kubernetes-Distribution der Wahl ist in diesem Fall Vanilla Open Source Kubernetes, das mit dem kubeadm-Tool für Cluster-Bootstrapping geliefert wird. Vanilla Kubernetes hat einen grösseren Fussabdruck im Vergleich zu k3s und eignet sich deshalb nicht gut für Umgebungen mit eingeschränkten Ressourcen. Es ist jedoch herstellerunabhängig und vollständig Open-Source, hat keine Modifikationen und sowohl die API-Änderungen als auch die Tools haben die gleiche Release-Kadenz, sodass das Risiko von Inkompatibilitäten oder Verzögerungen geringer ist.

Voraussetzungen

  • Cluster VMs sollten bereits provisioniert und erreichbar sein via SSH
  • empfohlen ist Ubuntu 20.04 als Cluster OS zu verwenden
  • der Benutzer (in unserem Fall ubuntu) sollte Superuser Berechtigungen haben auf den Cluster Nodes
  • Ansible ist lokal bei dir installiert

Vorbereitungen

Lade dir die Terraform und Ansible Files von diesem Git Repo herunter.

Die Variablen in  ansible/group_vars/all sollten noch überprüft werden:

  • pod_subnet
  • service_subnet
  • dns
  • metallb_address_range

Weiter musst du die IPs deiner VMs in dieser Datei nachtragen: ansible/inventory.yaml

Vor der Bereitstellung von Kubernetes selbst müssen die Cluster-Knoten zusätzlich konfiguriert und Software installiert werden:

  • Knoten müssen Swap deaktiviert, iptables aktiviert haben und Weiterleitung und überbrückten Datenverkehr gemäss Bootstrapping-Clustern mit kubeadm zulassen.
  • Auf den Knoten muss die Containerlaufzeit installiert sein. Die gängigste Containerlaufzeit, die in verschiedenen Cloud- und Anbieter-Kubernetes-Distributionen verwendet wird, ist containerd, also werden wir sie verwenden.
  • Weitere Informationen darüber, warum wir Docker nicht verwenden werden, findest du in Don’t Panic: Kubernetes and Docker.
  • Auf Knoten müssen die folgenden Pakete installiert sein: kubelet, kubectl und kubeadm. Diese können über den Standard-Paketmanager wie apt installiert werden.

Es gibt ein dediziertes Playbook zum Bootstrapping der Knoten mit allen erforderlichen Konfigurationen und Abhängigkeiten, die unter ansible/bootstrap.yaml verfügbar sind. Überprüfe die Standardeinstellungen und führe das Playbook wie folgt aus:

#~$ ansible-playbook -i ansible/inventory.yaml ansible/bootstrap.yaml -u ubuntu
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

PLAY [Bootstrapping hosts] ************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [control-plane-0.k8s.cluster]
ok: [worker-1.k8s.cluster]
ok: [worker-0.k8s.cluster]
ok: [worker-2.k8s.cluster]

TASK [commons : install common packages] **********************************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [worker-0.k8s.cluster]

TASK [commons : disable swap] *********************************************************************************************************
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [control-plane-0.k8s.cluster]

TASK [commons : disable swap in fstab] ************************************************************************************************
ok: [worker-2.k8s.cluster]
ok: [control-plane-0.k8s.cluster]
ok: [worker-0.k8s.cluster]
ok: [worker-1.k8s.cluster]

TASK [commons : enable br_netfilter] **************************************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]

TASK [commons : ensure iptables enabled] **********************************************************************************************
changed: [worker-1.k8s.cluster]
changed: [control-plane-0.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]

TASK [commons : enable port forward] **************************************************************************************************
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [control-plane-0.k8s.cluster]

TASK [container-runtime : create config and data dirs] ********************************************************************************
changed: [worker-2.k8s.cluster] => (item=/etc/containerd)
changed: [control-plane-0.k8s.cluster] => (item=/etc/containerd)
changed: [worker-1.k8s.cluster] => (item=/etc/containerd)
changed: [worker-0.k8s.cluster] => (item=/etc/containerd)
changed: [control-plane-0.k8s.cluster] => (item=/tmp/containerd)
changed: [worker-2.k8s.cluster] => (item=/tmp/containerd)
changed: [worker-1.k8s.cluster] => (item=/tmp/containerd)
changed: [worker-0.k8s.cluster] => (item=/tmp/containerd)

TASK [container-runtime : download and install runc] **********************************************************************************
changed: [worker-1.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [control-plane-0.k8s.cluster]
changed: [worker-0.k8s.cluster]

TASK [container-runtime : download and install crictl] ********************************************************************************
changed: [worker-1.k8s.cluster]
changed: [control-plane-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-0.k8s.cluster]

TASK [container-runtime : download containerd] ****************************************************************************************
changed: [worker-0.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [control-plane-0.k8s.cluster]
changed: [worker-2.k8s.cluster]

TASK [container-runtime : copy containerd binaries] ***********************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]

TASK [container-runtime : copy containerd config] *************************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]

TASK [container-runtime : create containerd systemd service] **************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [worker-2.k8s.cluster]

TASK [container-runtime : reload systemd] *********************************************************************************************
ok: [worker-1.k8s.cluster]
ok: [worker-0.k8s.cluster]
ok: [worker-2.k8s.cluster]
ok: [control-plane-0.k8s.cluster]

TASK [container-runtime : enable containerd systemd service] **************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]

TASK [container-runtime : start containerd service] ***********************************************************************************
changed: [worker-0.k8s.cluster]
changed: [control-plane-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]

TASK [kubernetes-packages : adding Kubernetes repository apt key] *********************************************************************
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [control-plane-0.k8s.cluster]

TASK [kubernetes-packages : adding Kubernetes deb repository] *************************************************************************
changed: [worker-1.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [control-plane-0.k8s.cluster]

TASK [kubernetes-packages : installing Kubernetes packages] ***************************************************************************
changed: [control-plane-0.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-0.k8s.cluster]
changed: [worker-1.k8s.cluster]

TASK [kubernetes-packages : hold kubeadm, kubectl, kubelet] ***************************************************************************
changed: [worker-0.k8s.cluster] => (item=kubeadm)
changed: [worker-1.k8s.cluster] => (item=kubeadm)
changed: [control-plane-0.k8s.cluster] => (item=kubeadm)
changed: [worker-2.k8s.cluster] => (item=kubeadm)
changed: [worker-0.k8s.cluster] => (item=kubectl)
changed: [worker-1.k8s.cluster] => (item=kubectl)
changed: [worker-2.k8s.cluster] => (item=kubectl)
changed: [control-plane-0.k8s.cluster] => (item=kubectl)
changed: [worker-0.k8s.cluster] => (item=kubelet)
changed: [worker-1.k8s.cluster] => (item=kubelet)
changed: [worker-2.k8s.cluster] => (item=kubelet)
changed: [control-plane-0.k8s.cluster] => (item=kubelet)

PLAY RECAP ****************************************************************************************************************************
control-plane-0.k8s.cluster : ok=21  changed=18  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 
worker-0.k8s.cluster    : ok=21  changed=18  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 
worker-1.k8s.cluster    : ok=21  changed=18  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 
worker-2.k8s.cluster    : ok=21  changed=18  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 

Sobald alle Voraussetzungen erfüllt sind, können wir kubeadm für das Cluster-Bootstrapping verwenden. Die Installation des Kubernetes-Clusters besteht aus zwei Hauptschritten: Bootstrapping der Steuerungsebene und Joining zu den Worker-Knoten. Wir können dies tun, indem wir das Playbook ansible/kubernetes-install.yaml ausführen:

#~$ ansible-playbook -i ansible/inventory.yaml ansible/kubernetes-install.yaml -u ubuntu
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

PLAY [Bootstrap Kubernetes Control Plane] *********************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [control-plane-0.k8s.cluster]

TASK [kubeadm-init : copy kubeadm init config] ****************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [kubeadm-init : running kubeadm init] ********************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [calico : copy Calico manifests] *************************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [calico : install Calico] ********************************************************************************************************
changed: [control-plane-0.k8s.cluster]

PLAY [Retrieve join token and certificate hash] ***************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [control-plane-0.k8s.cluster]

TASK [kubeadm-join-config : create local dir for token and cert hash] *****************************************************************
changed: [control-plane-0.k8s.cluster -> localhost]

TASK [kubeadm-join-config : kubeadm token generate] ***********************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [kubeadm-join-config : generate cert hash] ***************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [kubeadm-join-config : persist token locally] ************************************************************************************
changed: [control-plane-0.k8s.cluster -> localhost]

TASK [kubeadm-join-config : persist cert hash locally] ********************************************************************************
changed: [control-plane-0.k8s.cluster -> localhost]

PLAY [Join Kubernetes worker nodes] ***************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [worker-1.k8s.cluster]
ok: [worker-2.k8s.cluster]
ok: [worker-0.k8s.cluster]

TASK [kubeadm-join : copy kubeadm join config] ****************************************************************************************
changed: [worker-0.k8s.cluster]
changed: [worker-1.k8s.cluster]
changed: [worker-2.k8s.cluster]

TASK [kubeadm-join : running kubeadm join] ********************************************************************************************
changed: [worker-1.k8s.cluster]
changed: [worker-2.k8s.cluster]
changed: [worker-0.k8s.cluster]

PLAY [Copy kubeconfig from remote] ****************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [control-plane-0.k8s.cluster]

TASK [fetching] ***********************************************************************************************************************
changed: [control-plane-0.k8s.cluster]

PLAY RECAP ****************************************************************************************************************************
control-plane-0.k8s.cluster : ok=13  changed=10  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 
worker-0.k8s.cluster    : ok=3  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 
worker-1.k8s.cluster    : ok=3  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 
worker-2.k8s.cluster    : ok=3  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0 

Das Playbook führt kubeadm init auf den Knoten der Steuerungsebene aus und verwendet eine deklarative Clusterkonfiguration, die die bevorzugte Methode zum Konfigurieren von kubeadm ist. Die Konfigurationsvorlage ist unter ansible/roles/kubeadm-init/templates/kubeadm.yaml verfügbar. Sobald der Bootstrap der Steuerungsebene abgeschlossen ist, ruft Ansible ein Token und einen Zertifikat-Hash ab, die für die Authentifizierung der Worker-Knoten beim API-Server erforderlich sind, und führt kubeadm join auf den Worker-Knoten aus.

Das Playbook stellt Calico für die Clusternetzwerke bereit. Die Wahl von Calico ist motiviert, weil es die am weitesten verbreitete Netzwerk- und Sicherheitslösung für Kubernetes ist (zum Zeitpunkt des Schreibens).

Sobald die Playbook-Ausführung abgeschlossen ist, wird eine kubeconfig-Datei admin.conf in das aktuelle Verzeichnis geholt. Um zu überprüfen, ob der Cluster gebootet und verbunden ist, führe Folgendes aus:

#~$  kubectl --kubeconfig=admin.conf get nodes
NAME                          STATUS   ROLES                  AGE     VERSION
control-plane-0.k8s.cluster   Ready    control-plane,master   3m30s   v1.21.6
worker-0                      Ready    <none>                 2m54s   v1.21.6
worker-1                      Ready    <none>                 2m55s   v1.21.6
worker-2                      Ready    <none>                 2m55s   v1.21.6

es ist empfohlen die Location der admin.conf Datei als env. Variabel zu exportieren, damit man die Datei nicht jedesmal mit --kubeconfig mitgeben muss:

export KUBECONFIG=$(pwd)/admin.conf

Notwendige Software

Wenn der Kubernetes-Cluster eingerichtet und ausgeführt wird, können wir jetzt Container darauf bereitstellen und ausführen. Ein paar wesentliche Teile des voll funktionsfähigen Clusters fehlen jedoch noch: die dynamische Volume-Bereitstellung und die Unterstützung für Dienste welche einen LoadBalancer benötigen.

Volume Provisioning mit OpenEBS

Die Volume Provisioner-Lösung ist sowohl in Situationen nützlich, in denen Anwendungen von Drittanbietern eine Standard-StorageClass zum Bereitstellen von PersistentVolumes benötigen, als auch in Situationen, in denen eine Datenreplikation für Hochverfügbarkeitsgarantien erforderlich ist.

Die Verwendung von OpenEBS für das Home-Lab-Setup erscheint vernünftig, da es lokale Engines für die Bereitstellung von PersistentVolumes bereitstellt, die direkt von den lokalen Festplatten auf Hosts unterstützt werden. Wenn eine Datenreplikation erforderlich ist, verfügt OpenEBS über mehrere Replicated Engines, deren Leistung jedoch unterschiedlich ist.

Um eine minimale Installation mit hostlokalen PersistentVolumes bereitzustellen, bietet OpenEBS eine „Lite“-Version:

#~$ kubectl apply -f https://openebs.github.io/charts/openebs-operator-lite.yaml
namespace/openebs created
serviceaccount/openebs-maya-operator created
clusterrole.rbac.authorization.k8s.io/openebs-maya-operator created
clusterrolebinding.rbac.authorization.k8s.io/openebs-maya-operator created
customresourcedefinition.apiextensions.k8s.io/blockdevices.openebs.io created
customresourcedefinition.apiextensions.k8s.io/blockdeviceclaims.openebs.io created
configmap/openebs-ndm-config created
daemonset.apps/openebs-ndm created
deployment.apps/openebs-ndm-operator created
deployment.apps/openebs-ndm-cluster-exporter created
service/openebs-ndm-cluster-exporter-service created
daemonset.apps/openebs-ndm-node-exporter created
service/openebs-ndm-node-exporter-service created
deployment.apps/openebs-localpv-provisioner created

Sobald der Operator installiert ist, erstellen wir eine StorageClass und setzen diese als Standard. Das ermöglicht die Verwendung von OpenEBS für die Volume-Bereitstellung, ohne dass jedes Mal die StorageClass für PersistentVolumes angegeben werden muss:

#~$ kubectl apply -f https://openebs.github.io/charts/openebs-operator-lite.yaml
namespace/openebs created
serviceaccount/openebs-maya-operator created
clusterrole.rbac.authorization.k8s.io/openebs-maya-operator created
clusterrolebinding.rbac.authorization.k8s.io/openebs-maya-operator created
customresourcedefinition.apiextensions.k8s.io/blockdevices.openebs.io created
customresourcedefinition.apiextensions.k8s.io/blockdeviceclaims.openebs.io created
configmap/openebs-ndm-config created
daemonset.apps/openebs-ndm created
deployment.apps/openebs-ndm-operator created
deployment.apps/openebs-ndm-cluster-exporter created
service/openebs-ndm-cluster-exporter-service created
daemonset.apps/openebs-ndm-node-exporter created
service/openebs-ndm-node-exporter-service created
deployment.apps/openebs-localpv-provisioner created
Dave@ChuckNorris[08:37:02]~/github/homelab$ kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: openebs-hostpath
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
    openebs.io/cas-type: local
    cas.openebs.io/config: |
      - name: StorageType
        value: "hostpath"
      - name: BasePath
        value: "/var/openebs/local/"
provisioner: openebs.io/local
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
EOF
storageclass.storage.k8s.io/openebs-hostpath created

LoadBalancer mit MetalLB

Eine letzte fehlende Funktionalität im bereitgestellten Cluster ist die Möglichkeit, Dienste vom Typ LoadBalancer für das lokale Netzwerk verfügbar zu machen. Bei der Ausführung in der Cloud wird diese Funktionalität von den Kubernetes-Integrationen mit Cloud-Anbietern bereitgestellt, und entsprechende netzwerkseitige Load Balancer werden über den Infrastrukturanbieter bereitgestellt. Bei der Ausführung auf Bare Metal ist eine solche Integration in Kubernetes standardmässig nicht verfügbar.

MetalLB ist die am weitesten verbreitete Lösung für den NetzwerkLB.

Die MetalLB-Installation wird über eine ConfigMap konfiguriert und kann mehrere Adresspools enthalten:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - "{{ lab.metallb_address_range }}" 

Die obere Vorlage ist Teil des Ansible-Playbooks ansible/metallb.yaml, das die MetalLB installiert und konfiguriert, um Adressen aus der in group_vars angegebenen Variable lab.metallb_address_range zuzuweisen. Der Adressbereich muss für die Zielumgebung relevant sein (Teil des reservierten statischen Adressbereichs, der im Abschnitt zum Bereitstellungslayout beschrieben ist, damit die Adressen zugewiesen werden können. Um MetalLB zu installieren, führe Folgendes aus:

#~$ ansible-playbook -i ansible/inventory.yaml ansible/metallb.yaml -u ubuntu
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

PLAY [Install MetalLB] ******************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************
ok: [control-plane-0.k8s.cluster]

TASK [metallb : copy MetalLB manifests] *************************************************************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [metallb : copy MetalLB config] ****************************************************************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [metallb : create namespace] *******************************************************************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [metallb : install MetalLB config] *************************************************************************************************************************************
changed: [control-plane-0.k8s.cluster]

TASK [metallb : install MetalLB] ********************************************************************************************************************************************
changed: [control-plane-0.k8s.cluster]

PLAY RECAP ******************************************************************************************************************************************************************
control-plane-0.k8s.cluster : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

Installation überprüfen

Um die Installation zu überprüfen, erstellen wir eine MinIO-Bereitstellung mit einem PersistentVolume für die Speicherung und stellen die Bereitstellung dem lokalen Netzwerk über den LoadBalancer-Diensttyp zur Verfügung. Das Beispiel basiert auf den Kubernetes-Speicherbeispielen.

  1. Erstellen ein PersistentVolumeClaim:
    #~$ kubectl apply -f - <<EOF
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: minio-pv-claim
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
    EOF
    persistentvolumeclaim/minio-pv-claim created
  2. Erstelle ein Deployment:
    #~$ kubectl apply -f - <<EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: minio-deployment
    spec:
      selector:
        matchLabels:
          app: minio
      strategy:
        type: Recreate
      template:
        metadata:
          labels:
            app: minio
        spec:
          volumes:
          - name: storage
    EOF       mountPath: "/storage"Ytim
    deployment.apps/minio-deployment created
  3. Überprüfe ob der PersistentVolumeClaim an das PersistentVolume gebunden ist und dies erstellt wurde:
    #~$ kubectl get pvc
    NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       AGE
    minio-pv-claim   Bound    pvc-8cb55047-43b6-4ab8-a8cb-20dc1ecf1979   1Gi        RWO            openebs-hostpath   3m4s
    #~$ kubectl get pv
    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS       REASON   AGE
    pvc-8cb55047-43b6-4ab8-a8cb-20dc1ecf1979   1Gi        RWO            Delete           Bound    default/minio-pv-claim   openebs-hostpath            28s
  4. Überprüfe, ob das Deployment erfolgreich war:
    #~$ kubectl describe deployment minio-deployment
    Name:               minio-deployment
    Namespace:          default
    CreationTimestamp:  Sat, 02 Jul 2022 08:54:36 +0200
    Labels:             <none>
    Annotations:        deployment.kubernetes.io/revision: 1
    Selector:           app=minio
    Replicas:           1 desired | 1 updated | 1 total | 1 available | 0 unavailable
    StrategyType:       Recreate
    MinReadySeconds:    0
    Pod Template:
      Labels:  app=minio
      Containers:
       minio:
        Image:       minio/minio:latest
        Ports:       9000/TCP, 9001/TCP
        Host Ports:  9000/TCP, 9001/TCP
        Args:
          server
          /storage
          --console-address
          :9001
        Environment:
          MINIO_ACCESS_KEY:  minio
          MINIO_SECRET_KEY:  minio123
        Mounts:
          /storage from storage (rw)
      Volumes:
       storage:
        Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
        ClaimName:  minio-pv-claim
        ReadOnly:   false
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    NewReplicaSetAvailable
    OldReplicaSets:  <none>
    NewReplicaSet:   minio-deployment-59659f9655 (1/1 replicas created)
    Events:
      Type    Reason             Age   From                   Message
      ----    ------             ----  ----                   -------
      Normal  ScalingReplicaSet  56s   deployment-controller  Scaled up replica set minio-deployment-59659f9655 to 1
  5. Stelle das Deployment via Service vom Typ LoadBalancer zur Verfügung:
    #~$ kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Service
    metadata:
      name: minio
    spec:
      ports:
      - name: http
        port: 9000
        protocol: TCP
        targetPort: 9000
      - name: http-ui
        port: 9001
        protocol: TCP
        targetPort: 9001
      selector:
        app: minio
      type: LoadBalancer
    EOF
  6. Überprüfe, ob der Service erstellt wurde und eine Externe IP erhalten hat:
    #~$ kubectl get service minio
    NAME    TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                         AGE
    minio   LoadBalancer   10.97.129.247   192.168.30.150   9000:31713/TCP,9001:32327/TCP   10s

Die externe IP Adresse sollte in dem Anfangs definierten lokalen Subnetz Range sein. Du kannst nun via Browser die MinIO Konsole öffnen (in meinem Fall war die URL: http://192.168.30.150:9001/login) und dich mit Benutzer minio und Passwort minio123 einloggen. Erstelle anschliessend ein Test Bucket, damit wir später auch überprüfen können ob die Daten persistent im Volume gespeichert werden:

Nun können wir direkt überprüfen, ob das Test-Bucket (testsetsetsgsdgfsdgagbadfg) im PersistentVolume gespeichert wurde:

#~$ kubectl exec deploy/minio-deployment -- bash -c "ls -la /storage"
drwxrwxrwx 4 root root 4096 Jul  2 06:59 .
drwxr-xr-x 1 root root 4096 Jul  2 06:54 ..
drwxr-xr-x 8 root root 4096 Jul  2 06:54 .minio.sys
drwxr-xr-x 5 root root 4096 Jul  2 15:06 testsetsetsgsdgfsdgagbadfg

Damit können wir sicher sein, dass die Daten im Testbucket im PersistentVolume landen.

Als letzter Schritt werden wir das Kubernetes Dashboard deployen, damit kann man den Gesamten K8s Cluster als WebUI bedienen.

Kubernetes Dashboard

Das Kubernetes-Dashboard ist die unverzichtbare Mindestlösung für die Beobachtbarkeit. Das Kubernetes-Dashboard verfügt über eine entsprechende Installationsanleitung, und hier konzentrieren wir uns auf die entsprechenden RBAC-Berechtigungen für den verwendeten ServiceAccount.

Zuerst installieren wir das Kubernetes Dashboard:

#~$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created

Während das Kubernetes-Dashboard das Erstellen neuer Ressourcen und das Bearbeiten vorhandener Ressourcen ermöglicht, ist die Verwendung im schreibgeschützten Modus sicherer und würde keine Sicherheitsrisiken mit sich bringen, falls jemand Zugriff auf die Benutzeroberfläche erhält. Der Sichtbarkeitsbereich des Dashboards wird über RBAC der darauf zugreifenden Benutzer gesteuert.

Der konservativste Ansatz wäre, eine Aggregated ClusterRole basierend auf der Standard-Viewer-Rolle zu verwenden und sie bei Bedarf mit zusätzlichen Regeln zu erweitern:

kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dashboard-viewer
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.authorization.k8s.io/aggregate-to-view: "true"
  - matchLabels:
      rbac.homelab.k8s.io/aggregate-to-view: "true"
rules: []

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dashboard-extended-view
  labels:
    rbac.homelab.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - extensions
  - apps
  - batch
  - storage
  - networking
  verbs:
  - get
  - list
  - watch
EOF

Die ClusterRole bietet erweiterte Anzeigeberechtigungen, erlaubt aber immer noch nicht das Anzeigen von Secrets und Ressourcen aus der API-Gruppe rbac.authorization.k8s.io. Wir erstellen uns nun ein dedizierter ServiceAccount und binden den Account an die erstellte ClusterRole:

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-viewer
  namespace: kubernetes-dashboard

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

Auf das Dashboard kann entweder über den kubectl-Proxy oder über die Portweiterleitung zugegriffen werden:

kubectl -n kubernetes-dashboard port-forward service/kubernetes-dashboard 8443:443

Das Dashboard kann nun unter dieser URL erreicht werden: https://localhost:8443/ :

Um das ServiceAccount-Token für den Zugriff auf das Dashboard zu ermitteln, führen wir Folgendes aus:

kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/dashboard-viewer -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"