Standardmäßig sind die Volumes eines Pods "ephemeral" also vergänglich.
Deshalb gibt es Persistant Volumes, die auch einen Neustart des Pods überdauern.
In Kubernetes gibt es viele Storage Provider, ich stelle hier mal eine einfache Variante
mit Openebs und Local Storage vor. Das ist für einen Single Node Cluster ausreichend.
Die Installation von OpenEBS muss für Alpine Linux etwas angepasst werden.
Deshalb laden wir erst das Manifest herunter
wget https://openebs.github.io/charts/openebs-operator.yaml
und passen es etwas an...
Zeile 497 state für udev-probe auf false setzen:
- key: udev-probe
name: udev probe
state: false
Zeile 595 und 596 auskommentieren:
# - name: udev
# mountPath: /run/udev
Zeile 639-642 auskommentieren:
# - name: udev
# hostPath:
# path: /run/udev
# type: Directory
Danach kann der OpenEBS Operator einfach deployed werden
kubectl apply -f openebs-operator.yaml
Abschließend wird noch die Default Storage Class festgelegt
kubectl patch storageclass openebs-hostpath -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
Das wars, jetzt können Helm Charts mit PVC ganz normal installiert werden.
Wie installiert man Software in einem Kubernetes Cluster? Am einfachsten mit Helm!
Es gibt schon viele fertige Helm Charts die man mit einem Befehl installieren kann (z.B. auf https://artifacthub.io/ ).
Ok man braucht zwei Befehle. Zuerst muss man das Helm Repository einbinden und dann kann man das Helm Chart installieren.
Hier mal ein paar Helm Charts wie ich sie bei mir installiert habe
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/prometheus --create-namespace --namespace monitoring --version 22.5.0 --set server.service.type=NodePort --set server.service.nodePort=30001
helm repo add grafana https://grafana.github.io/helm-charts
helm install grafana grafana/grafana --create-namespace --namespace monitoring --version 6.56.2 --set service.type=NodePort --set service.nodePort=30002
# Passwort für Benutzer admin
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager --create-namespace --namespace cert-manager --version v1.11.2 --set installCRDs=true
# issuer-letsencrypt.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
namespace: cert-manager
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: john.doe@inter.net
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx
--- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod namespace: cert-manager spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: john.doe@inter.net privateKeySecretRef: name: letsencrypt-prod solvers: - http01: ingress: class: nginx
kubectl apply -f issuer-letsencrypt.yaml
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --create-namespace --namespace ingress-nginx --version 4.6.1 --set controller.service.type=NodePort --set controller.service.nodePorts.http=30080 --set controller.service.nodePorts.https=30443
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install wordpress bitnami/wordpress --create-namespace --namespace wordpress --version 16.1.2 --set service.type=NodePort --set service.nodePorts.http=30000 --set mariadb.auth.rootPassword="strenggeheim" --set mariadb.auth.password="geheim" --set ingress.enabled="true" --set ingress.hostname="blog.jamba.cloud" --set ingress.ingressClassName="nginx" --set ingress.tls="true" --set ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-staging"
# Passwort für Benutzer user
kubectl get secret --namespace wordpress wordpress -o jsonpath="{.data.wordpress-password}" | base64 -d ; echo
helm repo add itzg https://itzg.github.io/minecraft-server-charts
helm install minecraft itzg/minecraft --create-namespace --namespace minecraft --version 4.7.4 --set persistence.dataDir.enabled=true --set minecraftServer.gameMode=creative --set minecraftServer.eula=true --set minecraftServer.serviceType=NodePort --set minecraftServer.nodePort=30003
In dem Beitrag über Alma Linux habe ich ja beschrieben wie man einen Secure Socket Funneling Server aufsetzt. Jetzt beschreibe ich wie man den dazugehörigen Client erstellt.
Damit der Client nicht manuell installiert werden muss packe ich ihn in einen Container. Die Idee ist genial und überzeugend. Man packt alle notwendigen Artefakte die der Client zur Ausführung braucht in einen Container. Ist der Container gebaut, kann man ihn überall hin transportieren und starten wo eine Container Runtime zur Verfügung steht.
Der Container, oder genauer gesagt das Container Image wird i.d.R. auf eine Registry hochgeladen (push) und dann auf dem Zielsystem wieder heruntergeladen (pull). Ich zeige hier mal einen Weg wie man das Container Image als Datei speichert und dann von Hand auf das Zielsystem kopiert. Wieso einfach, wenn es auch kompliziert geht ;-)
Zum Erstellen eines Container Images benötigt man Docker bzw. Podman und ein sogenanntes Dockerfile.
FROM debian:buster-slim
# expose server port and node port
EXPOSE 8011 32000
# set workdir to /app
WORKDIR /app
# environment variables
ENV MODE client
ARG HOST
ENV HOST $HOST
ENV PORT 8011
ARG FORWARD_HOST
ENV FORWARD_HOST $FORWARD_HOST
ENV FORWARD_SERVICE 32000
# create folder for certs and symbolic links
RUN mkdir -p /app/certs/trusted && \
ln -s /certs/ca.crt /app/certs/trusted/ca.crt && \
ln -s /certs/dh4096.pem /app/certs/dh4096.pem && \
ln -s /certs/certificate.crt /app/certs/certificate.crt && \
ln -s /certs/private.key /app/certs/private.key
# copy all needed files into container image
COPY ssf ssfd start.sh /app
# start command
CMD ["/app/start.sh"]
Zuerst muss man sich überlegen, was alles in das Container Image rein soll. Dann wählt man ein Basis Image aus was möglichst schlank ist aber alles bereitstellt, was für den Client benötigt wird. Ich habe hier mal debian:buster-slim genommen.
Dann lege ich fest welche Ports standardmäßig veröffentlicht werden sollen. Hier Port 8011 für den ssf Server und Port 32000 als Node Port für den Backend Service. Wie gesagt das sind Standard Ports die man später zur Laufzeit noch umbiegen kann.
Als Arbeitsverzeichnis verwende ich /app. Es ist einfach strukturierter wenn man seine Dateien in einem Arbeitsverzeichnis bündelt.
Die Umgebungsvariabeln setzen dann alle Werte die man später benötigt. Grundsätzlich verwendet man immer Variablen für Werte die veränderlich sind ;-)
Mit Run werden dann im Container symbolische Links für die Zertifikate angelegt. Über diese Links greift dann später ssf bzw. ssfd auf die Zertifikate zu. Ich kann ja nicht selbst die Zertifikate in den Container kopieren weil das sensible Daten sind. Die Zertifikate werden zur Laufzeit über z.B. ein Kubernetes Secret eingebunden.
Copy kopiert dann noch die executables ssf, ssfd und start.sh in den Container.
Als Command habe ich ein Start Skript hinterlegt, das natürlich auch in den Container rein muss. So ein Start Skript ist flexibler und man kann noch ein bisschen Logik einbauen, z.B. den Loglevel setzen oder steuern ob der Container im Client oder Server Modus gestartet werden soll.
#!/bin/bash
# loglevel critical|error|warning|info|debug|trace (default: info)
if [[ -z ${LOGLEVEL} ]] ; then
export LOGLEVEL=info
else
set -x
echo "LOGLEVEL=${LOGLEVEL}"
fi
# check environment variables
if [[ -z ${MODE} ]] ; then
echo "MODE not set!"
exit 1
else
if [[ ${MODE} == server || ${MODE} == client ]] ; then
echo "MODE=${MODE}"
else
echo "MODE must contain server or client!"
exit 1
fi
if [[ ${MODE} == client && -z ${FORWARD_HOST} ]] ; then
echo "FORWARD_HOST not set!"
exit 1
else
echo "FORWARD_HOST=${FORWARD_HOST}"
fi
if [[ ${MODE} == client && -z ${FORWARD_PORT} ]] ; then
echo "FORWARD_PORT not set!"
exit 1
else
echo "FORWARD_PORT=${FORWARD_PORT}"
fi
if [[ ${MODE} == client && -z ${HOST} ]] ; then
echo "HOST not set!"
exit 1
else
echo "HOST=${HOST}"
fi
fi
if [[ -z ${PORT} ]] ; then
echo "PORT not set!"
exit 1
else
echo "PORT=${PORT}"
fi
# start ssf server or client
cd /app
ls /app/certs
ls /app/certs/trusted
if [[ ${MODE} == server ]] ; then
/app/ssfd -v ${LOGLEVEL} -p ${PORT} -g
else
/app/ssf -v ${LOGLEVEL} -p ${PORT} -R ${FORWARD_PORT}:${FORWARD_HOST}:${FORWARD_PORT} ${HOST}
fi
Ok Let's go Hase. Jetzt kann das Container Image gebaut werden.
Wenn man seinen HOST und den FORWARD_HOST schon kennt, kann man diese Werte setzen.
export HOST=""
export FORWARD_HOST=""
podman build --build-arg HOST=$HOST --build-arg FORWARD_HOST=$FORWARD_HOST -t localhost/ssf:3.0.0 .
podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/ssf 3.0.0 fb2e07c2a585 12 days ago 98.2 MB quay.io/podman/hello latest 804953f8be6e 2 weeks ago 82.3 kB docker.io/library/debian buster-slim e1635ddc0b1d 2 weeks ago 72.5 MB
Wenn das Container Image gebaut ist kann man es ganz einfach mit diesem Befehl speichern.
podman image save -o ssf.tar localhost/ssf:3.0.0
ls -la ssf.tar
-rw-r--r-- 1 root root 98226688 Mar 5 12:43 ssf.tar
Das gespeicherte Container Image "ssf.tar" muss dann auf den Single Node Cluster kopiert werden. Das kann z.B. mit scp erfolgen.
Auf dem Single Node Cluster kann man dann das Image in die Container Runtime containerd "laden".
Dazu gibt es für containerd die die tools "crictl" und "ctr"
# ctr -n=k8s.io image import ssf.tar
TODO FIX: crictl images WARN und ERROR --> https://www.fatlan.com/08-08-2022-crictl-uyarisi-cozum/
# crictl images WARN[0000] image connect using default endpoints: [unix:///var/run/dockershim.sock unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead. ERRO[0000] validate service connection: validate CRI v1 image API for endpoint "unix:///var/run/dockershim.sock": rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial unix /var/run/dockershim.sock: connect: no such file or directory" IMAGE TAG IMAGE ID SIZE docker.io/flannel/flannel-cni-plugin v1.1.2 7a2dcab94698c 3.84MB docker.io/flannel/flannel v0.21.5 a6c0cb5dbd211 26.3MB docker.io/openebs/node-disk-exporter 2.1.0 5ab2c114fe2e9 49.2MB docker.io/openebs/node-disk-manager 2.1.0 297e6f2690c43 50.9MB docker.io/openebs/node-disk-operator 2.1.0 f9669ba5fa2b8 49.2MB docker.io/openebs/provisioner-localpv 3.4.0 382df876b172f 28.8MB quay.io/jetstack/cert-manager-cainjector v1.11.2 7eaaec69ab5cf 12.6MB quay.io/jetstack/cert-manager-controller v1.11.2 ddd138f621ece 18.4MB quay.io/jetstack/cert-manager-ctl v1.11.2 35320f786aaf5 16.7MB quay.io/jetstack/cert-manager-webhook v1.11.2 342b00843ed6d 14.3MB registry.k8s.io/coredns/coredns v1.9.3 5185b96f0becf 14.8MB registry.k8s.io/etcd 3.5.6-0 fce326961ae2d 103MB registry.k8s.io/ingress-nginx/controller <none> 2db0b57c87122 117MB registry.k8s.io/ingress-nginx/kube-webhook-certgen <none> 5a86b03a88d23 20.1MB localhost/ssf 3.0.0 fb2e07c2a585a 98.2MB registry.k8s.io/kube-apiserver v1.26.4 35acdf74569d8 35.5MB registry.k8s.io/kube-controller-manager v1.26.4 ab525045d05c7 32.4MB registry.k8s.io/kube-proxy v1.26.4 b19f8eada6a93 21.5MB registry.k8s.io/kube-scheduler v1.26.4 15a59f71e969c 17.7MB registry.k8s.io/pause 3.8 4873874c08efc 311kB registry.k8s.io/pause 3.9 e6f1816883972 322kB
Es ist zwar etwas umständlich Container Images ohne eine Registry zu übertragen, aber man lernt dafür wie es funktioniert und was dabei zu beachten ist. Außerdem kann man dieses Vorgehen für Container Images verwenden, welche man nicht auf einer Registry veröffentlichen möchte.
Es gibt unendlich viele Wege Kubernetes zu installieren. Auf jedem Fall sollte man sich mal das Tutorial "Kubernetes the hard way" von Kesley Hightower anschauen. Außerdem gibt es Tools wie Sand am Meer um einen Kubernetes Cluster aufzusetzen. Das hat alles seine Berechtigung, aber ich mag eher einen minimalistischen Ansatz.
Hier eine kurze Anleitung für einen Single Node Cluster auf Alpine Linux...
Die Original Anleitung habe ich vom Alpine Linux Wiki -> https://wiki.alpinelinux.org/wiki/K8s
# add edge repos
echo "https://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories
echo "https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories
echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
# add kernel module for networking stuff
echo "br_netfilter" > /etc/modules-load.d/k8s.conf
modprobe br_netfilter
echo "net.bridge.bridge-nf-call-iptables=1" >> /etc/sysctl.conf
sysctl net.bridge.bridge-nf-call-iptables=1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl net.ipv4.ip_forward=1
# disable swap
sed -ie '/swap/ s/^/#/' /etc/fstab
swapoff -a
# fix prometheus errors
mount --make-rshared /
echo "#!/bin/sh" > /etc/local.d/sharedmetrics.start
echo "mount --make-rshared /" >> /etc/local.d/sharedmetrics.start
chmod +x /etc/local.d/sharedmetrics.start
rc-update add local default
# install packages
apk add kubelet
apk add kubeadm
apk add kubectl
apk add cni-plugin-flannel
apk add cni-plugins
apk add flannel
apk add flannel-contrib-cni
apk add containerd
apk add containerd-ctr
apk add k9s
# add services and start container runtime
rc-update add kubelet
rc-update add containerd
/etc/init.d/containerd start
# create cluster
kubeadm init --pod-network-cidr=10.244.0.0/16 --node-name=$HOSTNAME
# copy kube config
mkdir ~/.kube
ln -s /etc/kubernetes/admin.conf ~/.kube/config
# add overlay network flannel
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# on single node cluster remove no schedule taint for control plane
kubectl taint nodes $HOSTNAME node-role.kubernetes.io/control-plane=:NoSchedule-
# have fun with k8s and use k9s
k9s
That's it ;-)
Und es hat auch nur 5 Minuten gedauert. In einem weiteren Beitrag werde ich dann mal beschreiben, was man mit so einem Cluster anstellen kann. Wir werden einen Container bauen und Tunnel bohren...