Image

De laaste tijd ben ik veel bezig geweest met Kubernetes voor productie applicaties, waardoor ik veel heb geleerd van het platform. Ik wil dit een stap verder oppakken en zelf een bare metal kubernetes cluster opzetten, om zo meer over het platform te leren en om mijn self hosted applicaties te hosten. In deze blog zal ik daarom ingaan op het opzetten van een Kubernetes cluster en zullen de mogelijkheden hiervoor uitgelegd worden.

Wat heb je nodig:

  • 2+ Raspberry Pi's 4B
  • 2+ Ethernet kabels
  • 2+ USB-C kabels inclusief adapter
  • 2+ micro SD 32 GB kaarten
  • Micro SD kaart lezer

Met wat extra onderdelen kunnen de hoeveelheid kabels en de levensduur van de Raspberry Pi’s verbeterd worden:

  • PoE switch
  • 2+ Raspberry Pi PoE hats
  • 2+ Raspberry Pi Heatsinks

Ik gebruik voor de benamingen van de master nodes 'master-xx' en voor de worker nodes 'worker-xx'. Xx zal vervangen worden met cijfers (bijvoorbeeld master-01, worker-01, worker-02). Bij gebruik van meerdere Raspberry Pi's is de ratio van masters en workers 1:3. Daarnaast gebruik ik 192.168.1.xxx voor het IP-adres van de Raspberry Pi's. Xxx moet vervangen worden met het de laatste cijfers van het IP-adres van de Raspberry Pi en kan gevonden worden in de router.

Setup

Als eerste moet er een operating systeem geflashed worden naar de micro SD kaarten. Ik heb gekozen om Ubuntu te gebruiken, omdat het vanaf versie 20.04 Raspberry Pi 4 support en het een 64-bit versie voor ARM beschikbaar heeft. Daarnaast worden er weinig 32-bit ARM docker images gebouwd. Door middel van Belena Etcher kan de micro SD kaart geflashed worden. Ubuntu 20.04 64-bit image voor ARM is via onderstaande link te downloaden.

http://cdimage.ubuntu.com/releases/20.04/release/ubuntu-20.04-preinstalled-server-arm64+raspi.img.xz

Wanneer het operating systeem op de micro SD kaarten is geflashed, kan deze in de Raspberry Pi geplaatst en opgestart worden. Wanneer de Raspberry Pi is opgestart, krijgt die een IP-adres toegewezen. Door middel van SSH kan er worden ingelogd op de Raspberry Pi.

ssh ubuntu@192.168.1.xxx

Bij het opstarten wordt er automatisch gezocht naar package updates en worden deze automatisch geupdateted. Om dit uit te zetten kan het volgende commando uitgevoerd worden.

sudo apt-get remove unattended-upgrades

Om handmatig de packages te updaten en de utility packages te installeren, die bij de volgende stappen gebruikt zullen worden, kunnen de volgende commando's uitgevoerd worden.

sudo apt-get update sudo apt-get upgrade -y sudo apt-get dist-upgrade -y sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common

Na het updaten en installeren van de packages kunnen oude packages die niet meer nodig zijn, inclusief de cache, verwijderd worden.

sudo apt-get --purge autoremove -y sudo apt-get autoclean sudo apt-get clean

Om ervoor te zorgen dat de Raspberry Pi altijd hetzelfde IP-adres krijgt, moet DHCP uitgezet worden en een statisch IP-adres toegewezen worden. Dit kan door het /etc/netplan/50-cloud-init.yaml bestand aan te passen.

sudo nano /etc/netplan/50-cloud-init.yaml

van:

network:
    ethernets:
        eth0:
            dhcp4: true
            optional: true
    version: 2
                                

naar:

network:
    ethernets:
        eth0:
            dhcp4: false
            optional: true
            addresses: [192.168.1.xxx/24]
            gateway4: 192.168.1.1
            nameservers:
                addresses: [192.168.1.1]
    version: 2
                                

Daarnaast kan de hostname aangepast worden; zo zijn de verschillende Raspberry Pi's beter uit elkaar te houden.

sudo hostnamectl set-hostname master-xx

Om ervoor te zorgen dat deze hostname onthouden wordt, moet het volgende commando uitgevoerd worden om /etc/cloud/cloud.cfg aan te passen.

sudo nano /etc/cloud/cloud.cfg

van:

preserve_hostname false
                                

naar:

preserve_hostname true
                                

