Schlagwort-Archive: Container

Kommentar: Linux-Container — Spreu und Weizen

In diesem Beitrag möchte ich meine persönliche Meinung zu und über Linux-Container mit euch teilen. Ich freue mich, wenn sich daraus eine Diskussion entwickelt, in der ihr eure Erfahrungen und Gedanken zum Thema einbringt.

Mir geht es dabei ausschließlich um Linux-Container, welche ich von ähnlichen Konzepten wie z.B. den LDOMs und Kernel-Zones unter Solaris, etc. bewusst abgrenzen möchte.

Was habe ich mit Containern zu tun?

Nun, Container haben in vielen Branchen Fuß gefasst, in denen IT/EDV eine Rolle spielt und werden voraussichtlich nicht mehr verschwinden. Ich selbst bin als Sysadmin und Nerd sowohl aus privatem Interesse, als auch beruflich mit dem Thema in Berührung gekommen.

Mit den Konzepten und der Architektur bin ich vertraut. Mit dem Wochenendprojekt „Kanboard im Container…“ verfüge ich über wenig praktische Erfahrung.

Wie lautet das Versprechen der Container-Technologie?

Alles wird agiler, schneller, effizienter, sicherer UND einfacher. Das Übliche halt.

Im Wesentlichen bieten Container die Chance, dass Anwendungen mit all ihren Abhängigkeiten ausgeliefert werden können.

Damit könnte das Ende der Zeit eingeläutet werden, wo einer Anwendung eine Installationsanleitung mit dem Kapitel „Systemvoraussetzungen“ beiliegt, mit welcher der Administrator sich auf die Suche nach den benötigten Bibliotheken und Paketen macht, welche in seiner konkreten Umgebung zu installieren und zu konfigurieren sind, bevor die eigentliche Anwendung installiert werden kann.

In der schönen neuen Welt verpacken Entwickler alles, was ihre Anwendung braucht, in einen Container, welchen sie anschließend in den Versand geben (deployen) können. Auf dem Zielsystem wird nur noch eine kompatible Container-Engine benötigt und die Anwendung läuft.

Dabei sind Szenarien realistisch, in denen Anwendungen z.B. in einem Ubuntu-Userland auf einem RHEL-Kern laufen und umgekehrt.

Auch Update-Szenarien können deutlich vereinfacht werden. Ein neues Release kommt in Form eines neuen Container-Images, welches instanziiert wird. Gibt es Probleme, bringt man die laufende Instanz um und startet eine Instanz vom vorherigen Container-Image. Vorausgesetzt die Container beinhalten keine persistent zu speichernden Daten (was sie laut Konzept nicht sollen), kann das wirklich gut funktionieren.

Und wie fühlt sich das bis jetzt in der Praxis an?

Die kurze Antwort auf diese Frage lautet: „Sehr unterschiedlich. Das Spektrum reicht von gut bis durchwachsen.“

Ich möchte noch einmal erwähnen, dass ich Systemadministrator und kein Anwendungsentwickler bin. Ich entwickle also keine Software, welche dann mit CI/CD-Pipelines verarbeitet und ausgerollt wird. Meine Aufgabe ist eher die Bereitstellung und der Betrieb von Umgebungen, in denen Container ausgeführt werden können. Zu möglichen Kunden/Nutzern zählen dabei Teams, die eigene Anwendungen entwickeln, aber auch Teams, welche Anwendungen in Form von Container-Repositorien bzw. -Images geliefert bekommen und diese dann betreiben müssen.

Insbesondere in dem Bereich, wo uns die Aufgabe des Betriebs von extern erstellten Anwendungen obliegt, machen wir gerade ein paar Erfahrungen, die ich hier gerne aus meiner persönlichen Sicht teilen möchte.

Beginnen möchte ich mit der insgesamt positiven Erfahrung, die ich mit meinem Wochenendprojekt „Kanboard im Container…“ gemacht habe. Hier laufen eine Anwendung und eine Datenbank jeweils als Container in einem Pod auf einem RHEL 8 Host mit einer Rootless-Podman-Installation und einem Reverse-Proxy. Die Dokumentation von Red Hat und dem Kanboard-Projekt sind hinreichend genau und verständlich, um diese Anwendung ohne große Mühe zu betreiben. Ohne große Verrenkungen kann ich unterschiedliche Versionen aus dem Postgres-Container-Repo ausprobieren und verschiedene Releases der Anwendungen testen.

Leider hat man nicht immer soviel Glück. Ein anderes Software-Projekt, dessen Namen ich hier bewusst nicht nenne, liefert eine kleine Sammlung von Bash-Skripten aus, welche in einer bestimmten Reihenfolge auszuführen sind, um zur Laufzeit eine Docker-Compose-Datei zu generieren, auf deren Basis dann entsprechende Container gestartet werden. Wenn nun ein Update ansteht, ist der ganze Prozess mit der Ausführung der Bash-Skripte in wohldefinierter Reihenfolge erneut durchzuturnen. Das ganze lässt sich ausschließlich auf einem Host mit einer Docker-Installation ausführen und ist zu Podman inkompatibel. Ob es auch mit einer Rootless-Docker-Installtion läuft, ist noch zu testen. Ein Deployment auf einem Kubernetes-Cluster ist undenkbar. Das Projekt stellt keinerlei Informationen dazu bereit.

Übrigens handelt es sich bei diesem Projekt nicht um ein 1-2 Personen FOSS-Projekt, sondern um eines, hinter dem ein Unternehmen steht, welches kostenpflichtige Support-Verträge für seine Anwendung vertreibt.

