Kubernetes Cluster in Proxmox VE (AlmaLinux/CentOS): Unterschied zwischen den Versionen
KKeine Bearbeitungszusammenfassung |
KKeine Bearbeitungszusammenfassung |
||
(74 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
{{Infobox | |||
| Titel = Testumgebung | |||
| Bildname = | |||
| Bildbreite = | |||
| Bildtext = | |||
| Stil = 2 | |||
| Titelfarbe = 6 | |||
| Abschnittsfarbe = | |||
| Farbe = | |||
| Style = | |||
| Feldstyle = | |||
| Feldname1 = OS | Daten1 = AlmaLinux 8.7 | |||
| Feldname3 = Kubernetes | Daten3 = 1.26.0 | |||
| Feldname4 = K8s-Dashboard | Daten4 = 2.7.0 | |||
| Feldname5 = Metrics-Server | Daten5 = 0.6.2 | |||
}} | |||
Anleitung um ein Kubernetes Cluster innerhalb von Proxmox VE einzurichten. | Anleitung um ein Kubernetes Cluster innerhalb von Proxmox VE einzurichten. | ||
Die Installation in einem VM Cluster wie Proxmox bietet sich aufgrund der Zeitersparnis an, dennoch sollte alles hier beschriebene grundsätzlich auch auf "echten" Maschinen funktionieren. | 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 == | == Vorbereitungen == | ||
Zeile 20: | Zeile 32: | ||
Mindestanforderungen: 2 GB RAM / 2 Cores / 32 GB Speicherplatz | Mindestanforderungen: 2 GB RAM / 2 Cores / 32 GB Speicherplatz | ||
Hinweis: | '''''Hinweis:''''' | ||
''Einige Kubernetes Deployments nutzen MongoDB als Datenbank.'' | |||
'' | ''Ab MongoDB 5.0 werden keine Systeme mehr unterstützt deren CPU das AVX-Flag nicht gesetzt hat.'' | ||
'' | ''Damit es später keine Probleme gibt, sollte daher als CPU nicht 'kvm64' sondern 'host' ausgewählt werden.'' | ||
Dies wird ebenfalls empfohlen um "Nested Virtualization" zu nutzen, wodurch die Performance erheblich verbessert werden kann.<ref>https://pve.proxmox.com/wiki/Nested_Virtualization</ref> | |||
=== Controller und Worker === | === Controller und Worker === | ||
'''Hostname festlegen''' | '''Hostname festlegen''' | ||
Für den Controller .z.B.:<syntaxhighlight lang="bash"> | Für den Controller .z.B.:<syntaxhighlight lang="bash"> | ||
Zeile 47: | Zeile 59: | ||
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo | yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo | ||
dnf install containerd | dnf install containerd | ||
</syntaxhighlight>< | </syntaxhighlight>Den Service auf Autostart setzen:<syntaxhighlight lang="bash"> | ||
systemctl enable containerd | |||
</syntaxhighlight> | |||
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: | |||
<syntaxhighlight lang="bash"> | |||
rm /etc/containerd/config.toml | rm /etc/containerd/config.toml | ||
containerd config default | tee /etc/containerd/config.toml | containerd config default | tee /etc/containerd/config.toml | ||
</syntaxhighlight | </syntaxhighlight>Die neu erzeugte config.toml bearbeiten:<syntaxhighlight lang="bash"> | ||
vi /etc/containerd/config.toml | vi /etc/containerd/config.toml | ||
</syntaxhighlight>Folgenden Absatz suchen und <code>SystemdCgroup</code> auf '''true''' setzen (Suchen in vi mit <code>/<Suchbegriff></code>):<syntaxhighlight lang="bash" line="1"> | </syntaxhighlight> | ||
Damit Kubernetes die Ressourcen(RAM/CPU) des Hosts verwalten kann, muss in der config.toml eine Option angepasst werden. | |||
Folgenden Absatz suchen und <code>SystemdCgroup</code> auf '''true''' setzen (Suchen in vi mit <code>/<Suchbegriff></code>)<ref>https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cgroup-drivers</ref>:<syntaxhighlight lang="bash" line="1"> | |||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] | ||
BinaryName = "" | BinaryName = "" | ||
Zeile 65: | Zeile 86: | ||
ShimCgroup = "" | ShimCgroup = "" | ||
SystemdCgroup = false | SystemdCgroup = false | ||
</syntaxhighlight> | </syntaxhighlight>Da die Container innerhalb von Kubernetes in einem eigenen Netzwerk laufen muss '''IP-Forwarding''' aktiviert werden:<syntaxhighlight lang="bash"> | ||
vi /etc/sysctl.conf | vi /etc/sysctl.conf | ||
</syntaxhighlight>Folgende Zeile am Ende einfügen, bzw. auskommentieren, falls schon vorhanden:<syntaxhighlight lang="bash"> | </syntaxhighlight>Folgende Zeile am Ende einfügen, bzw. auskommentieren, falls schon vorhanden:<syntaxhighlight lang="bash"> | ||
net.ipv4.ip_forward=1 | net.ipv4.ip_forward=1 | ||
</syntaxhighlight>< | </syntaxhighlight> | ||
Damit Kubernetes die internen VxLAN erstellen kann und die Pods(Container) untereinander kommunizieren können, muss das '''br_netfilter''' Modul aktiviert werden.<ref>https://docs.oracle.com/en/operating-systems/olcne/1.1/start/netfilter.html</ref> | |||
Hierfür wird eine neue Config-Datei erstellt: | |||
<syntaxhighlight lang="bash"> | |||
vi /etc/modules-load.d/k8s.conf | vi /etc/modules-load.d/k8s.conf | ||
</syntaxhighlight> | </syntaxhighlight>Dort einfach den Namen des zu ladenden Moduls einfügen:<syntaxhighlight lang="bash"> | ||
br_netfilter | br_netfilter | ||
</syntaxhighlight>< | </syntaxhighlight> | ||
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: | |||
<syntaxhighlight lang="bash"> | |||
vi /etc/fstab | |||
</syntaxhighlight>Den Eintrag mit 'swap' suchen und mit # auskommentieren:<syntaxhighlight lang="bash" line="1"> | |||
/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 | |||
</syntaxhighlight>Anschließend noch ein Neustart um alle Änderungen zu übernehmen:<syntaxhighlight lang="bash"> | |||
reboot | reboot | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Zeile 97: | Zeile 115: | ||
'''Feste IP einrichten''' | '''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:<syntaxhighlight lang="bash"> | Grafische Oberfläche:<syntaxhighlight lang="bash"> | ||
Zeile 112: | Zeile 130: | ||
</syntaxhighlight>Hosts-Datei anpassen:<syntaxhighlight lang="bash"> | </syntaxhighlight>Hosts-Datei anpassen:<syntaxhighlight lang="bash"> | ||
vi /etc/hosts | vi /etc/hosts | ||
</syntaxhighlight><syntaxhighlight lang="bash" line="1"> | </syntaxhighlight>Die festgelegte IP mit dazugehörigem Hostnamen einfügen:<syntaxhighlight lang="bash" line="1"> | ||
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 | 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 | ||
192.168.2.65 k8s-ctrlr.my.domain k8s-ctrlr | 192.168.2.65 k8s-ctrlr-1.my.domain k8s-ctrlr-1 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Damit die Änderungen wirksam werden starten wir den Netzwerkadapter neu (Interfacename muss ggf. angepasst werden):<syntaxhighlight lang="bash"> | |||
ifdown ens18 && ifup ens18 | |||
</syntaxhighlight> | |||
Falls ihr per SSH verbunden seid, wird die Sitzung getrennt und ihr müsst euch danach mit der neuen IP verbinden. | |||
== Kubernetes installieren == | == Kubernetes installieren == | ||
=== Controller und Worker === | |||
Zuerst das Kubernetes Repository hinzufügen:<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight> | |||
Da <code>kubelet</code> 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. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
setenforce 0 | |||
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config | |||
</syntaxhighlight>Jetzt können die Tools für den Cluster installiert werden:<syntaxhighlight lang="bash"> | |||
dnf update | dnf update | ||
dnf install kubeadm kubectl kubelet --disableexcludes=kubernetes | dnf install -y kubeadm kubectl kubelet nfs-utils --disableexcludes=kubernetes | ||
</syntaxhighlight> | </syntaxhighlight>- '''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:<syntaxhighlight lang="bash"> | ||
systemctl enable kubelet | |||
</syntaxhighlight> | |||
=== Firewall konfigurieren === | === Firewall konfigurieren === | ||
Zeile 153: | Zeile 195: | ||
firewall-cmd --reload | firewall-cmd --reload | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Zeile 158: | Zeile 201: | ||
Nachdem das Template erstellt wurde und wir neue Worker erzeugt haben, können wir nun auch dort eine feste IP vergeben. | |||
'''Feste IP einrichten''' | '''Feste IP einrichten''' | ||
Zeile 168: | Zeile 212: | ||
</syntaxhighlight>Beispiel:<syntaxhighlight lang="bash" line="1"> | </syntaxhighlight>Beispiel:<syntaxhighlight lang="bash" line="1"> | ||
BOOTPROTO=none | BOOTPROTO=none | ||
IPADDR=192.168.2. | IPADDR=192.168.2.67 | ||
PREFIX=24 | PREFIX=24 | ||
GATEWAY=192.16.2.1 | GATEWAY=192.16.2.1 | ||
DNS1=192.168.2.1 | DNS1=192.168.2.1 | ||
DNS2=1.1.1.1 | DNS2=1.1.1.1 | ||
</syntaxhighlight>Den Hostnamen für die neuen Worker anpassen:<syntaxhighlight lang="bash"> | |||
hostnamectl set-hostname k8s-worker-2 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Hosts-Datei anpassen:<syntaxhighlight lang="bash"> | Hosts-Datei anpassen:<syntaxhighlight lang="bash"> | ||
vi /etc/hosts | vi /etc/hosts | ||
</syntaxhighlight><syntaxhighlight lang="bash" line="1"> | </syntaxhighlight>Die festgelegte IP mit dazugehörigem Hostnamen einfügen:<syntaxhighlight lang="bash" line="1"> | ||
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 | 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 | ||
192.168.2. | 192.168.2.67 k8s-worker-2.my.domain k8s-worker-2 | ||
</syntaxhighlight> | |||
Damit die Änderungen wirksam werden starten wir den Netzwerkadapter neu (Interfacename muss ggf. angepasst werden):<syntaxhighlight lang="bash"> | |||
ifdown ens18 && ifup ens18 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Wenn alle VMs aus dem selben Template erstellt wurden, muss die 'machine-id' neu generiert werden damit sie | 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.[[Datei:Proxmox vm uuid.png|mini|VM UUID in Proxmox]]<syntaxhighlight lang="bash"> | |||
rm /etc/machine-id | rm /etc/machine-id | ||
systemd-machine-id-setup | systemd-machine-id-setup | ||
Zeile 192: | Zeile 245: | ||
Das bedeutet, dass die machine-id aus der UUID der Proxmox VM generiert wurde. | Das bedeutet, dass die machine-id aus der UUID der Proxmox VM generiert wurde. | ||
=== Controller === | |||
Nun endlich kann der Cluster initialisiert werden:<syntaxhighlight lang="bash"> | |||
kubeadm init --control-plane-endpoint=<Controller-IP> --node-name k8s-ctrlr-1 --pod-network-cidr=10.244.0.0/16 | |||
</syntaxhighlight><code>--control-plane-endpoint</code> / Die IP des Controllers (In diesem Beispiel 192.168.2.65) | |||
<code>--node-name</code> / Der Hostname des Controllers (In diesem Beispiel k8s-ctrlr-1) | |||
<code>--pod-network-cidr</code> / Der interne IP Adressraum für die Pods. (Sollte nicht geändert werden) | |||
Damit ein Linux User auf dem Controller Administrativen Zugriff auf den Cluster hat | Damit ein normaler Linux User auf dem Controller Administrativen Zugriff auf den Cluster hat müssen folgende Befehle ausgeführt werden:<syntaxhighlight lang="bash"> | ||
mkdir -p $HOME/.kube | mkdir -p $HOME/.kube | ||
cp /etc/kubernetes/admin.conf $HOME/.kube/config | cp /etc/kubernetes/admin.conf $HOME/.kube/config | ||
Zeile 206: | Zeile 261: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Wenn ihr als root angemeldet seid muss nur folgender Befehl ausgeführt werden:<syntaxhighlight lang="bash"> | |||
export KUBECONFIG=/etc/kubernetes/admin.conf | export KUBECONFIG=/etc/kubernetes/admin.conf | ||
</syntaxhighlight>Damit das nicht bei jeder neuen Sitzung wiederholt werden muss die Zeile am Ende von <code>.bash_profile</code> | </syntaxhighlight> | ||
Damit das nicht bei jeder neuen Sitzung wiederholt werden muss, kann die Zeile am Ende von <code>.bash_profile</code> eingefügt werden: | |||
<syntaxhighlight lang="bash"> | |||
vi ~/.bash_profile | vi ~/.bash_profile | ||
</syntaxhighlight>< | </syntaxhighlight>'''''Die Join Kommandos braucht man sich nicht zu speichern, da der Token jederzeit mit''''' <code>kubeadm token create --print-join-command</code> '''''neu generiert werden kann.''''' | ||
Wenn man jetzt mit <code>kubectl get nodes</code> den Status des einen Nodes abfragt wird man feststellen, dass dieser auf "Not Ready" bleibt... | |||
Mit <code>kubectl get pods -n kube-system</code> sieht man, dass die Pods "coredns" im Status "Pending" sind.<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight>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 [https://github.com/flannel-io/flannel 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. | |||
<syntaxhighlight lang="bash"> | |||
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml | kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml | ||
</syntaxhighlight>< | </syntaxhighlight> | ||
</syntaxhighlight> | Anschließend kann mit <code>kubectl get nodes</code> überprüft werden, ob der Controller läuft:<syntaxhighlight lang="bash"> | ||
NAME STATUS ROLES AGE VERSION | |||
k8s-ctrlr-1 Ready control-plane 8m7s v1.26.0 | |||
</syntaxhighlight> | |||
'''''Es kann ein, zwei Minuten dauern bis der Status READY ist.''''' | |||
[[File:Kubernetes.png|thumb|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:<syntaxhighlight lang="bash"> | ||
kubeadm token create --print-join-command | |||
</syntaxhighlight> | |||
=== Worker === | |||
Den neu generierten Join-Befehl auf jedem Worker ausführen:<syntaxhighlight lang="bash"> | |||
kubeadm join 192.168.2.65:6443 --token b1j6iu.s91gq99vytd2x096 --discovery-token-ca-cert-hash sha256:244dcacceb61419bc00d6dff2bea8ec694732be1d03b289308a58436da5e17d0 | kubeadm join 192.168.2.65:6443 --token b1j6iu.s91gq99vytd2x096 --discovery-token-ca-cert-hash sha256:244dcacceb61419bc00d6dff2bea8ec694732be1d03b289308a58436da5e17d0 | ||
</syntaxhighlight>< | </syntaxhighlight> | ||
Auf dem Controller kann mit <code>kubectl get nodes</code> überprüft werden ob der Worker registriert wurde: | |||
<syntaxhighlight lang="bash"> | |||
kubectl get nodes | kubectl get nodes | ||
</syntaxhighlight>Ausgabe:<syntaxhighlight lang="bash"> | </syntaxhighlight>Ausgabe:<syntaxhighlight lang="bash"> | ||
k8s-ctrlr Ready control-plane 19m v1.26.0 | k8s-ctrlr Ready control-plane 19m v1.26.0 | ||
k8s-node-1 Ready <none> 3m1s v1.26.0 | k8s-node-1 Ready <none> 3m1s v1.26.0 | ||
</syntaxhighlight>''Es kann ein, zwei Minuten dauern bis der Status READY ist.'' | |||
'''Glückwunsch zu eurem neuen Kubernetes Cluster!''' | |||
== Dashboard == | |||
[[Datei:Kubernetes dashboard with metrics.png|mini|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: | |||
<syntaxhighlight lang="bash"> | |||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Nach ein paar Minuten sollten die Pods laufen und das Dashboard über https://localhost<nowiki/>:<NodePort> erreichbar sein. | |||
</ | |||
Damit man auch über das LAN Zugriff hat, muss der Dashboard Service angepasst werden. | |||
Den Dashboard Service editieren:<syntaxhighlight lang="bash"> | Den Dashboard Service editieren:<syntaxhighlight lang="bash"> | ||
kubectl -n kubernetes-dashboard | kubectl edit service kubernetes-dashboard -n kubernetes-dashboard | ||
</ | </syntaxhighlight><code>-n kubernetes-dashboard</code> / 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 <code>type: ClusterIP</code> in '''NodePort''' ändern:<syntaxhighlight lang="bash"> | |||
apiVersion: v1 | apiVersion: v1 | ||
kind: Service | kind: Service | ||
Zeile 270: | Zeile 389: | ||
status: | status: | ||
loadBalancer: {} | loadBalancer: {} | ||
</syntaxhighlight> | </syntaxhighlight>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:<syntaxhighlight lang="bash"> | ||
kubectl get service -n kubernetes-dashboard | |||
</syntaxhighlight><syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight>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.<ref>https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md</ref>''''' | |||
Auf dem Controller in einem beliebigen Ordner folgende Dateien anlegen: | |||
admin-user.yaml / Manifest um einen Service Account anzulegen.<syntaxhighlight lang="bash" line="1"> | |||
apiVersion: v1 | |||
kind: ServiceAccount | |||
metadata: | |||
name: admin-user | |||
namespace: kubernetes-dashboard | |||
</syntaxhighlight>cluster-admin.yaml / Manifest um dem Service Account Adminrechte zuzuweisen.<syntaxhighlight lang="bash" line="1"> | |||
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 | |||
</syntaxhighlight>Anschließend die Konfiguration übernehmen:<syntaxhighlight lang="bash"> | |||
kubectl apply -f admin-user.yaml | |||
kubectl apply -f cluster-admin.yaml | |||
</syntaxhighlight>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:<syntaxhighlight lang="bash"> | |||
kubectl | kubectl -n kubernetes-dashboard create token admin-user | ||
</syntaxhighlight | </syntaxhighlight> | ||
==== Keycloak (OIDC) ==== | |||
Siehe [[Kubernetes Dashboard mit Keycloak OIDC]] | |||
=== 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:<syntaxhighlight lang="bash"> | |||
curl -LO https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml | |||
</syntaxhighlight><code>- --kubelet-insecure-tls</code> in deployment.spec.template.spec.containers.args<ref>https://www.scmgalaxy.com/tutorials/kubernetes-metrics-server-error-readiness-probe-failed-http-probe-failed-with-statuscode/</ref><syntaxhighlight lang="bash" line="1"> | |||
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 | |||
</syntaxhighlight><syntaxhighlight lang="bash"> | |||
kubectl apply -f components.yaml | |||
</syntaxhighlight>Metrics-Server testen:<syntaxhighlight lang="bash"> | |||
kubectl top nodes | |||
</syntaxhighlight>Ausgabe:<syntaxhighlight lang="bash"> | |||
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% | |||
</syntaxhighlight> | |||
== Weiterführende | == Weiterführende Links == | ||
*[https://krew.sigs.k8s.io/plugins/ Plugins für kubectl Kommando] | *[https://krew.sigs.k8s.io/plugins/ Plugins für kubectl Kommando] | ||
*[https://github.com/collabnix/kubetools Liste mit Tools für Kubernetes] | *[https://github.com/collabnix/kubetools Liste mit Tools für Kubernetes] | ||
*[https://artifacthub.io/ Sammlung von Kubernetes Packages (Helm)] | |||
== Quellen == | == Quellen == | ||
https://docs.docker.com/engine/install/centos/ | * https://www.youtube.com/watch?v=U1VzcjCB_sY | ||
* https://docs.docker.com/engine/install/centos/ | |||
* https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ | |||
* https://www.thegeekdiary.com/how-to-access-kubernetes-dashboard-externally/ | |||
=== Einzelnachweise === | |||
<references /> | |||
== Kommentare == | |||
<comments nocache=true /> |
Aktuelle Version vom 17. Januar 2023, 02:03 Uhr
Testumgebung | |
---|---|
OS | AlmaLinux 8.7 |
Kubernetes | 1.26.0 |
K8s-Dashboard | 2.7.0 |
Metrics-Server | 0.6.2 |
Anleitung um ein Kubernetes Cluster innerhalb von Proxmox VE einzurichten.
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 gesetzt hat.
Damit es später keine Probleme gibt, sollte daher als CPU nicht 'kvm64' sondern 'host' ausgewählt werden.
Dies wird ebenfalls empfohlen um "Nested Virtualization" zu nutzen, wodurch die Performance erheblich verbessert werden kann.[1]
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>
)[2]:
[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.[3]
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.
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.
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
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.[4]
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)
Siehe Kubernetes Dashboard mit Keycloak OIDC
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[5]
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
- https://www.youtube.com/watch?v=U1VzcjCB_sY
- https://docs.docker.com/engine/install/centos/
- https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
- https://www.thegeekdiary.com/how-to-access-kubernetes-dashboard-externally/
Einzelnachweise
- ↑ https://pve.proxmox.com/wiki/Nested_Virtualization
- ↑ https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cgroup-drivers
- ↑ https://docs.oracle.com/en/operating-systems/olcne/1.1/start/netfilter.html
- ↑ https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md
- ↑ https://www.scmgalaxy.com/tutorials/kubernetes-metrics-server-error-readiness-probe-failed-http-probe-failed-with-statuscode/