Normale Ansicht

Es gibt neue verfügbare Artikel. Klicken Sie, um die Seite zu aktualisieren.
Ältere BeiträgeMy-IT-Brain

Don’t Push To Production On Friday

25. September 2023 um 05:00

So stand es an einem Freitag auf Mastodon geschrieben. Nach einem Schmunzeln fragte ich mich: „Ja warum eigentlich nicht?“ Dieser Frage möchte ich heute nachgehen.

Der englischsprachige Satz aus dem Titel ist eine Aufforderung, an einem Freitag keine Änderungen an produktiven Systemen vorzunehmen, um das Wochenende nicht zu gefährden. Viele von euch kennen vermutlich berühmte letzte Worte wie:

  • Was soll schon schiefgehen?
  • Nur noch diese kleine Änderung, dann ist Feierabend.
  • Das wurde getestet, da kann nichts passieren.
  • Das geht ganz schnell, ich mache das noch eben.

Nicht selten hat sich der Feierabend oder das Wochenende nach diesen Sätzen erheblich verzögert oder sind ganz ausgefallen, weil eben doch etwas schiefgegangen ist. In der Folge waren wichtige Dienste nicht mehr verfügbar und Systemadministratoren haben das Abendessen mit ihrer Familie versäumt, weil sie den Klump wieder zum Laufen bringen mussten. Solche Erlebnisse führen zu Aussagen wie:

  • Never change a running system. Oder eben
  • Don’t push to production on Friday

Die Logik dahinter ist bestechend einfach. Wenn etwas funktioniert und man nichts daran ändert, wird wohl auch nichts kaputt gehen. Allerdings stehen diese Aussagen dem DevOps-Mantra von Continuous Integration and Continuous Delivery (CI/CD) entgegen, welches fordert, dass Änderungen zu jeder Zeit möglich sein müssen.

Und wer hat nun recht? Ich denke, die Wahrheit liegt wie so oft irgendwo in der Mitte.

Ob Änderungen durchgeführt werden können, hängt in meinen Augen nicht vom Wochentag ab, sondern vielmehr von den Antworten auf die folgenden Fragen:

  • Sind alle für die Abnahmetests erforderlichen Key-User nach der Änderungen verfügbar und können direkt im Anschluss testen?
  • Sind alle Verantwortlichen anwesend bzw. verfügbar, welche entscheiden, ob die Änderung bzw. das Deployment erfolgreich war oder nicht?
  • Liegt das Wartungsfenster in einem Zeitraum, in dem ggf. externe Supportdienstleister erreichbar und diese Zeiträume durch Service-Level-Agreements (SLA) abgedeckt sind?
  • Findet die Änderung in einem Zeitfenster statt, in dem Störungen toleriert werden können?

Sind zum Beispiel alle 37 Key-User, 8 Abteilungsleiterinnen und das 20-köpfige Betriebs-Team für die Personal- und Buchhaltungsanwendung Freitag nach 18:00 bis voraussichtlich 21:00 Uhr alle verfügbar und können im Fehlerfall mit offenem Ende verfügbar bleiben, steht einer Änderung bzw. einem Deployment nichts im Wege. Ist dies jedoch nicht der Fall und man stellt Fehler möglicherweise erst im Laufe des kommenden Montags fest, wo ein Rollback evtl. schon nicht mehr möglich ist, sollte man den Change vielleicht lieber Montagmorgen starten?

In einem anderen Fall ist das Team nicht sicher, ob sie das System im Fehlerfall ohne Hilfe des Herstellers wiederherstellen können. Der Support-Vertrag deckt jedoch nur die Zeiten Mo-Fr von jeweils 08:00-17:00 Uhr mit 4 Stunden Reaktionszeit ab. Hier ist es vielleicht ebenfalls besser, das Wartungsfenster in den frühen Morgen als in den Freitagabend zu legen.

Habe ich hingegen einen 24/7-Supportvertrag und meine IT-Betriebsabteilung darf auch am Wochenende arbeiten, bietet sich ein Change mit langer Dauer am Wochenende an, um die Betriebsabläufe möglichst wenig zu beeinträchtigen.

Sind Änderungen nur von kurzer Dauer und man möchte möglichst viele User verfügbar haben, die sofort testen und mögliche Fehler finden, ist vermutlich auch Dienstag Vormittag 10:00 Uhr eine gute Zeit.

Es hängt also nicht primär vom Wochentag, sondern vielmehr von einigen anderen Faktoren ab, wann Änderungen in Produktion ausgerollt werden sollten.

Wie seht ihr das? Nach welchen Kriterien werden bei euch Deployments geplant und durchgeführt?

PS: Ich finde jedoch absolut nichts Verwerfliches daran, wenn man sich den Freitag für die Pflege und Aktualisierung der Dokumentation reservieren kann und nicht mit aller Gewalt noch etwas kaputtfuckeln muss. ;-)

RHEL System Roles: rhc

11. September 2023 um 05:00

Im siebten Teil meiner losen Reihe über die RHEL System Roles stelle ich die Rolle rhc vor, mit welcher sich RHEL-Systeme (Version >= 8.6) in der Hybrid Cloud Console, Insights und dem RHSM registrieren lassen.

Das Tool rhc selbst habe ich bereits im Artikel Red Hat Remote Host Configuration ausführlich vorgestellt.

Anwendungsfall

Möchte man ein oder mehrere RHEL-Systeme in der Hybrid Cloud Console registrieren, kann man dazu die RHEL System Role rhc verwenden.

Die Rolle

Durch die Installation des Pakets rhel-system-roles existiert die Rolle rhc bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.rhc/ und die Dokumentation in /usr/share/doc/rhel-system-roles/rhc/README.md.

Das Playbook

- name: Register systems
  hosts: all
  vars:
    rhc_auth:
      activation_keys:
        keys: ["key-1", ...]
    rhc_organization: "your-organization"
  roles:
    - rhel-system-roles.rhc
  • key-1 ist durch den eigenen Activation Key zu ersetzen
  • your-organization ist durch die eigene Org-ID zu ersetzen
  • Mit diesem Playbook werden die Hosts im RHSM und der Hybrid Cloud Console registriert
  • Die Systeme werden bei Insights registriert und laden regelmäßig aktuelle Daten hoch
  • Die Systeme werden für die Ausführung von Remediation Playbooks konfiguriert

Fazit

Mit dieser System Role ist es einfach möglich, eine große Anzahl Systeme in die Hybrid Cloud Console aufzunehmen. Dabei lässt sich konfigurieren, ob Funktionen wie Insights und Remediation Playbooks genutzt werden können.

Eine weitere tolle Rolle aus dem Paket rhel-system-roles, die sich einfach zur Anwendung bringen lässt.

Weiterführende Quellen und Links

  1. Red Hat Enterprise Linux (RHEL) System Roles {en}
  2. Ansible Documentation: Role Directory Structure {en}
  3. Red Hat Software and Download Center {en}
  4. Die Vorteile einer Red Hat Subskription
  5. RHEL System Roles: selinux
  6. RHEL System Roles: timesync
  7. RHEL System Roles: sshd
  8. RHEL System Roles: firewall
  9. RHEL System Roles: storage

Wie ich mich 2023 vor Datenverlust schütze

04. September 2023 um 05:00

Auf Mobiltelefonen, Tablets, Laptops, Netzwerkspeichern und in diversen Anwendungen in unserem Heimnetzwerk befinden sich Daten, welche wir schmerzlich vermissen würden, wenn sie dauerhaft verloren gingen.

Im Folgenden beschreibe ich, wie ich z.B. Fotos, Videos, Zeugnisse, Verträge, Urkunden, Versicherungspolicen, etc. vor Verlust schütze.

Der Artikel behandelt nicht:

Daten von Mobiltelefonen und Tablets

Sie sind unsere ständigen Begleiter, verfügen über große Speicher und beinhalten häufig jede Menge an Fotos und Videos. Diese werden mit DS-Apps auf eine Synology Diskstation im heimischen Netzwerk gesichert.

Für Threema benutze ich Threema Safe. Ein Datenbackup führe ich nicht durch. Der Inhalt der Nachrichten ist mir nicht so wichtig, als dass mich ein Verlust schmerzen würde.

E-Mails, Kalender und Kontakte werden zwischen Mailbox.org und diversen Geräten synchronisiert, jedoch nicht gesichert. Wenn jemand z.B. das Adressbuch löscht, müsste ich das Netzwerk vom PC trennen, um das Adressbuch ohne Netzwerkverbindung zu starten, um den letzten Stand von dort wiederherstellen zu können. Für mich ist das ausreichend, da ich bei einem GAU meine wichtigsten Kontakte auch ohne Adressbuch zu erreichen weiß und sich Kontaktinformationen von Handwerkern und sonstigen Unternehmen wieder recherchieren lassen.

Sollte ich Zugriff auf die App Aegis Authenticater verlieren, muss ich auf die Backup-Codes zurückgreifen, welche in einer KeePass-Datenbank lagern.

Falls ihr einfache Lösungen kennt, um sämtliche Apps samt deren Inhalte sichern und auf abweichenden Telefonen wiederherstellen zu können, freue ich mich, wenn ihr mir davon berichtet.

Network Attached Storage (NAS)

Meine Synology Diskstation ist:

  • Das Ziel für automatisierte Datensicherungen von Mobiltelefonen und Tablets
  • Datensicherungsziel für die Backups meiner virtuellen Server im LAN und in der Cloud
  • Primärer Speicherort für Fotos, Videos, eine Musiksammlung, Git-Repos
  • Primärspeicher für archivierte Daten, die ich vermutlich nie wieder benötige

Ausgewählte Daten werden wöchentlich mit Hyper Backup (Backup-Anwendung der Diskstation) auf eine angeschlossene USB-Festplatte gesichert. Darüber hinaus habe ich mir ein Offsite-Backup gebastelt, welches ich in diesem Artikel beschrieben habe.

Über Erfolg und Misserfolg der Sicherungen werde ich per E-Mail benachrichtigt.

Die größte Herausforderung für mich ist es, die Wiederherstellbarkeit der gesicherten Daten zu kontrollieren. Dies mache ich bisher sporadisch und manuell. Und vermutlich viel zu selten.

Daten von Laptops

Daten meiner Laptops synchronisiere ich teilweise mit Nextcloud, welche ich auf einem virtuellen Server betreibe. Gehostet wird dieser bei Contabo in Deutschland.

Darüber hinaus nutze ich Déjà Dup Backups für eine wöchentliche Datensicherung meines HOME-Verzeichnisses in die Nextcloud mit 180 Tagen Vorhaltezeit. Auch hier teste dich die Wiederherstellbarkeit sporadisch.

Das HOME-Verzeichnis meines Dienstlaptops wird täglich mit Déjà Dup in das Google Drive meines Arbeitgebers gesichert.

Urkunden, Verträge, Zeugnisse und weiterer Papierkram

Auch wir haben jede Menge totes Holz im Schrank stehen, dessen Wiederbeschaffung bei Verlust mit viel Zeit und Mühe verbunden ist.

Meine Lösung für diese Herausforderung habe ich in Mein Paperless-NGX-Mini-Konzept niedergeschrieben.

Hier möchte ich die Wiederherstellung noch verbessern, indem ich auf meinem Laptop ein Ansible-Playbook ablege, welches die Paperless-NGX-Instanz auf meinem Laptop wiederherstellt. So teste ich die Wiederherstellbarkeit und habe immer eine relativ aktuelle Kopie auf der verschlüsselten Festplatte meines Laptops bei mir.

Auf einem virtuellen Server in der Cloud möchte ich diese Daten aktuell nicht so gerne hosten. Dazu muss ich mir zuvor in Ruhe Gedanken über mögliche Risiken für die Integrität und Vertraulichkeit der Daten machen.

Meine produktive Paperless-NGX-Instanz steht mit dem Papier im gleichen Haus. Das Backup beinhaltet alle PDF-Dateien und liegt verschlüsselt in der Cloud. Da die Dateinamen halbwegs sinnvoll benannt sind, scheint im Falle eines GAU die Suche im Heuhaufen nicht hoffnungslos.

Blog und Nextcloud

Für beide Dienste, welche auf einem virtuellen Server bei Contabo laufen, wird zur Datensicherung wöchentlich ein Datenbank-Dump und ein Archiv der Verzeichnisstruktur mit Ordnern und Dateien erstellt. Dieses Backup wird lokal auf dem Server abgelegt und für 30 Tage vorgehalten. Ich nutze dafür selbstgeschriebene Bash-Skripte, welche durch Cron ausgeführt werden.

Auf meinem NAS läuft ein Skript, welches die Backups vom Server auf das NAS synchronisiert.

Über Erfolg und Misserfolg der einzelnen Jobs werde ich per E-Mail benachrichtigt.

Die Wiederherstellbarkeit teste ich sporadisch und sollte dies mutmaßlich häufiger tun. Ein Automat dafür befindet sich aktuell in Arbeit. Den aktuellen Stand kann man in folgenden Artikeln nachlesen:

Virtuelle Server und Dienste in der Cloud

Ruhende Daten werden verschlüsselt, bevor sie in die Cloud hochgeladen werden. Das Restrisiko, dass der fremde Betreiber prinzipiell die Kontrolle über meinen virtuellen Server übernehmen kann, bleibt.

Betreibe ich Server oder Dienst nicht im eigenen Heimnetzwerk, nutze ich für den Betrieb deutsche Anbieter mit Standorten in Deutschland. Zu diesen habe ich einfach das größte Vertrauen, dass sie sich an geltende Gesetze halten, die Hoffnung, dass sie den Datenschutz ernst nehmen und meine Daten dort gut aufbewahrt sind.

Wie sichert ihr eure Daten außer Haus? Welche Dienste verwendet ihr dazu? Ich freue mich, wenn ihr eure Erfahrungen in den Kommentaren teilt.

Zusammenfassung

Ich habe mir zumindest mal Gedanken gemacht, welche Daten so wichtig sind, dass ich sie vor Verlust schützen möchte. Zum Schutz vor Verlust kommen verschiedene Verfahren zum Einsatz, die auf der Schaffung von Redundanz durch Synchronisierung oder der Datensicherung mit Versionierung beruhen.

Die Wiederherstellbarkeit wird zumindest sporadisch getestet. Dieser Punkt ist sicherlich ausbaufähig.

Wer die einzelnen Abschnitte gelesen hat, stellt jedoch auch fest, dass es schnell ein wenig unübersichtlich wird. Zumindest hilft dieser Artikel etwas, den Überblick zu behalten.

Für die Zukunft wünsche ich mir eine dedizierte Backup-Senke im Keller, in der sich ausschließlich Backups befinden und eine Offsite-Senke, auf welche die Daten der Backup-Senke gesichert werden. Bis ich da hinkomme, wird sicherlich noch etwas Zeit vergehen.

Wie installiere ich den Netzwerk-Drucker Brother MFC-J890DW in Fedora 38 und RHEL 9?

31. August 2023 um 07:24
  1. Schalte den Drucker ein, verbinde ihn mit dem LAN und notiere seine IP-Adresse
  2. Lade das RPM-Paket mit dem Linux-Treiber auf den RHEL 9 Computer herunter
  3. Führe in RHEL 9 folgende Schritte aus
  4. sudo dnf in <linux-treiber-name.rpm>
  5. Öffne in einem Webbrowser die URL http://localhost:631/printers
  6. Klicke unter Administration auf „Modify Printer“ und setze die folgenden Parameter
    • AppSocket/HP JetDirect für Device
    • ipp://<IP-Adresse-des-Druckers>/ipp/port1 für Device URI
    • Brother als Hersteller
    • IPP Everywhere als Treiber

Da es mich mal wieder tierisch genervt hat, bis das Höllengerät endlich druckte, habe ich die Installationsschritte kurz aufgeschrieben.

Quellen:

Container mit podman-auto-update automatisch aktualisieren

28. August 2023 um 05:00

In diesem Tutorial zeige ich euch, wie ihr eine automatische Aktualisierung für Container in rootless-Podman-Umgebungen konfigurieren und diese Container als systemd-Services verwalten könnt.

Das Tutorial gliedert sich in folgende Abschnitte:

  1. Anwendungsfälle
  2. Voraussetzungen
  3. Umgebung und verwendetes Container-Image
  4. Konfiguration des systemd-Service mit Auto-Update-Funktion
  5. Container (automatisch) aktualisieren

Wer sich nicht für die möglichen Anwendungsfälle interessiert und lieber gleich starten möchte, kann den ersten Abschnitt überspringen. Die übrigen Abschnitte sollten in der angegebenen Reihenfolge gelesen werden.

Anwendungsfälle

  • Container werden auf einem Single-Container-Host ausgeführt und nicht in K8s-Umgebungen
  • Man vertraut dem Anbieter, dass dieser stabile und nutzbare Container-Images bereitstellt
  • Es soll regelmäßig geprüft werden, ob aktualisierte Container-Images vorhanden sind
  • Sind aktuellere Images vorhanden, sollen laufende Container entfernt und unter Verwendung der aktuellen Images neu erstellt werden

Voraussetzungen

Um diesem Tutorial folgen zu können, benötigt ihr einen Host mit einer rootless-Podman-Umgebung. Podman muss dabei in der Version >= 3.3 verfügbar sein. In der folgenden Liste findet ihr einige Links, die euch helfen, eine solche Umgebung aufzusetzen.

Darüber hinaus solltet ihr Manpages lesen können.

Umgebung und verwendetes Container-Image

Das Betriebssystem spielt eine untergeordnete Rolle, da wir durch die Verwendung von Containern die Anwendung vom Betriebssystem entkoppeln. Alle zur Ausführung der Anwendung notwendigen Abhängigkeiten sind im Container-Image enthalten.

Uptime Kuma ist eine schlanke und schnelle Monitoring-Anwendung, welche unter anderem als Container-Image bereitgestellt wird. Ich habe die Anwendung als Beispiel für dieses Tutorial ausgewählt, da ich die Anwendung selbst nutzen möchte und so Synergieeffekte nutzen kann.

Wer ein anderes Container-Image nutzen möchte, muss in den folgenden Beispielen louislam/uptime-kuma:latest durch den fully qualified container name des zu nutzenden Images ersetzen.

Für die Konfiguration werden die auf dem System verfügbaren Podman-Manpages benutzt.

Konfiguration des Systemd-Service mit Auto-Update-Funktion

Bei den folgenden Schritten habe ich mich am Beispiel aus podman-auto-update(1) orientiert. Ich zeige zuerst den jeweils auszuführenden Befehl in einem Code-Block, gefolgt von einer Erläuterung der genutzten Optionen.

Podman-Volume erzeugen

$ podman volume create uptime-kuma
uptime-kuma

Um Daten persistent speichern zu können, müssen diese außerhalb des Containers abgelegt werden. Der dargestellte Befehl erzeugt ein Podman-Volume mit dem Namen „uptime-kuma“.

Mit dem folgenden Befehl lassen sich detaillierte Informationen zum gerade erstellten Volume anzeigen:

$ podman volume inspect uptime-kuma
[
     {
          "Name": "uptime-kuma",
          "Driver": "local",
          "Mountpoint": "/home/tronde/.local/share/containers/storage/volumes/uptime-kuma/_data",
          "CreatedAt": "2023-08-22T20:52:06.477341481+02:00",
          "Labels": {},
          "Scope": "local",
          "Options": {},
          "MountCount": 0,
          "NeedsCopyUp": true,
          "NeedsChown": true
     }
]

Der Schlüssel Mountpoint enthält den Pfad im lokalen Dateisystem, in dem das Volume erstellt wurde.

Manpages zum Nachschlagen:

  • podman-volume(1)
  • podman-volume-create(1)
  • podman-volume-inspect(1)

Container starten

$ podman run --label "io.containers.autoupdate=registry" -d -p 3001:3001 -v uptime-kuma:/app/data:Z --name=uptime-kuma docker.io/louislam/uptime-kuma:latest
Trying to pull docker.io/louislam/uptime-kuma:latest...
Getting image source signatures
Copying blob d7ca72974892 done  
Copying blob 475646a04a73 done  
Copying blob 1d496c24aec8 done  
Copying blob 6a3727d8681d done  
Copying blob b00c91ba9805 done  
Copying blob ddade83992f9 done  
Copying blob b8454ed537a7 done  
Copying blob 849e609eff67 done  
Copying blob f861188db6a1 done  
Copying blob 5f3c3e6d7f1c done  
Copying blob 4f4fb700ef54 skipped: already exists  
Copying blob 68b7bcf7c878 done  
Copying config fb3a3565b2 done  
Writing manifest to image destination
Storing signatures
ad7b049d9b84962311f5bafb5329f59961d8a031e54a571f079b8243ea8059ee
  • podman run ist das Kommando, mit dem ein neuer Container gestartet wird
  • Die Option --label "io.containers.autoupdate=registry" gibt an, dass Podman die Remote-Registry prüft, ob dort ein aktualisiertes Image vorhanden ist; dieses Label ist Voraussetzung, um die Auto-Update-Funktion nutzen zu können
  • Mit der Option -d wird der Container im Hintergrund gestartet und die Container-ID auf STDOUT ausgegeben
  • Durch -p 3001:3001 wird der Host-Port 3001 mit dem Port der Anwendung (ebenfalls 3001) im Container verbunden
  • Die Option -v uptime-kuma:/app/data:Z hängt das im vorhergehenden Schritt erstellte Podman-Volume in das Verzeichnis /app/data innerhalb des Containers ein; :Z sorgt dafür, dass der SELinux-Kontext korrekt gesetzt wird
  • --name=uptime-kuma spezifiziert den Namen des Containers; dieser ist etwas leichter zu merken als die Container-ID
  • Der Befehl endet mit dem fully qualified container name docker.io/louslam/uptime-kuma:latest
  • Die letzte Zeile des Code-Blocks enthält die Container-ID