Es bleibt also wieder mal nur, die eigene Umgebung der Anwendung anzupassen, die sich andernfalls nur mit extrem hohen Aufwand integrieren lässt. Das kennen wir schon aus dem Zeitalter vor der Container-Erscheinung. Es ist in diesem Fall halt etwas anders, aber nicht besser.

In einem anderen Fall blieb das Erfolgserlebnis aus ähnlichen Gründen aus. Der Container mit der gewünschten Anwendung lies sich nicht in die Zielumgebung integrieren, da ein für die Kommunikation benötigtes Software-Modul fehlt. Erste Aussage des Herstellers: „Da müssen sie noch Paket XY im Container nachinstallieren.“

Halt Stopp! Sollten mit dem Container nicht alle notwendigen Abhängigkeiten mit ausgeliefert werden? Jetzt sollen wir Software im Container nachinstallieren, was zur Folge hat, dass wir damit ein neues Image erzeugen, welches wir zukünftig wieder selbst weiterpflegen dürfen? So habe ich mir die schöne neue Welt aber nicht vorgestellt. Den Aufwand mit den eigenen Anpassungen haben wir ohne Container auch. Auch hier wird es erstmal nur anders, aber nicht besser.

Allerdings möchte ich zur Ehrenrettung des Anbieters hinzufügen, dass er das fehlende Modul in sein Image einbauen und zukünftig selbst pflegen und ausliefern möchte und uns hier lediglich um Unterstützung beim Test bittet. Dies ist für mich im Sinne von FOSS und vollkommen akzeptabel. Wir wissen allerdings noch nicht, ob der Anbieter sein Versprechen hält.

Wo ist nun das Dilemma?

Ich persönlich habe eine Präferenz, wie Betriebsplattformen für Container aussehen sollten. So sehe ich die Nutzung von Rootless-Podman für einfache Anwendungen und Kubernetes bzw. Kubernetes-kompatible Lösungen für die Orchestrierung als sinnvoll an.

Leider kann ich mir nicht immer aussuchen, welche Software es zu betreiben gilt. Allzu oft muss mit dem gearbeitet werden, was bestellt bzw. geliefert wurde. Und hier sind die Probleme fast so zahlreich wie außerhalb der Container-Welt. Die einen laufen nur mit Docker, aber nicht im Rootless-Modus. Die anderen verlangen eine Docker-Swarm-Umgebung und Kubernetes mögen sie beide nicht. Das ist halt Pech.

Manchmal lassen sich die gelieferten Container auch ganz einfach starten, dafür aber überhaupt nicht in bestehende Umgebungen integrieren. So kann es schon in Arbeit ausarten, wenn man den voreingestellten Datenbank-Container herausoperieren muss, um den vorhandenen Datenbank-Cluster nutzen zu können.

Ein wenig neidisch blicke ich dabei zu Berufskollegen, die eigene Softwareentwicklung betreiben und von den Vorzügen ihrer Kubernetes-Cluster schwärmen.

Allerdings stehe ich mit meinen Erfahrungen noch ganz am Anfang und lasse mich gerne überraschen, was die Zukunft noch bringt.

Was habt ihr für Erfahrungen im Umgang und mit dem Betrieb von Containern gemacht? Könnt ihr hier Geschriebenes in Teilen nachvollziehen oder sind eure Erfahrungen völlig anderer Natur? Ich freue mich auf eure Beiträge in den Kommentaren und per E-Mail.

Bis bald im schönen neuen Container-Land.

Reverse-Proxy für Kanboard im Container

Im folgenden möchte ich kurz die Konfiguration dokumentieren, mit der ich Ziel 4 aus „Kanboard im Container…“ umgesetzt habe.

Zuerst habe ich meinen Pod mit dem Kanboard- und dem Posgresql-Container erstellt:

$ cat create-kanboard-pod.sh 
#!/bin/bash
kanboard_data=$(podman volume inspect kanboard_data --format {{.Mountpoint}})
kanboard_plugins=$(podman volume inspect kanboard_plugins --format {{.Mountpoint}})
kanboard_ssl=$(podman volume inspect kanboard_ssl --format {{.Mountpoint}})
pgsql_data=$(podman volume inspect pgsql_data --format {{.Mountpoint}})
podman run -d --pod new:kanboardpod --name kanboard -p 127.0.0.1:8080:80 -v $kanboard_data:/var/www/app/data:Z -v $kanboard_plugins:/var/www/app/plugins:Z kanboard/kanboard
podman run -d --pod kanboardpod --name pgsql_db -e POSTGRESQL_USER=root -e POSTGRESQL_PASSWORD=SuperGeheimesPasswort -e POSTGRESQL_DATABASE=kanboard -v $pgsql_data:/var/lib/pgsql/data:Z rhel8/postgresql-96

Als Reverse-Proxy nutze ich nginx, welchen ich mittels sudo dnf -y install nginx installiert habe. Die Konfigurationsdatei /etc/nginx/nginx.conf habe ich wie folgt ergänzt:

    server {
        listen 80;
        listen [::]:80;
        server_name kanboard.beispiel.de;
        return 301 https://kanboard.beispiel.de$request_uri;
    }

    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name kanboard.beispiel.de;

        ssl_certificate "/etc/letsencrypt/kanboard_fullchain.cer";
        ssl_certificate_key "/etc/letsencrypt/kanboard.beispiel.de.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers PROFILE=SYSTEM;
        ssl_prefer_server_ciphers on;

        location / {
            proxy_pass http://127.0.0.1:8080;
        }
    }

