Normale Ansicht

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

Wird 2023 das Jahr der E-Mail-Verschlüsselung?

18. Januar 2023 um 07:34

Ich entschuldige mich direkt für den reißerischen Titel. Ich persönlich glaube nicht, dass sich in dieser Hinsicht 2023 viel verändern wird.

Ich möchte an dieser Stelle lediglich darauf hinweisen, dass ihr meinen aktuellen GnuPG-Public-Key an dieser Stelle zum Download und per Web Key Directory findet.

Vorstellung der Red Hat Enterprise Linux (RHEL) System Roles

23. Januar 2023 um 06:00

In diesem Artikel möchte ich euch die RHEL System Roles vorstellen. Er bildet den Beginn einer losen Artikelserie, welche in die einzelnen Rollen einführt und deren Nutzung beispielhaft darstellt.

Dieser Artikel gibt Antworten auf die folgenden Fragen:

  1. Was sind RHEL System Roles?
  2. Für wen sind RHEL System Roles gedacht?
  3. Wie nutzt man RHEL System Roles?
  4. Wie geht es nach diesem Artikel weiter?

Was sind RHEL System Roles?

Die RHEL System Roles sind eine Sammlung von Ansible-Rollen [2]. Sie stellen eine stabile Konfigurations-Schnittstelle bereit, um mehrere RHEL-Releases verwalten und konfigurieren zu können.

Sie leiten sich aus dem Upstream-Projekt Linux System Roles ab, welches die Distributionen CentOS (6+), Fedora und RHEL (6+) unterstützt.

Für wen sind RHEL System Roles gedacht?

Die RHEL System Roles sollen Systemadministratoren bei weit verbreiteten Anwendungsfällen unterstützen. Dazu zählen z.B. die Konfiguration von Server-Diensten wie SSH, Postfix, Zeitsynchronisation und SELinux sowie Netzwerkschnittstellen, Firewallregeln, etc.

Möchte ein Admin z.B. seine Server dafür konfigurieren, dass sie sich alle mit den gleichen Zeitservern synchronisieren, kann er dafür die Rolle timesync nutzen. Dabei müssen sich Admins nicht mit Implementierungsdetails oder der Frage beschäftigen, ob die Zielsysteme ntp oder chrony als Dienst für die Synchronisierung verwenden. Sie definieren lediglich Werte für die Variablen der Ansible-Rolle und überlassen die Details dem Konfigurationsmanagement (Ansible).

Die Rollen können direkt genutzt werden. Der Aufwand zur Erstellung und Test der eigenen Rollen entfällt bzw. wird stark reduziert.

Wie nutzt man RHEL System Roles?

Besitzer einer RHEL-Subskription [4] können das Paket rhel-system-roles auf ihrem Ansible Controller installieren. Dies kann aus den Repositorien RHEL 7 Extras bzw. den Application Streams für RHEL 8 und RHEL 9 installiert werden (vgl [1]).

Beispiel für RHEL 8 und RHEL 9:

# dnf install rhel-system-roles

Anschließend findet man die Dokumentation zu den einzelnen Rollen im Pfad /usr/share/doc/rhel-system-roles/<ROLLENNAME>/README.md. Neben dem README findet man auch einige Beispiel-Playbooks.

Die Rollen selbst liegen nach der Installation im Verzeichnis /usr/share/ansible/roles/rhel-system-roles.<ROLLENNAME>. Da sich die RHEL System Roles an die Role Directory Structure halten, findet sich auch hier eine README.md-Datei mit der Dokumentation zur Rolle.

Beispiel für Fedora:

# dnf install linux-system-roles

Hier befindet sich die Dokumentation im Pfad /usr/share/doc/linux-system-roles/<ROLLENNAME> und die Rollen selbst in /usr/share/ansible/roles/linux-system-roles.<ROLLENNAME>.

Darüber hinaus kann man sich die Rollen auch von Ansible Galaxy herunterladen.

Damit sind die Vorbereitungen abgeschlossen und man kann mit der Nutzung beginnen. Der folgende Codeblock zeigt dazu das Beispiel eines Playbooks zur Konfiguration von NTP auf einer Gruppe von Hosts (Inhalt der Variable targets):

