Kubernetes Single Node Cluster aufsetzen

Kubernetes Single Node Cluster aufsetzen
Inhaltsverzeichnis

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

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:

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

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
# 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 zu ipvs Vorteile? Aktuell wird iptables 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