Damit nimmt der NGINX Anfragen auf den TCP-Ports 80 und 443 an, wobei ein Redirect von Port 80 auf 443 erfolgt. Diese Anfragen leitet der NGINX an den TCP-Port 8080 des Kanboard-Pods weiter.

Die Erneuerung des SSL-Zertifikats habe ich nach dem Tutorial „Let’s Encrypt: Nutzung des DNS-Alias-Modus mit dem acme.sh-Client“ automatisiert.

Backup und Restore im Kanboard-Container-Land

In „Kanboard im Container“ habe ich einen Pod ins Leben gerufen, welcher die Anwendung Kanboard und eine dazugehörige Postgresql-Datenbank mittels Container bereitstellt. Backup und Restore zu konfigurieren und zu testen, habe ich letztes Wochenende nicht mehr geschafft. Dies hole ich in diesem Artikel nach.

Da die Container selbst zustandslos sind, interessieren mich nur die persistent gespeicherten Daten, welche außerhalb der Container im Dateisystem des Hosts gespeichert werden.

Umgebung

Auf einer virtuellen Maschine mit dem Gastbetriebssystem RHEL 8 läuft ein podman-Pod namens kanboardpod. Dieser Pod beinhaltet neben dem Infrastruktur-Container, einen Kanboard-Container namens kanboard und einen Postgresql-Container namens pgsql_db.

Die persistenten Daten des Kanboard-Containers werden in den Volumes kanboard_data und kanboard_plugins gespeichert. Die Datenbank-Dateien der Postgresql-DB liegen im Volume pgsql_data.

Die Namen des Pods und der Container sind hilfreich, da die Container darüber referenziert werden können. So muss man nicht mit den sperrigen IDs hantieren.

Backup

Kein Restore ohne Backup! Kein Backup, kein Mitleid!

Ich habe im Folgenden beschriebe Ansätze für ein Backup. Wenn ihr noch weitere habt, freue ich mich über eure Eingaben.

Ansatz 1: Backup auf Dateiebene — verworfen

Dieser Ansatz liegt nahe und ist einfach umzusetzen.

  1. Container stoppen
  2. Verzeichnisse im Dateisystem sichern
  3. Container wieder starten

Für meinen konkreten Anwendungsfall wäre dies auch ausreichend. Einen Dienst für die Dauer einer Datensicherung komplett stoppen zu müssen, ist jedoch nicht ideal. Daher schaue ich nach weiteren Möglichkeiten.

Ansatz 2: DB-Dump und Datei-Backup — Praktikabel aber möglicherweise inkonsistent.

Bei diesem Ansatz bleibt die Anwendung während der Datensicherung verfügbar. Während das Volume kanboard_data auf Dateiebene mittels tar gesichert wird, wird die Datenbank mittels pg_dump aus dem Container heraus gesichert. Das Backup-Skript sieht wie folgt aus:

#!/bin/bash
kanboard_data=$(podman volume inspect kanboard_data --format {{.Mountpoint}})
kanboard_plugins=$(podman volume inspect kanboard_plugins --format {{.Mountpoint}})
tar czf kanboardfiles.tgz $kanboard_data $kanboard_plugins
podman exec -t pgsql_db /usr/bin/pg_dump kanboard | gzip > ~/kanboard.sql.gz

Hinweis: Obiger Code-Schnipsel stammt aus der Kategorie Schnell-und-Schmutzig und sollte nicht in produktiven Umgebungen verwendet werden.

Der DB-Dump wird dabei direkt aus dem Container in mein Home-Verzeichnis geschrieben. Am Ende habe ich zwei unabhängige Dateien, die nun außerhalb der Container-Umgebung liegen. Da diese jedoch immer noch auf dem gleichen Host liegen, handelt es sich um kein richtiges Backup. Doch ist es nun nicht mehr schwer, sie auf ein anderes, entferntes Medium zu übertragen.

Der größte Nachteil dieses Ansatzes besteht darin, dass die Konsistenz der Sicherung nicht garantiert ist. Die Datenbank enthält Referenzen in das Dateisystem. Referenzen in der Datenbank und Inhalt des gesicherten Dateisystems müssen nicht in jedem Fall zueinander passen, da während der Sicherung weiter in der Anwendung gearbeitet werden kann.

Nun kann man natürlich vor der Sicherung den Container kanboard stoppen und anschließend wieder starten. Damit erhält man eine konsistente Sicherung zu dem Preis, dass die Anwendung temporär nicht verfügbar ist. Damit unterscheidet sich der Ansatz gegenüber Ansatz 1 nur noch darin, dass der Container pgsql_db online bleibt und man die DB mit pg_dump sichert, statt eine Sicherung auf Dateisystemebene zu machen.

Schön ist das nicht, doch mache ich es meiner Umgebung genau so.

Ansatz 3: Backup auf Basis eines konsistenten Dateisystem-Snapshots

Dies ist in meinen Augen der vernünftigste Ansatz.

Die persistenten Daten der Container liegen in einem Dateisystem. Unterstützt dieses Dateisystem Snapshots, können diese genutzt werden, um die Dateisysteminhalte zum Zeitpunkt des Snapshots auf das Backup-Medium zu übertragen und den Snapshot anschließend wieder zu entfernen. Anwendung und Datenbank können bei diesem Verfahren während der Sicherung weiterlaufen.