Manpages zum Nachschlagen:

  • podman-run(1)
  • podman-auto-update(1)

Systemd-Service-Unit mit podman-generate-systemd erstellen

$ podman generate systemd --name --new uptime-kuma
# container-uptime-kuma.service
# autogenerated by Podman 4.4.1
# Tue Aug 22 21:29:46 CEST 2023

[Unit]
Description=Podman container-uptime-kuma.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
	--cidfile=%t/%n.ctr-id \
	--cgroups=no-conmon \
	--rm \
	--sdnotify=conmon \
	--replace \
	--label io.containers.autoupdate=registry \
	-d \
	-p 3001:3001 \
	-v uptime-kuma:/app/data:Z \
	--name=uptime-kuma docker.io/louislam/uptime-kuma:latest
ExecStop=/usr/bin/podman stop \
	--ignore -t 10 \
	--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
	-f \
	--ignore -t 10 \
	--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all

[Install]
WantedBy=default.target
  • Der Befehl gibt den Inhalt der generierten Service-Unit auf STDOUT aus
  • Die Option --name verwendet den Namen des Containers anstelle der Container-ID im Dateinamen der Service-Unit (hier: container-uptime-kuma.service)
  • Wichtig ist die Option --new, um Container von aktualisierten Images erstellen zu können; ohne diese Option können Systemd-Units Container nur unter Verwendung des ursprünglichen Images starten und stoppen und ein Auto-Update ist nicht möglich
  • Der folgende Code-Block fügt dem Befehl die Option --files hinzu, um eine Service-Unit-Datei zu erstellen
$ podman generate systemd --name --new --files uptime-kuma
/home/tronde/container-uptime-kuma.service

Manpages zum Nachschlagen:

  • podman-auto-update(1)
  • podman-generate-systemd(1)

Die erstellte Systemd-Unit aktivieren und starten

$ mv -Z container-uptime-kuma.service ~/.config/systemd/user/container-uptime-kuma.service
$ systemctl --user daemon-reload
  • Der erste Befehl verschiebt die Service-Unit in einen Pfad, wo systemd sie findet und einlesen kann
  • Die Option -Z stellt sicher, dass die Datei den SELinux-Kontext des Zielverzeichnisses zugewiesen bekommt, andernfalls kann systemd die Datei ggf. nicht verarbeiten
  • Durch den zweiten Befehl wird die Unit-Datei systemd bekannt gemacht
  • An dieser Stelle ist der neue Systemd-Service geladen, jedoch inaktiv