- hosts: "{{ targets }}"
  vars:
    timesync_ntp_servers:
      - hostname: 2.pool.ntp.org
        pool: yes
        iburst: yes
  roles:
    - rhel-system-roles.timesync

Wie geht es nach diesem Artikel weiter?

Um mich selbst mit den RHEL System Roles vertraut zu machen, werde ich mir nach und nach eine Rolle herauspicken, diese in einem separaten Artikel kurz vorstellen und in meiner Labor-Umgebung einsetzen.

Die Labor-Umgebung besteht aus einem Ansible-Controller basierend auf RHEL 8 mit den Paketen ansible-core in Version 2.13.3 und rhel-system-roles in Version 1.20.1. Die genutzten Versionen mögen sich im Laufe der Zeit und in Abhängigkeit zur Verfügbarkeit neuer Releases ändern. Als Remote-Nodes verwende ich jeweils eine virtuelle Maschine mit RHEL 7, 8 und 9. Die Labor-Umgebung selbst provisioniere ich ebenfalls mit Ansible und der Rolle kvm_provision_lab.

Die VMs basieren auf qcow2-Images, welche ich aus dem Red Hat Customer Portal heruntergeladen habe (siehe [3]).

Die weiteren Artikel werde ich nach deren Veröffentlichung im folgenden Abschnitt verlinken, so dass diese auch von hier aus erreichbar sind.

Ich hoffe, dass diese Serie auch für euch nützliche Informationen bereitstellt und freue mich natürlich jederzeit über eure Rückmeldungen dazu.

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
  8. RHEL System Roles: firewall

RHEL System Roles: selinux

06. Februar 2023 um 06:00

Dies ist Teil 2 meiner kleinen Serie zu den RHEL System Roles. Ich beschreibe hierin, wie die Ansible-Rolle selinux genutzt werden kann, um Einstellungen für SELinux auf mehreren/allen Hosts in der eigenen Infrastruktur zu konfigurieren.

Die Anforderung dies zu tun, lässt sich bspw. aus den IT-Grundschutzbausteinen SYS.1.3.A10, SYS.1.3.A16, SYS.2.3.A8 und SYS.2.3.A17 des BSI [2] ableiten.

Falls euch SELinux noch nichts sagt, schaut zuerst in meine Einführung in das grundlegende Konzept von SELinux [1].

In dem folgenden und zugegeben sehr einfachen Beispiel nutze ich ein Playbook, welches sicherstellt, dass SELinux auf allen Ziel-Hosts im Modus Enforcing läuft. Dieses Playbook kann ich dann bspw. durch cron(8) in regelmäßigen Abständen laufen lassen, um sicherzustellen, dass sich SELinux im gewünschten Modus befindet bzw. in diesen zurückversetzt wird.

Voraussetzungen

Auf dem Ansible-Controller müssen die Pakete ansible-core und rhel-system-roles installiert sein.

Das Playbook

Die Dokumentation zu dieser Ansible-Rolle befindet sich in /usr/share/doc/rhel-system-roles/selinux/README.md. Darin enthalten ist auch ein Code-Beispiel, aus dem ich das folgende Playbook erstellt habe:

---
- name: Enforce SELinux Policy
  hosts: all
  vars:
    selinux_policy: targeted
    selinux_state: enforcing
  roles:
    - role: rhel-system-roles.selinux
      become: true

Testlauf in der Laborumgebung

Der erste Code-Block gibt die Ausgabe des Playbook-Laufs wieder. Der zweite Code-Block zeigt ein Ansible-Ad-Hoc-Kommando, mit dem ich kontrolliere, ob Ansible auf allen Ziel-Hosts im Enforcing-Modus läuft.

[root@ansible-ctrl ansible]# pwd
/root/ansible
[root@ansible-ctrl ansible]# ansible-playbook enfoce_selinux.yml 

PLAY [Enforce SELinux Policy] **************************************************************************

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

TASK [rhel-system-roles.selinux : Set ansible_facts required by role and install packages] *************
included: /usr/share/ansible/roles/rhel-system-roles.selinux/tasks/set_facts_packages.yml for ansible-pctrl, rhel7, rhel8, rhel9

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

TASK [rhel-system-roles.selinux : Install SELinux python2 tools] ***************************************
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel9]
ok: [rhel7]

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