Das so erstellte Datenbank-Backup befindet sich allerdings in einem Zustand, als wäre die Datenbank unsauber beendet worden. Nach einem Restore werden demnach die WAL-Logs benötigt (siehe PostgreSQL 9.6.20 Documentation: 25.2. File System Level Backup).

Ich habe mich gegen diesen Ansatz entschieden, da mir in meiner Test-Umgebung noch die Erfahrung mit Dateisystem-Snapshots fehlt. Dies werde ich evtl. zu einem späteren Zeitpunkt unter die Lupe nehmen.

Restore

Ich habe mich für den Ansatz 2 entschieden. Damit habe ich folgende zwei Dateien:

  • kanboard.tgz – Enthält die persistenten Daten des Kanboards
  • kanboard.sql.gz – Enthält den Dump der Kanboard-Datenbank

Das tar-Archiv wird extrahiert und fertig. Um den DB-Dump mit dem Werkzeug pg_restore wieder einspielen zu können, muss dieser zuvor in ein Volume kopiert werden, das innerhalb des Postgresql-Containers zur Verfügung steht. Anschließend kann die Datenbank mit folgendem Befehl wiederhergestellt werden:

# pg_restore -C -d kanboard kanboard.sql

Fazit

Grundsätzlich habe ich Ziel Nummer 5 „Backup und Restore“ ebenfalls erreicht.

Der Restore erfordert noch einiges an Handarbeit und ist etwas fummelig. Soetwas möchte man in einer angespannten Situation nicht gerne haben. Hier ist noch etwas Feinschliff nötig.

Darüber hinaus kann ich mir vorstellen auch noch einen automatisierten Restore-Test zu etablieren, welcher prüft, ob sich ein erstellter DB-Dump auch wieder herstellen lässt. Das ist dann aber sicher ein eigenes Wochenendprojekt. Und nächstes Wochenende mache ich mal frei.

Kanboard im Container…

… aber das sind ja gleich zwei Dinge auf einmal. Richtig. Denn hier versuche ich, etwas nützliches (Kanboard) mit der Möglichkeit, etwas zu lernen (Container), zu kombinieren.

Inspiriert durch Dirks Artikel und einem darauf folgenden, regen E-Mail-Verkehr, widme ich mich mal wieder dem Thema Linux-Container. Zuletzt hatte ich mich ca. 2016/2017 damit befasst und es mit der Erkenntnis zu den Akten gelegt, dass es noch ein Hype und für den produktiven Einsatz wenig geeignet war. Mittlerweile hat sich die Lage etwas geändert. Einige Unternehmen haben sich des Themas angenommen und arbeiten daran, Linux-Container Enterprise-Ready zu gestalten. So nehme ich wahr, dass in meinem beruflichen Netzwerk seit ca. Anfang 2019 OpenShift-Cluster wie Pilze aus dem Boden schießen. Man könnte den Eindruck gewinnen, dass Red Hat diese Subskriptionen wie geschnitten Brot verkauft. Andere Hersteller scheinen den relativ jungen Markt nicht allein den roten Hüten überlassen zu wollen. So hat VMware mit vSphere 7 und Tanzu hier ebenfalls eine Lösung im Portfolio und auch SUSE scheint sich mit dem Kauf von Rancher in diesem Segment stärker zu engagieren.

Ich selbst möchte mein Wissen rund um dieses Thema auffrischen und habe mir daher folgendes Wochenendprojekt überlegt. Um Projekte, Aufgaben oder schlicht den Alltag besser zu organisieren, möchte ich zukünftig die Anwendung Kanboard nutzen. Diese Anwendung unterstützt die Aufgaben- bzw. Projekt-Organisation nach der Kanban-Methode. Sie macht einen minimalistischen Eindruck, kommt ohne viel Schnick-Schnack daher und scheint daher gut zu mir zu passen. Um gleichzeitig praktische Erfahrungen im Umgang mit Linux-Containern zu sammeln, werde ich Kanboard mit einer Postgresql-Datenbank mit Hilfe von zwei Containern betreiben.

In meinen Augen wird Docker in den nächsten Jahren sowohl als Firma wie auch als Werkzeug stetig an Bedeutung verlieren. Daher setze ich bei der Umsetzung meines Wochenend-Projekts auf die Werkzeuge podman, skopeo und buildah.

Ich gehe in diesem Text nicht auf die Konzepte, die Architektur, sowie die Vor- und Nachteile von Linux-Containern ein. Hierzu wurde in den letzten Jahren bereits genug an anderer Stelle geschrieben. Informationen zu diesen Themen finden sich in der — im Aufbau befindlichen — Linksammlung und am Ende dieses Artikels.

Umfeld

Als Basis für dieses Projekt dient mir eine virtuelle Maschine in meinem heimischen Labor. Als Betriebssystem nutze ich ein aktuelles RHEL 8 mit der kostenlosen Developer-Subskription. Diese VM dient mir als Host zum Ausführen diverser Linux-Container. Um die Container aus dem Netzwerk erreichbar zu machen, installiere ich NGINX aus den Paketquellen von RHEL 8. Dieser kümmert sich als Reverse-Proxy um die Portweiterleitung zu den Containern.

Ziele

Mit diesem Wochenendprojekt möchte ich folgende Ziele erreichen:

  1. Bereitstellung der Anwendung Kanboard mittels Linux-Container
  2. Nutzung von Postgresql mittels Container als Kanboard-Datenbank-Backend
  3. Persistente Speicherung der Kanboard-Inhalte im Dateisystem des Hosts
  4. Erreichbarkeit und Nutzbarkeit von Kanboard über den NGINX-Reverse-Proxy
  5. Einrichtung Backup und Restore
  6. Updates