$ systemctl --user status container-uptime-kuma.service
○ container-uptime-kuma.service - Podman container-uptime-kuma.service
     Loaded: loaded (/home/tronde/.config/systemd/user/container-uptime-kuma>
     Active: inactive (dead)
       Docs: man:podman-generate-systemd(1)
$ podman stop uptime-kumauptime-kuma
$ podman rm uptime-kumauptime-kuma
$ systemctl --user start container-uptime-kuma.service
$ systemctl --user status container-uptime-kuma.service
● container-uptime-kuma.service - Podman container-uptime-kuma.service     Loaded: loaded (/home/tronde/.config/systemd/user/container-uptime-kuma>     Active: active (running) since Tue 2023-08-22 21:59:56 CEST; 14s ago
…
  • Der erste Befehl in obigen Code-Block prüft den aktuellen Status des Service
  • Der zweite und dritte Befehl stoppen und entfernen den laufenden Container, den wir weiter oben gestartet haben
  • Befehl Nummer 4 startet den Uptime-Kuma-Service
  • Befehl Nummer 5 prüft den neuen Status des Service; dieser ist nun up-and-running

Manpages zum Nachschlagen:

  • mv(1)
  • systemd.unit(5)
  • systemctl(1)

Auf neue Container-Images prüfen

$ podman auto-update --dry-run --format "{{.Image}} {{.Updated}}"
docker.io/louislam/uptime-kuma:latest false
  • Durch die Option --dry-run wird sichergestellt, dass nur auf die Verfügbarkeit neuer Images geprüft wird, es werden jedoch keine Pull-Operationen ausgeführt und keine Container neu erstellt
  • Es wird eine Liste von Container-Images ausgegeben, die mit dem Label io.containers.autoupdate=registry gestartet wurden
  • Die erste Spalte enthält den Image-Namen
  • Die zweite Splate zeigt an, ob ein Update verfügbar ist; in diesem Fall ist kein Update verfügbar (false)

Container (automatisch) aktualisieren

Wurde die Konfiguration erfolgreich abgeschlossen, können die entsprechenden Container durch folgenden Befehl manuell aktualisiert werden:

$ podman auto-update
            UNIT                           CONTAINER                   IMAGE                                  POLICY      UPDATED
            container-uptime-kuma.service  df21116f2573 (uptime-kuma)  docker.io/louislam/uptime-kuma:latest  registry    false

Leider ist aktuell kein Update verfügbar, weshalb es hier nichts zu tun gibt und der Status von Updated gleich false ist.

Podman bringt bei der Installation die beiden systemd units podman-auto-update.timer und podman-auto-update.service mit, welche zumindest unter RHEL 9 manuell aktiviert werden müssen:

$ systemctl --user enable podman-auto-update.{service,timer}
Created symlink /home/tronde/.config/systemd/user/default.target.wants/podman-auto-update.service → /usr/lib/systemd/user/podman-auto-update.service.
Created symlink /home/tronde/.config/systemd/user/timers.target.wants/podman-auto-update.timer → /usr/lib/systemd/user/podman-auto-update.timer.

$ systemctl --user start podman-auto-update.timer
$ systemctl --user status podman-auto-update.{service,timer}
○ podman-auto-update.service - Podman auto-update service
     Loaded: loaded (/usr/lib/systemd/user/podman-auto-update.service; enabled; preset: disabled)
     Active: inactive (dead)
TriggeredBy: ● podman-auto-update.timer
       Docs: man:podman-auto-update(1)

● podman-auto-update.timer - Podman auto-update timer
     Loaded: loaded (/usr/lib/systemd/user/podman-auto-update.timer; enabled; preset: disabled)
     Active: active (waiting) since Sat 2023-09-02 20:56:09 CEST; 1s ago
      Until: Sat 2023-09-02 20:56:09 CEST; 1s ago
    Trigger: Sun 2023-09-03 00:12:22 CEST; 3h 16min left
   Triggers: ● podman-auto-update.service
  • Der Timer startet jeden Tag um Mitternacht den Auto-Update-Service
  • Der Service prüft, ob aktualisierte Container-Images verfügbar sind und führt ggf. ein Update der Container durch
  • Schlägt ein Start nach Aktualisierung des Container-Images fehl, wird der Dienst automatisch von der vorherigen Image-Version gestartet; siehe --rollback in podman-auto-update(1)
  • Der folgende Code-Block zeigt den Status, nachdem ein Update durchgeführt wurde
$ systemctl --user --no-pager -l status podman-auto-update
○ podman-auto-update.service - Podman auto-update service
     Loaded: loaded (/usr/lib/systemd/user/podman-auto-update.service; enabled; preset: disabled)
     Active: inactive (dead) since Sun 2023-09-03 00:12:56 CEST; 7h ago
TriggeredBy: ● podman-auto-update.timer
       Docs: man:podman-auto-update(1)
    Process: 309875 ExecStart=/usr/bin/podman auto-update (code=exited, status=0/SUCCESS)
    Process: 310009 ExecStartPost=/usr/bin/podman image prune -f (code=exited, status=0/SUCCESS)
   Main PID: 309875 (code=exited, status=0/SUCCESS)
        CPU: 5.128s

Sep 03 00:12:50 example.com podman[309875]: Copying config sha256:d56b643e048f2d351ed536ec9a588555dfd4c70de3c8d510ed61078a499ba464
Sep 03 00:12:50 example.com podman[309875]: Writing manifest to image destination
Sep 03 00:12:50 example.com podman[309875]: Storing signatures
Sep 03 00:12:51 example.com podman[309875]: 2023-09-03 00:12:41.98296115 +0200 CEST m=+1.880671312 image pull  docker.io/louislam/uptime-kuma:latest
Sep 03 00:12:55 example.com podman[309875]:             UNIT                           CONTAINER                   IMAGE                                  POLICY      UPDATED
Sep 03 00:12:55 example.com podman[309875]:             container-uptime-kuma.service  814407c7312c (uptime-kuma)  docker.io/louislam/uptime-kuma:latest  registry    true
Sep 03 00:12:56 example.com podman[310009]: fb3a3565b2da641402e99594e09b3cdadd1b9aa84f59e7960b9961662da5ff65
Sep 03 00:12:56 example.com podman[310009]: 2023-09-03 00:12:55.421998943 +0200 CEST m=+0.020260134 image remove fb3a3565b2da641402e99594e09b3cdadd1b9aa84f59e7960b9961662da5ff65 
Sep 03 00:12:56 example.com systemd[686]: Finished Podman auto-update service.
Sep 03 00:12:56 example.com systemd[686]: podman-auto-update.service: Consumed 5.128s CPU time.

Ich hoffe, das Tutorial hat euch gefallen und konnte euch einen Eindruck vermitteln, wie man automatische Updates für Container konfigurieren kann.

Red Hat Remote Host Configuration

21. August 2023 um 05:00

Im folgenden Text gebe ich eine Einführung in Red Hat Remote Host Configuration (rhc). Dabei handelt es sich um ein Werkzeug, um Red Hat Enterprise Linux (RHEL) System mit der Hybrid Cloud Console zu verbinden und aus dieser heraus verwalten zu können.

Der Artikel soll interessierten RHEL-Nutzern zur Information und Wissensvermittlung dienen. Dazu wird rhc auch im Kontext subscription-manager und insights-client eingeordnet.

Aus Gründen der Transparenz weise ich hiermit darauf hin, dass ich Angestellter der Firma Red Hat bin.

Hintergrund

Als ich persönlich angefangen habe, mich für RHEL zu interessieren, war die Version 7 aktuell und von Simple Content Access (SCA) noch keine Rede. Um RHEL-Systeme betreiben zu können, waren diese beim Red Hat Subscription Management (RHSM), einem Satellite-Server oder offline zu registrieren, um Subscription Entitlements zuweisen zu können. Hierfür gab und gibt es das Kommando subscription-manager.

Im Laufe der Zeit kam der Dienst Red Hat Insights hinzu, zu welchem es hier im Blog bereits eine Einführung gab. Um Systeme hierfür zu registrieren, gibt es das Kommando insights-client. Die Console, die einst nur Red Hat Insights beheimatete, hat sich zur Hybrid Cloud Console entwickelt, welche heute Heimat für viele weitere Dienste rund um RHEL, OpenShift und die Ansible Automation Platform (AAP) ist.

Es hat sich viel getan. Dank SCA [1] entfällt die Notwendigkeit, Entitlements zuweisen zu müssen und das Subscription Management befindet sich im Übergang zur Hybrid Cloud Console. Übergang bedeutet hier insbesondere, dass viele Teile in Bewegung sind und sich in Zukunft noch ändern werden. Der Artikel unter [6] gibt einen Überblick dazu.

Hybrid Cloud Console mit Insights und Ansible Remediation Playbooks

Die folgenden vier Absätze wurden der Dokumentation entnommen und mit www.DeepL.com/Translator (kostenlose Version) übersetzt.

Die Red Hat Hybrid Cloud Console ist eine webbasierte, einheitliche Verwaltungsoberfläche für Red Hat-Lösungen. Mit der Hybrid Cloud Console können Sie eine Verbindung zu Ihren verschiedenen Plattformen herstellen und dann Ihre Hybrid Cloud und die darin enthaltenen Systeme zentral verwalten und automatisieren.

Verwenden Sie die Hybrid Cloud Console, um Ihre RHEL-Infrastruktur, Red Hat OpenShift-Cluster, AAP-Infrastruktur und Anwendungsdienste zu verwalten.

Die Red Hat Hybrid Cloud Console bietet einen zentralen Einblick in Betrieb, Sicherheit und Subscriptions für Red Hat Enterprise Linux (RHEL).

Mithilfe von Tools, regelbasierten Analysemodellen und der Unterstützung von Red Hat können Sie die Konsole nutzen, um viele der Aufgaben und Analysen zu optimieren, die für den Aufbau und die Bereitstellung einer stabilen und sicheren Umgebung für Anwendungen auf RHEL erforderlich sind.

Dem Marketing-Text der vorstehenden Absätze möchte ich einen Hinweis hinterherschicken. Mit der Hybrid Cloud Console verhält es sich wie mit allen extern gehosteten Cloud-Diensten. Hat der Anbieter ein Problem oder ist die Cloud bzw. das Internet nicht verfügbar, ist auch der Cloud-Dienst nicht verfügbar. Bei der Fähigkeit, meine Infrastruktur zu administrieren, möchte ich mich persönlich daher nicht allein auf einen externen Dienst verlassen und empfehle dies auch niemanden. In meinen Augen ist die Hybrid Cloud Console ein zusätzliches Werkzeug, welches mit Red Hat Insights einen hohen Mehrwert bietet.

In den nun folgenden Abschnitten beschreibe ich, wie in der Hybrid Cloud Console ein Activation Key erstellt wird und wie man diesen nutzt, um Systeme mittels rhc in der Console zu registrieren. Anschließend zeige ich, wie dank rhc Ansible Remediation Playbooks direkt aus der Console heraus auf verbundenen RHEL-Systemen ausgeführt werden können.

Ob man einem extern durch Dritte gehosteten Dienst das Recht einräumen möchte, Änderungen an den eigenen Server-Systemen durchführen zu können, muss jeder für sich selbst und seine Umgebung bewerten. Ich möchte hier lediglich die Funktionalität in meiner Lab-Umgebung demonstrieren.

Activation Key erstellen

Um einen Activation Key zu erstellen [10], meldet man sich an der Hybrid Cloud Console (https://console.redhat.com) an und tippt in das Suchfeld im oberen Bereich „create activation key“ ein.

Startseite der Hybrid Cloud Console. In das Suchfeld im oberen Bereich der Seite wurde der Text "create activation key" eingegeben. Es werden zwei Suchergebnisse angezeigt.
Ausgefüllte Suchmaske in der Hybrid Cloud Console

Der erste Treffer führt uns zu folgender Maske, in der ein Activation Key erstellt werden kann:

Menü zur Erstellung von Activation Keys
Dialog zum Erstellen von Activation Keys. Hier werden die Parameter Name, Role, SLA und Usage definiert.
Dialog zur Erstellung des Activation Keys

Nach einem Klick auf die Schaltfläche Create activation key erscheint der oben dargestellte Dialog. Die Optionen, die unter Role, Service Level Agreement (SLA) und Usage zur Auswahl stehen, hängen von den im Account vorhandenen Subscriptions ab. Mit ihnen wird der sogenannte System Purpose bestimmt. Der Name kann frei gewählt werden. Er erscheint anschließend in der Übersicht.

Übersicht existierender Activation Keys
Übersicht der existierenden Activation Keys

Hinweis: Die Organization ID und der Name des Activation Key sind vertraulich zu behandeln, da mit diesen Informationen Systeme für die Hybrid Cloud Console registriert werden können.

System mit rhc registrieren

Mit dem Befehl rhc -h erhält man eine Beschreibung, wie Organization ID und Activation Key genutzt werden, um das System bei Red Hat zu registrieren:

DESCRIPTION:
   The rhc command controls the system's connection to Red Hat.
   
   To connect the system using an activation key:
     rhc connect --organization ID --activation-key KEY

Führt man den Befehl wie angegeben aus und ist die Registrierung erfolgreich, erhält man folgende Ausgabe:

Connecting host.example.com to Red Hat.
This might take a few seconds.

● Connected to Red Hat Subscription Management
● Connected to Red Hat Insights
● Activated the Remote Host Configuration daemon
● Enabled console.redhat.com services: remote configuration, insights, remediations, compliance

Successfully connected to Red Hat!

Manage your connected systems: https://red.ht/connector

Unter der URL https://red.ht/connector ist der Remote Host Configuration Manager erreichbar. Hier werden die aktuellen Einstellungen angezeigt und können bei Bedarf geändert werden.

Das Bild zeigt die Konfigurationsseite des Remote Host Configuration Manager. Hier werden die Einstellungen für Insights Remediations, rhc Einstellungen und OpenSCAP Policies vorgenommen.
Darstellung der Seite Remote Host Configuration Manager

Der rhc Client konfiguriert auf dem RHEL host den rhcd service, welcher die Verbindung zur Hybrid Cloud Console initiiert und über eine MQTT-Verbindung auf Instruktionen lauscht [14].

Möchte man mehrere Systeme registrieren, empfehle ich die Verwendung der RHEL System Role rhc. Auf diese werde ich in einem folgenden Beitrag noch genauer eingehen.

Die Registrierung und Einbindung in die Hybrid Cloud Console ist damit abgeschlossen.

Ansible Remediation Playbook erstellen und ausführen

Die offizielle Dokumentation für die folgenden Schritte befindet sich unter [12]. Ich habe ein System gewählt, welches noch nicht aktualisiert wurde und daher einige Schwachstellen aufweist.

Das Bild zeigt eine Übersicht von CVEs. Zwei CVEs wurden für die Remediation mit Ansible ausgewählt.
Übersicht der vorhandenen CVE. Zwei Einträge wurden für die Remediation mit Ansible ausgewählt.

In der Übersicht können CVE ausgewählt werden, welche mit Hilfe eines Ansible Remediation Playbook geschlossen werden sollen. Mit einem Klick auf die Schaltfläche Remediate gelangt man in den Assistenten zur Erstellung des Playbooks.

Das Bild zeigt einen Dialog, in dem ein Name für ein neues Playbook vergeben wird.
Der Name des Playbooks kann frei gewählt werden.
Im dargestellten Dialog werden die ausgewählten Systeme angezeigt. Eine Bearbeitung ist hier nochmals möglich.
In Schritt zwei können verwundbare Systeme ausgewählt werden.
Das Bild zeigt den letzten Dialog des Remediate-Assistenten. Es wird auf einen automatischen Neustart hingewiesen.
Review der Einstellungen mit dem Hinweis, dass das Zielsystem durch das Playbook automatisch neugestartet wird.
Das Bild zeigt einen Screenshot von der erfolgreichen Erstellung eines Playbooks.
Bis hierher wurde nur ein Playbook erstellt. Es wurde noch keine Remediation durchgeführt.

Die erstellten Playbooks findet man im Menü unter Red Hat Insights –> Automation Toolkit –> Remediations. Bisher kann das Playbook hier allerdings nur heruntergeladen werden, um es auf einem Ansible Controller in der eigenen Infrastruktur auszuführen. Um diese Playbooks direkt aus der Hybrid Cloud Console heraus ausführen zu können, muss der verwendete User Mitglied einer Gruppe mit der Rolle Remediations administrator sein.

Ein Exkurs in die Rollen- und Rechteverwaltung der Hybrid Cloud Console würde an dieser Stelle zu weit führen. Nachdem die Voraussetzungen für die Ausführung von Remediation Playbooks geschafften wurden, stehen folgende Schritte zur Verfügung.

In der Ansicht des Remediation Jobs kann das Playbook nun direkt ausgeführt werden.
Eine letzte Bestätigung, dann geht es los.

Im Hintergrund passiert nun folgendes:

  1. Das Playbook wird auf den oder die Hosts übertragen
  2. Auf den Hosts wird es durch die dort lokal installierte Ansible Engine (Paket ansible-core) ausgeführt
  3. Der Host wird anschließend automatisch neugestartet
  4. In der Console wird anschließend sichbar, dass die Remediation abgeschlossen wurde

Ob man einem SaaS-Dienst, der von einem US-Unternehmen in den USA gehostet wird, Zugriff auf die eigenen Server gewähren möchte bzw. darf, muss individuell bewertet werden.

Ich gestehe dem Service allerdings zu, dass er die Verwaltung und Remediation von Sicherheitslücken, fehlenden Advisories und Konfigurationsanpassungen durch den Advisor denkbar einfach gestaltet.

Ein Werkzeug für alles?

Für die in diesem Text aufgeführten Anwendungsfälle

  • Registrieren eines RHEL-Hosts an der Hybrid Cloud Console
  • Ausführen von Ansible Remediation Playbooks

ist die Verwendung des rhc-Clients ausreichend; ein Ersatz für den insights-client ist er allerdings nicht. Letzterer wird im Hintergrund weiterhin verwendet, um Insights-Reports an die Hybrid Cloud Console zu senden.

Auch die vielfältigen Optionen des subscription-manager werden nicht abgebildet. Der rhc-Client ist daher mehr eine Ergänzung als ein Ersatz für die bekannten Kommandos.

Fazit

Der rhc-Client ist in meinen Augen das Mittel der Wahl, möchte man RHEL-Systeme für die Verwaltung durch Insights und die Ausführung von Ansible Remediation Playbooks an die Hybrid Cloud Console anbinden.

Ich hoffe euch interessierten Lesern, die bishierhin ausgehalten haben, hat diese Einführung gefallen. In der folgenden Liste findet ihr einige Links, hinter denen ihr euer Wissen noch vertiefen könnt.

Quellen und weiterführende Links

  1. Remote Host Configuration and Management – Using the remote host configuration and management features for Red Hat Insights
  2. Simple Content Access
  3. Red Hat Subscription Management
  4. Red Hat Insights
  5. Einführung in Red Hat Insights
  6. Transition of Red Hat’s subscription services to console.redhat.com
  7. Red Hat Hybrid Cloud Console
  8. Product Documentation for Red Hat Hybrid Cloud Console 2023
  9. Subscription-Manager for the former RHN user, part 13: System Purpose
  10. Remote Host Configuration and Management: Chapter 6. Creating and managing activation keys in the Red Hat Hybrid Cloud Console
  11. Automating system administration by using RHEL System Roles: Chapter 7. Using the rhc System Role to register the system
  12. Creating and managing remediation playbooks in Insights
  13. Executing remediation playbooks
  14. Remote Host Configuration (rhc)

Warum entwickelt/testet ihr (nicht) auf CentOS Stream, Fedora oder RHEL?

07. August 2023 um 05:00

Hi, mein Name ist Jörg. Ich arbeite seit März 2023 als Senior Technical Account Manager für Red Hat und mir schwirren derzeit folgende Fragen im Kopf herum:

  • Wer sind die Menschen, die Open Source Software auf bzw. für CentOS Stream, Fedora und/oder RHEL entwickeln?
    • Sind es Menschen, die dies ausschließlich in ihrer Freizeit tun?
    • Arbeiten sie in Unternehmen, welche nach dem Open Source Entwicklungsmodell arbeiten?
  • Warum habt ihr euch für oder gegen die eine oder andere Distribution entschieden?
  • Was hindert euch daran, eine der genannten Distributionen zu verwenden?
  • Aus welchem Grund bevorzugt ihr andere Distributionen und welche sind dies?

Hinsichtlich dieser Fragen habe ich selbst offensichtlich einen Interessenskonflikt und bin darüber hinaus zu einem hohen Grad betriebsblind. Deshalb bin ich umso mehr daran interessiert, eure Antworten auf diese Fragen zu lesen.

Ich freue mich, wenn ihr euch die Zeit nehmt, um mir zu antworten und mir zu erläutern, wie ihr dazu steht. Eure Nachrichten nehme ich gern auf folgenden Kanälen entgegen:

  • Als Kommentar unter diesem Artikel
  • Als E-Mail an jkastning+distribution (at) my-it-brain (dot) de
  • Als Chat-Nachricht in #my-it-brain:matrix.org

Es freut mich, wenn daraus eine freundliche und konstruktive Diskussion entsteht. Sollte es dabei allerdings zu Trolling oder unangemessenen Äußerungen kommen, werde ich die Kommentare schließen und die Kommunikation einstellen. Bitte geht daher höflich miteinander um und behandelt einander so, wie ihr selbst auch behandelt werden möchtet.

Mein Paperless-NGX-Mini-Konzept

31. Juli 2023 um 05:00

Paperless-NGX ist ein bekanntes und beliebtes Open Source Dokumenten-Management-System (DMS). Auch ich möchte zukünftig meinen „Papierkram“ darin verwalten.

In diesem Artikel halte ich meine Gedanken fest, wie ich plane, paperless-ngx in meiner Umgebung aufzusetzen und zu betreiben.

Dies hilft mir, zu prüfen, ob ich auch an alles Wichtige gedacht habe. Falls euch das Konzept gefällt, dürft ihr es selbstverständlich gerne nachahmen. Und wenn ihr schwerwiegende Fehler darin entdeckt, freue ich mich über einen Hinweis.

Es ist kein Tutorial und keine Schritt-für-Schritt-Anleitung. Zwar mag dieser Text dazu dienen, sich eine eigene Umgebung aufzubauen, Mitdenken ist dabei jedoch zwingend erforderlich.

Ziele

  • Betrieb von Paperless-NGX als rootless-Podman-Container
  • Consumption-Ordner als Samba-Share freigegeben, um via Netzwerk Dateien hineinkopieren zu können
  • Getrennte Benutzerkonten für meine Frau und mich
  • Freigabe gewisser Dokumente für weitere Benutzer(gruppen)
  • Erfolgreicher Restore-Test

Infrastruktur

In meinem Heimnetzwerk betreibe ich einen Desktop-/Server-PC. Auf diesem läuft aktuell RHEL 9 als KVM/QEMU-Hypervisor. Er dient mir ebenfalls als Ansible Control Node. Hierauf betreibe ich eine RHEL-9-VM mit einer rootless-Podman-Installation. Diese VM wird auch meine Paperless-NGX-Instanz hosten.

In der VM wird das EPEL-Repository aktiviert, um daraus die Pakete podman-compose und python3-pexpect installieren zu können.

Falls ich mal nicht mehr weiß, wie dieses Setup aufgebaut wird, finden sich dazu Hinweise in folgenden Links:

Installation mit Ansible

Für die Installation der Anwendung wird die Container Route verwendet. Die Installation wird dabei unter Nutzung der Ansible-Rolle tronde.paperless_ngx_with_rootless_podman automatisiert.

Das Playbook deploy_paperless_ngx.yml, welches auf meiner Synology Diskstation abgelegt ist, führt die Installation und Konfiguration der Anwendung durch. Es installiert und konfiguriert zudem Samba und die Datei-Freigabe des Consumption-Verzeichnisses.

In dem Playbook werden folgende Rollen verwendet:

Die Rollen sind mit dem Playbook in meinem Ansible-Projekt-Verzeichnis auf meiner Synology Diskstation installiert.

Alle Playbooks und Rollen werden mit Git versioniert. Die Repositories werden auf entfernte Rechner synchronisiert.

Vorbereitung

Die Dateien docker-compose.postgres-tika.yml, docker-compose.env und .env werden aus dem Projekt-Repository in das Rollen-Verzeichnis files meiner Ansible-Rolle heruntergeladen. Die Datei docker-compose.postgres-tika.yml wird dabei zu docker-compose.yml umbenannt und bearbeitet.

Um Datenverlust vorzubeugen, wird die Ansible-Rolle mit den angepassten Dateien in die regelmäßige Datensicherung aufgenommen.

Folgende Variablen werden in meinem Ansible-Vault hinterlegt:

# Paperless-ngx with podman-compose
pnwrp_podman_user: alice
pnwrp_podman_group: alice
pnwrp_compose_dir: /home/{{ pnwrp_podman_user }}/paperless-ngx
pnwrp_paperless_superuser: alice
pnwrp_paperless_superuser_email: alice@example.com
pnwrp_paperless_superuser_password: ImWunderland
## Username and password for document scanner
brother_scanner_user: scanner
brother_scanner_pass: ImWunderland

Die Werte der Variablen werden selbstverständlich angepasst.

Das Playbook

Folgender Code-Block zeigt das fertige Playbook mit Beispielwerten:

---
- hosts: host.example.com
  remote_user: alice
  debugger: never
  vars_files:
    - files/alice.vault
  tasks:
    - name: Setup Paperless-NGX with podman-compose in rootless Podman
      include_role:
        name: ansible_role_paperless-ngx_with_rootless_podman

    - name: Enable Port 8000/tcp in host firewall
      ansible.posix.firewalld:
        port: 8000/tcp
        immediate: true
        permanent: true
        state: enabled
      become: true

    - name: >-
        Create and add {{ brother_scanner_user }} to
        {{ pnwrp_podman_group }}
      ansible.builtin.user:
        name: "{{ brother_scanner_user }}"
        comment: "Brother Dokumenten-Scanner"
        create_home: false
        groups: "{{ pnwrp_podman_group }}"
        append: true
        shell: /usr/sbin/nologin
        state: present
        system: true
      become: true

    - name: Include role vladgh.samba.server
      include_role:
        name: vladgh.samba.server
      vars:
        ansible_become: true
        samba_users:
          - name: "{{ pnwrp_podman_user }}"
            password: "{{ alice_password }}"
          - name: "{{ brother_scanner_user }}"
            password: "{{ brother_scanner_pass }}"
        samba_shares:
          - name: consumption
            comment: 'paperless consumption directory'
            path: "{{ pnwrp_compose_dir }}/consume"
            read_only: false
            guest_ok: false
            browseable: true
            owner: "{{ pnwrp_podman_user }}"
            group: "{{ pnwrp_podman_group }}"
            write_list: +{{ pnwrp_podman_group }}

    - name: Enable samba in host firewall
      ansible.posix.firewalld:
        service: samba
        immediate: true
        permanent: true
        state: enabled
      become: true

Das Playbook gewinnt sicher keinen Schönheitswettbewerb, doch arbeitet es bisher robust und tut, was es soll. In Tests habe ich meine Wunschumgebung damit mehrmals provisioniert.

Backup & Restore

Wie es sich gehört, wird erst ein Backup erstellt, dann die Anwendung inkl. aller Daten gelöscht und wiederhergestellt.

Backup

Für das Backup verwende ich ein Ansible-Playbook, welches sich zum Podman-Host verbindet und dort folgende Aufgaben ausführt:

  1. Stelle sicher, dass das Backup-Verzeichnis auf dem Remote-Host existiert
  2. Stoppe alle Paperless-NGX-Container
  3. Exportiere Podman-Volumes in TAR-Archive; hänge das aktuelle Datum an die Dateinamen an
  4. Archiviere und komprimiere paperless-ngx-Verzeichnis auf Remote-Host; hänge das aktuelle Datum an die Dateinamen an
  5. Starte alle Paperless-NGX-Container
  6. Komprimiere die Exporte der Podman-Volumes
  7. Synchronisiere das Backup-Verzeichnis auf dem Remote-Host mit einem Verzeichnis auf meiner Diskstation
  8. Synchronisiere das Diskstation-Verzeichnis mit einem verschlüsselten S3-Bucket

Es fehlt die Aufgabe, alte Backups aufzuräumen. Darum werde ich mich kümmern, wenn 70% des verfügbaren Speicherplatzes belegt sind.

Der folgende Code-Block zeigt ein Muster des verwendeten Playbooks:

---
- name: Backup podman volumes
  hosts: host.example.com
  gather_facts: true
  vars:
    paperless_ngx_dir: /home/alice/paperless-ngx
    docker_compose_file: docker-compose.yml
    remote_backup_dir: /home/alice/backups
    diskstation_backup_dir: /home/alice/diskstation/home/backups/host.example.com

  tasks:
    - name: Ensure backup directory exists
      ansible.builtin.file:
        path: "{{ remote_backup_dir }}"
        state: directory
        owner: alice
        group: alice
        mode: 0777

    - name: Stop paperless-ngx containers
      ansible.builtin.command: >
        podman-compose -f {{ paperless_ngx_dir }}/{{ docker_compose_file }} stop

    - name: List podman volumes
      ansible.builtin.command: podman volume ls --quiet
      register: __podman_volumes
      tags:
        - volumes

    - name: Output __podman_volumes
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{ __podman_volumes['stdout_lines'] }}"
      tags:
        - volumes

    - name: Export podman volumes
      ansible.builtin.command: >
        podman volume export {{ item }} --output {{ remote_backup_dir }}/{{ item }}_{{ ansible_facts['date_time']['date'] }}.tar
      loop: "{{ __podman_volumes['stdout_lines'] }}"

    - name: Compact {{ paperless_ngx_dir }}
      community.general.archive:
        path: "{{ paperless_ngx_dir }}"
        dest: "{{ remote_backup_dir }}/paperless-ngx_{{ ansible_facts['date_time']['date'] }}.tar.gz"
        format: gz

    - name: Start paperless-ngx containers
      ansible.builtin.command: >
        podman-compose -f {{ paperless_ngx_dir }}/{{ docker_compose_file }} start

    - name: Compress volume exports
      community.general.archive:
        path: "{{ remote_backup_dir }}/{{ item }}_{{ ansible_facts['date_time']['date'] }}.tar"
        format: gz
        remove: true
      loop: "{{ __podman_volumes['stdout_lines'] }}"
      tags:
        - compress

    - name: Sync backups to diskstation
      ansible.posix.synchronize:
        archive: true
        compress: false
        delete: false
        dest: "{{ diskstation_backup_dir }}"
        mode: pull
        private_key: /home/alice/.ssh/ansible_id_rsa
        src: "{{ remote_backup_dir }}/"
      delegate_to: localhost
      tags:
        - rsync

    - name: Sync backups from diskstation to contabo S3
      ansible.builtin.command: rclone sync -P ../backups/ secure:backups
      delegate_to: localhost
      tags:
        - rsync

Das Playbook wird einmal wöchentlich ausgeführt. Ob ich für die geplante Ausführung cron oder systemd timer units verwende, habe ich noch nicht entschieden. Ich tendiere aus Neugier zu letzterem.

Um ein Offsite-Backup zu haben, werden die Dateien von der Diskstation einmal wöchentlich verschlüsselt und in einen S3-Speicher synchronisiert. Ich nutze dafür das Programm rclone und S3-kompatiblen Speicher von Contabo. Die folgenden Links bieten weiterführende Informationen dazu:

Restore

Der Ablaufplan für die Wiederherstellung der Anwendung mit ihren Daten sieht wie folgt aus:

  1. Eine rootless-Podman-Umgebung bereitstellen
  2. podman-compose bereitstellen
  3. TAR-Archive auf Zielsystem übertragen
  4. Paperless-NGX mit Playbook installieren
  5. Alle Container stoppen
  6. Inhalt der TAR-Archive in die Podman-Volumes importieren (siehe podman-volume-import(1)): gunzip -c hello.tar.gz | podman volume import myvol -
  7. Alle Container starten

Fazit

Bereitstellung, Sicherung und Wiederherstellung funktionieren wie beschrieben. Damit kann ich nun beginnen und die Anwendung konfigurieren und mit meinem Papierkram füttern.

Die Samba-Freigabe habe ich ebenfalls getestet. Sie funktioniert wie erwartet. PDF-Dateien mit dem Befehl find Documents -type f -iname "*.pdf" -exec cp {} /consume \; hineinzukopieren ist übrigens besonders dann eine gute Idee, wenn man sehen möchte, welche Dateien so in den Tiefen des eigenen Dateisystems schlummern.

Mit Ansible über YAML Lists and Dictionaries iterieren

17. Juli 2023 um 05:00

In diesem Artikel beschreibe ich die beiden Ansible-Variablen-Typen „List variables“ und „Dictionary variables“ sowie die Kombination beider Typen. Ich zeige mit einem einfachen Playbook, wie diese Variablen-Typen in einer Schleife (eng. loop) durchlaufen werden können.

Während der Text mir zur Übung und Erinnerung dient, hoffe ich, dass er für die Einsteiger unter euch eine hilfreiche Einführung bietet. Für weiterführende Informationen verlinke ich im Text direkt auf die Ansible-Dokumentation.

List

Eine Liste ist eine Variable mit einem Namen und einem bis mehreren Werten. Folgender Code-Block zeigt die List-Variable namens list mit ihren zwei Werten:

list:
  - Alice Cooper
  - Bob Marley

Die Einrückung der Werte ist wichtig. Sie erhöht nicht nur die Lesbarkeit, sondern vermeidet auch Lint-Fehler bei der Anwendung von ansible-lint. Bedauerlicherweise läuft euer Playbook auch, wenn ihr die Werte nicht einrückt, doch bitte ich euch, euch diesen schlechten Stil nicht anzugewöhnen.

Listen sind mit Arrays verwand. Sie besitzen einen Index, welcher bei 0 beginnt und für jedes Listen-Element (für jeden Wert) um 1 inkrementiert wird. Folgendes Beispiel zeigt, wie man das Listen-Element mit dem Wert „Alice Cooper“ der einfachen Variable favorit zuweisen kann:

favorit: "{{ list[0] }}"

Dictionary

Ein Dictionary speichert Daten in Schlüssel-Wert-Paaren (excuse my German). Dabei darf der Wert eines Dictionary wiederum ein Dictionary sein.

Ein einfaches Dictionary sieht wie folgt aus:

Felder:
  Feld1: 10ha
  Feld2: 40ha

Möchte man z.B. auf den Wert des Schlüssels Feld2 aus dem Dictionary Felder zugreifen, geht dies wie folgt:

mein_feld: "{{ Felder['Feld2'] }}"
# oder
mein_feld: "{{ Felder.Feld2 }}"

Die beiden folgenden Code-Blöcke zeigen zwei Beispiele für etwas komplexere Dictionaries, über die ich später iterieren werde:

dict:
  Alice:
    last_name: Cooper
    job: singer
  Bob:
    last_name: Marley
    job: singer
virtual_machines_with_params:
  vm1:
    cpu_count: 2
    memory_mb: 2048
    guest_os: rhel8
  vm2:
    cpu_count: 2
    memory_mb: 1024
    guest_os: rhel9

Auch hier ist die Einrückung sehr wichtig. Macht man dabei einen Fehler, fängt man sich einen Syntax-Fehler bei der Ausführung des Playbooks ein.

List of Dictionaries

Beide zuvor beschriebenen Variablen-Typen können miteinander kombiniert werden. Folgendes Beispiel zeigt eine Liste von Dictionaries:

list_of_dicts:
  - first_name: Alice
    last_name: Cooper
    job: singer
  - first_name: Bob
    last_name: Marley
    job: singer

Syntaxfehler

In Ansible und YAML spielt die Einrückung von Code eine sehr wichtige Rolle. Des Weiteren ist bei der Kombination von Variablen-Typen nicht alles erlaubt. Folgender Code-Block zeigt ein fehlerhaftes Beispiel und die Fehlermeldungen, die es generiert:

$ cat nonsense.yml 
---
nonsense:
 - first_name: Alice
     last_name: Cooper
     job: singer
 - first_name: Bob
     last_name: Marley
     job: singer

$ ansible-lint nonsense.yml
WARNING  Listing 1 violation(s) that are fatal
load-failure: Failed to load YAML file
nonsense.yml:1 mapping values are not allowed in this context
  in "<unicode string>", line 4, column 15


             Rule Violation Summary              
 count tag          profile rule associated tags 
     1 load-failure min     core, unskippable    

Failed after : 1 failure(s), 0 warning(s) on 1 files.

Tipp: Um Fehler bei der Eingabe zu vermeiden, habe ich meinen Editor Vim mit folgenden Optionen konfiguriert: set ts=2 sts=2 sw=2 et ai cursorcolumn

Playbook

Das folgende Playbook nutzt das Modul ansible.builtin.debug, um Werte der genannten Variablen-Typen auszugeben. Es zeigt dabei, wie diese Variablen in einer Schleife durchlaufen werden können.

Damit es übersichtlich bleibt, nutze ich Tags, um die Tasks im Playbook einzeln ausführen zu können.

$ cat output_dicts_and_lists.yml 
---
- name: Output content of dicts_and_lists.yml
  hosts: localhost
  gather_facts: false
  become: false
  vars_files:
    - dicts_and_lists.yml
  tasks:
    - name: Task 1 Ouput all vars in dicts_and_lists.yml
      loop:
        - "{{ list }}"
        - "{{ dict }}"
        - "{{ list_of_dicts }}"
      ansible.builtin.debug:
        var: item
      tags:
        - dicts_and_lists

    - name: Task 2 Loop over some list
      loop: "{{ list }}"
      ansible.builtin.debug:
        msg: "Name: {{ item }}"
      tags:
        - list

    - name: Task 3 Loop over some dictionary
      loop: "{{ dict | dict2items }}"
      ansible.builtin.debug:
        msg: "Firstname: {{ item.key }} Lastname: {{ item.value.last_name }}"
      tags:
        - dict

    - name: Task 4 Loop over some list_of_dicts
      loop: "{{ list_of_dicts }}"
      ansible.builtin.debug:
        msg: "Firstname: {{ item.first_name }} Lastname: {{ item.last_name }}"
      tags:
        - list_of_dicts

Task 1: Ouput all vars in dicts_and_lists.yml

Dies ist der erste Task aus obigem Playbook. Er gibt die Werte der Variablen list, dict und list_of_dicts aus. Diese habe ich als Liste an loop übergeben (siehe Loops in der Dokumentation).

$ ansible-playbook output_dicts_and_lists.yml --tags dicts_and_lists
…
PLAY [Output content of dicts_and_lists.yml] ***********************************

TASK [Task 1 Ouput all vars in dicts_and_lists.yml] ****************************
ok: [localhost] => (item=['Alice Cooper', 'Bob Marley']) => {
    "ansible_loop_var": "item",
    "item": [
        "Alice Cooper",
        "Bob Marley"
    ]
}
ok: [localhost] => (item={'Alice': {'last_name': 'Cooper', 'job': 'singer'}, 'Bob': {'last_name': 'Marley', 'job': 'singer'}}) => {
    "ansible_loop_var": "item",
    "item": {
        "Alice": {
            "job": "singer",
            "last_name": "Cooper"
        },
        "Bob": {
            "job": "singer",
            "last_name": "Marley"
        }
    }
}
ok: [localhost] => (item=[{'first_name': 'Alice', 'last_name': 'Cooper', 'job': 'singer'}, {'first_name': 'Bob', 'last_name': 'Marley', 'job': 'singer'}]) => {
    "ansible_loop_var": "item",
    "item": [
        {
            "first_name": "Alice",
            "job": "singer",
            "last_name": "Cooper"
        },
        {
            "first_name": "Bob",
            "job": "singer",
            "last_name": "Marley"
        }
    ]
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

In der Ausgabe ist zu erkennen, dass Listen mit [ ] und Dictonaries mit { } umschlossen werden. Der folgende Code-Block zeigt zum Vergleich den Inhalt der Datei dicts_and_lists.yml.

---
list:
  - Alice Cooper
  - Bob Marley

dict:
  Alice:
    last_name: Cooper
    job: singer
  Bob:
    last_name: Marley
    job: singer

list_of_dicts:
  - first_name: Alice
    last_name: Cooper
    job: singer
  - first_name: Bob
    last_name: Marley
    job: singer

Task 2: Loop over some list

Als Nächstes schauen wir uns die Ausgabe von Task 2 an, welcher lediglich die einzelnen Listenelemente nacheinander ausgibt.

$ ansible-playbook output_dicts_and_lists.yml --tags list
PLAY [Output content of dicts_and_lists.yml] ***********************************

TASK [Task 2 Loop over some list] **********************************************
ok: [localhost] => (item=Alice Cooper) => {
    "msg": "Name: Alice Cooper"
}
ok: [localhost] => (item=Bob Marley) => {
    "msg": "Name: Bob Marley"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Die Variable item referenziert das jeweils aktuelle Element der Liste, welche mit loop durchlaufen wird.

Task 3: Loop over some dictionary

Um Scrollen zu vermeiden, zeigten die beiden folgenden Code-Blöcke noch einmal den entsprechenden Task aus obigem Playbook und das Dictionary, welches für dieses Beispiel benutzt wird. Der dritte Code-Block zeigt dann die dazugehörige Ausgabe.

    - name: Task 3 Loop over some dictionary
      loop: "{{ dict | dict2items }}"
      ansible.builtin.debug:
        msg: "Firstname: {{ item.key }} Lastname: {{ item.value.last_name }}"
      tags:
        - dict

Bevor ein Dictionary mit loop verarbeitet werden kann, muss es in eine Liste transformiert werden. Dies geschieht mit Hilfe des Filters dict2items.

In einigen älteren Playbooks sieht man statt loop ein Lookup-Plugin in der Form with_dict: "{{ dict }}". Dies ist ebenfalls korrekt, heute jedoch nicht mehr gebräuchlich.

dict:
  Alice:
    last_name: Cooper
    job: singer
  Bob:
    last_name: Marley
    job: singer
$ ansible-playbook output_dicts_and_lists.yml --tags dict

PLAY [Output content of dicts_and_lists.yml] ***********************************

TASK [Task 3 Loop over some dictionary] *****************************************
ok: [localhost] => (item={'key': 'Alice', 'value': {'last_name': 'Cooper', 'job': 'singer'}}) => {
    "msg": "Firstname: Alice Lastname: Cooper"
}
ok: [localhost] => (item={'key': 'Bob', 'value': {'last_name': 'Marley', 'job': 'singer'}}) => {
    "msg": "Firstname: Bob Lastname: Marley"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Task 4: Loop over some list_of_dicts

Auch hier sind Dictionaries involviert, doch wird kein Filter dict2items benötigt, da es sich bereits um eine Liste handelt, welche an loop übergeben wird.

Die drei folgenden Code-Blöcke zeigen die verwendete Liste, den Task aus obigem Playbook und die Ausgabe.

list_of_dicts:
  - first_name: Alice
    last_name: Cooper
    job: singer
  - first_name: Bob
    last_name: Marley
    job: singer
    - name: Task 4 Loop over some list_of_dicts
      loop: "{{ list_of_dicts }}"
      ansible.builtin.debug:
        msg: "Firstname: {{ item.first_name }} Lastname: {{ item.last_name }}"
      tags:
        - list_of_dicts
$ ansible-playbook output_dicts_and_lists.yml --tags list_of_dicts

PLAY [Output content of dicts_and_lists.yml] ***********************************

TASK [Loop over some list_of_dicts] ********************************************
ok: [localhost] => (item={'first_name': 'Alice', 'last_name': 'Cooper', 'job': 'singer'}) => {
    "msg": "Firstname: Alice Lastname: Cooper"
}
ok: [localhost] => (item={'first_name': 'Bob', 'last_name': 'Marley', 'job': 'singer'}) => {
    "msg": "Firstname: Bob Lastname: Marley"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Auch hier ist wieder an den { } zu erkennen, dass item jeweils eine Liste enthält. Der Zugriff auf die Werte geschieht durch die Referenzierung des jeweiligen Schlüssel-Namens. So ist z.B. Alice der Wert des Schlüssels first_name.

Bonusmaterial: Lists in Dicts

Jörn hat ein weiteres Beispiel beigesteuert. Da die Kommentarfunktion von WordPress den Code nicht sauber darstellt, spendiere ich Jörn eine eigene Überrschrift und baue sein Beispiel hier ein.

Jörns Datenstruktur sieht wie folgt aus:

dict_of_lists:
  - name: foo 
    elems:
      - foo one 
      - foo two 
      - foo three
  - name: bar 
    elems:
      - bar one 
      - bar two 
  - name: baz 
    elems:
      - baz one 
      - baz two 
      - baz three

Jörn möchte nun zuerst auf alle Elemente (elems) von foo zugreifen, dann auf jene von bar usw. Dazu nutzt Jörn das Lookup Plugin subelements.

Folgender Code-Block nutzt die Datenstruktur in einem Playbook, um die verschachtelten Elemente auszugeben. Aufruf und Ausgabe zeigt der darauf folgende Block.

---
- name: Lists in dicts
  hosts: localhost
  become: false
  vars:
    dict_of_lists:
      - name: foo
        elems:
          - foo one
          - foo two
          - foo three
      - name: bar
        elems:
          - bar one
          - bar two
      - name: baz
        elems:
          - baz one
          - baz two
          - baz three

  tasks:
    - name: Loop over lists in dicts
      ansible.builtin.debug:
        msg: "Name: {{ item.0.name }}, element {{ item.1 }}"
      loop: "{{ dict_of_lists | subelements('elems') }}

Und hier nun der Playbook-Aufruf mit Ausgabe:

$ ansible-playbook dict_of_lists.yml

PLAY [Lists in dicts] ************************************************************************

TASK [Gathering Facts] ************************************************************************
ok: [localhost]

TASK [Loop over lists in dicts] ************************************************************************
ok: [localhost] => (item=[{'name': 'foo', 'elems': ['foo one', 'foo two', 'foo three']}, 'foo one']) => {
    "msg": "Name: foo, element foo one"
}
ok: [localhost] => (item=[{'name': 'foo', 'elems': ['foo one', 'foo two', 'foo three']}, 'foo two']) => {
    "msg": "Name: foo, element foo two"
}
ok: [localhost] => (item=[{'name': 'foo', 'elems': ['foo one', 'foo two', 'foo three']}, 'foo three']) => {
    "msg": "Name: foo, element foo three"
}
ok: [localhost] => (item=[{'name': 'bar', 'elems': ['bar one', 'bar two']}, 'bar one']) => {
    "msg": "Name: bar, element bar one"
}
ok: [localhost] => (item=[{'name': 'bar', 'elems': ['bar one', 'bar two']}, 'bar two']) => {
    "msg": "Name: bar, element bar two"
}
ok: [localhost] => (item=[{'name': 'baz', 'elems': ['baz one', 'baz two', 'baz three']}, 'baz one']) => {
    "msg": "Name: baz, element baz one"
}
ok: [localhost] => (item=[{'name': 'baz', 'elems': ['baz one', 'baz two', 'baz three']}, 'baz two']) => {
    "msg": "Name: baz, element baz two"
}
ok: [localhost] => (item=[{'name': 'baz', 'elems': ['baz one', 'baz two', 'baz three']}, 'baz three']) => {
    "msg": "Name: baz, element baz three"
}

PLAY RECAP ************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Fazit

Das waren nun eine Menge Code-Blöcke. Mir hat es geholfen, dieses Thema noch einmal zu rekapitulieren. Listen können direkt an loop übergeben werden. Dictionaries müssen zuerst den Filter dict2items durchlaufen.

In diesem Text wurden noch nicht alle Fälle besprochen. So wurden nested lists und tiefer verschachtelte Dictionaries ausgespart, um den Artikel nicht noch mehr in die Länge zu ziehen.

Ich hoffe, der Text war auch für die Anfänger und Einsteiger unter euch hilfreich.

Internetanbindung um zweiten Anschluss erweitern

03. Juli 2023 um 05:00

Hi. Heute bin ich an euren Ideen und Empfehlungen interessiert. Wie würdet ihr folgende Aufgabe angehen?

Ist-Zustand

Das Bild zeigt eine vereinfachte Skizze des Heimnetzwerks, bestehend aus Connect Box (Router), Pi-Hole und LAN.
Vereinfachte Skizze meines Heimnetzwerks

Aktuell ist mein Heimnetzwerk über einen Unitymedia/Vodafone-Kabelanschluss mit dem Internet verbunden. Für die Verbindung zum Netz verwende ich die Unitymedia-Connect-Box. Die WLAN-Funktion und der DHCPv4-Server sind deaktiviert, da sich der Pi-Hole und ein Netgear-Orbi-System um diese Funktionen kümmern.

Dieses Setup hat einen Nachteil. Das Router Advertisement der Connect Box lässt sich nicht deaktivieren und nicht weiter konfigurieren. Mit diesem werden die DNS-Server des Internet Service Provider (ISP) im LAN announced. Diese werden von den Clients mit einer höheren Priorität genutzt als der Pi-Hole, was dessen Einsatz ad absurdum führte. Um dieses Problem zu mitigieren, habe ich die DHCPv6 Adressvergabe der Connect Box auf das Minimum von 1 Adresse reduziert. Damit entfaltet der Pi-Hole seine Wirkung und wir bleiben von Werbung weitestgehend verschont. Da die Clients im LAN nun jedoch keine IPv6-Adressen mit Scope Global besitzen, lassen sich Hosts im Internet, welche nur über IPv6 erreichbar sind, nicht ansprechen.

Es kommt ein Glasfaseranschluss hinzu

Voraussichtlich gegen Ende diesen Jahres kommt ein Glasfaseranschluss des Anbieters Sewikom hinzu. Dieser wird bis in den Keller gelegt und endet im sogenannten Hausübergabepunkt (HÜP). An den HÜP wird ein eigener Router via Kupfer-Patch-Kabel angeschlossen.

Der Router ist noch nicht vorhanden und muss von mir gekauft werden.

Gewünschtes Zielszenario

Ich möchte zukünftig beide Anschlüsse parallel nutzen können. Entweder in einer Active-Standby-Konfiguration oder Active-Active-Konfiguration. In letzten Fall wäre auch eine Möglichkeit charmant, in der ich konfigurieren kann, welche Clients welchen WAN-Anschluss bevorzugt nutzen sollen. Fällt ein Anschluss aus, sollen alle Geräte den verbliebenen Anschluss nutzen können.

Damit der Router nicht zum Single-Point-of-Failure wird, möchte ich für diesen ein Cold-Standby-Gerät vorhalten, da Hardware mit Hot-Standby-Unterstützung für das Heimnetzwerk vermutlich übertrieben teuer wird.

Bei der Lösung muss berücksichtigt werden, dass ich auch zukünftig nicht auf DNS-Filterlisten verzichten möchte. Ob diese Funktionalität vom Pi-Hole oder von einer integrierten Router-/Firewall-Lösung bereitgestellt wird, ist jedoch zweitrangig.

Vermutlich bietet es sich an, ein Netzwerkgerät zu verwenden, welches neben der Gateway-/Routing-Funktionalität auch eine Firewall bietet, die den Verkehr zwischen verschiedenen Netzwerkzonen steuern kann.

Wie habt ihr das gelöst?

An dieser Stelle kommt ihr ins Spiel. Wer von euch betreibt bereits ein solches Szenario mit zwei Internetanschlüssen (Glasfaser und Kabel) und wie habt ihr das gelöst?

Welche Hardware verwendet ihr? Warum habt ihr euch für diese entschieden und wovon würdet ihr aus Erfahrung abraten?

Bitte schreibt mir eure Antworten in die Kommentare, per E-Mail oder in den Chat (#my-it-brain:matrix.org).

RHEL System Roles: storage

19. Juni 2023 um 05:00

Willkommen zu Teil 6 meiner losen Reihe über die RHEL System Roles. In diesem Teil stelle ich euch die Rolle storage vor, mit welcher sich unpartitionierte Laufwerke und LVM-Volumes verwalten lassen.

Zuerst stelle ich meinen Anwendungsfall vor. Anschließend beschreibe ich, wie ich diesen mithilfe der RHEL System Role storage löse.

Während mir dieser Artikel zur Dokumentation dient, soll er euch den Einsatz von RHEL System Roles verdeutlichen.

Hinweis: Zum Zeitpunkt der Erstellung dieses Artikels unterstützt die Rolle die LVM-Konfiguration lediglich auf unpartitionierten Laufwerken, welche komplett als Physical Volume (PV) genutzt werden.

Wer sich an dieser Stelle fragt, was RHEL System Roles sind, wie man sie installiert und nutzt, dem empfehle ich am Anfang zu beginnen: Vorstellung der Red Hat Enterprise Linux (RHEL) System Roles.

Anwendungsfall

Mit der Ansible-Rolle kvm_provision_lab (siehe [1,2]) provisioniere ich virtuelle Maschinen (VM) auf KVM/QEMU-Hypervisoren. In „Labor-Umgebung mit Ansible in KVM erstellen“ habe ich die Anwendung dieser Rolle bereits detailliert beschrieben. Eine VM wird darin als ein YAML-Dictionary nach folgendem Muster definiert:

test-vm1:
    vm_ram_mb: 512
    vm_vcpus: 1
    vm_iftype: network
    vm_net: default
    os_type: rhel9
    file_type: qcow2
    base_image_name: rhel9-template
    vm_template: "rhel9-template"
    second_hdd: true
    second_hdd_size: "2G"

Das Beispiel im Code-Block provisioniert eine VM mit einem zweiten Blocklaufwerk. Dieses wird in der VM als /dev/vdb konfigruiert.

Um das zweite Laufwerk nutzen zu können, müssen zuerst eine Partitionstabelle und eine Partition erstellt und diese mit einem Dateisystem formatiert werden. Alternativ kann das Gerät auch für LVM verwendet werden.

Ich möchte aus /dev/vdb ein PV für LVM machen, um es einer Volume Group (VG) vg_data hinzuzufügen und ein Logical Volume (LV) zu erstellen, welches die gesamte Speicherkapazität von /dev/vdb nutzt.

Die Rolle

Durch die Installation des Pakets rhel-system-roles existiert diese Rolle storage bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.stroage/ und die Dokumentation in /usr/share/doc/rhel-system-roles/storage/README.md. Aus letzterer stammt auch folgendes Beispiel:

Example Playbook
----------------

```yaml
- hosts: all

  roles:
    - name: rhel-system-roles.storage
      storage_pools:
        - name: app
          disks:
            - sdb
            - sdc
          volumes:
            - name: shared
              size: "100 GiB"
              mount_point: "/mnt/app/shared"
              #fs_type: xfs
              state: present
            - name: users
              size: "400g"
              fs_type: ext4
              mount_point: "/mnt/app/users"
      storage_volumes:
        - name: images
          type: disk
          disks: ["mpathc"]
          mount_point: /opt/images
          fs_label: images

```

Da ich auf /dev/vdb ein LVM konfigurieren möchte, kopiere ich mir das Dictionary storage_pools aus obigen Beispiel und passe es für mein Playbook an.

Das Playbook

---
- hosts: test-vm1

  roles:
    - name: rhel-system-roles.storage
      storage_pools:
        - name: vg_data
          disks:
            - vdb
          volumes:
            - name: data1
              size: "2 GiB"
              mount_point: "/mnt"
              fs_type: ext4
              state: present

Obiges Playbook führt folgende Schritte auf dem Host test-vm1 durch:

  1. Das Blockgerät /dev/vdb wird als PV für LVM konfiguriert.
  2. Es wird die Volume Group (VG) vg_data auf dem PV /dev/vdb erstellt.
  3. In der VG vg_data wird das LV data1 erstellt.
  4. Das LV wird mit dem Dateisystem Ext4 formatiert.
  5. Das LV wird unterhalb von /mnt eingehängt.

Innerhalb des Gast-Betriebssystems lässt sich mit folgenden Kommandos prüfen, dass die Konfiguration wie gewünscht durchgeführt wurde.

[root@test-vm1 ~]# lsblk vdb
lsblk: vdb: not a block device
[root@test-vm1 ~]# lsblk /dev/vdb
NAME            MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vdb             252:16   0   2G  0 disk 
└─vg_data-data1 253:0    0   2G  0 lvm  /mnt
[root@test-vm1 ~]# pvs
  PV         VG      Fmt  Attr PSize  PFree
  /dev/vdb   vg_data lvm2 a--  <2.00g    0 
[root@test-vm1 ~]# vgs
  VG      #PV #LV #SN Attr   VSize  VFree
  vg_data   1   1   0 wz--n- <2.00g    0 
[root@test-vm1 ~]# lvs
  LV    VG      Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  data1 vg_data -wi-ao---- <2.00g                                                    
[root@test-vm1 ~]# mount | grep mnt
/dev/mapper/vg_data-data1 on /mnt type ext4 (rw,relatime,seclabel)
[root@test-vm1 ~]# echo "Hallo Welt!" >/mnt/world.txt
[root@test-vm1 ~]# cat /mnt/world.txt
Hallo Welt!

Fazit

Der betrachtete Anwendungsfall lässt sich mit der vorgestellten Ansible-Rolle schnell und einfach umsetzen. Man deklariert lediglich die Wunschkonfiguration im Ansible-Playbook und die Rolle kümmert sich um den Rest, wie die Installation der notwendigen Pakete auf den Zielsystemen.

Unterstützt werden als Zielsysteme aktuell EL 7-9 sowie Fedora. Damit ist sie für die Anwendung auf Debian bzw. darauf basierende Systeme nicht geeignet. Wie man auch für diese Systeme ein einfaches Playbook entwirft, um LVM für Blockgeräte zu konfigurieren, werde ich in einem folgenden Artikel zeigen.

Ich hoffe, dass euch auch die Vorstellung dieser Rolle gefallen hat und wünsche euch viel Spaß bei der Nutzung der RHEL System Roles.

Quellen und weiterführende Links

  1. https://galaxy.ansible.com/Tronde/kvm_provision_lab
  2. https://github.com/Tronde/kvm_provision_lab
  3. https://github.com/linux-system-roles/storage
  4. Vorstellung der Red Hat Enterprise Linux (RHEL) System Roles
  5. RHEL System Roles: selinux
  6. RHEL System Roles: timesync
  7. RHEL System Roles: sshd
  8. RHEL System Roles: firewall

Utilize PowerCLI to resize VMDK files of multiple VMs

13. Juni 2023 um 19:20

This is the translated version of an article written in German originally. I translated it as a non-German speaking person has shown interest in the topic.

Disclaimer: This tutorial comes without warranty and support. Use at your own risk.

In this tutorial, I would like to show a minimal example on how selected VMDK files of specific VMs can be resized with using the PowerCLI.

This is useful, for example, when so many VMs are affected that the time and effort required to manually enlarge them via the vSphere (web) client seems too great.

Only the resizing of the VMDK file is considered here. The subsequent resizing of the partition and file system within the guest operating system, which is also necessary, is not part of this tutorial.

Goal

For a minimal example, from a group of VMs the second and third hard disk of VM-Test-5 and VM-Test-6 are to be enlarged. The respective second hard disk is to be enlarged from 250 GB to 500 GB and the respective third hard disk is to be enlarged from 400 GB to 800 GB.

Requirements

A working installation of the VMware PowerCLI and the ability to access the vCenter Server is a prerequisite to follow this tutorial.

Here we go

The following code block shows how the necessary information about the VMs is read out to be processed.

PowerCLI C:\Scripts> Get-VM | Where-Object {$_ | Select-String -pattern "VM-Test-\d"}

Name                 PowerState Num CPUs MemoryGB
----                 ---------- -------- --------
VM-Test-5        PoweredOn  4        24.000
VM-Test-7        PoweredOn  4        16.000
VM-Test-6        PoweredOn  4        24.000


PowerCLI C:\Scripts> Get-VM | Where-Object {$_ | Select-String -pattern "VM-Test-[5,6]{1}"}

Name                 PowerState Num CPUs MemoryGB
----                 ---------- -------- --------
VM-Test-5        PoweredOn  4        24.000
VM-Test-6        PoweredOn  4        24.000


PowerCLI C:\Scripts> $VM = Get-VM | Where-Object {$_ | Select-String -pattern "VM-Test-[5,6]{1}"}
PowerCLI C:\Scripts> Get-VM $VM | Get-HardDisk | FT Parent, Name, CapacityGB -AutoSize

Parent        Name        CapacityGB
------        ----        ----------
VM-Test-5 Hard disk 1         40
VM-Test-5 Hard disk 2        250
VM-Test-5 Hard disk 3        400
VM-Test-5 Hard disk 4         80
VM-Test-6 Hard disk 1         40
VM-Test-6 Hard disk 2        250
VM-Test-6 Hard disk 3        400
VM-Test-6 Hard disk 4         80

From the above output you can see that we want to enlarge the VMDK files which are called „Hard disk 2“ and „Hard disk 3“ respectively.

In the code block that follows, I first define a few variables, then double check that I am selecting the correct VMDK files for the operation, and then I resize them.

PowerCLI C:\Scripts> $HardDisk = 2
PowerCLI C:\Scripts> $HardDisk = "Hard disk " + $HardDisk
PowerCLI C:\Scripts> $HardDiskSize = 500
PowerCLI C:\Scripts> Get-HardDisk -vm $VM | where {$_.Name -eq $HardDisk}

CapacityGB      Persistence                                              Filename
----------      -----------                                                    --------
250.000         IndependentPersis... ...STD-2.9T-02] VM-Test-5/VM-Test-5_1.vmdk
250.000         IndependentPersis... ...STD-2.9T-01] VM-Test-6/VM-Test-6_1.vmdk


PowerCLI C:\Scripts> Get-HardDisk -vm $VM | where {$_.Name -eq $HardDisk} | Set-HardDisk -CapacityGB $HardDiskSize -Conf
irm:$false

CapacityGB      Persistence                                                    Filename
----------      -----------                                                    --------
500.000         IndependentPersis... ...STD-2.9T-02] VM-Test-5/VM-Test-5_1.vmdk
500.000         IndependentPersis... ...STD-2.9T-01] VM-Test-6/VM-Test-6_1.vmdk


PowerCLI C:\Scripts> Get-VM $VM | Get-HardDisk | FT Parent, Name, CapacityGB -AutoSize

Parent        Name        CapacityGB
------        ----        ----------
VM-Test-5 Hard disk 1         40
VM-Test-5 Hard disk 2        500
VM-Test-5 Hard disk 3        400
VM-Test-5 Hard disk 4         80
VM-Test-6 Hard disk 1         40
VM-Test-6 Hard disk 2        500
VM-Test-6 Hard disk 3        400
VM-Test-6 Hard disk 4         80


PowerCLI C:\Scripts> $HardDisk = 3
PowerCLI C:\Scripts> $HardDisk = "Hard disk " + $HardDisk
PowerCLI C:\Scripts> $HardDiskSize = 800
PowerCLI C:\Scripts> Get-HardDisk -vm $VM | where {$_.Name -eq $HardDisk}

CapacityGB      Persistence                                                    Filename
----------      -----------                                                    --------
400.000         IndependentPersis... ...STD-2.9T-02] VM-Test-5/VM-Test-5_2.vmdk
400.000         IndependentPersis... ...STD-2.9T-01] VM-Test-6/VM-Test-6_2.vmdk


PowerCLI C:\Scripts> Get-HardDisk -vm $VM | where {$_.Name -eq $HardDisk} | Set-HardDisk -CapacityGB $HardDiskSize -Conf
irm:$false

CapacityGB      Persistence                                                    Filename
----------      -----------                                                    --------
800.000         IndependentPersis... ...STD-2.9T-02] VM-Test-5/VM-Test-5_2.vmdk
800.000         IndependentPersis... ...STD-2.9T-01] VM-Test-6/VM-Test-6_2.vmdk