Verder wordt er een nieuwe gebruiker aan gemaakt en wordt deze gebruiker toegevoegd aan de standaard groepen.

sudo adduser manager sudo usermod -a -G adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev,lxd manager

Nu kan de Raspberry Pi opnieuw opgestart worden.

sudo reboot

Om gemakkelijk zonder wachtwoord in te loggen in de Raspberry Pi, kan er een SSH key gemaakt worden.

cd ~/.ssh ssh-keygen ssh-copy-id -i ~/.ssh/id_master-xx manager@192.168.1.xxx

Deze SSH key kan ingesteld worden op je eigen computer, om ervoor te zorgen dat je alleen via de hostname kan inloggen.

sudo nano config

Hierna kan het volgende toegevoegd worden:

Host master-xx
HostName 192.168.1.xxx
User manager
IdentityFile ~/.ssh/id_master-xx
                                

Nu kan er met onderstaand commando ingelogd worden op de Raspberry Pi.

ssh master-xx

Om ervoor te zorgen dat er alleen maar met de SSH key ingelogd kan worden, moet /etc/ssh/sshd_config aangepast worden.

sudo nano /etc/ssh/sshd_config

van:

#PermitRootLogin prohibit-password
PasswordAuthentication yes
#PubkeyAuthentication yes
                                

naar:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
                                

Om de configuratie te testen en SSH opnieuw op te starten, kunnen de volgende commando's uitgevoerd worden.

sudo /usr/sbin/sshd -t sudo systemctl restart sshd.service

Nu SSH correct is ingesteld kan de standaard user (ubuntu) worden verwijderd.

sudo deluser --remove-home ubuntu

Om ervoor te zorgen dat Kubernetes geisoleerd kan draaien en de resources kan monitoren, moet dit in /boot/firmware/cmdline.txt ingesteld worden.

sudo sed -i '$ s/$/ cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory/' /boot/firmware/cmdline.txt

Om ervoor te zorgen dat alle services met de juiste configuratie gestart zijn, moet de Raspberry Pi opnieuw opgestart worden.

sudo reboot

Nu kan er opnieuw ingelogd worden met het volgende commando.

ssh master-xx


Installatie

De volgende stap is het installeren van de verschillende programma's die benodigd zijn om Kubernetes te draaien. Hierin zijn veel mogelijkheden in container runtime interface (CRI) en container network interface (CNI). Als CRI heb ik gekozen voor Containerd, omdat deze een kleinere cpu en memory footprint heeft. Als CNI heb ik gekozen voor Calico, omdat deze gebruik maakt van eBPF, wat sneller is dan de standaard iptables wat andere gebruiken. Ook kan Calico als vervanging dienen voor kube-proxy. Als laatst maak ik gebruik van MetalLB als load balancer, MetalLB zal IP-adressen toewijzen aan de services in de cluster.

Om Containerd en Kubernetes te installeren moeten eerst de key en repository toegevoegd worden.

curl -s https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb https://download.docker.com/linux/ubuntu focal stable" curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - sudo add-apt-repository "deb https://apt.kubernetes.io/ kubernetes-xenial main"

Nu kan Containerd geinstalleerd worden samen met de 3 benodigde Kubernetes packages (kubelet, kubeadm en kubectl).

sudo apt-get update sudo apt-get install -y containerd.io sudo apt-get install -y kubelet kubeadm kubectl

Omdat het update proces van de Kubernetes lastiger is dan de standaard updates, moet dit uitgezet worden.

sudo apt-mark hold kubelet kubeadm kubectl

Om Containerd te gebruiken zijn de volgende instellingen vereist; overlay en br_netfilter modules en een aantal sysctl parameters moeten geenabled worden.

echo 'overlay' | sudo tee -a /etc/modules-load.d/containerd.conf echo 'br_netfilter' | sudo tee -a /etc/modules-load.d/containerd.conf

sudo modprobe overlay sudo modprobe br_netfilter

echo 'net.bridge.bridge-nf-call-iptables = 1' | sudo tee -a /etc/sysctl.d/99-kubernetes-cri.conf echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-kubernetes-cri.conf echo 'net.bridge.bridge-nf-call-ip6tables = 1' | sudo tee -a /etc/sysctl.d/99-kubernetes-cri.conf

sudo sysctl --system

Om de configuratie aan te passen, wordt deze als eerst weggeschreven naar /etc/containerd/config.toml.

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