Schritt 1: rootless-Container-Umgebung einrichten

Während Entwickler viel Schweiß und Tränen investiert haben, damit Dienste wie Apache oder NGINX nach ihrem Start die root-Rechte ablegen können, liefen die ersten Linux-Container durchgängig mit root-Rechten. Dies ist aus Sicht der IT-Sicherheit nicht wünschenswert. Daher ist es in meinen Augen erfreulich, dass es mittlerweile auch ohne root-Rechte geht; Kernel User Namespaces sei Dank.

Ich folge der Red Hat Dokumentation (Kapitel 1.4, [1]), um den User Alice für die Nutzung von rootless-Containern einzurichten.

# echo "alice:165537:65536" >> /etc/subuid
[root@podhost-r8-1 ~]# echo "alice:165537:65536" >> /etc/subgid
[root@podhost-r8-1 ~]# echo "user.max_user_namespaces=65636" > /etc/sysctl.d/userns.conf
[root@podhost-r8-1 ~]# sysctl -p /etc/sysctl.d/userns.conf
user.max_user_namespaces = 65636

Anschließend installiere ich wie in Kap. 1.3 [1] beschrieben die Container-Tools.

# yum module install -y container-tools
$ podman --version
podman version 2.0.5

Der Werkzeugkasten ist bestückt. Weiter zu Schritt 2.

Schritt 2: Container-Images suchen, inspizieren und herunterladen

Mit dem Kommando podman mache ich mich auf die Suche nach Containern für Kanboard.

$ podman search kanboard
INDEX       NAME                                            DESCRIPTION                                       STARS   OFFICIAL   AUTOMATED
docker.io   docker.io/kanboard/kanboard                     Official Docker image for Kanboard                34
docker.io   docker.io/webhippie/kanboard                    Docker images for Kanboard                        2                  [OK]
docker.io   docker.io/larueli/kanboard-nonroot              Safe image for Kanboard as Non Root / Suitab...   0
docker.io   docker.io/masker/kanboard                       use alpine linux build kanboard server            0
docker.io   docker.io/xoxys/kanboard                        Deprecated                                        0
docker.io   docker.io/dotriver/kanboard                     Kanboard on Alpine Linux + S6 Overlay             0
docker.io   docker.io/thegeeklab/kanboard                   Custom image for Kanboard Kanban project man...   0
docker.io   docker.io/jonats/kanboard-pi                    Raspberry Pi image for Kanboard                   0
docker.io   docker.io/bastilian/kanboard                                                                      0
docker.io   docker.io/oriaks/kanboard                       Kanboard                                          0                  [OK]
docker.io   docker.io/kanboard/tests                                                                          0
docker.io   docker.io/blufor/kanboard                       Kanboard with Postgres, SMTP and GitLab inte...   0                  [OK]
docker.io   docker.io/boomer/kanboard                       Kanboard is a simple visual task board web a...   0
docker.io   docker.io/joshuacox/kanboard-redmine            kanboard redmine importer                         0                  [OK]
docker.io   docker.io/janost/kanboard-unit                  Kanboard + nginx unit, running rootless with...   0
docker.io   docker.io/benoit/kanboard                                                                         0                  [OK]
docker.io   docker.io/lidstah/kanboard                      Kanboard armv71 debian (nginx/php7-fpm) base...   0
docker.io   docker.io/doc75/kanboard                                                                          0
docker.io   docker.io/witsec/kanboard                       Kanboard, with the option to filter (hide) s...   0                  [OK]
docker.io   docker.io/ionutalexandru97/kanboard-openshift   Kanboard ready to be deployed on OpenShift        0
docker.io   docker.io/hihouhou/kanboard                     simple kanboard                                   0                  [OK]
docker.io   docker.io/alxsdhm/kanboard                      kanboard image                                    0
docker.io   docker.io/papango/kanboard                                                                        0
docker.io   docker.io/mrtheduke/kanboard                    kanboard                                          0
docker.io   docker.io/kvorobyev/kanboard_app

Herzlichen Glückwunsch. Die Trefferliste stellt für mich als SysAdmin einen Alptraum dar. Sämtliche Treffer stammen vom Docker-Hub, einem riesigen Misthaufen für Software (welcher durchaus ein paar Perlen enthalten kann). Von den 26 Treffern ist keiner als OFFICIAL markiert, lediglich die Anzahl STARS bietet einen Anhaltspunkt, welcher Container den meisten Zuspruch findet. In einer Produktiv-Umgebung sollte man sich jedoch nicht allein auf diese Sterne verlassen. Ich inspiziere das Container-Image mit den meisten Sternen mit skopeo:

$ skopeo inspect docker://docker.io/kanboard/kanboard | less

Die vollständige Ausgabe spare ich hier aus. Sie ist wenig hilfreich. Mit ein wenig Internet-Recherche ([2], [3] und [4]) bin ich hinreichend sicher, das „offizielle“ Container-Image des Projekts gefunden zu haben.

Als nächstes mache ich mich auf die Suche nach Postgresql:

$ podman search postgresql | wc -l
64

Naja, zumindest an Auswahl scheint es auch diesmal nicht zu mangeln. Hier komme ich so jedoch nicht weiter. Also nehme ich einen Webbrowser zur Hand und recherchiere ein geeignetes Container-Image unter der URL: https://catalog.redhat.com/software/containers/explore