PowerCLI C:\Scripts>

If the above code block is not self-explanatory, feel free to post your questions about it in the comments. I’ll try to complete the tutorial in a timely manner.

WP fail2ban: Gutes Plugin mit ausbaufähiger Dokumentation

12. Juni 2023 um 05:00

WP fail2ban ist ein Plugin zum Schutz von WordPress-Installationen vor Brute-Force-Angriffen. Installation und Konfiguration habe ich bereits im Artikel WordPress mit Fail2Ban vor Brute-Force-Angriffen schützen dokumentiert. Dieser Text beschäftigt sich mit der offiziellen Dokumentation des Plugins, bzw. dem Text, der sich als Dokumentation ausgibt.

Ich stelle hier meine Erwartung an eine Dokumentation heraus, zeige die Schwächen der exemplarisch ausgewählten Dokumentation heraus und gebe einen Tipp, wie man weniger schlechte Dokus schreibt. Ich schließe mit Fragen an meine Leserinnen und Leser und freue mich auf eure Rückmeldungen.

In meinen Augen sind folgende drei Abschnitte zwingender Bestandteil einer jeden Dokumentation:

  1. Was ist dies für eine Anwendung? Wofür wird sie verwendet und wofür nicht?
  2. Wie wird diese Anwendung installiert?
  3. Wie wird diese Anwendung konfiguriert?