TASK [rhel-system-roles.selinux : refresh facts] *******************************************************
ok: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]

TASK [rhel-system-roles.selinux : Install SELinux tool semanage] ***************************************
skipping: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]

TASK [rhel-system-roles.selinux : Set permanent SELinux state if enabled] ******************************
ok: [rhel7]
ok: [rhel9]
ok: [rhel8]
ok: [ansible-pctrl]

TASK [rhel-system-roles.selinux : Set permanent SELinux state if disabled] *****************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Set selinux_reboot_required] *****************************************
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel7]
ok: [rhel8]

TASK [rhel-system-roles.selinux : Fail if reboot is required] ******************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Warn if SELinux is disabled] *****************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Drop all local modifications] ****************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Purge all SELinux boolean local modifications] ***********************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Purge all SELinux file context local modifications] ******************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Purge all SELinux port local modifications] **************************
skipping: [rhel7]
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Purge all SELinux login local modifications] *************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

TASK [rhel-system-roles.selinux : Set SELinux booleans] ************************************************

TASK [rhel-system-roles.selinux : Set SELinux file contexts] *******************************************

TASK [rhel-system-roles.selinux : Restore SELinux labels on filesystem tree] ***************************

TASK [rhel-system-roles.selinux : Restore SELinux labels on filesystem tree in check mode] *************

TASK [rhel-system-roles.selinux : Set an SELinux label on a port] **************************************

TASK [rhel-system-roles.selinux : Set linux user to SELinux user mapping] ******************************

TASK [rhel-system-roles.selinux : Get SELinux modules facts] *******************************************
ok: [rhel8]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel7]

TASK [rhel-system-roles.selinux : include_tasks] *******************************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]

PLAY RECAP *********************************************************************************************
ansible-pctrl              : ok=8    changed=0    unreachable=0    failed=0    skipped=17   rescued=0    ignored=0   
rhel7                      : ok=7    changed=0    unreachable=0    failed=0    skipped=18   rescued=0    ignored=0   
rhel8                      : ok=8    changed=0    unreachable=0    failed=0    skipped=17   rescued=0    ignored=0   
rhel9                      : ok=8    changed=0    unreachable=0    failed=0    skipped=17   rescued=0    ignored=0   

[root@ansible-ctrl ansible]#
[root@ansible-ctrl ansible]# ansible -m command -a'getenforce' all
rhel7 | CHANGED | rc=0 >>
Enforcing
rhel8 | CHANGED | rc=0 >>
Enforcing
ansible-pctrl | CHANGED | rc=0 >>
Enforcing
rhel9 | CHANGED | rc=0 >>
Enforcing

Zusammenfassung

Mit einem sehr einfachen Beispiel habe ich gezeigt, wie die RHEL System Role SELinux genutzt werden kann, um sicherzustellen, dass SELinux auf allen Ziel-Hosts im Enforcing-Modus läuft.

Dazu habe ich keine Zeile Ansible-Code selbst geschrieben. Den Quelltext für mein Playbook habe ich per Copy-Paste-and-Modify aus der mitgelieferten Dokumentation übernommen. Anschließend habe ich die notwendige Variable definiert und das Playbook ausgeführt. Fertig.

Quellen und weiterführende Links

  1. BSI IT-Grundschutz-Kompendium 2022
  2. Einführung in das grundlegende Konzept von SELinux
  3. Quelltext im Upstream-Projekt {en}
  4. Red Hat Enterprise Linux (RHEL) System Roles {en}
  5. Ansible Documentation: Role Directory Structure {en}
  6. Red Hat Software and Download Center {en}
  7. Die Vorteile einer Red Hat Subskription
  8. RHEL System Roles: timesync
  9. RHEL System Roles: sshd
  10. RHEL System Roles: firewall

Vorfreude auf die Chemnitzer Linux-Tage 2023

13. Februar 2023 um 06:00

Im März ist es endlich wieder soweit. Die Chemnitzer Linux-Tage laden am 11. und 12. März in das Hörsaal- und Seminar-Gebäude der Technischen Universität Chemnitz ein.

Nach den Online-Konferenzen der letzten Jahre, mit denen ich nicht wirklich warm geworden bin, freue ich mich sehr darauf, Open Source begeisterte Menschen wieder vor Ort treffen zu können.