Da ich Red Hat bereits zutraue, eine stabile und hinreichend sichere Enterprise Linux Distribution zu bauen, traue ich ihnen auch zu, ordentliche Container-Images für Postgresql zu bauen. Daher fasse ich folgende drei Kandidaten ins Auge:

  1. rhel8/postgresql-96
  2. rhel8/postgresql-10
  3. rhel8/postgresql-12

Zu diesem Zeitpunkt (2020-12-27) fehlt Nr. 3 eine ordentliche Beschreibung. Dafür kommt dieses Image mit 6 offenen Sicherheitslücken daher. Nr. 2 besitzt nur 3 Schwachstellen und eine deutliche bessere Dokumentation zu dessen Verwendung. Und Nr. 1 ist zwar das Älteste, jedoch auch das mit einer guten Dokumentation und ohne Schwachstellen.

Kanboard erfordert Postgresql >= 9.4. Damit ist Nummer 1 mein Gewinner. Mit den beiden folgenden Kommandos hole ich mir die Kanboard- und Postgresql-Container-Images auf meinen Host.

$ podman pull docker.io/kanboard/kanboard
Trying to pull docker.io/kanboard/kanboard...
Getting image source signatures
Copying blob df20fa9351a1 done  
Copying blob 3108c8300796 done  
Copying blob b190b4dd9bb5 done  
Copying blob bb1f52abd628 done  
Copying blob e37ffd2cbe7b done  
Copying config c355188e0c done  
Writing manifest to image destination
Storing signatures
c355188e0c187bc891826d282cc850cbe0907ccd7df28d4487d024d831c4f9af

$ podman login --username=Joerg-Dev registry.redhat.io
Password: 
Login Succeeded!
$ podman pull registry.redhat.io/rhel8/postgresql-96
Trying to pull registry.redhat.io/rhel8/postgresql-96...
Getting image source signatures
Copying blob cca21acb641a done  
Copying blob 620696f92fec done  
Copying blob fca753c96be9 done  
Copying blob d9e72d058dc5 done  
Copying config f7266b012d done  
Writing manifest to image destination
Storing signatures
f7266b012db03478b858eba6af4264829b99ce9ac67d6bc8a7c273b5fc5c8e9a

Damit ist dieser Schritt abgeschlossen. In Schritt drei erstelle ich sogenannte Volumes, um Daten außerhalb der Container persistent im Dateisystem des Hosts speichern zu können.

Schritt 3: Persistenten Speicher für Container erzeugen

Nach dem Container-Mantra haben diese zustandslos zu sein. Dies bedeutet, dass in ihnen gespeicherte Daten verloren gehen, wenn der Container entfernt wird. Nun hat es die Elektronische Datenverarbeitung (EDV) so an sich, dass das Ergebnis der Verarbeitung häufig persistent zu speichern ist. Dies kann im Container-Universum mit sogenannten Volumes erledigt werden. Hierbei wird ein Verzeichnis vom Host in den Container eingehängt.

Für mein Projekt erstelle ich nach Kapitel 3.4 [1] folgende Volumes:

  • kanboard_data
  • kanboard_plugins
  • kanboard_ssl
  • pgsql_db
$ podman volume create VOLUMENAME

Um im Folgenden etwas leichter mit diesen Volumes arbeiten zu können, speichere ich den Einhängepfad in Variablen à la:

$ mntPoint=$(podman volume inspect VOLUMENAME --format {{.Mountpoint}})

Die obige Streichung erfolgte, da dieser Schritt nicht notwendig ist und im weiteren Artikel nicht mit entsprechenden Variablen gearbeitet wird.

Schritt 4: Kanboard konfigurieren

Um eine angepasste, persistente config.php-Datei für den Kanboard-Container zu schreiben, ist etwas Vorarbeit notwendig. Der Kanboard-Container wird gestartet und das Volume „kanboard_data“ wird dabei in den Pfad /var/www/app/data gemountet. Anschließend starte ich eine Shell im Container und kopiere die Datei /var/www/app/config.default.php nach /var/www/app/data/config.php.

$ podman run -d --name kanboard -v kanboard_data:/var/www/app/data:Z  kanboard/kanboard
93e6d7e3847fb94639b8fce89ddb93a3879a80522f95ed13dff91f6558594ac6
$ podman ps
CONTAINER ID  IMAGE                               COMMAND  CREATED        STATUS            PORTS   NAMES
93e6d7e3847f  docker.io/kanboard/kanboard:latest           5 seconds ago  Up 5 seconds ago          kanboard
$ podman exec -it 93e6d7e3847f /bin/bash
bash-5.0# cp /var/www/app/config.default.php /var/www/app/data/config.php
bash-5.0# exit
exit
$ podman stop 93e6d7e3847f && podman rm 93e6d7e3847f
$ vi $kanboard_data/config.php

Um Postgresql als Datenbank-Backend zu nutzen, werden folgende Werte in der config.php gesetzt:

// Run automatically database migrations
// If set to false, you will have to run manually the SQL migrations from the CLI during the next Kanboard upgrade
// Do not run the migrations from multiple processes at the same time (example: web page + background worker)
define('DB_RUN_MIGRATIONS', true);

// Database driver: sqlite, mysql or postgres (sqlite by default)
define('DB_DRIVER', 'postgres');

// Mysql/Postgres username
define('DB_USERNAME', 'root');

// Mysql/Postgres password
define('DB_PASSWORD', 'SuperSicheresPasswort');

// Mysql/Postgres hostname
define('DB_HOSTNAME', 'localhost');

// Mysql/Postgres database name
define('DB_NAME', 'kanboard');

// Mysql/Postgres custom port (null = default port)
define('DB_PORT', null);

