Hier sehen wir uns an, wie man einen Multi-Master Kubernetes Cluster auf Basis von Rancher/k3s aufsetzt. Als Hardwareplattform verwende ich fünf Raspberry Pi 3b+ in einem Picocluster auf denen HypriotOS läuft.
Die Hochverfügbarkeit des Kubernetes Clusters wird durch die Verwendung von
HA-Proxy und keepalived
erreicht.
Eine virtuelle Failover IP-Adresse zeigt so immer auf einen verfügbaren Kubernetes Apiserver. Persistenter Speicherplatz mit Unterstützung für read-write many
Zugriff wird Dank
Rook über ein verteiltes Clusterfilesystem bereitgestellt, der den verfügbaren Speicherplatz der RPi SD-Karten nutzt.
Mac-Nutzer aufgepasst: In manchen Befehlen, setze ich voraus, das GNU coreutils
verfügbar und eine aktuelle Bash Version installiert sind!
Cloud-init Konfiguration
Nachfolgende cloud-init Templates verwende ich als Basis, wobei ich drei Raspberries als Controller definiere, auf diesen Nodes wird später zusätzlich zum workload die Kubernetes control plane laufen. Die anderen beiden sind normale Worker.
In allen cloud-init Konfigurationen müssen üblicherweise folgende Werte angepasst werden:
- Einzigartige Hostnamen. Meine Controller-Nodes heißen hier Bane, Loki und Mandarin. Die Worker Poison-Ivy und Venom
- Jeder Node bekommt eine statische IP-Adresse aus dem lokalen Netzwerk, ggf. muss die Netzmaske (CIDR) angepasst werden
- Und es muss eine virtuelle Failover Adresse definiert werden, die später auf den Kubernetes Apiserver zeigt
- Die Administrator Benutzerinformationen
- Der Öffentlicher SSH-Schlüssel des Administrators
- Optional kann aoe.local durch eine eigene DNS Zone ersetzen
Für den 2. und 3. Controller-Node müssen zusätzlich noch die Zeilen 108-109 angepasst werden:
# Suche nach:
state MASTER
priority 102
# Ersetze mit:
state BACKUP
priority 100
Der Einfachheit halber habe ich für jeden Node eine eigene cloud-init Datei angelegt, die ich im Ordner ./cloud-init
ablege.
Expand to view: Controller Template
#cloud-config
hostname: "bane"
manage_etc_hosts: false
locale: "en_US.UTF-8"
timezone: "Europe/Berlin"
users:
- name: ff0x
gecos: “Max Buelte”
primary-group: aoe
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
groups: aoe,users,docker,adm,dialout,audio,plugdev,netdev,video
lock_passwd: true
ssh_pwauth: false
ssh-authorized-keys:
- ssh-rsa AAAAB3N…
apt:
preserve_sources_list: true
conf: |
APT {
Get {
Assume-Yes “true”;
Fix-Broken “true”;
};
};
sources:
kubernetes-xenial.list:
source: “deb http://apt.kubernetes.io/ kubernetes-xenial main”
keyid: 6A030B21BA07F4FB
debian-unstable.list:
source: “deb http://deb.debian.org/debian/ unstable main”
keyid: 04EE7237B7D453EC
#ignored0: # Just get the signing key
# keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77
# keyserver: pgp.mit.edu
package_upgrade: true
package_reboot_if_required: true
packages:
- vim
- lrzsz
- socat
- dhcpcd5
- psmisc
- xfsprogs
- nfs-common
- libipset3
- libnss-mdns
- keepalived
- haproxy
write_files:
-
content: |
persistent
slaac private
interface eth0
static ip_address=192.168.2.241/24
static routers=192.168.2.1
static domain_name_servers=1.1.1.1
path: /etc/dhcpcd.conf
-
content: |
127.0.0.1 localhost.localdomain localhost
::1 localhost.localdomain localhost
192.168.2.250 k3s.aoe.local k3s
192.168.2.241 bane.aoe.local bane
192.168.2.242 loki.aoe.local loki
192.168.2.243 mandarin.aoe.local mandarin
192.168.2.244 poison-ivy.aoe.local poison-ivy
192.168.2.245 venom.aoe.local venom
path: /etc/hosts
-
content: |
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_nonlocal_bind = 1
path: /etc/sysctl.d/100-kubernetes.conf
-
content: |
#!/bin/sh
echo “All runlevel operations denied by policy” >&2
exit 101
path: /usr/sbin/policy-rc.d
owner: root:root
permissions: ‘0755’
-
content: |
global_defs {
enable_script_security
script_user haproxy
}
vrrp_script chk_haproxy {
script “/usr/bin/killall -0 haproxy”
interval 2
}
vrrp_sync_group SG_1 {
group {
INTERN
}
}
vrrp_instance INTERN {
interface eth0
virtual_router_id 51
state MASTER
priority 102
advert_int 1
virtual_ipaddress {
192.168.2.250/24
}
track_script {
chk_haproxy
}
}
path: /etc/keepalived/keepalived.conf
owner: root:root
permissions: ‘0640’
-
content: |
[Unit]
After=
After=network-online.target
[Service]
ExecStartPre=
ExecStartPre=/bin/sleep 10
path: /etc/systemd/system/keepalived.service.d/override.conf
owner: root:root
permissions: ‘0644’
-
content: |
global
maxconn 256000
log 127.0.0.1 local0 notice
user haproxy
group haproxy
chroot /usr/share/haproxy
pidfile /run/haproxy.pid
stats socket /run/haproxy.sock mode 660 level admin
stats timeout 30s
daemon
# SSL specific
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
tune.ssl.default-dh-param 2048
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 15s
timeout client 10m
timeout server 10m
STATS
—–
listen stats
bind *:8081
mode http
option httplog
log global
maxconn 5
stats enable
stats refresh 5s
stats show-node
stats auth admin:WkjaBIdCJxqWJpoIE6hp7PdRIJpRnebQ
stats uri /stats
ANY
—
listen k3s-ingress
bind 192.168.2.250:80
mode tcp
log global
# Options
option tcplog
# Backend
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server metallb1 192.168.2.251:80 check
listen k3s-ingress-ssl
bind 192.168.2.250:443
mode tcp
log global
# Options
option tcplog
option ssl-hello-chk
# Backend
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server metallb1 192.168.2.251:443 check
INTERN
——
frontend k8s-api
bind 192.168.2.250:6443
mode tcp
# Options
option tcplog
default_backend k8s-api
backend k8s-api
mode tcp
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server bane bane.aoe.local:6443 check
server loki loki.aoe.local:6443 check backup
server mandarin mandarin.aoe.local:6443 check backup
path: /etc/haproxy/haproxy.cfg
owner: root:root
permissions: ‘0640’
bootcmd:
- ‘date -s @1576147737’ # WTF
- ‘printf “Package: *\nPin: release a=unstable\nPin-Priority: 150\n” > /etc/apt/preferences.d/limit-unstable’
- [ cloud-init-per, once, prepare_storage-X, apt-get, update ]
- [ cloud-init-per, once, prepare_storage-0, apt-get, install, -y, xfsprogs ]
- [ cloud-init-per, once, prepare_storage-1, mkdir, -p, /data ]
runcmd:
- ‘sed -i “s+cgroup_memory=1+cgroup_memory=1 cgroup_enable=memory+” /boot/cmdline.txt’
- ‘apt-get purge –yes docker-ce docker-ce-cli containerd.io; apt-get autoremove –yes’
- ‘apt-get purge –yes isc-dhcp-client isc-dhcp-common; apt-get autoremove –yes’
- ‘mkdir -p /usr/share/haproxy; chown haproxy:haproxy -R /usr/share/haproxy’
- ‘killall ntp >/dev/null 2>&1; apt-get purge –yes ntp >/dev/null 2>&1’
- ‘timedatectl set-ntp true’
- ‘systemctl disable –now wpa_supplicant’
- ‘systemctl disable –now bluetooth’
- ‘systemctl mask bluetooth’
- ‘systemctl enable keepalived’
- ‘systemctl enable haproxy’
Expand to view: Worker Template
#cloud-config
hostname: "venom"
manage_etc_hosts: false
locale: "en_US.UTF-8"
timezone: "Europe/Berlin"
users:
- name: ff0x
gecos: “Max Woelfing”
primary-group: aoe
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
groups: aoe,users,docker,adm,dialout,audio,plugdev,netdev,video
lock_passwd: true
ssh_pwauth: false
ssh-authorized-keys:
- ssh-rsa AAAAB3N…
apt:
preserve_sources_list: true
conf: |
APT {
Get {
Assume-Yes “true”;
Fix-Broken “true”;
};
};
sources:
kubernetes-xenial.list:
source: “deb http://apt.kubernetes.io/ kubernetes-xenial main”
keyid: 6A030B21BA07F4FB
debian-unstable.list:
source: “deb http://deb.debian.org/debian/ unstable main”
keyid: 04EE7237B7D453EC
#ignored0: # Just get the signing key
# keyid: B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77
# keyserver: pgp.mit.edu
package_upgrade: true
package_reboot_if_required: true
packages:
- vim
- lrzsz
- socat
- dhcpcd5
- psmisc
- xfsprogs
- nfs-common
- libnss-mdns
write_files:
-
content: |
persistent
slaac private
interface eth0
static ip_address=192.168.2.245/24
static routers=192.168.2.1
static domain_name_servers=1.1.1.1
path: /etc/dhcpcd.conf
-
content: |
127.0.0.1 localhost.localdomain localhost
::1 localhost.localdomain localhost
192.168.2.250 k3s.aoe.local k3s
192.168.2.241 bane.aoe.local bane
192.168.2.242 loki.aoe.local loki
192.168.2.243 mandarin.aoe.local mandarin
192.168.2.244 poison-ivy.aoe.local poison-ivy
192.168.2.245 venom.aoe.local venom
path: /etc/hosts
-
content: |
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.bridge.bridge-nf-call-iptables = 1
path: /etc/sysctl.d/100-kubernetes.conf
-
content: |
#!/bin/sh
echo “All runlevel operations denied by policy” >&2
exit 101
path: /usr/sbin/policy-rc.d
owner: root:root
permissions: ‘0755’
bootcmd:
- ‘date -s @1576147737’ # WTF
- ‘printf “Package: *\nPin: release a=unstable\nPin-Priority: 150\n” > /etc/apt/preferences.d/limit-unstable’
- [ cloud-init-per, once, prepare_storage-X, apt-get, update ]
- [ cloud-init-per, once, prepare_storage-0, apt-get, install, -y, xfsprogs ]
- [ cloud-init-per, once, prepare_storage-1, mkdir, -p, /data ]
runcmd:
- ‘sed -i “s+cgroup_memory=1+cgroup_memory=1 cgroup_enable=memory+” /boot/cmdline.txt’
- ‘apt-get purge –yes docker-ce docker-ce-cli containerd.io; apt-get autoremove –yes’
- ‘apt-get purge –yes isc-dhcp-client isc-dhcp-common; apt-get autoremove –yes’
- ‘killall ntp >/dev/null 2>&1; apt-get purge –yes ntp >/dev/null 2>&1’
- ‘timedatectl set-ntp true’
- ‘systemctl disable –now wpa_supplicant’
- ‘systemctl disable –now bluetooth’
- ‘systemctl mask bluetooth’
Anschließend erstelle ich die Datei env
im aktuellen Ordner, mit Informationen zu meiner Netzwerkumgebung.
Sie wird später von diversen Bash Scripten eingelesen werden.
local_domain='aoe.local'
declare -A ADRESSES=( [cluster]='192.168.2.250' [cluster_fqdn]="k3s.${local_domain}" [ingress]='192.168.2.251' )
declare -A NODES=( [bane]='192.168.2.241' [loki]='192.168.2.242' [mandarin]='192.168.2.243' [poison-ivy]='192.168.2.244' [venom]='192.168.2.245' )
HypriotOS mit 64bit Kernel
UPDATE: Nachdem es mittlerweile eine offizielle 64bit Version von HypriotOS gibt, ist der nachfolgende Teil nur noch erforderlich, wenn man eigene Kernelparameter aktivieren möchte, wie z.B. nativen Wireguard-Support.
Ein etwas älteres Projekt auf Github erklärt Schritt-für-Schritt, wie man einen 64bit Kernel für HypriotOS kompiliert. An dieser Stelle macht es nichts, dass das Projekt schon ein paar Tage auf dem Buckel hat, man kann selbstverständlich einen aktuellen Kernel erzeugen. Der große Vorteil ist hierbei, dass man eigene Kernelmodule wie die native Unterstützung für Wireguard fest in den Kernel backen kann - oder unbenötigte Module, bzw. Features entfernen, um den Kernel kleiner zu machen.
Installation des Betriebssystems
Flashen der SD-Karten
Abhängigkeiten
Zum flashen der RPi Micro SD-Karten verwende ich flash.
Bekannte Probleme
Aktuell gibt es einen Bug, der verhindert das apt-get update
ordentlich aufgerufen werden kann, wenn der Zeitunterschied
zu groß ist.
Hier geht es zum offenen Github Issue.
Als quick ‘n dirty Lösung, setze ich die aktuelle Systemzeit über cloud-init.
Mit folgendem Script kann man die Konfigurationen aktualisieren:
source ./env
date="$(date +%s)"
for host in ${!NODES[@]}; do
sed -ri "s/date -s @[[:digit:]]+/date -s @${date}/" ./cloud-init/${host}.yaml
done
Micro SD-Karte für jeden Node bereitstellen
Natürlich muss die SD Karte nach jedem Vorgang gewechselt werden.
export IMAGE="$(pwd)/hypriotos-rpi64-4.19.86-custom.img.zip"
flash --userdata ./cloud-init/bane.yaml --device /dev/mmcblk0 "$IMAGE"
flash --userdata ./cloud-init/loki.yaml --device /dev/mmcblk0 "$IMAGE"
flash --userdata ./cloud-init/mandarin.yaml --device /dev/mmcblk0 "$IMAGE"
flash --userdata ./cloud-init/poison-ivy.yaml --device /dev/mmcblk0 "$IMAGE"
flash --userdata ./cloud-init/venom.yaml --device /dev/mmcblk0 "$IMAGE"
RPi Nodes booten
SD-Karten einstecken, wenn noch nicht geschehen. Anschließend Picocluster, bzw. jeden einzelnen Raspberry neustarten und warten. Es kann eine ganze Weile dauern, bis die Konfiguration von HypriotOS komplett abgeschlossen ist.
SSH-Verbindung aufbauen
Um den Konfigurationsfortschritt abzufragen, kann man folgendes Script ausführen. Ich setze voraus, dass der avahi-daemon (vgl. service discovery via mDNS, Zeroconf DNS, Bonjour) aktiviert ist:
source ./env
for host in ${!NODES[@]}; do
echo "--- $host ---"
\ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 $host.local | awk '{print $2}') "sudo tail -n5 /var/log/cloud-init-output.log"
done
Nachdem die Konfiguration via cloud-init abgeschlossen ist, müssen alle Nodes einmalig manuell neugestartet werden. Dieser Schritt ist erforderlich, damit die statischen IP-Adressen korrekt zugewiesen werden:
source ./env
for host in ${!NODES[@]}; do
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 $host.local | awk '{print $2}') "sudo reboot"
done
Sobald man in der Lage ist, die zuvor vergebene Failover IP-Adresse anzupingen, sind alle RPis bereit für die Installation von Kubernetes.
Installation von Kubernetes
Zur Verwaltung des Kubernetes Clusters ist eine lokale Installation von kubectl
notwendig. Nachfolgender Befehl läd die letzte Version herunter:
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
Controller
Mit folgendem Befehl wird k3s auf dem ersten Controller bane
installiert. Das hier generierte TOKEN wird später bei der Installation weiterer Nodes benötigt.
Wenn der HypriotOS Kernel keine Unterstützung für WIreguard mitbringt, muss der Parameter --flannel-backend
entfernt werden.
source ./env
export CLUSTER_TOKEN="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-64} | head -n 1)"
\ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 bane.local | awk '{print $2}') \
"sudo curl -sfL https://get.k3s.io | sh -s - server --tls-san ${ADRESSES[cluster_fqdn]} \
--advertise-address ${ADRESSES[cluster]} --bind-address ${NODES[bane]} --node-ip ${NODES[bane]} \
--token $CLUSTER_TOKEN --flannel-backend wireguard --no-deploy servicelb \
--cluster-init --write-kubeconfig '/tmp/kubeconfig'"
Die Installation dauert nur wenige Sekunden. Nach Abschluss kann man sich die KUBECONFIG herunterladen und entweder
die globale ~/.kube/config
überschreiben oder die Datei unter anderem Namen in ~/.kube
ablegen und die Umgebungsvariable
$KUBECONFIG auf die Konfiguration zeigen lassen. Wenn der Ordner .kube
noch nicht im Heimatverzeichnis existiert,
muss er angelegt werden.
source ./env
mkdir -p ~/.kube
\ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 bane.local | awk '{print $2}') \
"sudo cat /tmp/kubeconfig" > ~/.kube/config
chmod 600 ~/.kube/config
sed -i "s+server:.*$+server: https://${ADRESSES[cluster_fqdn]}:6443+" ~/.kube/config
Jetzt muss sichergestellt werden, dass der Kubernetes Apiserver Hostname zur Failover IP-Adresse auflöst.
Hierfür müssen wir folgende Einträge in der lokalen /etc/hosts
Datei hinzufügen. Wenn man einen Nameserver betreibt,
können die A-Records natürlich in der Zonendatei des Servers angelegt werden.
source ./env
sudo echo
echo "# k3s cluster" | sudo tee -a /etc/hosts
echo "${ADRESSES[cluster]} ${ADRESSES[cluster_fqdn]}" | sudo tee -a /etc/hosts
echo "${ADRESSES[ingress]} minio.${local_domain}" | sudo tee -a /etc/hosts
Die beiden nachfolgenden Controller loki
und mandarin
werden nun mit folgendem Befehl eingerichtet:
source ./env
export TOKEN="$CLUSTER_TOKEN" # Generiertes Token von oben
for host in ${!NODES[@]}; do
[[ "${host}" =~ (bane|poison-ivy|venom) ]] && continue
\ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 ${host}.local | awk '{print $2}') \
"sudo curl -sfL https://get.k3s.io | sh -s - server --advertise-address ${ADRESSES[cluster]} --bind-address ${NODES[$host]} --node-ip ${NODES[$host]} \
--token $TOKEN --server 'https://${ADRESSES[cluster_fqdn]}:6443'"
done
Wenn die Installation abgeschlossen ist, sollte man alsbald alle drei Master Nodes angezeigt bekommen:
kubectl get nodes -w
Worker
Die restliches RPis fügen wir als normale Kubernetes Worker dem Cluster hinzu:
source ./env
export TOKEN="$CLUSTER_TOKEN" # Generiertes Token von oben
for host in ${!NODES[@]}; do
[[ "${host}" =~ (bane|loki|mandarin) ]] && continue
\ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 ${host}.local | awk '{print $2}') \
"sudo curl -sfL https://get.k3s.io | sh -s - agent --node-ip ${NODES[$host]} \
--token $TOKEN --server 'https://${ADRESSES[cluster_fqdn]}:6443'"
done
Nach wenigen Minuten sollte der Kubernetes Cluster aus 5 Nodes bestehen:
kubectl get nodes -w
Installation von Kubernetes Addons
Alles nachfolgende ist optional, der Kubernetes Cluster besteht und kann nach Belieben verwendet werden.
Kubernetes Dashboard
Installation vom Kubernetes Dashboard.
Benutzer für die Administration hinzufügen:
kubectl apply -f - <<END
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kube-system
---
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: kube-system
END
Das Dashboard für ARM-Systeme ausrollen:
curl -sLo - https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml | sed -E 's@(image:.*)-amd64:(.*$)@\1-arm64:\2@g' | kubectl apply -f -
Token des Administrator-Benutzers anzeigen:
kubectl get secret -n kube-system $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}') -o jsonpath="{.data.token}" | base64 --decode
Um auf das Dashboard zugreifen zu können, starten wir einen Proxy in den Kubernetes Cluster mit kubectl proxy
.
Dadurch sind wir in der Lage uns mit dem Token von oben über http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login
einloggen zu können.
Loadbalancer
Wenn k3s - wie oben - ohne Rancher “servicelb” installiert wurde, macht es Sinn mit metallb Unterstützung für “Loadbalancer” Services bereitzustellen. Hierfür wird eine weitere virtuelle Failover IP-Adresse aus dem lokalen Netzwerk (analog zu der für den Apiserver) benötigt.
Installation des Manifests:
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/manifests/metallb.yaml
Neuere metallb Version erwarten ein Kubernetes Secret mit einem Zufallswert:
kubectl create secret -n metallb-system generic memberlist --from-literal=secretkey="$(openssl rand -base64 128)" --dry-run=client -o yaml | kubectl apply -f -
metallb konfigurieren (Failover Adresse und L2-Routing Modus):
source ./env
kubectl create -f - <<END
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: home
protocol: layer2
addresses:
- ${ADRESSES[ingress]}/32
END
Ich gehe hier davon aus, dass alle in Kubernetes laufende Dienste über den durch Rancher/k3s installierten Standard-Ingress-Controller traefik
freigegeben werden.
Sollte das nicht der Fall sein gibt es zwei Möglichkeiten:
- Entweder man fügt der metallb Konfiguration einen Pool mit mehreren virtuellen IP-Adressen zu
- Alternativ - wenn man weiterhin nur eine einzige virtuelle Failover-Adresse für metallb verwenden möchte - kann man IP address sharing aktivieren. Allerdings müssen hierfür gewisse Bedingungen erfüllt sein.
Ingress Controller
Wie schon erwähnt verwenden wir hier der Einfachheithalber den Ingress Controller, der mit k3s kommt.
Möchte man direkt nach der Installation einen eigenen Controller verwenden, kann man die Standardinstallation mit --no-deploy traefik
unterdrücken. Sollte man traefik
später durch einen anderen Controller ersetzten wollen, muss man auf allen Controller Nodes die
Datei /etc/systemd/system/k3s.service
editieren und entsprechenden Parameter einfügen. Danach kann man den laufenden Controller
mit kubectl delete -n kube-system helmcharts traefik
entfernen.
Persistenter Speicherplatz
Rancher/k3s kommt standardmäßig mit Unterstützung für persistente Volumes. Diese StorageClass ist als default markiert. Um jedoch Datenverlust durch ein verteiltes Dateisystem, bzw. Redundanz der Daten entgegenzuwirken und zusätzlich read-write many Zugriff zu ermöglichen, kann man mit Rook einen exzellenten cloud-native Storage Operator in den Cluster installieren.
Minio
UPDATE: Die Minio-Unterstützung in Rook wurde vor Kurzem abgekündigt. Das war zu erwarten, da der Code nicht ordentlich gepflegt wurde und wohl auch der Rückhalt in der Community nicht da war. Wenn man nun object storage benötigt und Rook einsetzen möchte, kann man alternativ Ceph verwenden.
ARM Container images erzeugen
Workaround für Minio aarch64 Image:
docker pull --platform linux/arm64 webhippie/minio
docker tag webhippie/minio ff0x/minio-aarch64
cd $GOPATH/src
mkdir -p github.com/rook/ && cd github.com/rook/
git clone https://github.com/rook/rook.git
cd rook/
sed -i 's+ARCH=$(GOARCH)+ARCH=arm64v8 --platform linux/arm64+' images/minio/Makefile
sed -i 's+-t $(MINIO_IMAGE)+-t ff0x/minio-arm64+' images/minio/Makefile
sed -i 's+FROM minio/minio.*+FROM ff0x/minio-aarch64+' images/minio/Dockerfile
GOARCH=arm64 IMAGES=minio make build.all
docker push ff0x/minio-arm64
Installation und Konfiguration von Rook
Rook installieren und Minio S3-kompatiblen Storage konfigurieren. Mit $MINIO_USER wird einem Benutzer Zugriff auf Minio gewährt.
cd /tmp && git clone https://github.com/rook/rook.git
cd rook/cluster/examples/kubernetes/minio/
export MINIO_USER='minio_user'
export MINIO_PASS=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-32} | head -n 1)
sed -i 's+rook/minio:master+ff0x/minio-arm64:latest+g' operator.yaml
kubectl create -f ./operator.yaml
sed -i "s/\(username:*: \).*/\1$(echo -n ${MINIO_USER} | base64)/" object-store.yaml
sed -i "s/\(password:*: \).*/\1$(echo -n ${MINIO_PASS} | base64)/" object-store.yaml
kubectl create -f ./object-store.yaml
Konfiguration der Ingress Route
Eine Ingress Route für den Minio webserver hinzufügen:
source ./env
kubectl create -f - <<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: minio-my-store
namespace: rook-minio
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: minio.${local_domain}
http:
paths:
- path: /
backend:
serviceName: minio-my-store
servicePort: 9000
EOF
Wenn alle Pods gestartet sind und die Konfiguration abgeschlossen ist, kann man http://minio.${local_domain}
im Browser aufrufen,
sich mit den erzeugten Zugangsdaten einloggen und versuchen ein Volume zu allokieren.
Je nach Applikation muss der S3-kompatible object storage anders angebunden werden. Mehr Informationen gibt es in der Rook Dokumentation.
HELM
Rancher/k3s kommt von Haus aus mit integriertem Helm Controller, es ist also nicht erforderlich die Cluster-Komponente “Tiller” (bei HELMv2) manuell zu installieren.
Verwaltung der Cluster-Nodes
Nachfolgend ein kurzes Bash Script, das beispielhaft zeigt, wie man komfortabel und gracefully alle Raspberry Pis herunterfahren kann:
#!/usr/bin/env bash
source ./env
function print_style() {
if [ "$2" == "info" ] ; then
COLOR="96m"
elif [ "$2" == "success" ] ; then
COLOR="92m"
elif [ "$2" == "warning" ] ; then
COLOR="93m"
elif [ "$2" == "danger" ] ; then
COLOR="91m"
else #default color
COLOR="0m"
fi
STARTCOLOR="\e[$COLOR"
ENDCOLOR="\e[0m"
printf "$STARTCOLOR%b$ENDCOLOR" "=== $1\n"
}
function service_active() {
local _service="$1"
if systemctl -q is-active "$_service"; then
return 0
else
return 1
fi
}
service_active "avahi-daemon.service" || sudo systemctl start avahi-daemon.service
for host in ${!NODES[@]}; do
print_style "Powering off ${host}" "danger"
\ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $(avahi-resolve-host-name -4 ${host}.local | awk '{print $2}') \
"sudo shutdown -h now"
done