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.