Das Programm bietet dieses Jahr 90 Vorträge in sechs parallelen Tracks, neun Workshops und ein spezielles Junior-Programm.

Ich selbst bin dieses Jahr mit zwei Vorträgen am Sonntag vertreten. Dirk, Sujeevan und ich werden euch gemeinsam bewusst machen „Warum man nicht in der IT arbeiten sollte und warum wir es trotzdem tun.“ In meinem Vortrag „Einstieg in die Automatisierung mit Ansible“ geht es darum, dass Konzepte wie Automatisierung und Konfigurationsmanagement nicht nur in die Werkzeugkästen für Hyperscaler gehören, sondern wie sie jedem Sysadmin die tägliche Arbeit erleichtern und der gesamten Organisation von Nutzen sein können.

Als passionierter Autofahrer setze ich dieses Jahr mein Vertrauen in die Bahn, welche mich am Freitag nach Chemnitz und am Sonntag wieder heim bringen soll. Ihr werdet in meinem Konferenzbericht lesen, wie mir diese Erfahrung gefallen haben wird.

So alles klappt, sehen wir uns in Karl-Marx-Stadt.

RHEL System Roles: timesync

20. Februar 2023 um 06:00

In diesem dritten Teil meiner Serie über RHEL System Roles nutze ich die Rolle timesync, um die NTP-Pool-Zone de.pool.ntp.org für meine Hosts zu konfigurieren.

Ich möchte mit diesem Artikel zeigen, wie einfach die Nutzung der RHEL System Roles ist, um eine Gruppe von RHEL-Servern zu konfigurieren. Dabei muss ich mich nicht um Details wie die Frage kümmern, ob auf meinen Zielhosts ntpd oder chronyd für die Zeitsynchronisierung genutzt wird. Diese Aufgabe löst die Ansible-Rolle für mich.

Bevor ich fortfahre, habe ich eine Warnung: Diese Rolle ersetzt die Konfiguration auf den Zielsystemen. Alle zuvor dort getroffenen Einstellungen werden verloren gehen.

Man muss sich also entscheiden, ob man die Zeitsynchronisation komplett über diese Rolle steuern möchte oder gar nicht.

Voraussetzungen

Auf dem Ansible-Controller müssen die Pakete ansible-core und rhel-system-roles installiert sein.

Das Playbook

Ich möchte mehrere NTP-Server konfigurieren. Für diesen Anwendungsfall liefert die Rolle timesync bereits ein Beispiel mit, welches ich mittels Copy-Paste-and-Modify in mein Playbook übernehme.

[root@ansible-ctrl ]# cp /usr/share/doc/rhel-system-roles/timesync/example-multiple-ntp-servers-playbook.yml ansible/use_de_ntp_servers.yml

Das Playbook sieht nach der Anpassung wie folgt aus:

- hosts: all
  vars:
    timesync_ntp_servers:
      - hostname: 0.de.pool.ntp.org
        iburst: yes
      - hostname: 1.de.pool.ntp.org
        iburst: yes
      - hostname: 2.de.pool.ntp.org
        iburst: yes
      - hostname: 3.de.pool.ntp.org
        iburst: yes
  roles:
    - rhel-system-roles.timesync

Testlauf in Labor-Umgebung

Um zu sehen, wie die Datei /etc/chrony.conf vor und nach dem Playbook-Lauf aussieht, lasse ich das Playbook zuerst mit den Optionen -C (aktiviert Check-Mode) und -D (zeigt die Änderungen an) laufen. So kann ich vorab prüfen, welche Änderungen vorgenommen werden, bevor es ernst wird. Die Ausgabe ist über 500 Zeilen lang. Ich habe sie auf Gist gepostet und hier eingebunden. Wer sich für die Ausgabe nicht interessiert, kann direkt zur Zusammenfassung springen.

Anschließend habe ich das Playbook ohne die Optionen -C und -D ausgeführt und meine Hosts wie gewünscht konfiguriert.

Zusammenfassung

Mit der RHEL System Role timesync kann die Zeitsynchronisation verschiedener RHEL-Releases schnell und einfach konfiguriert werden, ohne Kenntnis über die konkrete Implementierung auf den Zielsystemen zu besitzen.