Die ersten beiden Punkte mag ich als erfüllt betrachten. Beim dritten Punkt sehe ich ein Problem.

Bildschirmfoto der WP fail2ban-Installationsanleitung. Zeitstempel 2023-06-04T20:54+02

Die Installationsanleitung ist prägnant. Sie enthält einen Link, der suggeriert, zur Konfigurationsanleitung zu führen. Dort erwartet den neugierigen Leser folgendes Bild.

Bildschirmfoto vom Einstieg in die WB fail2ban-Konfigurationsanleitung. Zeitstempel 2023-06-04T20:59+02

Hier gibt es noch keine nützlichen Informationen zu sehen. Die Seite ist in meinen Augen überflüssig. Aber gut, ein Satz tut nicht weh. Mit einem Klick auf „Next“ geht es weiter.

Bildschirmfoto WP fail2ban-Dokumentation. Zeitstempel 2023-06-04T21:04+02

Hier steht im Wesentlichen, dass Nutzer, welche sich bereits auskennen, zum nächsten Abschnitt gehen können. Leider finden sich für neue Nutzer hier kaum brauchbare Informationen. Der Hinweis, die Datei wp-config.php zu sichern, bevor man diese bearbeitet, ist nett, mehr nicht. Es wird erwähnt, dass die freie (im Sinne von Freibier) Version des Plugins durch Definition von Konstanten in der Datei wp-config.php konfiguriert wird. Wie so eine Konstante aussieht oder wo man weiterführende Informationen dazu findet, steht hier nicht. Ich habe an dieser Stelle entsprechende Hinweise erwartet. Gut, ich kann ja noch auf „Next“ klicken.

Bildschirmfoto WP fail2ban-Dokumentation, Abschnitt Logging. Zeitstempel 2023-06-04T21:14+02

Auch in diesem Abschnitt findet sich keine Information, was man nun mit der Datei wp-config.php soll. Immerhin gibt es in Abschnitt 4.2.1. einen Link, der Nutzer, welche nicht mit der Konfiguration vertraut sind, zur Konfigurationsanleitung führen soll. Ich habe ein Déja Vu und fühle mich langsam ver…hohnepiepelt. Also klicke ich auf den nächsten LInk. Es heißt ja schließlich: „Was lange währt, wird endlich gut.“

TL;DR: Auch auf der nächsten Seite erfahren wir nichts über die wp-config.php.

Bildschirmfoto der URL: https://docs.wp-fail2ban.com/en/5.0/configuration/fail2ban.html#configuration-fail2ban. Zeitstempel 2023-06-04T21:21+02

Zur Erinnerung: Ich nutze die freie Version des Plugins WP fail2ban, welches angeblich durch die Definition von Konstanten in der Datei wp-config.php konfiguriert wird. Welche Konstanten dies sind und wie man diese konfiguriert, wird auch auf Seite 4 immer noch nicht mit einem Wort erklärt.

Stattdessen lernt man in Abschnitt „4.3.1.1. Typical Settings“, dass die Dateien wordpress-hard.conf und wordpress-soft.conf in das Verzeichnis fail2ban/filters.d zu kopieren sind. Hier wurden in meinen Augen zwei Fehler gemacht:

  1. Es wird nicht erwähnt, wie man die Dateien wordpress-,{hard,soft}.conf erhält bzw. erstellt. Als unerfahrener Nutzer strandet man hier. Zum Glück hatte ich mir das damals aufgeschrieben.
  2. Es wird eine relative Pfadangabe fail2ban/filters.d genutzt. Dies ist nicht ganz so wild, ich persönlich bevorzuge es, wenn vollständige Pfadangaben genutzt werden, damit Nutzer das entsprechende Verzeichnis sicher finden.

Fazit

Eine Dokumentation sollte die notwendigen Informationen bereitstellen, mit denen auch neue Nutzer eine Anwendung installieren und konfigurieren können. Dies ist nicht so leicht, wie es auf den ersten Blick erscheint, leiden Autoren, welche die Anwendung bereits kennen, doch häufig unter Betriebsblindheit und können sich nur schwer in die Rolle des unerfahrenen Nutzers versetzen.

Wenn ich selbst Dokumentationen schreibe, gebe ich diese meist Kolleginnen und Kollegen zu lesen und arbeite deren Rückmeldungen mit ein. Dies hat in der Vergangenheit zu deutlich besseren Ergebnissen geführt.

Die hier kritisierte Dokumentation ist ein Beispiel dafür. Sie befindet sich damit leider in guter Gesellschaft im Internet.

Ohne meinen Eingangs erwähnten Artikel hier im Blog wäre ich nicht in der Lage gewesen, mir dieses Plugin wieder einzurichten. Nun werde ich einige Tage prüfen, ob es wie erhofft arbeitet und dann ggf. einen Merge-Request mit einigen Verbesserungsvorschlägen einreichen.

Fragen an meine Leserinnen und Leser

Wie steht ihr zu Dokumentation? Ist das eher etwas für alte weiße Männer mit grauen Bärten? Oder wünscht ihr euch ebenfalls belastbare und ausführliche Dokumentationen zu den von euch verwendeten Anwendungen?

Tragt ihr selbst zu Dokumentationen bei? Welche Erfahrungen habt ihr dabei gemacht? Welche Tipps könnt ihr Schreiberlingen geben, die ihr Projekt dokumentieren möchten?

Bitte hinterlasst mir eure Antworten in den Kommentaren oder im Chat.

Rollen müssen verfügbar sein, nicht einzelne Personen.

05. Juni 2023 um 05:00

In diesem Artikel möchte ich euch ein Modell vorstellen, nach dem sich Wissen auf verschiedene Personen verteilen lässt, um dieses Wissen und die Funktion von Rollen in einem Unternehmen verfügbar zu halten.

Bei den Rollen kann es sich zum Beispiel um den Virtualisierungs-Administrator, den Linux-Admin oder die Rechnungseingangsprüfung sowie den Versand handeln. Die jeweilige Rolle kann dabei von ein oder mehreren Personen ausgefüllt werden. Wichtig ist lediglich, dass die Rolle jederzeit ihre Funktion erfüllen kann.

Das Modell, welches ich gleich vorstelle, stammt nicht von mir. Ein Nerd aus den USA, dessen Namen ich leider nicht mehr weiß, hat es mir am Rande des OpenStack Summit 2018 in Berlin vorgestellt.

RAID – Redundant Array of Independent Dudes

Ja, ihr habt richtig gelesen. Es geht um RAID, aber ohne Festplatten und dafür mit Dudes oder allgemein Personen, die Rollen ausfüllen. Ihr werdet in den folgenden Abschnitten lernen, wie sich die verschiedenen RAID-Level auf die Verfügbarkeit einer Rolle auswirken.

RAID 0 – Nur gemeinsam sind wir stark

Das Bild zeigt zwei Personen, die jeweils zwei Aufgaben wahrnehmen. Die beiden Personen haben nichts weiter miteinander zu tun. Es findet kein Wissensaustausch zwischen ihnen statt.
Zwei Personen nehmen unabhängig voneinander verschiedene Aufgaben wahr.

Dieses Konstrukt habe ich zum Glück noch nicht in der Praxis angetroffen. Hierbei wird eine Rolle von zwei Personen ausgefüllt, welche die Funktion der Rolle jedoch nur im Team sicherstellen können. Fällt eine Person aus, ist die andere allein nicht ausreichend handlungsfähig. Die Funktion der Rolle ist nicht sichergestellt.

RAID 1 – Zwei Personen für eine Rolle

Hier herrscht volle Redundanz. Zwei Personen sind für eine Rolle verantwortlich. Fällt eine Person aus, kann die andere sämtliche damit zusammenhängende Aufgaben allein ausführen. Die Vertretung ist im Falle von Krankheit, Urlaub oder Knast damit zu 100 % sichergestellt.

Gleichzeitig ist der Abstimmungsbedarf minimal, da sich nur zwei Personen miteinander abstimmen müssen.

Das Bild zeigt vier Personen. Jeweils zwei Personen nehmen die gleichen Aufgaben wahr. Pfeile symbolisieren den Informationsfluss zwischen den Personen.
Jeweils zwei Personen stimmen sich miteinander ab, um sich gegenseitig vertreten zu können (RAID 1).

Wenn es gut läuft und man bei der Personal-Akquise ein glückliches Händchen hat, lassen sich vielleicht sogar zwei Personen kombinieren, die zwei oder vielleicht sogar drei Rollen in Personalunion besetzen können.

Zu beachten ist, dass die Leistungsfähigkeit der Rolle beeinträchtigt sein mag, wenn 50 % der normalen Kapazität fehlen. Dies ist von der regelmäßigen Belastung bzw. Auslastung der einzelnen Personen abhängig.

RAID 5 – Parität und Leistung mit erhöhtem Abstimmungsbedarf

Das insgesamt erforderliche Wissen ist in diesem RAID-Level auf mindestens drei oder mehr Personen verteilt. Keine Person verfügt für sich allein über genug Wissen, um eine Rolle vollständig ausfüllen zu können. In der Gesamtheit ist das Wissen jedoch so verteilt, dass bei Ausfall einer Person (Krankheit, Urlaub, Knast, etc.) kein Wissen unwiederbringlich verloren geht, sondern der Verlust durch die verbliebenen Personen aufgefangen werden kann. Dies bezieht sich nicht nur auf das Wissen, sondern auch auf die zu erbringende Leistung.

Das Bild zeigt sechs Personen, von denen jeweils drei die gleichen Aufgaben wahrnehmen. Pfeile unterschiedlicher Farbe zeigen, welche Personen dabei in einer Kommunikationsbeziehung zueinander stehen.
Zwei Teams bestehend aus jeweils drei Personen. Jedes Team stimmt sich zu seinen Aufgaben ab. Es handelt sich quasi um zwei separate RAID-5-Verbünde.

Da jede Person nun mehr als eine Kollegin oder einen Kollegen hat, wird der Abstimmungsbedarf größer. Die einzelnen Personen müssen sich auf dem Laufenden halten, was die Kollegen so tun, um diese bei Bedarf vertreten zu können.

Hat jede Person in diesem RAID-Verbund nur die eine Rolle, für die dieses RAID gebildet wurde, ist das kein Problem. Man hat einfach ein größeres Team, das seinen Job macht. Das funktioniert auch noch, wenn alle in diesem Team/RAID noch eine zweite oder vielleicht auch dritte Rolle ausfüllen. Denn es bleiben dieselben beteiligten Personen, die sich untereinander austauschen können und müssen.

RAID 55 – Wie konnte das passieren?

Ein Bild sagt auf mehr als viele Worte:

Das Bild zeigt sechs Personen, von denen jede zwei Rollen ausfüllt. Jede Rolle ist dreimal im Bild vorhanden. Pfeile verbinden die Personen, die die gleiche Rolle ausfüllen, um darzustellen, wie unübersichtlich es bei ungünstiger Aufgabenverteilung wird.
RAID 55 – Wenn die Aufgabenverteilung unübersichtlich wird

In vorstehendem Bild werden die gleichen Aufgaben durch die gleiche Anzahl von Personen wahrgenommen. Allerdings sind in diesem Bild die Rollen ungeschickt zwischen den Personen verteilt, was einen hohen Kommunikationsaufwand zur Folge hat, da sich mehr Personen zu mehr Themen miteinander abstimmen müssen.

Hinzukommt, dass die Urlaubsplanung in diesem unübersichtlichen Szenario ebenfalls erschwert wird, muss doch darauf geachtet werden, dass die Funktionen der einzelnen Rollen verfügbar bleiben und man nicht versehentlich zwei von drei Personen in den Urlaub schickt, welche die gleiche Rolle ausfüllen und von denen mindestens zwei Personen verfügbar sein müssen.

Leider habe ich dieses Bild in der Praxis schon mehr als einmal gesehen. Es entsteht immer dann, wenn Teams immer weitere Themen übernehmen müssen und man versucht, eine Aufgabe auf immer mehr vorhandene Köpfe zu verteilen, weil gerade jemand da ist, der noch 10 Minuten freie Zeit pro Woche hat.

Dabei mag man sich ausmalen, wie das vorstehende Bild sich verändert, wenn die Anzahl der Personen und Aufgaben unter Beibehaltung der ungeschickten Aufgabenverteilung steigt. Ein Modell, das es in meinen Augen zu verhindern lohnt.

Zusammenfassung

Ich mag RAIDs, da sich mit ihnen auf einfache Weise die Komplexität von Aufgaben-, Arbeitslast-Verteilung und Kommunikations- und Abstimmungs-Aufwände visualisieren und erklären lassen.

Wie gefällt euch das Modell? Habt ihr ähnliche oder ganz andere Erfahrungen in vergleichbaren Team-Strukturen gemacht?

Ehre dem Ehrenamt

29. Mai 2023 um 05:00

In diesem Text geht es einmal nicht um IT. Es ist ein Kommentar zu Tims Artikel „Zum Wochenende: Freiwillige Gesellschaft“ auf GNU/Linux.ch, welcher mir gut gefallen hat.

Tim und ich haben mehrere Dinge gemeinsam. Wir haben beide Familie, einen 40-Stunden-Job, Hobbys und sind Mitglieder einer Freiwilligen Feuerwehr. Ich „leide“ darüber hinaus noch an gewissen Wohlstandkrankheiten und zähle mich selbst daher definitiv nicht zu den fitten Menschen in unserer Gesellschaft. Das hält mich jedoch nicht davon ab, mich ehrenamtlich zu engagieren. Denn es ist mir wichtig, der Gesellschaft etwas zurückzugeben und Menschen in Not zu helfen.

Meine Ausbildung zum Fachinformatiker Systemintegration oder mein Studium der Angewandten Informatik helfen dabei nicht weiter. Das müssen sie aber auch gar nicht. Denn wer sich in der Freiwilligen Feuerwehr engagiert, wird auf Lehrgängen und regelmäßigen Dienstabenden ausgebildet.

Und ja, man muss auch manchmal Kompromisse eingehen. So sind für meine Frau und mich schon gemeinsame Abende ausgefallen, weil Papa zu einem Einsatz musste, um z.B. eine Ölspur abzustreuen, ein Feuer zu löschen, oder die Straße zu sperren, bis das Gasleck geschlossen wurde. Das fällt mir nicht immer leicht.

Erst diese Woche ist unser gemeinsamer Tanzabend ausgefallen, weil ich mit Kameraden zur Unterstützung einer Nachbarwehr gefahren bin. Ein Unwetter hat Massen von Schlamm und Wasser in die Keller, Gärten und Häuser vieler Menschen gespült. Wir konnten sehen, wo der Schlamm ca. 1,60-2,00 Meter hoch in den Kellern stand.

Schön zu sehen war die Hilfsbereitschaft der Menschen. Feuerwehrleute anderer Gemeinden und Städte, Nachbarn und Menschen aus ganz anderen Teilen der Stadt bildeten Eimerketten, um zerstörte Besitztümer und Schlamm aus den Kellern in Mulden zu transportieren. Wer nicht irgendwelchen Unrat schleppte, versorgte die Helfenden mit Verpflegung. In den Straßen gab es Bratwurst, belegte Brötchen, Kuchen, Suppe und Getränke. Es fehlte eigentlich an nichts.

Es war ein gutes Gefühl mitgeholfen zu haben, einen kleinen Teil dazu beigetragen zu haben, anderen Menschen in Not zu helfen. Noch schöner war allerdings, nach nicht ganz 5 Stunden mit langen Armen nach Hause zu kommen und von meiner Frau den Satz zu hören: „Ich bin stolz auf dich und finde es toll, dass du das machst.“

Wenn auch ihr euch ehrenamtlich engagieren möchtet und nicht wisst wie, seid nicht schüchtern oder ängstlich. Schaut bei eurer lokalen Feuerwehr, dem THW, dem Sportverein, der Kleiderkammer, etc. vorbei und fragt, ob ihr euch mal anschauen könnt, was da so passiert und ob das etwas für euch ist. Ich bin mir sicher, man wird euch freundlich empfangen und euch zeigen, was euch erwartet. Und denkt immer daran: „Vieles geht leichter, wenn wir es gemeinsam anpacken.“

Dokumentation meines Proxmox-Setups

22. Mai 2023 um 05:00

Dies ist die lückenhafte Dokumentation meines Proxmox-Setups.

Als langjähriger Administrator von VMware vSphere probiere ich etwas Neues aus. Mein Setup und erste Erkenntnisse halte ich in diesem Artikel fest. Ich werde ihn in der Zukunft weiter ausgestalten und ergänzen.

Ich möchte euch nicht vom Lesen abhalten, doch erwartet nicht zu viel.

Betreiber, Standort und Server-Hardware

Für den Betrieb von Labor-Umgebungen habe ich mir einen Server mit folgender Hardware-Ausstattung in Hetzners Serverbörse gemietet.

Ausstattung:

  • Intel Core i9-9900K (8 Cores/16 Threads)
  • 2x SSD M.2 NVMe 1 TB
  • 4x RAM 32768 MB DDR4
  • NIC 1 Gbit Intel I219-LM
  • Standort: Deutschland, FSN1
  • Rescue-System (Englisch)
  • 1 x Primäre IPv4
  • 1x /64-Subnetz IPv4

