Storage

30. Mai 2023 - Lesezeit: 2 Minuten

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.

OpenEBS

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.

 


Helm

19. Mai 2023 - Lesezeit: 4 Minuten

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

Prometheus

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

Grafana

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

Cert-Manager

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

Ingress-Nginx

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

Wordpress

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

Minecraft

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

Free WiFi

12. Mai 2023 - Lesezeit: ~1 Minute


Container

15. März 2023 - Lesezeit: 9 Minuten

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 ;-)

Build

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

Save

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

Load

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"

Container Image laden

# ctr -n=k8s.io image import ssf.tar

Container Images anzeigen

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

Fazit

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. 


Wal

14. März 2023 - Lesezeit: ~1 Minute


K8S auf Alpine Linux installieren

13. März 2023 - Lesezeit: 4 Minuten

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...