Daarnaast moet systemd_cgroup geenabled worden. Dit moet aangepast worden in /etc/containerd/config.toml.

sudo nano /etc/containerd/config.toml

van:

systemd_cgroup = false
                                

naar:

systemd_cgroup = true
                                

Nu kan Containerd gerestart worden.

sudo systemctl restart containerd

Om ervoor te zorgen dat Kubelet de Containerd container runtime gebruikt moet dit geconfigureerd worden. Dit kan geconfigureerd worden in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf.

sudo nano /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

Onderstaande kan toegevoegd worden onder de andere Environment regels:

Environment="KUBELET_EXTRA_ARGS=--cgroup-driver=systemd --container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
                                

Nu kan Kubelet gerestart worden.

sudo systemctl daemon-reload sudo systemctl restart kubelet.service

De endpoint van crictl moet gezet worden zodat deze de Containerd socket gebruikt.

sudo crictl config runtime-endpoint unix:///run/containerd/containerd.sock

Om de standaard Kubernetes services te draaien, kunnen de container images binnengehaald worden en kan de cluster aangemaakt worden.

sudo kubeadm config images pull sudo kubeadm init --cri-socket /run/containerd/containerd.sock

Nu kan de Kubernetes config naar de .kube map in de home folder gezet worden.

mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

De andere Raspberry Pi's kunnen aansluiten bij het Kubernetes cluster door het volgende commado. Zoals eerder is beschreven, moet de xxx vervangen worden met de laatste cijfers van het IP-adres van de Raspberry Pi. Daarnaast is de token en certificate hash een voorbeeld en moet vervangen worden met de uitkomst waardes van het kubeadm init commando.

sudo kubeadm join 192.168.1.xxx:6443 --token hsqows.kn74dviv5hfuk6bg --discovery-token-ca-cert-hash sha256:0f08b0d92ca2eadd91d0c5d56bd20e1899b0bbc020acca62953bc59b3ab17ec1

Nu alle Raspberry Pi's in het Kubernetes cluster zijn aangesloten, kunnen de nodes opgevraagd, gelabeld en een rol gegeven worden.

kubectl get nodes kubectl label node worker-xx node-role.kubernetes.io/worker=worker

Om Calico op te zetten kunnen de Calico Kubernetes objecten aangemaakt worden met het volgende commando.

kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

Om te kijken of alle componenten van Calico draaien, kan het volgende commando uitgevoerd worden. Dit kan een aantal minuten duren.

kubectl -n kube-system get pods

Nu zullen de nodes zich ook in een ready status bevinden. Dit kan bekeken worden door volgend commando uit te voeren.

kubectl get nodes

Om MetalLB op te zetten moeten de volgende commando's uitgevoerd worden.

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml

Daarnaast moet er een IP-adres pool geconfigureerd worden.

sudo nano metallb-config.yaml

Het volgende moet in het bestand gezet worden:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.1.200-192.168.1.254
                                

Deze configuratie kan daarna doorgezet worden naar het Kubernetes cluster.

kubectl apply -f metallb-config.yaml


Testen

Als laatst kan het Kubernetes cluster getest worden. Hiervoor kan een nginx test uitgevoerd worden om te kijken of het binnenhalen van containers, het toewijzen van IP-adressen werkt en dat de nginx service van buiten de cluster te benaderen is.

sudo nano nginx.yaml

Het volgende moet in het bestand gezet worden:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  externalTrafficPolicy: Local
  type: LoadBalancer
                                

Deze configuratie kan daarna doorgezet worden naar het Kubernetes cluster.

kubectl apply -f nginx.yaml

De status van de Kubernetes objecten voor nginx kunnen bekeken worden door het volgende commando.

kubectl get all

Om te kijken op welk IP-adres de service te benaderen is kan het volgende commando uitgevoerd worden.

kubectl get service nginx-service

Nu kan er via de browser gekeken worden of alles werkt, door naar het IP-adres van de service te gaan. Hier zul je de standaard pagina van nginx zien.

Wanneer alles werkt kunnen de aangemaakte Kuberentes objecten voor nginx verwijderd worden.

kubectl delete -f nginx.yaml

Dit was het voor nu. De Kubernetes cluster is succesvol opgezet! Voor vragen of meer informatie kun je contact opnemen via het contactformulier.


Bronnen

https://kubernetes.io/docs/setup/production-environment/container-runtimes/ https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/ https://medium.com/faun/kubernetes-without-kube-proxy-1c5d25786e18