Installation

Auf der Hardware habe ich eine Debootstrap-Installation mit Debian 11 (Bullseye) durchgeführt, bei der alle Datenträger mit Ausnahme von /boot LUKS-verschlüsselt sind. Dazu bin ich der hervorragenden Anleitung meines Kollegen Steffen gefolgt: „Manually installing Debian 11 (Bullseye) with fully encrypted LUKS (besides /boot) using debootstrap

Anschließend habe ich die Proxmox-Paketquellen eingebunden und Proxmox VE installiert. Ich weiß nicht mehr genau, welchem Tutorial ich gefolgt bin. Daher habe ich in [1-3] ein paar vielversprechend erscheinende Suchergebnisse verlinkt.

Netzwerkkonfiguration bis 2023-07-21

Die physikalische NIC meines Hosts wird mit meiner öffentlichen IPv4 und einer IPv6-Adresse für die Außenanbindung konfiguriert. Im Betriebssystem erstelle ich eine Bridge, an welche später die virtuellen Maschinen (VMs) angeschlossen werden. Über diese Bridge haben die VMs Zugriff auf das Internet und sind vom Internet aus zu erreichen.

Das folgende Bild stellt die Konfiguration exemplarisch dar. Statt IPv4-Adressen verwende ich für die Bridge und VMs jedoch ausschließlich IPv6-Adressen.

Quelle: Proxmox-Wiki – Routed Configuration

Die Datei /etc/network/interfaces meines Hosts hat folgenden Inhalt. Die realen IP-Adressen habe ich dabei durch Adressen aus RFC 3849 und RFC 5737 ersetzt.

$ cat /etc/network/interfaces
# network interface settings; autogenerated
# Please do NOT modify this file directly, unless you know what
# you're doing.
#
# If you want to manage parts of the network configuration manually,
# please utilize the 'source' or 'source-directory' directives to do
# so.
# PVE will preserve these directives, but will NOT read its network
# configuration from sourced files, so do not attempt to move any of
# the PVE managed interfaces into external files!