Gleichzeitig kann ein Blick in die Struktur der Rolle und den Inhalt der dazugehörigen Dateien Aufschluss darüber geben, wie Ansible-Rollen für mehrere RHEL-Major-Releases erstellt werden können. Man kann dies für die Erstellung eigener Rollen mit ein wenig Transferleistung wiederverwenden.

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: sshd
  7. RHEL System Roles: firewall

Meine neue berufliche Herausforderung in 2023

06. März 2023 um 06:00

Wenn dieser Artikel erscheint, habe ich das Bielefelder IT-Service-Zentrum (BITS) der Universität Bielefeld und damit den öffentlichen Dienst nach acht Jahren verlassen. Seit dem 1. März arbeite ich als Senior Technical Account Manager – Platforms für die Red Hat GmbH in München. Ja, ich bin jetzt tatsächlich auf dem besten Weg, ein echter Red Hatter zu werden.

Warum?

Seit 2016 arbeite ich beruflich mit Red Hat Enterprise Linux (RHEL). Und seit 2019 bin ich stolz, zu den Red Hat Accelerators zu gehören. Beruflich habe ich mich jedoch nicht nur um Linux gekümmert.

Es fielen noch etliche weitere Themen in meinen Aufgabenbereich. In mir wuchs jedoch der Wunsch, meinen beruflichen Schwerpunkt in Richtung Linux und Open Source zu verlagern. So ist meine Motivation zum Jobwechsel, dass ich nun in einem Unternehmen arbeite, wo ich von Linux- und Open-Source-Experten umgeben bin. Hier kann ich jeden Tag mit und an Technologien arbeiten, die mich interessieren und meinen Kunden dabei helfen, diese in ihrem Umfeld optimal einzusetzen. Darüber hinaus habe ich hier die besten Chancen, mein Wissen zu erweitern und zu vertiefen. Dies freut mich sehr.

Hey, ich tue Dinge für und mit Open Source und werde dafür bezahlt. Was kann sich ein Linux-Nerd denn mehr wünschen?

Was ändert sich für das BITS?

Na hoffentlich nichts. :-)

Ich hoffe, dass ich euch in guter Erinnerung bleibe und euch von Zeit zu Zeit besuchen darf. Ich beobachte übrigens den Mensaplan. Wenn mal wieder der Burrito mit Wedges auf dem Speiseplan steht, begleite ich die Post-Pandemische-Peer-Group-01 gern zum Essen. ;-)

Was ändert sich für Red Hat?

Ich hoffe, dass durch meine Anstellung nicht gleich ein Ruck durch das ganze Unternehmen geht. Mein Team hat mit mir jetzt ein Mitglied mehr, welches neugierig ist, jeden Tag dazulernen möchte und bereit ist, euch mit Fragen zu löchern.

Habt vielen Dank für den tollen Empfang und die Aufnahme in den ersten Tagen. Ich freue mich auf die weitere Zusammenarbeit mit euch und darauf noch viele weitere Kolleg*innen kennenzulernen.

Was ändert sich für My-IT-Brain?

Hier ändert sich auch nichts. Dies ist und bleibt mein persönlicher Blog, in dem ich meine Meinung zu allen Themen schreibe, die mich interessieren. Die Artikel spiegeln dabei meine persönlichen Ansichten wider. Diese können mit denen meines Arbeitgebers übereinstimmen, müssen dies aber nicht.

Evtl. wird es hier in der nächsten Zeit ruhiger, während ich mich voll und ganz auf das Onboarding bei Red Hat konzentriere. In Zukunft dürft ihr aber wieder mit Artikeln, Anleitungen und Wochenend-Projekten rund um Linux und Open Source rechnen.

Wenn sich die Artikel zukünftig thematisch stärker mit RHEL und Fedora beschäftigen, liegt dies schlicht daran, dass ich mich mit diesen Distributionen stärker beschäftige als mit anderen. Berufliches und privates Interesse liegen bei mir schon immer eng beieinander.

Jetzt stürze ich mich ins Onboarding und versuche, so viel wie möglich von dieser neuen Welt im ersten Anlauf aufzunehmen. Bis neulich. :-)

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

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

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.

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

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.

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

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

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

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

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?

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.

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.

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

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

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.

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.

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.

❌
❌