Normalerweise würde man eine Anwendung niemals mit dem Datenbank-Root-User auf eine Datenbank zugreifen lassen. In dieser Umgebung ist es hingegen verschmerzbar, da nur die Daten des Kanboards in diesem Postgresql-Container gespeichert werden. Im Falle einer Kompromittierung verliere ich nur die zur Anwendung gehörende Datenbank.

Schritt 5: Pod erstellen und Container hinzufügen

Mit diesem Schritt habe ich etwas Mühe. Zuerst wollte ich einen Pod erstellen, den Kanboard- und Postgresql-Container zu diesem hinzufügen, um sie stets gemeinsam starten und stoppen zu können. Dies ist laut [1] und [7] der einfachste Weg. Allerdings habe ich dann in [5] und [7] gelesen, dass sich die Container eines Pods dessen IP, MAC und Port-Bindings teilen. Dies bedeutet, dass Portfreigaben für Kanboard (80 und 443 TCP) auch für den Postgresql-Container gültig sind. Dies möchte ich eigentlich nicht. Doch ist mir bisher nichts besseres eingefallen. Falls ihr Anregungen oder konkrete Beschreibungen habt, wie ich dies besser umsetzen kann, immer her damit.

Frickelpit hat mich in seinem Kommentar darauf hingewiesen, dass man den Zugriff auf den Port des Pods noch weiter beschränken kann, indem man diesen an 127.0.0.1 bindet. Ich habe unten stehenden Code-Block entsprechend aktualisiert.

Ich erstelle nun gemäß [7] einen neuen Pod, welcher den Kanboard-Container beinhaltet und für diesen Port-Bindings besitzt:

$ podman run -d --pod new:kanboardpod --name kanboard -p 127.0.0.1:8080:80 -v kanboard_data:/var/www/app/data:Z -v kanboard_plugins:/var/www/app/plugins:Z kanboard/kanboard
e62c7fa2ecf771f4085e788e9f0f7d24b7f87d487e9951a403847d8a7a2a6471

$ podman pod ps
POD ID        NAME         STATUS   CREATED        # OF CONTAINERS  INFRA ID
d7afa6821382  kanboardpod  Running  8 seconds ago  2                6b065fe7ecc7

$ podman ps
CONTAINER ID  IMAGE                               COMMAND  CREATED         STATUS             PORTS                 NAMES
6b065fe7ecc7  k8s.gcr.io/pause:3.2                         10 seconds ago  Up 10 seconds ago  0.0.0.0:8080->80/tcp  d7afa6821382-infra
e62c7fa2ecf7  docker.io/kanboard/kanboard:latest           10 seconds ago  Up 10 seconds ago  0.0.0.0:8080->80/tcp  kanboard

Im zweiten Schritt füge ich den Postgresql-Container hinzu:

$ podman run -d --pod kanboardpod --name pgsql_db -e POSTGRESQL_USER=root -e POSTGRESQL_PASSWORD=SuperGeheimesPasswort -e POSTGRESQL_DATABASE=kanboard -v pgsql_data:/var/lib/pgsql/data:Z rhel8/postgresql-96
c242a4b9b57d53a822585c9eb83d081d5abbd40cb2b5952aee4457fee041e128

$ podman ps
CONTAINER ID  IMAGE                                          COMMAND         CREATED        STATUS            PORTS                 NAMES
6b065fe7ecc7  k8s.gcr.io/pause:3.2                                           2 minutes ago  Up 2 minutes ago  0.0.0.0:8080->80/tcp  d7afa6821382-infra
c242a4b9b57d  registry.redhat.io/rhel8/postgresql-96:latest  run-postgresql  3 seconds ago  Up 3 seconds ago  0.0.0.0:8080->80/tcp  pgsql_db
e62c7fa2ecf7  docker.io/kanboard/kanboard:latest                             2 minutes ago  Up 2 minutes ago  0.0.0.0:8080->80/tcp  kanboard

Nun läuft ein Pod mit drei Containern (Infra-, Kanboard- und Postgresql-Container). Rufe ich http://IP-DES-HOSTS:8080 in einem Webbrowser auf, begrüßt mich bereits die Kanboard-Anmeldemaske (Bild 1).

Bild 1: Kanboard-Anmeldemaske

Schritt 6: Nacharbeiten

Zwar konnte ich mich bereits an der Kanboard-Anwendung anmelden, neue Nutzer erstellen und Profileinstellungen verwalten, doch beim Dateiupload klemmte es noch. Hier musste ich in der config.php-Datei des Kanboard-Containers den folgenen Standardwert, wie im Codeblock gezeigt, angepassen:

// Folder for uploaded files (must be writeable by the web server user)
// Folgende Zeilen wurden auskommentiert
// define('FILES_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'files');

// Folgender Eintrag wurde hinzugefuegt
define('FILES_DIR', 'data/files');

Abschließend habe ich den Kanboard-Container mittels podman restart kanboard neugestartet.

Fazit

Bei der Internet-Recherche nach guter Dokumentation und der Arbeit mit einigen Container-Registries erinnere ich mich an ein Zitat:

Das Internet ist ein großer Misthaufen, in dem man allerdings auch kleine Schätze und Perlen finden kann.

Joseph Weizenbaum, Vortrag in Hamburg am 2. Mai 2001, heise.de

Bisher wurden die Ziele 1-3 erreicht. Die dabei verwendeten Befehlszeilen besitzen eine beachtliche Länge. Hier bietet es sich an, die Befehle in kurze Shell-Wrapper zu verpacken.