source /etc/network/interfaces.d/*

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
	address 198.51.100.38/27
	gateway 198.51.100.33
	pre-up /sbin/ip addr flush dev eth0 || true

iface eth0 inet6 static
	address 2001:db8:dead:beef::1
	netmask 128
	gateway fe80::1
	up sysctl -w net.ipv6.conf.all.forwarding=1
	up sysctl -p

auto vmbr0
iface vmbr0 inet6 static
	address 2001:db8:dead:beef::2
	netmask 64
	up ip -6 route add 2001:db8:dead:beef::/64 dev vmbr0
	bridge-ports none
	bridge-stp off
	bridge-fd 0

Erläuterungen dazu folgen ggf. später. Als Einstieg empfehle ich einen Blick in die interfaces(5).

Damit eine VM auf das Routing-Netzwerk zugreifen und darüber das Internet erreichen kann, wird diese an die Bridge vmbr0 angeschlossen. Anschließend wird dieser eine IPv6-Adresse aus dem /64-Addressblock konfiguriert, wie es der folgende Code-Block exemplarisch darstellt, wobei static-ipv6 der Name der Verbindung und ens192 der Name der NIC ist:

# nmcli con add con-name static-ipv6 ifname ens192 type ethernet
# nmcli con mod static-ipv6 ipv6.addresses 2001:db8:dead:beef::3/128 ipv6.method manual ipv6.gateway 2001:db8:dead:beef::2
# nmcli con up static-ipv6

Zur Verwendung von nmcli habe ich die Red Hat Dokumentation unter [4] zu Rate gezogen.

Netzwerkkonfiguration ab 2023-07-21

Ursprünglich habe ich die Absicht verfolgt, alle VMs auf meinem Hetzner-Server ausschließlich mit IPv6 zu betreiben. Wir schreiben ja schließlich das Jahr 2023. Da sollte das doch problemlos möglich sein. Wäre da nicht (mindestens) ein Dienst, der IPv6 nicht unterstützt.

Da es offenbar nicht ganz ohne IPv4 geht, habe ich mir bei Hetzner eine zweite IPv4-Adresse gemietet [5] und bin der Anleitung Netzwerkkonfiguration für Proxmox VE in der Hetzner-Community gefolgt. Da die Anleitung mein bestehendes Setup für IPv6 nicht berücksichtigt, waren ein paar Anpassungen notwendig. Der folgende Code-Block stellt den Inhalt der Dateien /etc/network/interfaces und /etc/network/interfaces.d/vmbr0-extra dar, wobei die IP-Adressen ersetzt wurden.

$ cat /etc/network/interfaces
# network interface settings; autogenerated
# Please do NOT modify this file directly, unless you know what
# you're doing.
#
# If you want to manage parts of the network configuration manually,
# please utilize the 'source' or 'source-directory' directives to do
# so.
# PVE will preserve these directives, but will NOT read its network
# configuration from sourced files, so do not attempt to move any of
# the PVE managed interfaces into external files!

source /etc/network/interfaces.d/*

auto lo
iface lo inet loopback

iface eth0 inet manual
	pre-up /sbin/ip addr flush dev eth0 || true

auto vmbr0
iface vmbr0 inet static
	address 198.51.100.38/27
	gateway 198.51.100.33
	bridge-ports eth0
	bridge-stp off
	bridge-fd 0

iface vmbr0 inet6 static
	address 2001:db8:dead:beef::1/64
	gateway fe80::1

$ cat /etc/network/interfaces.d/vmbr0-extra 
iface vmbr0 inet static
	hwaddress <zweite MAC-Adresse aus dem Kundenportal>

Die virtuelle Netzwerkkarte der VM, welche über die zweite IPv4-Adresse erreichbar sein soll, wird an die Bridge vmbr0 angeschlossen. Das Interface ens18 im Gastbetriebssystem wurde wie folgt konfiguriert:

# nmcli
ens18: connected to ens18
        "Red Hat Virtio"
        ethernet (virtio_net), 00:50:56:00:8C:22, hw, mtu 1500
        ip4 default, ip6 default
        inet4 198.51.100.36/32
        route4 198.51.100.33/32 metric 100
        route4 default via 198.51.100.33 metric 100
        inet6 fe80::250:56ff:fe00:8c22/64
        inet6 2001:db8:dead:beef::2/64
        route6 2001:db8:dead:beef::/64 metric 100
        route6 default via fe80::1 metric 100
        route6 fe80::/64 metric 1024

# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 185.12.64.1
nameserver 185.12.64.2
nameserver 2a01:4ff:ff00::add:1

Getestet habe ich die Konfiguration, indem ich eine Webseite einmal über IPv4 und einmal über IPv6 abgerufen habe. Dies kann man recht einfach mit curl nach folgendem Muster erledigen:

$ curl -4 [URL]
$ curl -6 [URL]

Erster Eindruck

Wie ESXi und vSphere verfügt auch Proxmox VE über ein WebUI zur Administration der Umgebung und zur Erstellung virtueller Maschinen.

Selbstverständlich gibt es einige Unterschiede und nicht alles wirkt sofort vertraut. Bisher konnten die Proxmox-Dokumentation und das Proxmox-Wiki meine Fragen jedoch schnell beantworten.

Eine erste VM war schnell erstellt, installiert und in ein Template umgewandelt. Interessanterweise kann man Templates im WebUI nicht wieder in VMs umwandeln, was bei vSphere kein Problem ist.

Im Gegenzug können ISO-Images oder sonstige Dateien einfach per rsync auf den Proxmox-Host kopiert werden, was gerade beim erneuten Übertragen vorhandener Dateien eine enorme Zeitersparnis mit sich bringt. Hier muss bei vSphere und ESXi zum Upload von Dateien in den Datenspeicher das WebUI, SCP oder die Powershell bemüht werden und erneut zu kopierende Dateien werden jedes Mal komplett übertragen. Was bei Netzwerkgeschwindigkeiten im Datacenter nicht so dramatisch ist, nervt doch sehr, wenn man große ISO-Images über eine Internetleitung übertragen muss.

Der erste Eindruck ist zufriedenstellend. Als nächstes werde ich mich mal damit beschäftigen, wie man VMs mit Ansible auf Proxmox provisioniert. Das community.general.proxmox_kvm-Modul scheint dafür ein guter Einstiegspunkt zu sein.

Quellen und weiterführende Links

  1. Install Proxmox VE on Debian 11 Bullseye
  2. So installieren Sie Proxmox auf einem Debian 11-Server
  3. How To Install Proxmox On Debian 11 – A Step-By-Step Guide
  4. Configuring an Ethernet connection with a static IP address by using nmcli
  5. Zusätzliche IP-Adressen bei Hetzner
  6. Proxmox VE Netzwerkkonfiguration

Gold-Images für KVM/QEMU erstellen

08. Mai 2023 um 05:00

Nachdem ich bereits beschrieben habe, wie ich Labor-Umgebungen mit Ansible in KVM erstelle, beschreibe ich in diesem Artikel, wie ich die dort verwendeten Gold-Images erstelle.

Ich erkläre kurz, was ein Gold-Image ist und wofür man es verwendet. Anschließend zeige ich, wie man mit dem Programm qemu-img eine Image-Datei auf der Kommandozeile erzeugt und diese im Installationsprozess für die Partitionierung und Erzeugung des LVM nutzt. Im Anschluss daran dokumentiere ich, welche Laufwerke und Dateisysteme ich für welchen Zweck erstelle und warum ich mich so entschieden habe. Der Text endet mit einer Sammlung von Quellen und weiterführenden Links zum Thema.

Der Text soll mir helfen, in Zukunft nachvollziehen zu können, warum ich mich so entschieden habe. Für euch mag er als Information dienen, wie ich im Unterschied zu euch an das Thema herangehe. Wer noch nie etwas von Gold-Images gehört hat, findet in diesem Text eine Erklärung und passende Quellen dazu.

Was ist ein Gold-Image?

Ein Gold-Image, auch Golden Image oder Baseline-Image genannt, bezeichnet eine Vorlage (engl. template) in virtuellen Umgebungen, woraus sich virtuelle Maschinen (VMs) klonen lassen (siehe [1-5]).

In ein Gold-Image werden all die Softwarekomponenten, Anwendungen und Konfigurationsoptionen aufgenommen, welche Bestandteil aller davon abgeleiteten VMs sein sollen. Klonen ermöglicht die schnelle Bereitstellung neuer Systeme. Dies ist besonders dann nützlich, wenn sich die VMs sehr ähnlich sind und man nur eine geringe Anzahl von Gold-Images pflegen muss.

Ich nutze Gold-Images, um für die von mir verwendeten Linux-Distributionen jeweils eine Installation manuell durchzuführen, welche die minimal erforderlichen Pakete enthält, um die abgeleiteten Klone mit Ansible fertig konfigurieren zu können. Wie ich dabei vorgehe, beschreibe ich in den folgenden Abschnitten.

Erstellung der Image-Datei

Im ersten Schritt erstelle ich Image-Dateien im qcow2-Format. Bei diesem Format handelt es sich um das heute gebräuchliche Format in KVM-/QEMU-Umgebungen. Zur Erstellung verwende ich das QEMU disk image utility qemu-img.

Die allgemeine Form des Befehls lautet (Details siehe Manpage qemu-img(1)):

qemu-img create -f FORMAT DATEINAME GRÖßE

Der folgende Befehl erstellt eine Image-Datei im qcow2-Format, mit 20 Gigabyte Größe und dem Dateinamen debian11-template.qcow2 im Pfad /var/lib/libvirt/images/:

$ qemu-img create -f qcow2 /var/lib/libvirt/images/debian11-template.qcow2 20G
Formatting '/var/lib/libvirt/images/debian11-template.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=21474836480 lazy_refcounts=off refcount_bits=16

Dabei werden die 20 GB nicht sofort alloziert. Zu Beginn belegt die Datei lediglich einige Kilobyte und kann je nach Nutzung auf die maximale Größe von 20 GB anwachsen. Diese Bereitstellungsform ist auch als Thin provisioning bekannt.

ls -sh /var/lib/libvirt/images/debian11-template.qcow2
193K /var/lib/libvirt/images/debian11-template.qcow2

Es lässt sich auf diese Weise mehr Speicherplatz provisionieren, als tatsächlich im System zur Verfügung steht. Dabei gilt jedoch zu beachten, dass sich die Physik nicht betrügen lässt. Man ist gut beraten, den realen Speicherverbrauch zu überwachen, um volllaufende Dateisysteme zu vermeiden.

Installation und Partitionierung

Bei der Installation des Gold-Images für Labor-Umgebungen mache ich es mir relativ einfach. Ich erstelle z.B. im virt-manager oder cockpit eine VM, die ich von einem Installations-ISO-Image der jeweiligen Distribution installiere.

Bei Debian installiere ich für gewöhnlich ein System ohne grafische Oberfläche, welches zusätzlich zur Basis-Installation lediglich die Paketgruppen SSH-Server und System-Werkzeuge umfasst. Bei Fedora oder RHEL führe ich die Minimal-Installation durch.

Ob bzw. welches Passwort für den Benutzer root vergeben wird, ist an dieser Stelle nicht wichtig, da dies beim Klonen in eine neue VM durch die Ansible-Rolle kvm_provision_lab geändert wird.

Der Installer erkennt eine Festplatte, die in diesem Text exemplarisch als /dev/vda bezeichnet wird. Diese wird nun wie folgt konfiguriert.

Vorwort zur Partitionierung

Das optimale Partitionslayout hängt vom konkreten Anwendungsfall ab und kann sich je nach Umgebung stark unterscheiden. Das von mir verwendete Layout passt aktuell am besten zu meinen Anforderungen. Es mag in Zukunft völlig anders aussehen.

Ich beschreibe, welche Partitionen ich erstelle und erläutere, warum ich mich zum Zeitpunkt der Erstellung dieses Textes dafür entschieden habe. Bitte übernehmt dieses Layout nicht stumpf, sondern überlegt, ob es zu euren Anforderungen passt.

Primäre Partition /dev/vda1 für /boot

In dieser Partition werden die Kernel und das initramfs abgelegt. Nach meiner Erfahrung reicht eine Größe von 1 GiB aus, um auch einige ältere Kernel vorzuhalten. Formatiert habe ich diese Partition mit dem Dateisystem ext4.

Ich bevorzuge ext4 gegenüber XFS, da es sich im Gegensatz zu letzterem auch verkleinern lässt. Zugegeben, dass dies notwendig ist, ist mir in 20 Jahren nur einmal untergekommen. Doch in diesem einen Moment war ich froh, dass es möglich war.

LVM, PV, VG, LV, Dateisysteme und Einhängepunkte

Der Logical Volume Manager (LVM) (siehe [11-13]) bietet die Möglichkeit, Partitionen (genaugenommen Logical Volumes) für weitere Einhängepunkte zu erstellen, welche sich später noch flexibel in der Größe anpassen lassen (Vergrößern und Verkleinern). Und dies, ohne die verwendeten Dateisysteme aushängen zu müssen.

Wer mit den für LVM relevanten Begriffen Physical Volume, Volume Group und Logical Volume nicht vertraut ist, findet weiterführende Hinweise in [12] für Debian bzw. [13] für RHEL. Ich werde die Erstellung hier nicht im Detail beschreiben.

Ich erstelle eine zweite primäre Partition /dev/vda2 mit Typ LVM, welcher ich die verbleibende Speicherkapazität von 19 GiB zuweise. Mein fertiges Partitionslayout sieht wie folgt aus:

lsblk -o +FSTYPE
NAME                  MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT FSTYPE
sr0                    11:0    1 1024M  0 rom             
vda                   254:0    0   20G  0 disk            
├─vda1                254:1    0  953M  0 part /boot      ext4
└─vda2                254:2    0 19.1G  0 part            LVM2_member
  ├─vg_system-root    253:0    0  9.3G  0 lvm  /          ext4
  ├─vg_system-var_log 253:1    0  4.7G  0 lvm  /var/log   ext4
  └─vg_system-home    253:2    0  1.9G  0 lvm  /home      ext4

Vorstehendem Code-Block ist zu entnehmen, dass ich drei Logical Volumes für die Einhängepunkte /, /var/log und /home verwende. Ich verwende auch hier durchgängig das ext4-Dateisystem.

Log-Dateien und unkontrolliert wachsende Daten in HOME-Verzeichnissen führen schnell dazu, dass das Root-Dateisystem (/) vollläuft. Dies lässt sich mit der Verwendung separater Einhängepunkte sicher verhindern.

Eurem aufmerksamen Auge ist sicher aufgefallen, dass ich keine Swap-Partition verwende. Da ich sie für die meist kurzlebigen Labor-VMs nicht benötige, lasse ich diese in der Regel weg. Für Systeme, die ich für langfristigen Betrieb installiere, füge ich diese nachträglich hinzu. Dank LVM ist dies kein Problem.

Darüber, ob man eine Swap-Partition braucht und wie groß diese sein sollte, werden teils esoterische Diskussionen geführt. Ich selbst orientiere mich an Table B.1. Recommended system swap space aus [14].

Damit habe ich ein Gold-Image erstellt, welches mir als Vorlage für weitere VMs dient und dabei nur wenig Platz auf der Festplatte des Hypervisor belegt:

ls -sh /var/lib/libvirt/images/debian11-template.qcow2
2.3G /var/lib/libvirt/images/debian11-template.qcow2

Sysprep

Sysprep ist ursprünglich ein Programm von Microsoft, mit welchem Gold-Images für die automatische Verteilung von Microsoft Windows vorbereitet werden. Heute taucht dieser Begriff in den Programmbezeichnungen weiterer Projekte auf und beschreibt gleichzeitig die Tätigkeit, ein Gold-Image für die weitere Verwendung vorzubereiten. Ich selbst verwende das Programm virt-sysprep von Richard W.M. Jones (Red Hat) und Wanglong Gao (Fujitsu Ltd.).

Virt-sysprep entfernt Einstellungen, User, Host-spezifische Dateien und leert Protokolldateien in dem erzeugten Image. Damit soll sichergestellt werden, dass die von dem Gold-Image abgeleiteten VMs keine Eigenschaften besitzen, die spezifisch für das Original sind, wie z.B. der Hostname, MAC-Adressen oder die SSH-Host-Keys, um nur drei Beispiele zu nennen. Die Anwendung ist daher dringend empfohlen.

Mit dem Befehl virt-sysprep --list-operations kann man sich die Aktionen anzeigen lassen, die virt-sysprep ausführen kann. Die Standard-Aktionen sind in der Ausgabe mit einem ‚*‘ markiert. Unter RHEL 9 sieht die Ausgabe wie folgt aus:

$ virt-sysprep --list-operations
abrt-data * Remove the crash data generated by ABRT
backup-files * Remove editor backup files from the guest
bash-history * Remove the bash history in the guest
blkid-tab * Remove blkid tab in the guest
ca-certificates   Remove CA certificates in the guest
crash-data * Remove the crash data generated by kexec-tools
cron-spool * Remove user at-jobs and cron-jobs
customize * Customize the guest
dhcp-client-state * Remove DHCP client leases
dhcp-server-state * Remove DHCP server leases
dovecot-data * Remove Dovecot (mail server) data
firewall-rules   Remove the firewall rules
flag-reconfiguration   Flag the system for reconfiguration
fs-uuids   Change filesystem UUIDs
ipa-client * Remove the IPA files
kerberos-data   Remove Kerberos data in the guest
kerberos-hostkeytab * Remove the Kerberos host keytab file in the guest
logfiles * Remove many log files from the guest
lvm-system-devices * Remove LVM2 system.devices file
lvm-uuids * Change LVM2 PV and VG UUIDs
machine-id * Remove the local machine ID
mail-spool * Remove email from the local mail spool directory
net-hostname * Remove HOSTNAME and DHCP_HOSTNAME in network interface configuration
net-hwaddr * Remove HWADDR (hard-coded MAC address) configuration
net-nmconn * Remove system-local NetworkManager connection profiles (keyfiles)
pacct-log * Remove the process accounting log files
package-manager-cache * Remove package manager cache
pam-data * Remove the PAM data in the guest
passwd-backups * Remove /etc/passwd- and similar backup files
puppet-data-log * Remove the data and log files of puppet
rh-subscription-manager * Remove the RH subscription manager files
rhn-systemid * Remove the RHN system ID
rpm-db * Remove host-specific RPM database files
samba-db-log * Remove the database and log files of Samba
script * Run arbitrary scripts against the guest
smolt-uuid * Remove the Smolt hardware UUID
ssh-hostkeys * Remove the SSH host keys in the guest
ssh-userdir * Remove ".ssh" directories in the guest
sssd-db-log * Remove the database and log files of sssd
tmp-files * Remove temporary files
udev-persistent-net * Remove udev persistent net rules
user-account   Remove the user accounts in the guest
utmp * Remove the utmp file
yum-uuid * Remove the yum UUID
customize * Customize the guest
dhcp-client-state * Remove DHCP client leases
dhcp-server-state * Remove DHCP server leases
dovecot-data * Remove Dovecot (mail server) data
firewall-rules   Remove the firewall rules
flag-reconfiguration   Flag the system for reconfiguration
fs-uuids   Change filesystem UUIDs
ipa-client * Remove the IPA files
kerberos-data   Remove Kerberos data in the guest
kerberos-hostkeytab * Remove the Kerberos host keytab file in the guest
logfiles * Remove many log files from the guest
lvm-system-devices * Remove LVM2 system.devices file
lvm-uuids * Change LVM2 PV and VG UUIDs
machine-id * Remove the local machine ID
mail-spool * Remove email from the local mail spool directory
net-hostname * Remove HOSTNAME and DHCP_HOSTNAME in network interface configuration
net-hwaddr * Remove HWADDR (hard-coded MAC address) configuration
net-nmconn * Remove system-local NetworkManager connection profiles (keyfiles)
pacct-log * Remove the process accounting log files
package-manager-cache * Remove package manager cache
pam-data * Remove the PAM data in the guest
passwd-backups * Remove /etc/passwd- and similar backup files
puppet-data-log * Remove the data and log files of puppet
rh-subscription-manager * Remove the RH subscription manager files
rhn-systemid * Remove the RHN system ID
rpm-db * Remove host-specific RPM database files
samba-db-log * Remove the database and log files of Samba
script * Run arbitrary scripts against the guest
smolt-uuid * Remove the Smolt hardware UUID
ssh-hostkeys * Remove the SSH host keys in the guest
ssh-userdir * Remove ".ssh" directories in the guest
sssd-db-log * Remove the database and log files of sssd
tmp-files * Remove temporary files
udev-persistent-net * Remove udev persistent net rules
user-account   Remove the user accounts in the guest
utmp * Remove the utmp file
yum-uuid * Remove the yum UUID

Selbstverständlich gibt es mit virt-sysprep(1) auch eine Manpage, welche die Nutzung des Programms und sämtliche Optionen erläutert.

Es ist sehr wichtig, dass die zu behandelnde Domain (VM) ausgeschaltet ist, bevor virt-sysprep gestartet wird, um eine Korruption der Image-Datei zu vermeiden.

Der nun folgende Code-Block zeigt die Anwendung von virt-sysprep auf die qcow2-Datei meines debian11-templates. Die dabei verwendeten Option --operations defaults,-ssh-userdir sorgt dafür, dass alle Standard-Operationen mit der Ausnahme durchgeführt werden, dass die .ssh-Verzeichnisse der User erhalten bleiben. Die Option --firstboot-command 'dpkg-reconfigure openssh-server' stellt sicher, dass beim ersten Start des Klons neue SSH-Hostkeys generiert werden. Andernfalls kann der SSH-Dienst nicht starten und ich wäre nicht in der Lage mich via SSH anzumelden. Anschließend ist das Image bereit, um geklont bzw. kopiert zu werden.

$ virt-sysprep -a /var/lib/libvirt/images/debian11-template.qcow2 --operations defaults,-ssh-userdir --firstboot-command 'dpkg-reconfigure openssh-server'
[   0.0] Examining the guest ...
[   2.0] Performing "abrt-data" ...
[   2.0] Performing "backup-files" ...
[   2.1] Performing "bash-history" ...
[   2.1] Performing "blkid-tab" ...
[   2.1] Performing "crash-data" ...
[   2.1] Performing "cron-spool" ...
[   2.1] Performing "dhcp-client-state" ...
[   2.1] Performing "dhcp-server-state" ...
[   2.1] Performing "dovecot-data" ...
[   2.1] Performing "ipa-client" ...
[   2.1] Performing "kerberos-hostkeytab" ...
[   2.2] Performing "logfiles" ...
[   2.2] Performing "lvm-system-devices" ...
[   2.2] Performing "machine-id" ...
[   2.2] Performing "mail-spool" ...
[   2.2] Performing "net-hostname" ...
[   2.2] Performing "net-hwaddr" ...
[   2.3] Performing "net-nmconn" ...
[   2.3] Performing "pacct-log" ...
[   2.3] Performing "package-manager-cache" ...
[   2.3] Performing "pam-data" ...
[   2.3] Performing "passwd-backups" ...
[   2.3] Performing "puppet-data-log" ...
[   2.3] Performing "rh-subscription-manager" ...
[   2.3] Performing "rhn-systemid" ...
[   2.4] Performing "rpm-db" ...
[   2.4] Performing "samba-db-log" ...
[   2.4] Performing "script" ...
[   2.4] Performing "smolt-uuid" ...
[   2.4] Performing "ssh-hostkeys" ...
[   2.4] Performing "sssd-db-log" ...
[   2.4] Performing "tmp-files" ...
[   2.4] Performing "udev-persistent-net" ...
[   2.4] Performing "utmp" ...
[   2.4] Performing "yum-uuid" ...
[   2.4] Performing "customize" ...
[   2.4] Setting a random seed
[   2.5] Setting the machine ID in /etc/machine-id
[   2.5] Installing firstboot command: dpkg-reconfigure openssh-server
[   2.5] SELinux relabelling
[   2.5] Performing "lvm-uuids" ...

Das Programm ist nicht auf qcow2-Images beschränkt. Einen weiteren Anwendungsfall habe ich bereits hier im Blog beschrieben: VMware ESXi: VMDK-Datei einer Gast-VM mit virt-sysprep bereinigen.

Verwendung der Gold-Images

Die Gold-Images werden verwendet, um durch Klonen neue VMs zu erstellen. Ich verwende dazu die Eingangs erwähnte Ansible-Rolle kvm_provision_lab.

Was tun, wenn der Platz knapp wird?

Wird im Laufe des Lebenszyklus einer VM mehr Speicherplatz benötigt, so lässt sich das Vorgehen wie folgt skizzieren:

  1. Neue virtuelle Festplatte mit ausreichend Speicherkapazität der VM hinzufügen.
  2. Eine Partition auf der neuen virtuellen Festplatte erstellen (optional).
  3. Mit pvcreate ein neues Physical Volume erstellen.
  4. Das Physical Volume mit vgextend der Volume Group hinzufügen.
  5. Das Logical Volume mit lvextend vergrößern.

Die Schritte 3-5 werden ausführlich in der RHEL-9-Dokumentation in Chapter 5. Modifying the size of a logical volume beschrieben. Dort wird auch beschrieben, wie ein Logical Volume verkleinert werden kann.

Falls ihr euch hierzu ein Tutorial wünscht, lasst es mich bitte in den Kommentaren wissen. Dann liefere ich ein entsprechendes Beispiel nach.

Quellen und weiterführende Links

  1. Was ist ein Golden Image? URL: https://www.redhat.com/de/topics/linux/what-is-a-golden-image.
  2. What is a golden image? On opensource.com by Seth Kenlon (Team, Red Hat). URL: https://opensource.com/article/19/7/what-golden-image.
  3. What is a golden image? Definition by Nick Martin on TechTarget. URL: https://www.techtarget.com/searchitoperations/definition/golden-image
  4. Definition Golden Image (auch Master Image oder Goldenes Abbild). ComputerWeekly.de. URL: https://www.computerweekly.com/de/definition/Golden-Image-auch-Master-Image-oder-Goldenes-Abbild
  5. Was ist ein Golden Image? 21.11.2018: Autor / Redakteur: Dipl. Betriebswirt Otto Geißler / Ulrike Ostler. URL: https://www.datacenter-insider.de/was-ist-ein-golden-image-a-775197/
  6. Wikipedia-Artikel zu qcow {EN}
  7. QEMU disk image utility
  8. Wikepdia-Artikel Thin zu provisioning {EN}
  9. Performing a standard RHEL 9 installation; Appendix B. Partitioning reference; URL: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/performing_a_standard_rhel_9_installation/partitioning-reference_installing-rhel#recommended-partitioning-scheme_partitioning-reference
  10. Debian: Empfohlene Partitionsschemata. URL: https://www.debian.org/releases/bullseye/amd64/apcs03.de.html
  11. Wikipedia-Artikel zu LVM
  12. LVM – Debian Wiki {EN}
  13. Configuring and managing logical volumes. Red Hat Enterprise Linux 9. A guide to the configuration and management of LVM logical volumes. {EN}
  14. Appendix B. Partitioning reference. Performing a standard RHEL 9 installation. {EN}
  15. VMware ESXi: VMDK-Datei einer Gast-VM mit virt-sysprep bereinigen

Der Irrsinn mit den Zeitzonen

17. April 2023 um 05:00

In diesem Beitrag möchte ich eine Lanze für die koordinierte Weltzeit (UTC) [1] brechen.

Die Geschichte dazu

Es waren einmal Alice und Bob. Diese lebten in unterschiedlichen Ländern auf unterschiedlichen Kontinenten unserer schönen Erde. Beide wollten sich zu einer Videokonferenz verabreden, am Samstag, dem 1.4.2023 um 11:00 Uhr (IST). Wobei (IST) die Abkürzung für die verwendete Zeitzone ist (siehe [2]).

Leider verharrten Alice und Bob jeweils allein im Videokonferenzraum und fanden nicht zueinander. Denn während Alice (IST) als Irish Standard Time (UTC+01) interpretierte, richtete sich Bob nach (IST) wie in Indian Standard Time (UTC+05:30). Schade, so haben sich die zwei um 4,5 Stunden verpasst.

Das Problem

Die Angabe der Zeitzone erfolgt überwiegend als Drei-Buchstaben-Akronym. Ungünstigerweise sind diese Akronyme häufig nicht eindeutig.

So findet das Akronym IST z.B. für folgende Zeitzonen Verwendung:

  • Indian Standard Time (UTC+05:30)
  • Irish Standard Time (UTC+01)
  • Israel Standard Time (UTC+02)

Und es lassen sich weitere schöne Beispiele finden. Hier nur eine kleine Auswahl:

AMT für:

  • Amazon Time (Brazil) (UTC-04)
  • Armenia Time (UTC+04)

AST für:

  • Arabia Standard Time (UTC+03)
  • Atlantic Standard Time (UTC-04)

CST für:

  • Central Standard Time (UTC-06)
  • China Standard Time (UTC+08)
  • Cuba Standard Time (UTC-05)

Hach, was habe ich es gut. Ich lebe im Winter in den Zeitzonen CET und MET. Bzw. im Sommer dementsprechend in CEST und MEST. Da ist es dann auch genauso spät

wie in IST. Ach Moment, welches IST ist das denn jetzt schon wieder?

Von der zusätzlichen Komplexität, welche durch die Zeitumstellung zum Winter bzw. Sommer entsteht, möchte ich gar nicht erst anfangen.

Doch verzagt nicht, es besteht Hoffnung, dass Alice und Bob doch noch zueinander finden.

Die koordinierte Weltzeit (UTC)

Wie bei allen anderen Zeitzonen auch wird bei der UTC [1] die Welt in Zonen eingeteilt. Der Vorteil in der Verwendung besteht darin, dass Alice und Bob sich nicht mehr darum kümmern müssen, in welcher Zeitzone der jeweils andere lebt und wie viel Uhr 10:00 (EDT) wohl in IST oder GMT+8 sein mag. Sie müssen sich nur merken, wie viele Stunden sie selbst der UTC-Zeit voraus bzw. hinterher sind.

Beispiel:
Meine lokale Zeit entspricht im Winter der Zone UTC+01 und im Sommer, wenn wir die Uhr eine Stunde vorstellen, der Zone UTC+02. Das kann ich mir einfach merken. Wenn sich jemand mit mir um 15:00 (UTC) treffen möchte, weiß ich abhängig von der Jahreszeit, dass ich entweder um 16:00 Uhr oder um 17:00 Uhr meiner lokalen Zeit am vereinbarten Treffpunkt sein muss.

Möchte ich mich selbst verabreden und meinem Besuch ersparen, meine Zeitzone zu raten oder die Zeit umrechnen zu müssen, rechne ich meine lokale Zeit einfach in UTC um. So entspricht in diesem Monat 15:00 Uhr (UTC+02) ganz einfach 13:00 Uhr (UTC). Lebt mein Besuch in UTC-03, so kann er leicht bestimmen, dass ich ihn um 10:00 Uhr seiner lokalen Zeit erwarte.

Das Happy End

Alice und Bob haben sich darauf geeinigt, zukünftig die koordinierte Weltzeit zu verwenden. Alice lebt in Irland und möchte sich mit Bob verabreden, wenn es bei ihr 10:00 Uhr ist. Die Zeitzone von Alice entspricht UTC+01. Sie verabredet sich mit Bob um 09:00 Uhr (UTC).

Bob weiß, dass er 5,5 Stunden zur UTC-Zeit hinzuaddieren muss, um seine lokale Zeit zu bestimmen. Er wählt sich also um 14:30 Uhr (UTC+05:30) in die Videokonferenz ein.

Und wenn sie nicht gestorben sind, so zoomen sie noch heute.

Zusammenfassung

Ich hoffe, diese kleine Geschichte hat euch ein wenig unterhalten und die Vorteile, die sich aus der Verwendung der koordinierten Weltzeit ergeben, deutlich gemacht.

Bis neulich (in UTC).

Quellen und weiterführende Links

  1. https://de.wikipedia.org/wiki/Koordinierte_Weltzeit
  2. https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations (EN)
  3. https://de.wikipedia.org/wiki/Drei-Buchstaben-Akronym

Virtuellen Maschinen mithilfe der PowerCLI RAM hinzufügen oder entfernen

03. April 2023 um 06:00

In diesem Tutorial beschreibe ich, wie mithilfe der PowerCLI die RAM-Größe einer virtuellen Maschine (VM) bearbeitet werden kann. Diese Methode lässt sich auch anwenden, um mehrere VMs in einem Arbeitsablauf zu bearbeiten. Dies bietet sich z.B. immer dann an, wenn so viele VMs betroffen sind, dass der Aufwand der manuellen Bearbeitung im vSphere-Client zu groß erscheint.

Die Idee hierzu stammt ursprünglich aus dem Artikel PowerShell Friday: Adding Memory with PowerCLI von Anne Jan Elsinga aus dem Jahr 2015.

Zielstellung

Es soll die Größe des Arbeitsspeichers von zunächst einer und anschließend mehrerer VMs bearbeitet werden. Dabei wird gezeigt, wie RAM im laufenden Betrieb erhöht und bei ausgeschalteten VMs reduziert werden kann.

Voraussetzungen

Eine funktionsfähige Installation der VMware PowerCLI und die Möglichkeit den vCenter Server über diese ansprechen zu können, ist Voraussetzung, um diesem Tutorial zu folgen.

Um den RAM einer VM im laufenden Zustand erhöhen zu können, muss die Option Memory Hot Plug der betreffenden VM aktiviert sein.

Anmeldung am vCenter

Zu Beginn verbindet man sich zu der vCenter-Instanz, deren VMs man bearbeiten möchte. Das Kommando hat folgenden Aufbau:

Connect-VIServer [-Server] <String[]> [-Protocol {http | https}] [-User <String>]

Beispiel:

Connect-VIServer -Server vcsa.beispiel.de -Protocol https -User alice@vsphere.local

Beispiel 1: RAM einer einzelnen VM erhöhen

Dieses Beispiel ist direkt dem Artikel PowerShell Friday: Adding Memory with PowerCLI entnommen.

Der Befehl ist ein Einzeiler:

Get-VM -Name MeineVM | Set-VM -MemoryGB 2

Werte kleiner GB werden dezimal spezifiziert:

Get-VM -Name MeineVM |Set-VM -MemoryGB 0.75

Beispiel 2: RAM mehrerer VMs erhöhen

Angenommen in meiner Umgebung existieren mehrere VMs, die nach dem Muster VM-Test- benannt sind, deren RAM auf 24 GB erhöht werden soll.

Zuerst kann man sich die gewünschte Zielgruppe anzeigen lassen:

PS C:\Users\joerg> Get-VM | Where-Object {$_ | Select-String -pattern "VM-Test-\d"} | Sort

Name          PowerState Num CPUs MemoryGB
----          ---------- -------- --------
VM-Test-01    PoweredOn  4        16.000
VM-Test-02    PoweredOn  4        16.000
VM-Test-03    PoweredOn  4        16.000
VM-Test-04    PoweredOn  4        16.000
VM-Test-05    PoweredOn  4        16.000
VM-Test-07    PoweredOn  4        16.000
VM-Test-08    PoweredOn  4        16.000
VM-Test-09    PoweredOn  4        16.000
VM-Test-10    PoweredOn  4        16.000
VM-Test-11    PoweredOn  4        16.000
VM-Test-12    PoweredOn  4        16.000
VM-Test-13    PoweredOn  4        16.000
VM-Test-14    PoweredOn  4        16.000
VM-Test-15    PoweredOn  4        16.000
VM-Test-17    PoweredOn  4        16.000
VM-Test-18    PoweredOn  4        16.000

Der Platzhalter ‚\d‘ steht dabei für beliebige Dezimalzahl.

Der RAM kann nun wie folgt auf jeweils 24 GB erhöht werden:

PS C:\Users\joerg> Get-VM | Where-Object {$_ | Select-String -pattern "VM-Test-\d"} | Set-VM -MemoryGB 24

Confirmation
Proceed to configure the following parameters of the virtual machine with name 'VM-Test-01'?
New MemoryMB: 24765MB
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): A

Name          PowerState Num CPUs MemoryGB
----          ---------- -------- --------
VM-Test-01    PoweredOn  4        24.000
VM-Test-02    PoweredOn  4        24.000
VM-Test-03    PoweredOn  4        24.000
VM-Test-04    PoweredOn  4        24.000
VM-Test-05    PoweredOn  4        24.000
VM-Test-07    PoweredOn  4        24.000
VM-Test-08    PoweredOn  4        24.000
VM-Test-09    PoweredOn  4        24.000
VM-Test-10    PoweredOn  4        24.000
VM-Test-11    PoweredOn  4        24.000
VM-Test-12    PoweredOn  4        24.000
VM-Test-13    PoweredOn  4        24.000
VM-Test-14    PoweredOn  4        24.000
VM-Test-15    PoweredOn  4        24.000
VM-Test-17    PoweredOn  4        24.000
VM-Test-18    PoweredOn  4        24.000

Beispiel 3: RAM einer VM reduzieren

Auch dieses Beispiel ist direkt dem Artikel PowerShell Friday: Adding Memory with PowerCLI entnommen.

Die betroffene VM muss dazu ausgeschaltet werden:

Get-VM -Name MeineVM | Shutdown-VMGuest|Set-VM -MemoryGB 0.25

Zusammenfassung

Mit der PowerCLI ist es möglich sich wiederholende Tätigkeiten abarbeiten zu lassen. Dies spart Zeit und Nerven.

Mir hat der Tipp von Anne Jan Elsinga sehr geholfen, weshalb ich die Methode hier für mich, euch und die Nachwelt dokumentiert habe.

RHEL System Roles: firewall

27. März 2023 um 06:00

In Teil 5 meiner losen Reihe über die RHEL System Roles stelle ich die Rolle firewall vor. Diese dient der Konfiguration des Regelwerks für firewalld. Möchte man bestimmte Regelsätze für alle bzw. eine Gruppe von Servern konfigurieren, erleichtert der Einsatz dieser Rolle die Arbeit und reduziert den administrativen Aufwand.

Im Folgenden stelle ich meinen Anwendungsfall vor. Anschließend beschreibe ich, wie ich diesen mithilfe der RHEL System Role löse.

Während mir dieser Artikel zur Dokumentation dient, soll er euch den Einsatz von RHEL Roles verdeutlichen.

Warnung: Die Firewall-Konfiguration eines Hosts über das Netzwerk birgt die Gefahr, sich selbst auszusperren. Wenn dies passiert, benötigt man höchstwahrscheinlich physischen Zugriff auf den verkonfigurierten Host.

Anwendungsfall

Ich möchte sicherstellen, dass auf allen RHEL-Systemen in meiner Labor-Umgebung das Paket firewalld installiert ist, der Service aktiviert ist und läuft. Darüber hinaus möchte ich sicherstellen, dass ausschließlich der SSH-Zugriff in der lokalen Hostfirewall freigegeben ist und alle übrigen Regeln entfernt werden.

Die Rolle

Durch die Installation des Pakets rhel-system-roles existiert diese Rolle bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.firewall/ und die Dokumentation in /usr/share/doc/rhel-system-roles/firewall/README.md. Aus letzterer stammt auch folgendes Beispiel:

---
- name: Erase existing config and enable ssh service
  hosts: myhost

  vars:
    firewall:
      - previous: replaced
      - service: 'ssh'
        state: 'enabled'
  roles:
    - rhel-system-roles.firewall

Dieses Beispiel kann ich direkt in das folgende Playbook übernehmen.

Das Playbook

Das Playbook ist kompakt und übersichtlich:

---
- name: Ensure firewalld is started with SSH-acess only
  hosts: all

  vars:
    firewall:
      - previous: replaced
      - service: 'ssh'
        state: 'enabled'
  roles:
    - rhel-system-roles.firewall

Der folgende Playbook-Lauf in meiner Labor-Umgebung zeigt, dass sich die RHEL System Role auch um die Installation, Aktivierung und den Start des Dienstes firewalld kümmert, wenn dies erforderlich ist.

[root@ansible-ctrl ansible]# ansible-playbook firewall_config.yml 

PLAY [Ensure firewalld is started with SSH-acess only] ************************************

TASK [Gathering Facts] ********************************************************************
ok: [rhel7]
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel8]

TASK [rhel-system-roles.firewall : include_tasks] *****************************************
included: /usr/share/ansible/roles/rhel-system-roles.firewall/tasks/firewalld.yml for ansible-pctrl, rhel7, rhel8, rhel9

TASK [rhel-system-roles.firewall : Ensure ansible_facts used by role] *********************
ok: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]

TASK [rhel-system-roles.firewall : Install firewalld] *************************************
ok: [ansible-pctrl]
changed: [rhel9]
changed: [rhel8]
changed: [rhel7]

TASK [rhel-system-roles.firewall : Install python-firewall] *******************************
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel9]
ok: [rhel7]

TASK [rhel-system-roles.firewall : Install python3-firewall] ******************************
skipping: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]

TASK [rhel-system-roles.firewall : Enable and start firewalld service] ********************
ok: [ansible-pctrl]
changed: [rhel7]
changed: [rhel9]
changed: [rhel8]

TASK [rhel-system-roles.firewall : Check if previous replaced is defined] *****************
ok: [rhel7]
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel8]

TASK [rhel-system-roles.firewall : Get config files, checksums before and remove] *********
ok: [rhel9]
ok: [rhel7]
ok: [rhel8]
ok: [ansible-pctrl]

TASK [rhel-system-roles.firewall : Configure firewall] ************************************
ok: [rhel7] => (item={'service': 'ssh', 'state': 'enabled'})
ok: [rhel9] => (item={'service': 'ssh', 'state': 'enabled'})
ok: [rhel8] => (item={'service': 'ssh', 'state': 'enabled'})
ok: [ansible-pctrl] => (item={'service': 'ssh', 'state': 'enabled'})

TASK [rhel-system-roles.firewall : gather firewall config information] ********************
skipping: [ansible-pctrl] => (item={'service': 'ssh', 'state': 'enabled'}) 
skipping: [rhel9] => (item={'service': 'ssh', 'state': 'enabled'}) 
skipping: [rhel7] => (item={'service': 'ssh', 'state': 'enabled'}) 
skipping: [rhel8] => (item={'service': 'ssh', 'state': 'enabled'}) 

TASK [rhel-system-roles.firewall : update firewalld_config fact] **************************
skipping: [rhel7]
skipping: [ansible-pctrl]
skipping: [rhel9]
skipping: [rhel8]

TASK [rhel-system-roles.firewall : gather firewall config if no arguments] ****************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel9]
skipping: [rhel8]

TASK [rhel-system-roles.firewall : update firewalld_config fact] **************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel9]
skipping: [rhel8]

TASK [rhel-system-roles.firewall : Get config files, checksums after] *********************
ok: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]

TASK [rhel-system-roles.firewall : Calculate what has changed] ****************************
changed: [ansible-pctrl]
changed: [rhel7]
changed: [rhel9]
changed: [rhel8]

TASK [rhel-system-roles.firewall : Show diffs] ********************************************
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel7]
skipping: [rhel9]

PLAY RECAP ********************************************************************************
ansible-pctrl              : ok=11   changed=1    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   
rhel7                      : ok=11   changed=3    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   
rhel8                      : ok=11   changed=3    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   
rhel9                      : ok=11   changed=3    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0

Fazit

Ich beginne, mich an dieser Stelle zu wiederholen. Auch diesmal war es möglich, mithilfe einer RHEL System Role einen einfachen Anwendungsfall schnell und unkompliziert zu lösen, ohne selbst eine Ansible-Rolle schreiben zu müssen. Ein einfaches Copy-Past-and-Modify genügte.

In meinen Augen ist es Red Hat gelungen, den System-Administratoren mit den RHEL System Roles etwas Arbeit abzunehmen und sie beim Einsatz von Ansible zu unterstützen.

Lasst euch überraschen, welche Rolle ich mir als nächstes herauspicke.

Quellen und weiterführende Links

  1. Red Hat Enterprise Linux (RHEL) System Roles {en}
  2. Ansible Documentation: Role Directory Structure {en}
  3. Red Hat Software and Download Center {en}
  4. Die Vorteile einer Red Hat Subskription
  5. RHEL System Roles: selinux
  6. RHEL System Roles: timesync
  7. RHEL System Roles: sshd

Ansible: Seafile Professional Edition in Rootless-Podman-Umgebung bereitstellen

20. März 2023 um 06:00

Wer diesen Blog regelmäßig liest, kann den Eindruck gewinnen, es sei mein Hobby, Ansible-Rollen zu schreiben, mit denen von mir genutzte Web-Anwendungen auf Servern bereitgestellt werden können. Dieses Mal habe ich es mit Seafile getan und möchte in diesem Beitrag darüber berichten.

Was ist Seafile?

Seafile ist eine Sync&Share- bzw. Private-Cloud-Lösung ähnlich wie Nextcloud, ownCloud oder TeamDrive. Auf mich erweckt Seafile den Eindruck, als wenn der Schwerpunkt jedoch auf der Synchronisation und dem Teilen von Dateien liegt und damit genau meinem Suchmuster entspricht.

Seafile gibt es in einer Community und einer Professional Edition. Die Professional Edition darf mit bis zu drei Benutzern kostenlos verwendet werden.

Für weiterführende Informationen wird auf die Seiten des Herstellers und den Wikipedia-Artikel verwiesen.

Was ist das Ziel?

Nun, es gibt nicht das eine Ziel. Ich habe mit diesem kleinen Wochenendprojekt folgende Ziele verfolgt:

  • Beschäftige dich mit Ansible.
  • Beschäftige dich mit der Collection Containers.Podman.
  • Beschäftige dich mit rootless Podman.
  • Deploye Seafile Professional Edition in einer rootless-Podman-Umgebung, um es als Sync&Share-Lösung nutzen zu können.

Die Vorgehensweise

Zuerst habe ich mich informiert, ob es Container-Images für Seafile gibt und ob entsprechende Installationswege in der Dokumentation beschrieben sind. Meine Recherche förderte folgende Treffer zutage:

Ansible-Rollen haben eine einheitliche Struktur. Mit dem Befehl ansible-galaxy role init ansible_role_deploy_seafile_with_rootless_podman habe ich das Grundgerüst für meine Rolle erstellt. Anschließend habe ich die notwendigen Dateien {defaults,meta,tasks,vars}/main.yml mit Inhalt gefüllt und nicht benötigte Verzeichnisse wie handlers gelöscht. Mir ist dabei wichtig, dass alle notwendigen Parameter über Variablen definiert werden, die in defaults/main.yml zu finden sind. In vars/main.yml befinden sich hingegen nur Variablen, welche intern von der Rolle verwendet werden und vom Benutzer nicht explizit gesetzt werden sollen. So lässt sich die Rolle leicht wiederverwenden, um verschiedene Seafile-Instanzen auf dem gleichen Host oder in unterschiedlichen Umgebungen zu deployen.

Bevor ich die Rolle zum ersten Mal auf meinen Server angewendet habe, habe ich sie mit yamllint und ansible-lint geprüft und vorhandene Warnungen und Fehler behoben. Allerdings lassen sich mit den Lint-Werkzeugen und der Option --syntax-check nicht alle Fehler im Vorfeld finden. Da mir ein zweites Augenpaar fehlte, habe ich die letzten Tippfehler erst durch die Verwendung des Ansible Playbook Debugger gefunden.

Das Ergebnis

Das Ergebnis findet ihr auf:

Unter allen drei URLs könnt ihr meine Rolle herunterladen. Es ist damit möglich, eine lauffähige Seafile Pro Instanz bereitzustellen. Ein Test auf Idempotenz und ob diese Rolle auch zur Aktualisierung einer bestehenden Umgebung genutzt werden kann, steht noch aus.

Ihr seid herzlich eingeladen, die Rolle bei Interesse zu testen. Ich freue mich über Rückmeldungen zur Rolle und Dokumentation (Readme.md).

Ich habe das Deployment bisher nur auf Debian Buster getestet. Daher freue ich mich besonders über Rückmeldungen, wenn ihr die Rolle erfolgreich auf anderen Plattformen angewendet habt. Dann kann ich die entsprechenden Angaben für Ansible Galaxy ergänzen.

Eure Rückmeldungen nehme ich in den Kommentaren zu diesem Beitrag, per E-Mail oder in meinem neuen Matrix-Kanal #my-it-brain:matrix.org entgegen.

Fragen an meine Leser*innen

Ich interessiere mich für Themen rund um Ansible und Podman und frage mich, wie dies bei euch aussieht. Daher freue ich mich, wenn ihr in den Kommentaren oder gern auch per E-Mail und Chat folgende Fragen beantworten mögt:

  • Verwendet ihr Ansible für Software-Deployments?
  • Kennt ihr Podman und nutzt ihr es im privaten und/oder beruflichen Umfeld?
  • Findet ihr die Nutzung von Ansible zur Bereitstellung von Software auf (rootless) Podman sinnvoll? Oder bevorzugt ihr andere Bereitstellungsverfahren?

Ich freue mich auf eure Antworten.

Erfahrungsbericht zu den Chemnitzer Linux-Tagen 2023

15. März 2023 um 07:06

Endlich sind wieder Chemnitzer Linux-Tage vor Ort in der Karl-Marx-Stadt. Für mich beginnen diese schon Wochen vorher, wenn die Vorfreude auf dieses Ereignis einsetzt.

Wenn dieser Erlebnisbericht veröffentlicht wird, ist die Konferenz leider schon wieder vorbei. Wie es war, erfahrt ihr im nun folgenden Text.

Anreise

Nach der Vorfreude kommt die Anreise. Bisher bin ich immer mit dem Auto nach Chemnitz gefahren. Von meinem Wohnort aus war ich regelmäßig nach 3,5-4,5 Std. im Hotel, inkl. Pause, Tanken, Essen und Erleichtern.

Ich mag Autofahrten. Ich kann während der Fahrt entspannen, meine Musik so laut hören, wie ich mag und muss keine anderen Menschen neben mir dulden. Es ist für mich eine komfortable Art zu reisen. Einziger Nachteil, ich kann während der Fahrt nicht arbeiten, bloggen, etc.

Da mich diesmal mein Umweltgewissen plagte und ich die Zeit besser nutzen wollte, habe ich meine Reise mit der Bahn geplant. Um nicht ganz so unkomfortabel zu reisen, habe ich 1. Klasse gebucht. Kann man frühzeitig planen und Sparpreisangebote nutzen, ist der Preis gegenüber der 2. Klasse durchaus gerechtfertigt, zumal die Sitzplatzreservierung schon mit inbegriffen ist.

Ganz ohne Auto geht es dennoch nicht. Da ich den ersten Fernverkehrsbahnhof mit dem ÖPNV erst nach 1-1,5 Std. erreicht hätte, bin ich in 40 Min. mit dem Auto zum nächsten kostenlosen Park&Ride-Parkplatz gefahren und von dort mit der Stadtbahn weiter zum Hauptbahnhof. Hier ging meine Reise mit 7 Minuten Zugverspätung weiter. Bei einer geplanten Umstiegszeit von 8 Minuten sah ich meinen Anschlusszug schon ohne mich fahren. Doch auf unsere Bahn ist Verlass. Auch der Anschlusszug hatte Verspätung, sodass ich wie geplant mit diesem weiterreisen konnte.

In Leipzig wurde es dann auch nochmal kurz hektisch, da für den Umstieg nur 4 Minuten verblieben. Für derartige Sprints bin ich definitiv zu unsportlich. Doch auch hier habe ich den Anschluss noch erreicht und traf in einem sehr vollen RE auf die ersten CLT-Teilnehmerinnen und Teilnehmer. So verging auch die letzte Stunde der Anreise wie im Flug.

Insgesamt war die Anreise sehr entspannt, bequem und angenehm. WLAN funktionierte im ICE und IC durchgängig und ich konnte so mobil arbeiten.

Die Chemnitzer Linux-Tage 2023

Nun habe ich der Anreise soviel Raum gewidmet und weiß gar nicht, was ich groß über die Veranstaltung selbst schreiben soll. Dies ist vielleicht noch dem tollen Gefühl geschuldet, die Community endlich mal wieder vor Ort getroffen zu haben.

Laut Resümee der Veranstalter kamen rund 3000 Besucherinnen und Besucher zur Veranstaltung. Somit gab es reichlich Gelegenheit zum Fachsimpeln, Netzwerken und es einfach mal zu genießen, unter normalen Menschen zu sein.

Am Samstag habe ich zwei Vorträge besucht:

Mehr war nicht drin. Denn es war einfach zu schön, alte Bekannte wiederzutreffen und neue Nerds kennenzulernen. Nach drei Jahren Pause war mir dies ein Fest.

Das Bild zeigt Mitglieder der Red Hat Accelerators und Ubuntuusers Community vor dem Ubuntu Stand auf den Chemnitzer LInux-Tagen 2023.
Gruppenfoto mit Mitgliedern der Red Hat Accelerators und Ubuntuusers Community vor dem Ubuntu-Stand auf den Chemnitzer Linux-Tagen 2023.

Im Laufe des Tages haben Dirk, Sujeevan und ich noch unseren Gemeinschaftsvortrag geprobt. Wir konnten während des Tages bereits auffällig oft vernehmen, dass etliche Personen diesen unbedingt hören wollten. Bloß keinen Druck aufkommen lassen. ;-)

Die Samstagabendveranstaltung fand in diesem Jahr in der Mensa statt, welche schräg gegenüber dem Hörsaalgebäude liegt. In meinen Augen war es eine gute Entscheidung, das Abendprogramm hierhin zu verlegen. Hier konnte man gemütlich zusammensitzen und ein wenig essen, trinken und plaudern. Das Essen war übrigens großartig!

Nur die Musik passte meiner Meinung nach nicht so recht zum Publikum. Statt Gesang mit musikalischer Begleitung auf dem Flügel hätte ich mir eher ein paar Arcade-Sounds aus 8-Bit-Midi-Prozessoren gewünscht.

Am Sonntag hatte ich die Ehre, um 10:00 Uhr einen der ersten Vorträge (Link zur Aufzeichnung) des Tages zu halten. Der Raum war voll, die Moderation spitze (Danke Henning) und auch die kleine Live-Demo hat geklappt. Im Anschluss konnte ich noch einige Fragen zu Ansible beantworten und durfte positives Feedback entgegennehmen. Lieben Dank an meine Hörerinnen und Hörer. Es war mir ein Fest, vor euch sprechen zu dürfen.

Viel Zeit zum Ausruhen gab es nicht, denn um 13:00 Uhr erläuterten Dirk, Sujeevan und ich, warum man nicht in der IT arbeiten sollte und warum wir es trotzdem tun (Link zur Aufzeichnung). Ich glaube, das Publikum im gut gefüllten Hörsaal V5 hatte Spaß mit uns. :-)

Auch nach diesem Vortrag durften wir einiges an positivem Feedback entgegennehmen und haben Fragen beantwortet. Ich wiederhole mich gern: „Es war mir auch diesmal ein Fest.“ :-)

Fazit

Danke an das Veranstaltungs-Team der Chemnitzer Linux-Tage für die Ausrichtung dieser großartigen Veranstaltung und auch allen Besucherinnen und Besuchern, dass sie diese Konferenz zu einem der schönsten Community-Treffen in Deutschland machen. Ich freue mich schon heute auf ein Wiedersehen im nächsten Jahr!

Dank der Deutschen Bahn habe ich die Rückreise nicht entspannt im Zug, sondern in einem Mietwagen zurückgelegt. Doch dieses Ärgernis soll das schöne Wochenende nicht trüben.

Wie hat euch die Veranstaltung insgesamt gefallen? Welche Vorträge oder Workshops habt ihr besucht und wie fandet ihr sie? Schreibt mir eure Erfahrungen gern in die Kommentare oder hinterlasst einen Link zu eurem Erfahrungsbericht. Dann freue ich mich. :-)

RHEL System Roles: sshd

13. März 2023 um 06:00

In Teil 4 meiner losen Reihe über die RHEL System Roles stelle ich die Ansible-Rolle sshd vor. Diese dient der Konfiguration des OpenSSH-Servers, einem der wichtigsten Dienste in Linux- und UNIX-Systemen.

Wer die ersten Teile dieser Reihe gelesen hat, ist inzwischen mit der grundsätzlichen Anwendung dieser Ansible-Rollen vertraut. Die Rolle sshd bildet hier keine Ausnahme. Wendet man die Rolle ohne weitere Konfiguration auf Ziel-Systeme an, konfiguriert sie den OpenSSH-Server entsprechend der Standard-Konfiguration des jeweiligen Betriebssystems. Es werden alle Optionen der sshd_config(5) unterstützt.

Ein Wort der Warnung: Mit dieser Rolle konfiguriert ihr den SSH-Dienst der Zielsysteme. Wenn dabei ein Fehler passiert, könnt ihr euch und euren Ansible-Controller aussperren und verliert ggf. den Zugriff auf die Systeme. Behaltet dies bitte im Hinterkopf und sorgt ggf. für alternative Zugänge, wie z.B. über eine lokale Konsole.

Bei der Konfiguration meiner Server ist mir persönlich wichtig, dass

  • der Benutzer root sich nur mittels SSH-Public-Key-Verfahren anmelden kann,
  • die Public-Key-Authentifizierung aktiviert ist,
  • die Passwort-Authentifizierung deaktiviert ist und
  • in der Datei .ssh/authorized_keys des jeweiligen Benutzers nach dem SSH-Public-Key gesucht wird.

Darüber hinaus möchte ich alle Git-bezogenen Umgebungsvariablen (GIT_*) nutzen. Die übrigen Einstellungen möchte ich auf den Standard-Werten des jeweiligen Betriebssystems belassen.

Im Folgenden beschreibe ich, wie sich diese mit der RHEL System Role sshd umsetzen lässt.

Voraussetzungen

Wie bei allen RHEL System Roles müssen auch hier die Pakete ansible-core und rhel-system-roles inkl. ihrer Abhängigkeiten auf dem Ansible-Controller installiert sein. Der Ansible-Controller muss die Ziel-Hosts über SSH erreichen können und über einen Benutzer mit sudo-Berechtigungen verfügen.

Das Playbook

Es werden bereits zwei Beispiel-Playbooks mitgeliefert, die sich im Pfad /usr/share/doc/rhel-system-roles/sshd/ befinden. Diese heißen:

  • example-accept-env-playbook.yml und
  • example-root-login-playbook.yml.

Aus diesen beiden Beispieldateien habe ich das folgende Playbook für meine Labor-Umgebung erstellt:

---
- hosts: all
  tasks:
  - name: Configure sshd to accept some useful environment variables
    include_role:
      name: rhel-system-roles.sshd
    vars:
      sshd:
        PermitRootLogin: without-password
        PasswordAuthentication: no
        PubkeyAuthentication: yes
        AuthorizedKeysFile: .ssh/authorized_keys
        # there are some handy environment variables to accept
        AcceptEnv:
          LANG
          LS_COLORS
          EDITOR
          GIT_*

Wie zu sehen ist, habe ich mich entschieden, noch ein paar weitere Umgebungsvariablen zu konfigurieren. Diese habe ich aus dem Beispiel example-accept-env-playbook.yml übernommen.

Testlauf in Labor-Umgebung

Auch dieses Playbook habe ich in meiner Labor-Umgebung, bestehend aus einem RHEL8-Ansible-Controller und jeweils einem rhel{7..9}-Client laufen lassen. Mit den Optionen -C -D ist die Ausgabe 707 Zeilen lang, weswegen der folgende Code-Block nur den Aufruf und das Ergebnis zeigt.

[root@ansible-ctrl ansible]# ansible-playbook sshd_config.yml -C -D

PLAY [all] ************************************************************************************************************
[...]
PLAY RECAP *******************************************************************************************************************************
ansible-pctrl              : ok=20   changed=2    unreachable=0    failed=0    skipped=13   rescued=0    ignored=0   
rhel7                      : ok=20   changed=2    unreachable=0    failed=0    skipped=13   rescued=0    ignored=0   
rhel8                      : ok=20   changed=2    unreachable=0    failed=0    skipped=13   rescued=0    ignored=0   
rhel9                      : ok=21   changed=2    unreachable=0    failed=0    skipped=12   rescued=0    ignored=0

Zusammenfassung

Die RHEL System Role sshd wurde kurz vorgestellt und genutzt, um meine bevorzugten Einstellungen für den OpenSSH-Dienst in meiner Labor-Umgebung zu konfigurieren. Alle Optionen in der sshd_config(5), welche ich nicht explizit über die Ansible-Rolle konfiguriert habe, werden auf die Standardwerte des Betriebssystems eingestellt. Es ist also ggf. Vorsicht geboten, wenn Systeme mit bestehender Konfiguration bearbeitet werden.

Selbstverständlich schützt ein einmaliger Playbook-Lauf nicht davor, dass ein Benutzer mit root-Berechtigungen lokale Änderungen an der Datei /etc/ssh/sshd_config vornimmt. Dies mag vorübergehend für Tests auch so gewollt sein. Damit die Konfiguration nicht dauerhaft vom SOLL-Zustand abweicht, kann man das Playbook regelmäßig durch cron(8) ausführen lassen, um evtl. Abweichungen zu korrigieren.

Quellen und weiterführende Links

  1. Red Hat Enterprise Linux (RHEL) System Roles {en}
  2. Ansible Documentation: Role Directory Structure {en}
  3. Red Hat Software and Download Center {en}
  4. Die Vorteile einer Red Hat Subskription
  5. RHEL System Roles: selinux
  6. RHEL System Roles: timesync
  7. RHEL System Roles: firewall
❌
❌