Kubernetes Single Node Cluster aufsetzen
Ziel des Artikels ist es, die notwendigen Schritte zum Aufsetzen eines einfachen Kubernetes Systems zu dokumentieren.
Die Anforderungen im Detail:
- Lauffähig auf einem einzelnen Netcup vServer (keine High-Availability)
- Integration in Gitlab AutoDevops möglich
- IPv4/IPv6 Dual-Stack fähig
- Mit Kubernetes Network Policies abgesichert
- Nutzung der lokalen Platte als Persistent Storage
- Mögliche Erweiterbarkeit auf mehrere Nodes
Basisimage
- Ubuntu 18.04 LTS Docker Image von Netcup, kleine Partition oder
- Ubuntu 20.04 LTS Minimal Image von Netcup, kleine Partition
Absicherungen basierend auf
- https://help.ubuntu.com/lts/serverguide/security.html
- https://help.replicated.com/community/t/managing-firewalls-with-ufw-on-kubernetes/230
System absichern
Mit dem vorkonfigurierten Root-User per SSH:
adduser jo
#
adduser jo sudo
# nur bei 20.04
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
apt install docker-ce docker-ce-cli containerd.io
#
# SSH verlegen um Platz für Gitlab SSH Port zu schaffen:
sed -i -e 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
systemctl restart ssh
Mit dem neuen Benutzer jo
per SSH:
mkdir .ssh
ssh-keygen
echo "ssh-rsa AAAAB...3w== ssh-jowi-privat-aes" >> ~/.ssh/authorized_keys
sudo passwd -l root
sudo ufw allow OpenSSH
sudo ufw allow 2222
sudo ufw allow out http
sudo ufw allow out https
sudo ufw allow out 22
# allow dns queries
sudo ufw allow out 53/udp
# allow ntp systemd time syncing
sudo ufw allow out 123
sudo ufw default deny outgoing
sudo ufw default deny incoming
sudo ufw enable
Partitionierung
sudo fdisk /dev/sda
# create partition 4 and 5 with 75G and rest
sudo mke2fs /dev/sda4
sudo mke2fs /dev/sda5
echo "/dev/sda4 /var ext4 defaults 0 2" | sudo tee -a /etc/fstab
echo "/dev/sda5 /srv ext4 defaults 0 2" | sudo tee -a /etc/fstab
sudo mkdir /mnt/tmp
sudo mount /dev/sda4 /mnt/tmp
sudo mv /var/* /mnt/tmp
sudo mount /dev/sda4 /var
sudo mount /dev/sda5 /srv
sudo umount /mnt/tmp
Automatische Updates aktivieren
sudo sed -i 's/\/\/Unattended-Upgrade::Automatic-Reboot/Unattended-Upgrade::Automatic-Reboot/g' /etc/apt/apt.conf.d/50unattended-upgrades
cat <<EOF | sudo tee -a /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
EOF
Aufsetzen des Clusters
Basierend auf der offiziellen Dokumentation. Zusätzlich IPv6 Forwarding aktivieren (Zeile 4).
Vorbereitungen
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv6.conf.all.forwarding = 1
EOF
sudo sysctl --system
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl enable docker
sudo systemctl restart docker
sudo ufw allow 6443
sudo ufw allow out to 172.18.0.0/24
sudo ufw allow out to 172.18.1.0/24
sudo ufw allow out to fc00::/64
sudo ufw allow out to fc01::/110
sudo ufw allow in from 172.18.0.0/24
sudo ufw allow in from 172.18.1.0/24
sudo ufw allow in from fc00::/64
sudo ufw allow in from fc01::/110
Paketinstallation
Zum Zeitpunkt des Artikels wird Version 1.22.4 installiert.
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install -y apt-transport-https curl mc ipvsadm
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo snap install helm --classic
helm repo add stable https://charts.helm.sh/stable
helm repo update
Bash Tab-Vervollständigung analog zu https://kubernetes.io/de/docs/tasks/tools/install-kubectl/
echo 'source <(kubectl completion bash)' >> ~/.bashrc
Kubeadm Setup
sudo kubeadm init --config https://gitlab.com/jowi24/kubernetes-setup/-/raw/main/kubeadm/config.yaml
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl taint nodes --all node-role.kubernetes.io/master-
# edit /etc/kubernetes/manifests/kube-apiserver.yaml
# add ServerSideApply=false to --feature-gate parameter
Calico Networking Setup
Basierend auf https://docs.projectcalico.org/manifests/calico.yaml
kubectl apply -f https://gitlab.com/jowi24/kubernetes-setup/-/raw/main/kubeadm/calico.yaml
Hostpath Provider
kubectl apply -f https://gitlab.com/jowi24/kubernetes-setup/-/raw/main/persistence/hostpath-provisioner.yaml
NGINX Ingress
Auf Basis von https://github.com/helm/charts/tree/master/stable/nginx-ingress
sudo ufw allow in 22
sudo ufw allow in http
sudo ufw allow in https
Siehe Skripte unter https://gitlab.com/jowi24/kubernetes-setup/-/tree/main/ingress
cd ingress
./install.sh
Proxy für SMTP/IMAP
Helm-Konfiguration erweitern:
"22": "gitlab/gitlab-gitlab-shell:22",
"25": "mail/exim-auto-deploy:25::PROXY",
"53": "jowi-dyndns/production-auto-deploy:53",
"143": "mail/dovecot-auto-deploy:143::PROXY",
"465": "mail/exim-auto-deploy:465::PROXY",
"587": "mail/exim-auto-deploy:587::PROXY",
"993": "mail/dovecot-auto-deploy:993::PROXY"
Firewall freischalten:
sudo ufw allow 22
sudo ufw allow 25
sudo ufw allow 53
sudo ufw allow 143
sudo ufw allow 465
sudo ufw allow 587
sudo ufw allow 993
DynDNS Dienst
Auf Basis von https://gist.github.com/zoilomora/f7d264cefbb589f3f1b1fc2cea2c844c
SystemD resolved deaktivieren:
sudo systemctl disable systemd-resolved.service
sudo systemctl stop systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf
Certmanager
Automatisches Ausstellen von Zertifikaten mittels LetsEncrypt
Auf Basis von https://cert-manager.io/docs/tutorials/acme/ingress/
Achtung: ein abweichender Namespace (z.B. kube-cert-manager
) führt leider zu Problemen, hier müssten die CRDs modifiziert werden. (TODO: prüfen ob das mit helm chart noch stimmt)
Siehe Skripte unter https://gitlab.com/jowi24/kubernetes-setup/-/tree/main/certmanager
cd certmanager
./create-issuer.sh
./install.sh
Verwendung: Ein Ingress muss folgende Annotation aufweisen um vom Reverse-Proxy geroutet zu werden und ein TLS-Zertifikat mit dem defaultIssuer
zu erhalten:
metadata:
name: kuard
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/tls-acme: "true"
Kubernetes Dashboard
Auf Basis von:
- https://kubernetes.io/docs/tasks/debug-application-cluster/resource-usage-monitoring/#resource-metrics-pipeline
- https://github.com/kubernetes-sigs/metrics-server
- https://artifacthub.io/packages/helm/k8s-dashboard/kubernetes-dashboard
Siehe Skripte unter https://gitlab.com/jowi24/kubernetes-setup/-/tree/main/dashboard
cd dashboard
./install.sh
Gitlab
Installation
Verwendendung des Standard-Helm-Charts unter Berücksichtigung folgender Modifikationen https://docs.gitlab.com/charts/advanced/external-nginx/index.html and https://docs.gitlab.com/charts/installation/tls.html
helm repo add gitlab https://charts.gitlab.io/
kubectl create namespace gitlab
kubectl create secret generic gitlab-smtp-creds --from-literal=password='xxx' -n gitlab
Gitlab Integration
Den Cluster in Gitlab hinzufügen (nicht durch Gitlab managen lassen):
cat <<EOF > gitlab-admin-service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-admin
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: gitlab-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: gitlab-admin
namespace: kube-system
EOF
kubectl apply -f gitlab-admin-service-account.yaml
Folgende Informationen in der Einrichtung angeben:
# API URL
kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
# CA Certificate
kubectl get secret $(kubectl get secrets | grep token | cut -d " " -f 1) \
-o jsonpath="{['data']['ca\.crt']}" | base64 --decode
# Token
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret |\
grep gitlab-admin | awk '{print $1}')
Vorhandene Docker Projekte für Kubernetes anpassen
- Workaround für neue Kubernetes Versionen Kubernetes 1.16+
cd repo
cat <<EOF > .gitlab/auto-deploy-values.yaml
deploymentApiVersion: apps/v1
EOF
- Auto-Devops konfigurieren mit eingenem Namespace und Domain
# derived from https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
include:
- template: Auto-DevOps.gitlab-ci.yml
variables:
CODE_QUALITY_DISABLED: "1"
POSTGRES_ENABLED: "false"
TEST_DISABLED: "1"
PERFORMANCE_DISABLED: "1"
ADDITIONAL_HOSTS: "app2.joachim-wilke.de"
production:
environment:
name: production
url: https://app.joachim-wilke.de
kubernetes:
namespace: jowi-app
- Bei Bedarf eigenes Chart auf Basis des offiziellen Charts ableiten (Nutzung von Git Subtree)
git remote add -f autodeploy https://gitlab.com/gitlab-org/charts/auto-deploy-app.git
git subtree add --prefix chart autodeploy master --squash
# On each update:
git fetch autodeploy master
git subtree pull --prefix chart autodeploy master --squash
- Network Policies mit Default Chart aktivieren: https://docs.gitlab.com/ee/topics/autodevops/stages.html#network-policy
# allow no egress traffic, ingress only from ingress proxy
networkPolicy:
enabled: true
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector: {}
- podSelector:
matchLabels:
app: "nginx-ingress"
Troubleshooting
Zertifikate abgelaufen
Symptom: Fehlermeldung folgender Art
$ kubectl get pods -A
Unable to connect to the server: x509: certificate has expired or is not yet valid: current time 2022-03-25T18:14:53+01:00 is after 2022-03-11T06:28:24Z
Die Fehlermeldung kann auch bei Server-Neustart im Log von Kube-Apiserver auftauchen und dann dazu führen, dass der Cluster nicht mehr startet.
Lösung: Zertifikate erneuern und Dienste neu starten
sudo kubeadm certs renew all
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo reboot
Cheat-Sheet
Firewall
- Get Status:
sudo ufw status verbose
- Applikationsprofile aufrufen:
sudo ufw app list
Kubeadm und Kubectl
- Reset Kubeadm:
kubeadm reset && iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X && ipvsadm -C
- Get Node Status:
kubectl get nodes -o wide
- Get Status:
watch kubectl get all -A
Upgrade
apt-mark hold kubeadm kubelet kubectl
apt-get update
apt-get upgrade
# show available versions
apt-cache policy kubeadm
# upgrade to next minor version
apt-get install -y --allow-change-held-packages kubeadm=1.24.8-00
# show possible upgrade paths
kubeadm upgrade plan
# apply upgrade
kubeadm upgrade apply v1.24.8
# --> resolvConf Eintrag in /var/lib/kubelet/config.yaml prüfen (/etc)
# Upgrade verfolgen
kubectl get pods -A -w
kubectl drain v220211146370169713 --ignore-daemonsets --delete-emptydir-data
apt-get install -y --allow-change-held-packages kubelet=1.24.8-00 kubectl=1.24.8-00
systemctl daemon-reload
systemctl restart kubelet
kubectl uncordon v220211146370169713
Offene Punkte
- Bringt der
iptables
proxy mode, im Vergleich zuipvs
Vorteile? Aktuell wirdiptables
von calico i.V.m. DualStack noch nicht unterstützt. - Anzahl Replicas des Coredns Pods auf 1 beschränken (per default sind es 2)
- Ist es sinnvoll (und bei Netcup technisch machbar), global routbare IPv6 Adressen in den Pods zu verwenden? Siehe auch https://forum.netcup.de/administration-eines-server-vserver/vserver-server-kvm-server/9577-ipv6-addressvergabe-an-docker-container/
- Was hat es mit den Meldungen im Syslog auf sich:
systemd[1551]: Failed to canonicalize path permission denied message
? - Nginx Ingress Default Backend anpassen (eigene Fehlerseite)
-
- Alle Checks des Kube-Bench bestehen
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/master/job-master.yaml
- Alle Checks des Kube-Bench bestehen