Die Ziele 4 und 5 werde ich in einem Folgeartikel, an einem anderen Wochenende, in Angriff nehmen. Und auch der Frage, wie man diesen Verhau am besten aktualisiert und Updates implementiert, werde ich noch nachgehen.

Ihr habt bis hierhin durchgehalten? Dann danke ich euch für euer Interesse. Was haltet ihr von diesem Wochend-Projekt? Wieviel Sinn bzw. Unsinn steckt darin? Bitte lasst es mich in den Kommentaren oder auch gern per E-Mail wissen.

Quellen und weiterführende Links

  1. Building, running, and managing Linux containers on Red Hat Enterprise Linux 8
  2. Kanban Project Management Software — Kanboard
  3. Running Kanboard with Docker
  4. Kaboard Releases
  5. https://podman.io/getting-started/network
  6. Podman: Managing pods and containers in a local container runtime; Brent Baude; January 15, 2019

Kommentar zu Docker – Genauso schlimm wie befürchtet

Marius hat gestern in Braunschweig darüber gebloggt, dass Docker – Genauso schlimm wie befürchtet ist. Bis Marius zu seiner Analyse kommt, stimme ich ihm voll und ganz zu. Auch ich gehöre zu den ewig gestrigen, den Nörglern, Pessimisten und Schwarzsehern, welche es von Anfang an gesagt haben. Und unsere düstere Vorsehung ist eingetreten. Welch Überraschung.

Wo ich Marius nicht mehr zustimme, sind die Ergebnisse seiner Analyse, welche er in der zweiten Hälfte seines Artikels vornimmt. So schreibt er dort:

Der sicherere Ansatz ist und bleibt, daß jemand das OS vorgibt und sich die Apps an die Umgebung anpassen müssen. Damit sind die gezwungen Updates zu machen und das dient dann der allgemeinen Sicherheit.

URL: https://marius.bloggt-in-braunschweig.de/2019/02/27/docker-genauso-schlimm-wie-befuerchtet/

Diese Aussage ist in meinen Augen falsch. Denn niemand wird gezwungen, Updates für die in einem Betriebssystem installierten Anwendungen zu veröffentlichen. Folgende Beispiele sollen dies verdeutlichen.

Man nehme ein herkömmliches Betriebssystem, sagen wir Ubuntu oder Red Hat Enterprise Linux. Unter beiden Betriebssystemen kann ich mir nun eine Anwendung als DEB- bzw. RPM-Paket oder auch ganz klassisch als TAR-Archiv aus dem Internet herunterladen und auf meinem Betriebssystem installieren. Habe ich nun eine Garantie, dass ich Sicherheitsupdates für diese Software erhalte? Die Antwort lautet: Nein.

Verwendet man eine nach obigem Muster installierte Software und nutzt diese gemeinsame Bibliotheken, mag man das Glück haben, dass die Software nach der Aktualisierung eben dieser Bibliotheken nicht mehr funktioniert. Ob man dies als Sicherheitsgewinn wertet oder es doch nur ein Ärgernis ist, sei einmal dahingestellt.

Im Ubuntuversum soll die Paketquelle universe als abschreckendes Beispiel dienen. In dieser befindet sich so viel alte und nicht mehr gepflegte Software, dass man auch hier die Chance hat, Anwendungen mit ein oder mehreren Sicherheitslücken zu finden.

Zugegeben, mit einer zentralen Paketverwaltung ist es einfacher, sein System aktuell zu halten, um es vor Sicherheitslücken zu schützen. Eine Garantie ist es nicht und kein Sysadmin sollte sich darauf ausruhen.

Abzulehnen ist also alles, was eine App installiert, die mit eigener Umgebung kommt, sowas wie FlatPak, Docker und Snap.

URL: https://marius.bloggt-in-braunschweig.de/2019/02/27/docker-genauso-schlimm-wie-befuerchtet

Marius schließt seinen Beitrag mit obigem Satz. Dieser ist mir zu pauschal. Denn FlatPak, Docker und Snap sind nicht per Definition unsicher. Wer sich jedoch ohne lange nachzudenken, den erstbesten Container vom Docker Hub herunterlädt ist selber schuld.

Es gibt jedoch auch die Möglichkeit eine eigene Registry für Container aufzusetzen und zu betreiben, um nur Container zu verwenden, die man selbst geprüft bzw. gebaut hat. Oder man nutzt die Registries der Enterprise Distributionen wie z.B. Red Hat OpenShift, welche sich gegen Entgelt um die Bereitstellung geprüfter Container-Images kümmern. Auch dies ist keine Garantie für (absolute) Sicherheit und Aktualität. Doch hat hier wenigstens jemand den Sicherheitsaspekt im Auge und arbeitet an diesem Thema. Dies ist im Vergleich zur völlig freien, unkontrollierten und schwer prüfbaren Müllhalde ein großer Vorteil.

Ähnliches gilt für FlatPak und Snap. Letztlich stehen und fallen die kommerziellen Angebote damit, wie gut ihr Anbieter seinen Job macht. Auch hier wurden schon Pakete gefunden, die neben ihrer eigentlichen Aufgabe noch einen Zweitjob (Crypto currency mining) hatten.

Für pauschale bzw. absolute Aussagen ist es in meinen Augen noch zu früh. Wir werden noch eine Weile abwarten müssen, wie sich die einzelnen Projekte weiterentwickeln. Als Sysadmin sollte man weiterhin stets auf der Hut sein und seinen gesunden Menschenverstand benutzen.