Normale Ansicht

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

Labor-Umgebungen auf VMware vSphere erstellen mit der Ansible-Rolle vsphere_provision_lab

18. Juli 2022 um 05:00

In diesem Artikel stelle ich euch meine Ansible-Rolle vsphere_provision_lab vor. Mit dieser ist es möglich, vordefinierte Labor-Umgebungen, bestehend aus virtuellen Maschinen (VMs), in einem VMware vSphere Cluster zu provisionieren.

Um dem Text folgen zu können, ist es hilfreich zu wissen, was eine Ansible-Rolle (engl.) ist und worum es sich bei VMware vSphere handelt.

Anwendungsfall

Zum Test von Ansible-Modulen, -Playbooks und -Rollen sowie neuer Shell-Skripte und Konfigurationsparameter benötige ich wiederholt Test- bzw. Labor-Umgebungen. Diese bestehen aus VMs, welche im Vergleich zum Provisionierungszeitpunkt möglichst wenig Änderungen aufweisen sollten.

Da die Konfiguration der Gast-Betriebssysteme durch einen Testlauf verändert wird, soll eine solche Labor-Umgebung nach einem Test möglichst schnell dekommissioniert und neu-provisioniert werden können.

Der Vorgang der Provisionierung und Dekommissionierung soll automatisiert erfolgen.

Voraussetzungen

Um die Ansible-Rolle vsphere_provision_lab nutzen zu können, benötigt man:

  • Einen Ansible Control Node mit einer Ansible-Version >= 2.9
  • Eine vCenter Server Appliance oder einen vCenter Server; getestet habe ich die Rolle mit Version 6.7 U3

Die Ansible-Rolle verwendet die Module vmware_guest und vmware_guest_disk aus der Collection community.vmware. Um diese nutzen zu können, muss auf dem Ansible Control Node Python in einer Version >= 2.6 und PyVmomi installiert sein.

Da die Rolle neue VMs aus Templates erstellt, müssen entsprechende vSphere Templates vorhanden sein.

Variablen

In diesem Abschnitt werden die Variablen dokumentiert, welche mit Werten zu belegen sind. Diese sind in defaults/main.yml definiert und können entsprechend der Variablen-Präzedenz überschrieben werden.

vCenter-Variablen

  • vsphere_provision_lab_vc_hostname – Der FQDN für die vCenter Server Appliance.
  • vsphere_provision_lab_vc_username – Ein Benutzeraccount mit der Berechtigung, neue VMs zu erstellen.
  • vsphere_provision_lab_vc_password – Spezifiziert das Benutzer-Passwort.
  • vsphere_provision_lab_vc_datacenter – Name des vSphere Datacenter in der Bestandsliste, das verwendet werden soll.
  • vsphere_provision_lab_vc_cluster – Der zu verwendende vSphere Cluster aus der Bestandsliste.
  • vsphere_provision_lab_vc_datastore – Name des Datenspeichers, in dem die VMDK-Datei(n) erstellt wird/werden.
  • vsphere_provision_lab_vc_folder – Der vSphere Folder Name, in dem die VM erstellt wird. Der Wert kann zuvor mit dem Playbook find_vmware_guest_folder.yml ermittelt werden.

Ich empfehle, sensible Informationen wie z.B. Benutzername und Passwort in einer verschlüsselten Ansible-Vault-Datei zu speichern.

Dictionary zur Spezifikation der VMs

Namen und Parameter der zu erstellenden VMs werden in einem YAML-Dictionary gespeichert. Der folgende Code-Block zeigt ein Beispiel für ein solches Dictionary mit einem Eintrag:

  vm1:
    vm_template: rhel8-en
    vm_ram_mb: 1024
    vm_vcpu: 2
    vm_net: VLAN123
    vm_ip: 192.168.0.5
    vm_netmask: 255.255.255.0
    vm_gateway: 192.168.0.254
    vm_domain: sub.example.com
    vm_hostname: host2
    vm_dns_servers:
      - 192.168.0.2
      - 192.168.0.3
    vm_dns_suffix:
      - sub.example.com
      - sub2.example.com
      - sub3.example.com
    vm_second_hdd: true
    vm_second_hdd_size_gb: "10"
    vm_second_hdd_type: "thin"
    vm_second_hdd_datastore: "DS1"
    vm_scsi_controller: "1"
    vm_unit_number: "1"
    vm_scsi_type: 'paravirtual'

Aus dem obigen Dictionary wird eine VM mit den folgenden Parametern erstellt:

  • vSphere-Template ‚rhel8-en‘ wird als Vorlage verwendet.
  • Der VM werden 1 vCPU und 512 MB RAM zugewiesen.
  • Die vNIC an das VM-Netz ‚VLAN123‘ angeschlossen.
  • Die VM wird mit der IP 192.168.0.1/24 und dem Standardgateway 192.168.0.254 konfiguriert.
  • Im Gast-Betriebssystem wird der Hostname ‚host1‘ konfiguriert.
  • Es werden DNS-Server und DNS-Suffixe konfiguriert.
  • Es wird eine zweite Festplatte für die VM erstellt.

Diesen Block kann man nun für weitere VMs wiederholen. Wird keine zweite HDD benötigt, setzt man vm_second_hdd auf false.

Beispiel-Playbook

---
- hosts: localhost
  connection: local
  gather_facts: no
  vars_files:
    - /path/to/vault-with-vcenter-creds.vars
    - roles/vsphere_provision_lab/vars/rhel-lab.yml
 
  roles:
    - vsphere_provision_lab

In diesem Beispiel werden die Variablen aus zwei Dateien geladen.

  • /path/to/vault-with-vcenter-creds.vars ist eine Ansible-Vault-Datei, welche die Anmeldeinformationen für den vCenter Server beinhaltet.
  • roles/vsphere_provision_lab/vars/rhel-lab.yml enthält das Dictionary mit den zu erstellenden VMs.

Wie kann man diese Rolle nutzen?

Die Rolle steht unter der Lizenz GPLv2-or-later und kann aus dem GitLab-Repo vsphere_provision_lab heruntergeladen bzw. das Repo geklont werden. Sie ist im Role Path der Ansible-Installation zu speichern. Anschließend kann man sich an folgendem Ablaufplan orientieren:

  1. Im Vorfeld sollten IP-Adressen und FQDNs für die zu erstellenden VMs registriert werden. Dieser Punkt ist spezifisch für eure jeweilige Umgebung.
  2. Die Variablen sind mit gültigen Werten zu belegen.
  3. Die Rolle ist in ein Playbook einzubinden.
  4. Und schon kann das Playbook ausgeführt werden.

Fazit

Die Ansible-Rolle vsphere_provision_lab ermöglicht es, Labor-Umgebungen, bestehend aus VMs, als YAML-Dictionary zu definieren und auf einem VMware vSphere Cluster zu provisionieren.

Damit eignet sie sich, um wiederholt eine Umgebung mit gleichen Ausgangsbedingungen zu schaffen. Gleichzeitig wird die Zeit für die Provisionierung verkürzt, da manuelle Schritte entfallen.

So richtig idempotent arbeitet das Modul vmware_guest allerdings nicht. Führe ich das Playbook ein zweites Mal aus, wird der Status „Changed“ zurückgegeben, obwohl die VMs bereits existieren. Die VMs selbst überstehen den zweiten Lauf auch unbeschadet, jedoch zeigt der vSphere Client, dass sie rekonfiguriert wurden. Das könnte ich mir noch genauer ansehen, um der Ursache auf die Schliche zu kommen.

Was haltet ihr davon? Findet ihr diese Role nützlich, oder bevorzugt ihr andere Wege zur Provisionierung eurer VMs auf vSphere?

LVM: Logical Volumes (LV) in andere Volume Group (VG) verschieben und andere Arbeiten

27. Juni 2022 um 05:00

Der folgende Text dient mir als Dokumentation. Ich halte darin fest, wie ich LVs von einer VG in eine andere VG verschiebe und die Partitionstabelle der Festplatte mit der Quell-VG bearbeite. Der Verschiebe-Vorgang setzt sich dabei aus den zwei Vorgängen Kopieren und Löschen zusammen.

Der Text mag euch unterhalten und ggf. könnt ihr darauf zurückgreifen, wenn ihr ähnliche Arbeiten an euren Linux-Dateisystemen plant. Euch erwartet jedoch kein Tutorial, das in einzelne Themen oder Programme einführt. Falls ihr von hieran weiterlest, wünsche ich euch viel Spaß mit dem Text.

Ausgangslage

Es geht um meinen Desktop-PC. Dieser besitzt neben einer Geschichte auch einige Altlasten. Nun ist mir meine /boot-Partition zu klein. Da der Platz hinter der Partition jedoch von einer LUKS-verschlüsselten Partition mit LVM belegt ist, muss ich hier erst Platz schaffen, um die /boot-Partition vergrößern zu können.

Folgender Code-Block gibt einen Überblick über die Block-Geräte meines PCs, die darauf befindlichen Partitionen sowie deren Einhängepunkte in /etc/fstab und /etc/crypttab. Identifizierende Merkmale wie UUIDs wurden gekürzt, verändert oder Platzhalter an ihrer Stelle verwendet.

$ lsblk
NAME                          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                             8:0    0 931,5G  0 disk  
└─sdb_crypt                   253:4    0 931,5G  0 crypt 
  └─tower--pc--vg2-lv--images 253:5    0   360G  0 lvm   /var/lib/libvirt/images
sdb                             8:16   0 238,5G  0 disk  
├─sdb1                          8:17   0   243M  0 part  /boot
├─sdb2                          8:18   0     1K  0 part  
└─sdb5                          8:21   0 238,2G  0 part  
  └─sda5_crypt                253:0    0 238,2G  0 crypt 
    ├─tower--pc--vg-root      253:1    0  27,9G  0 lvm   /
    ├─tower--pc--vg-swap_1    253:2    0     4G  0 lvm   [SWAP]
    └─tower--pc--vg-home      253:3    0 204,3G  0 lvm   /home
sr0

$ cat /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
/dev/mapper/tower--pc--vg-root /               ext4    errors=remount-ro 0       1
# /boot was on /dev/sda1 during installation
UUID=a3809eb1-fc3d191e2ae9 /boot           ext2    defaults        0       2
/dev/mapper/tower--pc--vg-home /home           ext4    defaults        0       2
/dev/mapper/tower--pc--vg-swap_1 none            swap    sw              0       0
/dev/sr0        /media/cdrom0   udf,iso9660 user,noauto     0       0

# 1 TB SSD SANDisk
/dev/mapper/tower--pc--vg2-lv--images	/var/lib/libvirt/images	ext4	defaults	0	0

$ cat /etc/crypttab 
sda5_crypt UUID=07f0d6c5-257591190166 none luks,discard
sdb_crypt UUID="e605980c-307daf28b717" none luks,discard

$ sudo blkid
/dev/sdb1: UUID="a3809eb1-fc3d191e2ae9" BLOCK_SIZE="1024" TYPE="ext2" PARTUUID="6ee39e6a-01"
/dev/sdb5: UUID="07f0d6c5-257591190166" TYPE="crypto_LUKS" PARTUUID="6ee39e6a-05"
/dev/sda: UUID="e605980c-307daf28b717" TYPE="crypto_LUKS"
/dev/mapper/sda5_crypt: UUID="AZKTuQ-rVeB5S" TYPE="LVM2_member"
/dev/mapper/tower--pc--vg-root: UUID="be0ff8fd-7aee7ce75f3b" BLOCK_SIZE="4096" TYPE="ext4"
/dev/mapper/tower--pc--vg-swap_1: UUID="90823267-b6828aeca9b9" TYPE="swap"
/dev/mapper/tower--pc--vg-home: UUID="d410241d-04214b690522" BLOCK_SIZE="4096" TYPE="ext4"
/dev/mapper/sdb_crypt: UUID="Ff4Lt0-RKJrGd" TYPE="LVM2_member"
/dev/mapper/tower--pc--vg2-lv--images: UUID="3af3b461-cdd7b2bc9710" BLOCK_SIZE="4096" TYPE="ext4"

Ziel

Mein Ziel ist, die /boot-Partition auf 2 GB zu vergrößern, ohne das System neuinstallieren zu müssen oder die Daten in den vorhandenen Partitionen zu verlieren.

Mögliche Vorgehensweisen

Bei meiner Internet-Recherche bin ich auf folgende Lösungsmöglichkeiten gestoßen:

Wenn man diese Diskussionen und den Wiki-Artikel liest, erkennt man, dass es mehrere Wege zum Ziel gibt. Ich habe mich für folgendes Vorgehen entschieden, da es auf mich den Eindruck macht, unkompliziert zu sein und nur ein geringes Risiko für Datenverlust birgt:

  1. Datensicherung
  2. LUKS-Devices umbenennen
  3. LV und Dateisystem der /home-Partition verkleinern
  4. Neue LVs in zweiter VG erstellen
  5. Partitionen mit partclone kopieren
  6. Grub Neukonfigurieren (2x)

Schritt 1: Datensicherung durchführen

Bevor ich irgendwelche Änderungen an der Partitionstabelle von /dev/sdb durchführe, erstelle ich eine Datensicherung. Dazu verwende ich die freie Software Clonezilla, um die Partitionen von /dev/sdb in eine Image-Datei auf einer externen Festplatte zu sichern.

Das Programm ist einfach in der Bedienung und ermöglicht mir im Fehlerfall, die Partitionen der zu bearbeitenden Festplatte wiederherzustellen.

Schritt 2: LUKS-Devices umbenennen

Im IST-Zustand befindet sich das LUKS-Device sdb_crypt auf dem Gerät /dev/sda, während sich sda5_crypt auf dev/sdb5 befindet. Dies ist unschön und lässt sich wie folgt ändern:

root:~# dmsetup rename sda5_crypt sd_temp
root:~# dmsetup rename sdb_crypt sda5_crypt
root:~# dmsetup rename sd_temp sdb_crypt

Nun wird die Datei /etc/crypttab entsprechend angepasst (vgl. mit IST-Zustand):

root:~# cat /etc/crypttab 
sdb_crypt UUID=07f0d6c5-257591190166 none luks,discard
sda5_crypt UUID="e605980c-307daf28b717" none luks,discard

Damit die Partitionen beim Start des Rechners korrekt entschlüsselt und eingebunden werden, wird abschließend initramfs aktualisiert:

root:~# update-initramfs -u -k all
update-initramfs: Generating /boot/initrd.img-5.10.0-15-amd64
update-initramfs: Generating /boot/initrd.img-5.10.0-13-amd64

Nach einem Neustart ergibt sich das gewünschte Bild:

$ lsblk
NAME                          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                             8:0    0 931,5G  0 disk  
└─sda5_crypt                  253:4    0 931,5G  0 crypt 
  └─tower--pc--vg2-lv--images 253:5    0   360G  0 lvm   /var/lib/libvirt/images
sdb                             8:16   0 238,5G  0 disk  
├─sdb1                          8:17   0   243M  0 part  /boot
├─sdb2                          8:18   0     1K  0 part  
└─sdb5                          8:21   0 238,2G  0 part  
  └─sdb_crypt                 253:0    0 238,2G  0 crypt 
    ├─tower--pc--vg-root      253:1    0  27,9G  0 lvm   /
    ├─tower--pc--vg-swap_1    253:2    0     4G  0 lvm   [SWAP]
    └─tower--pc--vg-home      253:3    0 204,3G  0 lvm   /home

Schritt 3: LV und Dateisystem der /home-Partition verkleinern

Meine /home-Partition ist mir mit 204 GB etwas groß geraten. Daher möchte ich sie um 100 GB verkleinern. Um das Dateisystem verkleinern zu können, darf die Partition nicht eingehängt sein. Um die folgenden Schritte durchzuführen, nutze ich diesmal das Linux-System System Rescue. Dabei handelt es sich um ein Live-System, mit jeder Menge Werkzeugen, um einen (beschädigten) Rechner zu bearbeiten.

Der folgende Code-Block zeigt, wie zuerst das verschlüsselte LUKS-Device geöffnet und anschließend das LV der /home-Partition verkleinert wird. Der dabei verwendete Befehl führt die Verkleinerung des Dateisystems und des LV in einem Schritt aus:

# LUKS-Device öffnen
# cryptsetup open <device> <name> --type <device_type> see cryptsetup(8)

root@sysrescue ~]# cryptsetup open /dev/sda5 crypt_disk --type luks2
Enter passphrase for /dev/sda5: 
[root@sysrescue ~]# lsblk
NAME                       MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0                        7:0    0 647.7M  1 loop  /run/archiso/sfs/airootfs
sda                          8:0    0 238.5G  0 disk  
├─sda1                       8:1    0   243M  0 part  
├─sda2                       8:2    0     1K  0 part  
└─sda5                       8:5    0 238.2G  0 part  
  └─crypt_disk             254:0    0 238.2G  0 crypt 
    ├─tower--pc--vg-root   254:1    0  27.9G  0 lvm   
    ├─tower--pc--vg-swap_1 254:2    0     4G  0 lvm   
    └─tower--pc--vg-home   254:3    0 204.3G  0 lvm   
sdb                          8:16   0 931.5G  0 disk

# Dateisystem und LV in einem Schritt verkleinern
# Aufgrund der gewählten Größe dauert dieser Vorgang einige Minuten
# lvresize --size [+|-]Size[m|UNIT] --resizefs <lv name> see lvresize(8)

[root@sysrescue ~]# lvresize --size -100G --resizefs /dev/tower-pc-vg/home 
fsck from util-linux 2.38
/dev/mapper/tower--pc--vg-home: Inode 393223 extent tree (at level 1) could be narrower.  IGNORED.
/dev/mapper/tower--pc--vg-home: Inode 12847282 extent tree (at level 1) could be narrower.  IGNORED.
/dev/mapper/tower--pc--vg-home: 20959/13393920 files (1.2% non-contiguous), 14367863/53551104 blocks
resize2fs 1.46.5 (30-Dec-2021)
Resizing the filesystem on /dev/mapper/tower--pc--vg-home to 27336704 (4k) blocks.
The filesystem on /dev/mapper/tower--pc--vg-home is now 27336704 (4k) blocks long.

  Size of logical volume tower-pc-vg/home changed from 204.28 GiB (52296 extents) to 104.28 GiB (26696 extents).
  Logical volume tower-pc-vg/home successfully resized.

[root@sysrescue ~]# lsblk
NAME                       MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0                        7:0    0 647.7M  1 loop  /run/archiso/sfs/airootfs
sda                          8:0    0 238.5G  0 disk  
├─sda1                       8:1    0   243M  0 part  
├─sda2                       8:2    0     1K  0 part  
└─sda5                       8:5    0 238.2G  0 part  
  └─crypt_disk             254:0    0 238.2G  0 crypt 
    ├─tower--pc--vg-root   254:1    0  27.9G  0 lvm   
    ├─tower--pc--vg-swap_1 254:2    0     4G  0 lvm   
    └─tower--pc--vg-home   254:3    0 104.3G  0 lvm   
sdb                          8:16   0 931.5G  0 disk

Zur Sicherheit führe ich noch eine Dateisystemüberprüfung aus:

[root@sysrescue ~]# fsck -t ext4 /dev/mapper/tower--pc--vg-home
fsck from util-linux 2.38
e2fsck 1.46.5 (30-Dec-2021)
/dev/mapper/tower--pc--vg-home: clean, 20959/6840320 files, 13956503/27336704 blocks

Da alles in Ordnung ist, fahre ich mit dem nächsten Schritt fort. Dazu nutze ich weiterhin die System Rescue Umgebung.

Schritt 4: LVs in zweiter VG erstellen

Da ich für die folgenden Vorgänge Zugriff auf die zweite VG benötige, öffne ich zuerst das LUKS-Device, in dem sich diese befindet:

[root@sysrescue ~]# cryptsetup open /dev/sdb crypt_disk2 --type luks2

Nun erstelle ich drei neue LVs, welche den Inhalt der existierenden LVs root, swap_1 und home aufnehmen sollen. Die Ziel-LVs müssen dazu mindestens gleich groß oder größer als die Quell-LVs sein. Um die erforderliche Größe zu ermitteln, lasse ich mir die Größe der Quell-LVs in Byte anzeigen. Ich wähle bewusst die Einheit Byte, da die Ausgabe bei größeren Einheiten auf zwei Nachkommastellen gerundet wird und ich mir keine Probleme durch die Rundung einhandeln möchte.

# Es werden nur die relevanten Informationen wiedergegeben
root:~# lvdisplay --unit b
LV Path                /dev/tower-pc-vg/root
LV Name                root
VG Name                tower-pc-vg
...
LV Size                29997662208 B
-----
LV Path                /dev/tower-pc-vg/swap_1
LV Name                swap_1
VG Name                tower-pc-vg
...
LV Size                4290772992 B
-----
LV Path                /dev/tower-pc-vg/home
LV Name                home
VG Name                tower-pc-vg
...
LV Size                111971139584 B

Mit diesen Informationen erstelle ich die neuen LVs in der zweiten VG:

:~# lvcreate --size 29997662208B /dev/tower-pc-vg2 --name root
  Logical volume "root" created.
:~# lvcreate --size 4290772992B /dev/tower-pc-vg2 --name swap_1
  Logical volume "swap_1" created.
:~# lvcreate --size 111971139584B /dev/tower-pc-vg2 --name home
  Logical volume "home" created.

Schritt 5: Partitionen mit partclone kopieren

Für diesen Schritt nutze ich die freie Anwendung Partclone. Da meine LVs weiterhin ausgehängt sind, muss ich mir um Schreib-Zugriffe anderer Prozesse während des Kopiervorgangs keine Sorgen machen:

# Manpage partclone(8)
# partclone.<fs_type> --dev-to-dev --source <Quelle> --output <Ziel>

[root@sysrescue ~]# partclone.ext4 --dev-to-dev --source /dev/tower-pc-vg/root --output /dev/tower-pc-vg2/root 
Partclone v0.3.20 http://partclone.org
Starting to back up device(/dev/tower-pc-vg/root) to device(/dev/tower-pc-vg2/root)
Elapsed: 00:00:01, Remaining: 00:00:00, Completed: 100.00%                      
Total Time: 00:00:01, 100.00% completed!
done!
File system:  EXTFS
Device size:   30.0 GB = 7323648 Blocks
Space in use:  20.8 GB = 5078551 Blocks
Free Space:     9.2 GB = 2245097 Blocks
Block size:   4096 Byte
Elapsed: 00:03:16, Remaining: 00:00:00, Completed: 100.00%, Rate:   6.37GB/min, 
current block:    7323648, total block:    7323648, Complete: 100.00%           
Total Time: 00:03:16, Ave. Rate:    6.4GB/min, 100.00% completed!
Syncing... OK!
Partclone successfully cloned the device (/dev/tower-pc-vg/root) to the device (/dev/tower-pc-vg2/root)
Cloned successfully.

[root@sysrescue ~]# partclone.ext4 --dev-to-dev --source /dev/tower-pc-vg/home --output /dev/tower-pc-vg2/home 
Partclone v0.3.20 http://partclone.org
Starting to back up device(/dev/tower-pc-vg/home) to device(/dev/tower-pc-vg2/home)
Elapsed: 00:00:01, Remaining: 00:00:00, Completed: 100.00%                      
Total Time: 00:00:01, 100.00% completed!
done!
File system:  EXTFS
Device size:  112.0 GB = 27336704 Blocks
Space in use:  57.2 GB = 13956503 Blocks
Free Space:    54.8 GB = 13380201 Blocks
Block size:   4096 Byte
Elapsed: 00:12:22, Remaining: 00:00:00, Completed: 100.00%, Rate:   4.62GB/min, 
current block:   27336704, total block:   27336704, Complete: 100.00%           
Total Time: 00:12:22, Ave. Rate:    4.6GB/min, 100.00% completed!
Syncing... OK!
Partclone successfully cloned the device (/dev/tower-pc-vg/home) to the device (/dev/tower-pc-vg2/home)
Cloned successfully.

Die SWAP-Partition enthält keine Daten, die kopiert werden müssen. Hier formatiere ich das neue LV einfach als SWAP-Partition:

[root@sysrescue ~]# mkswap /dev/tower-pc-vg2/swap_1
Setting up swapspace version 1, size = 4 GiB (4290768896 bytes)
no label, UUID=f9181521-a06da5b8ade5

Schritt 6: Grub neukonfigurieren (2x)

An diesem Punkt habe ich meinen Rechner normal gestartet, um zu überprüfen, dass er wie gewohnt hochfährt. Die gute Nachricht lautet: „Er ist wie gewohnt gestartet.“

Nun verfüge ich über ein Clonezilla-Image der ersten Festplatte, dessen Wiederherstellbarkeit ich noch nicht durch Restore validiert habe und über die Kopien meiner Partitionen in der zweiten VG. Starten tut mein Rechner jedoch immer noch von den altbekannten Partitionen, da ich der Grub-Konfiguration noch nicht mitgeteilt habe, dass ein Wurzeldateisystem aus einer anderen Partition zu verwenden ist.

Leider habe ich es versäumt, mir während der Nutzung der System-Rescue-Umgebung Notizen zu machen. Daher kann ich die verwendeten Befehle an dieser Stelle nur unvollständig wiedergeben. Es ist mir jedoch gelungen, vom Wurzeldateisystem in /dev/tower-pc-vg2/root zu starten. Dies sieht man z.B. in der Ausgabe von lsblk:

:~$ lsblk
NAME                          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                             8:0    0 931,5G  0 disk  
└─sda5_crypt                  253:0    0 931,5G  0 crypt 
  ├─tower--pc--vg2-lv--images 253:1    0   360G  0 lvm   /var/lib/libvirt/images
  ├─tower--pc--vg2-root       253:2    0  27,9G  0 lvm   /
  ├─tower--pc--vg2-swap_1     253:3    0     4G  0 lvm   [SWAP]
  └─tower--pc--vg2-home       253:4    0 104,3G  0 lvm   /home
sdb                             8:16   0 238,5G  0 disk  
├─sdb1                          8:17   0     2G  0 part  /boot
├─sdb2                          8:18   0     1K  0 part  
└─sdb5                          8:21   0 234,2G  0 part  
  └─sdb_crypt                 253:5    0 234,2G  0 crypt 
    ├─tower--pc--vg-root      253:6    0  27,9G  0 lvm   
    ├─tower--pc--vg-swap_1    253:7    0     4G  0 lvm   
    └─tower--pc--vg-home      253:8    0 104,3G  0 lvm   
sr0

Wer den Code-Block genau studiert hat, wird festgestellt haben, dass meine /boot-Partition gegenüber dem Eingangs erwähnten IST-Zustand auf 2 GB angewachsen ist. Ich habe die Partitionstabelle zwischenzeitlich mit GParted bearbeitet, welches in der System-Rescue-Umgebung enthalten ist.

Damit habe ich mein Ziel erreicht. Der Artikel könnte an dieser Stelle enden. Ich möchte jedoch zukünftig wieder die Partitionen von /dev/sdb verwenden. Dazu muss ich nochmals Grub neukonfigurieren, welches ich diesmal in folgendem Code-Block zeige:

root@tower-pc:~# mount /dev/tower-pc-vg/root /mnt

# Die separate /boot-Partition muss ebenfalls mit eingehängt werden
root@tower-pc:~# mount /dev/sdb1 /mnt/boot

root@tower-pc:~# for DEVICE in /dev /dev/pts /proc /sys; do mount --bind $DEVICE /mnt$DEVICE; done
root@tower-pc:~# mount /dev/tower-pc-vg2/lv-images /mnt/var/lib/libvirt/images

# ID der Partition mit dem Wurzeldateisystem ermitteln
root@tower-pc:~# ls -l /dev/tower-pc-vg/root 
lrwxrwxrwx 1 root root 7 18. Jun 20:13 /dev/tower-pc-vg/root -> ../dm-6
root@tower-pc:~# ls -l /dev/disk/by-id/ | grep dm-6
lrwxrwxrwx 1 root root 10 18. Jun 20:13 dm-name-tower--pc--vg-root -> ../../dm-6

# In eine chroot-Umgebung wechseln
root@tower-pc:~# chroot /mnt
root@tower-pc:/# pwd
/

In der chroot-Umgebung wird die Datei /etc/default/grub mit einem Editor geöffnet und die Zeile GRUB_CMDLINE_LINUX_DEFAULT= bearbeitet. Dort trage ich die ID der Partition meines Wurzeldateisystems ein. Die Zeile sieht anschließend wie folgt aus:

GRUB_CMDLINE_LINUX_DEFAULT="root=/dev/disk/by-id/dm-name-tower--pc--vg-root iommu='soft' quiet"

Der folgende Code-Block stellt die Befehle dar, mit denen Grub neukonfiguriert und installiert sowie initramfs aktualisiert wird.

root@tower-pc:/# grub-mkconfig -o /boot/grub/grub.cfg 
Generating grub configuration file ...
Found background image: /usr/share/images/desktop-base/desktop-grub.png
Found linux image: /boot/vmlinuz-5.10.0-15-amd64
Found initrd image: /boot/initrd.img-5.10.0-15-amd64
Found linux image: /boot/vmlinuz-5.10.0-13-amd64
Found initrd image: /boot/initrd.img-5.10.0-13-amd64
Found Debian GNU/Linux 11 (bullseye) on /dev/mapper/tower--pc--vg2-root
done

root@tower-pc:/# update-initramfs -u -k all
update-initramfs: Generating /boot/initrd.img-5.10.0-15-amd64
update-initramfs: Generating /boot/initrd.img-5.10.0-13-amd64
root@tower-pc:/# exit
exit
root@tower-pc:~# reboot NOW

Nach dem Neustart habe ich noch überprüft, dass der Rechner wirklich die ursprünglichen Partitionen eingehängt hat:

$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 931,5G 0 disk
└─sda5_crypt 253:4 0 931,5G 0 crypt
├─tower--pc--vg2-lv--images 253:5 0 360G 0 lvm /var/lib/libvirt/images
├─tower--pc--vg2-root 253:6 0 27,9G 0 lvm
├─tower--pc--vg2-swap_1 253:7 0 4G 0 lvm
└─tower--pc--vg2-home 253:8 0 104,3G 0 lvm
sdb 8:16 0 238,5G 0 disk
├─sdb1 8:17 0 2G 0 part /boot
├─sdb2 8:18 0 1K 0 part
└─sdb5 8:21 0 234,2G 0 part
└─sdb_crypt 253:0 0 234,2G 0 crypt
├─tower--pc--vg-root 253:1 0 27,9G 0 lvm /
├─tower--pc--vg-swap_1 253:2 0 4G 0 lvm [SWAP]
└─tower--pc--vg-home 253:3 0 104,3G 0 lvm /home
sr0 11:0 1 1024M 0 rom

Ende gut alles gut

Operation gelungen. Patient rechnet noch.

Mein Ziel habe ich erreicht und als Bonus Grub so konfiguriert, dass wieder meine ursprünglichen Partitionen verwendet werden. Die LVs in VG2 behalte ich vorerst. Sie stören nicht und ich kann die dortige Installation ebenfalls booten. Diese kann ich evtl. noch für zukünftige Experimente hernehmen.

Mir gefällt, dass ich frei verfügbare Informationen und Werkzeuge benutzen konnte, um alle notwendigen Aufgaben zu erledigen. So habe ich wieder einiges dazugelernt. Dies ist einer der Gründe, weshalb mir Freie Software und Open Source so gut gefallen.

Nun werde ich diesen Text noch verschlagworten, damit ich ihn in einer fernen Zukunft auch wiederfinde.

Gedanken zum Thema Software-Verteilung

20. Juni 2022 um 05:00

Ein Freund bat mich kürzlich um meine Meinung zum Thema Software-Verteilung und Dokumentation des Installations- bzw. Bereitstellungsprozesses. Die Gedanken, die ich mir dazu gemacht habe, möchte ich an dieser Stelle mit euch teilen. Dabei schreibe ich aus dem Blickwinkel eines Systemintegrators, der von Dritten erstellte Software integrieren und betreiben muss.

Den Anwendungsentwicklern, Software-Architekten und Produkt-Managern unter euch soll dieser Text als Information dienen, was ich und ggf. weitere Personen meiner Zunft von einer Dokumentation erwarten bzw. wie wir uns Software-Verteilungsprozesse vorstellen.

Als Leser seid ihr alle eingeladen, eure Gedanken ebenfalls zu teilen und eine Diskussion zu diesem Thema zu führen.

Das Szenario

Ich habe bei meinen Gedanken ein Szenario im Kopf, in dem ein Anwendungsentwickler eine Anwendung in einer Sprache seiner Wahl schreibt. Diese Anwendung soll später über ein Web-Frontend erreichbar und nutzbar sein. Die Anwendung benötigt eine Datenbank, um Informationen darin abzulegen. Binärdateien werden ggf. außerhalb der Datenbank in einem Dateisystem gespeichert.

Die Anwendungsentwickler freuen sich, wenn ihre Software einen hohen Verbreitungsgrad erreicht. Dazu muss die dazugehörige Dokumentation folgende Informationen bieten.

Werkzeuge zum Erstellen und Ausführen der Anwendung

  • Welcher Compiler ab welcher Version wird benötigt?
  • Welche Bibliotheken werden ab welcher Version benötigt?
  • Sind abweichende Versionen möglich oder funktioniert nur genau eine?
  • Welche Laufzeitumgebung wird benötigt?
  • Was ist in welcher Reihenfolge zu tun, um vom Quelltext zu einem ausführbaren Programm zu kommen?

Voraussetzungen zur Integration der Anwendung

  • Wird eine Datenbank benötigt?
  • Welche Datenbank-Management-Systeme werden unterstützt?
  • Ab welcher Version?
  • Über welche Rechte muss ein DB-User mindestens verfügen, damit der Anwendungs-Installer die notwendigen Tabellen, Views, etc. erstellen kann?
  • Welche Webserver werden unterstützt?
  • Welche Protokolle, Parameter bzw. Optionen sind für die Kommunikation mit der Anwendung notwendig?

Ich bin immer enttäuscht, wenn hier auf ein einziges System eingeschränkt wird. Dies limitiert unnötig den Nutzerkreis.

  • Wird ein Snakeoil-Zertifikat mitgeliefert oder hat der Systemintegrator dieses beizustellen? Eigene Zertifikate auf Wunsch verwenden zu können ist obligatorisch.

Die bis jetzt genannten Informationen müssen so umfassend sein, dass Paketbetreuer bzw. Software-Paketierer daraus Debian-, Flatpak-, RPM-, Snap-Pakete, Container-Images und Windows-Installer bauen können.

Nehmen die Anwendungsentwickler in Personalunion die Rolle von Software-Paketierern wahr, scheint es dennoch unrealistisch, dass sie allein alle Formate unterstützen können. Sie sollten sich für einige wenige Formate entscheiden, die auf möglichst vielen Plattformen lauffähig sind. In dem von mir gedachten Szenario sind dies OCI-kompatible Container-Images.

Bei einer GUI-Anwendung würde ich mich vermutlich für Flatpak entscheiden. Die Paketierung als DEB oder RPM würde ich auf jeden Fall den Paketbetreuern der jeweiligen Distributionen überlassen.

Systemintegration

Für den Systemintegrator, welcher den Webserver, die Anwendung und die Datenbank zusammenbringt (integriert), muss klar beschrieben werden:

  • Gibt es ein fertiges Container-Image? Wo bekommt man es her?
  • Optional: Wie erstellt man das Container-Image selbst?
  • Welche (Umgebungs-)Variablen gehören zum Image? Welche Werte können diese annehmen?
  • Ein Kommunikationsdiagramm, damit der Sysadmin weiß, was wann mit wem spricht und welche Firewall-Regeln evtl. benötigt werden.

Referenzimplementierungen sind ebenfalls schön. Diese sollten für Docker, Docker-Compose, Kube-YAML (K8s) und Podman bereitstehen. Die Doku zum Container-Image muss jedoch so ausgestaltet sein, dass auch ohne Referenzimplementierung die Instanziierung mit den verschiedenen Engines und Orchestrierern gelingen kann.

Was Sysadmins (wie ich) nicht mögen

Ich mag es nicht, wenn in den Abhängigkeiten ein Webserver (Apache, NGINX, lighttpd) oder ein Datenbank-Management-System unnötig hart verdrahtet ist. Gleiches gilt für die Festlegung der zu verwendenden Container-Registries. Ich will dem Installer sagen können, wo er die Images findet und nicht auf den Dockerhub festgelegt werden.

Und ja, mir ist bewusst, dass eine gute Dokumentation genauso viel Aufwand und Pflege bedeutet wie der Quelltext einer Anwendung selbst. Aber ist es eine Alternative, wenn Systemintegratoren die Anwendung nach dem ersten Installationsversuch hassen?

Eure Meinung ist willkommen

Wie steht ihr als Entwickler zum Thema Dokumentation? Was erwartet ihr als Sysadmin und User von ihr?

Stimmt ihr mit mir überein oder habt ihr ganz andere Ansichten dazu? Nutzt gern die Kommentarfunktion und lasst es mich wissen.

Potenzielle Mehrwerte einer Red Hat Enterprise Linux Subscription

12. Juni 2022 um 05:00

Wer Red Hat Enterprise Linux (RHEL) betreiben möchte, benötigte dazu eine sogenannte Subskription, im Folgenden RHEL-Sub genannt. Die Kosten für eine RHEL-Sub ergeben sich aus der Art der RHEL-Sub und dem damit verbundenen Service-Level. Der schon etwas ältere Artikel Support-Subskriptionen von SUSE und Red Hat gibt hierzu einen kleinen Einblick.

Da RHEL-Klone wie AlmaLinux und Rocky Linux kostenlos verfügbar sind, kommt schnell die Frage auf: „Warum soll ich für RHEL soviel Geld bezahlen, wenn ich quasi das gleiche OS unter anderem Namen kostenlos nutzen kann?“

Um bei der Beantwortung dieser Frage zu helfen, stelle ich in diesem Artikel einige potenzielle Mehrwerte vor, die man mit dem Erwerb einer RHEL-Sub erhält.

Aus Gründen der Transparenz weise ich darauf hin, dass ich Mitglied der Red Hat Accelerators Community und System-Administrator diverser RHEL-Server bin. Dieser Text gibt ausschließlich meine persönlichen Ansichten und nicht die von Red Hat oder die meines Arbeitgebers wieder. Zwar können diese in einzelnen Punkten übereinstimmen, müssen es aber nicht.

Kostenlose RHEL-Subs

Nicht alle RHEL-Subs kosten Geld. Es gibt auch zwei Subskriptionen, mit denen sich RHEL und bestimmten Rahmenbedingungen kostenlos betreiben lässt.

Red Hat Developer Subscription for Individuals

Ein jeder kann über das Red Hat Developers Program (engl.) kostenlos eine persönliche RHEL Developer Subscription (engl.) erhalten. Mit dieser Sub erhält der Besitzer das Recht, bis zu 16 Systeme (physisch oder virtuell) zu betreiben; auch in Produktion.

Die Subskription muss jedes Jahr verlängert werden. Die Verlängerung ist ebenfalls kostenlos.

Die Subskription beinhaltet keinen kommerziellen Support. Man hat lediglich Zugriff auf die Wissensdatenbank und die Customer Portal Community. Die SaaS-Dienste Red Hat Insights und Image Builder können auch mit dieser Subskription genutzt werden.

Red Hat Developer Subscription for Teams

Im Unterschied zur oben vorgestellten individuellen Subskription dürfen Systeme mit der RHEL-Sub-for-Teams nicht in Produktion betrieben werden. Sie dient ausschließlich der Entwicklung, dem Test und der Qualitätssicherung. Dafür dürfen mit dieser Sub eine sehr große Anzahl RHEL-Systeme provisioniert werden.

Ich bin mir nicht sicher, ob ich eine genaue Zahl nennen darf. Darum glaubt mir bitte, wenn ich euch sage: „Es sind wirklich eine Menge Berechtigungen für physische und virtuelle Systeme enthalten.“

Diese Subskription erhaltet ihr nur über euer Red Hat Account Management. Falls ihr dieses (noch) nicht kennt, wendet euch im Zweifel an die Firma, wo ihr eure RHEL-Subs kauft.

Mich selbst hat es einige Mühen gekostet, an diese Sub zu kommen. Laut Aussage meines Account-Managers war ich der erste Kunde in Deutschland, der diese Sub kannte und haben wollte. Dank der Red Hat Accelerators und meines Account-Managers, der nicht aufgegeben hat, hat es schlussendlich geklappt. Falls ihr Probleme habt, diese Sub zu bekommen, schreibt mich einfach an. Vielleicht habe ich noch einen Tipp für euch.

Für den Support, Zugriff auf Red Hat Insights und Image Builder gelten die gleichen Bedingungen wie für die RHEL Developer Subscription for Individuals.

Support

Support meint an dieser Stelle nicht die Hilfe und Unterstützung, die man auf Mailinglisten, in Foren oder Chats erhalten kann. Gemeint ist ausschließlich der kommerzielle Support der Firma Red Hat, welchen man per Telefon, E-Mail oder das Customer Portal erreichen kann.

Über den Support hat man Kontakt zu den Menschen, die sich vermutlich am besten mit dem Produkt auskennen. Hier sitzen Menschen, die dafür bezahlt werden, uns Kunden bestmöglich zu unterstützen. Dabei mag es von Fall zu Fall Schwankungen in der Reaktionszeit und/oder der Qualität geben.

Mir persönlich hat der Support schon in etlichen Fragestellungen und bei einigen hartnäckigen Problemen weiterhelfen können. Dabei waren so schöne Dinge wie Kernel Panic bei Zugriff auf eine eingehängte DFS-Ressource, deren Shares in einer HNAS-Speicher-Infrastruktur liegen. Ich glaube, sowas haben die wenigsten Menschen bei sich daheim im Keller stehen. Und die Chance, bei diesem Problem Hilfe in einem Forum oder Chat zu finden, dürfte dementsprechend gering sein. Der Support stellte in diesem Fall Kontakt zu einem Engineer her, der über eine hervorragende Kenntnis der beteiligten Protokolle und Protokollversionen verfügte und beim Debugging unterstützte. Am Ende erhielten wir einen angepassten Kernel, der das Problem löste und den wir nutzen konnten, bis das Problem auch Upstream und im regulären RHEL-Kernel gefixt wurde.

Nicht zuletzt ist kommerzieller Support ein guter Verbündeter, wenn man selbst nicht weiterweiß und sich Druck aufbaut. Nach dem Motto: „Wenn selbst der Hersteller nicht weiter weiß, woher soll ich dann wissen, warum es nicht geht?“ Es ist nie schön, wenn es soweit kommt. Noch hässlicher wird es, wenn man dann nicht auf jemand anderen zeigen kann.

Red Hat Insights

Zu Red Hat Insights (engl.) habe ich in der Vergangenheit bereits eine eigene Serie geschrieben, auf die ich an dieser Stelle verweise:

  1. Einführung in Red Hat Insights
  2. Erkundung von Red Hat Insights — Advisor
  3. Schwachstellen-Management mit Red Hat Insights
  4. Red Hat Insights – Compliance
  5. Red Hat Insights – Patch and Drift
  6. Persönliche Bewertung von Red Hat Insights

Dieser Dienst steht auch mit den kostenlosen Developer-Subs zur Verfügung. Vorausgesetzt, die Nutzung ist unter geltendem Recht möglich, stellt dieser Dienst einen echten Mehrwert dar. Ein vergleichbarer Dienst existiert für AlmaLinux und Rocky Linux nicht.

Image Builder (SaaS)

In größeren Umgebungen (IHMO >2 Systeme) werden Server meist nicht mehr individuell installiert, sondern von sogenannten Images oder Templates provisioniert. Diese sind initial zu erstellen und fortlaufend zu pflegen.

Image Builder ist ein SaaS-Dienst in Red Hat Insights, welcher bei der Erstellung solcher Templates unterstützt. Dabei können Images direkt für die Cloud Anbieter Amazon Web Services, Google Cloud Platform, Microsoft Azure, VMware vSphere, KVM/QEMU (.qcow2) und Bare Metal erstellt werden.

Es handelt sich dabei um einen noch recht jungen Dienst, der einige Einschränkungen hat. So ist das einzige derzeit unterstützte Dateisystem für Images XFS. Ein RFE, um Ext4 als unterstütztes Dateisystem hinzuzufügen, ist gestellt.

Auch lassen sich aktuell über diesen Dienst noch keine User oder SSH-Public-Keys in die Images integrieren. Für diese Funktionalität habe ich ebenfalls bereits RFEs geschrieben.

Ich bin gespannt, wie Red Hat diesen Dienst weiterentwickelt. In meinen Augen hat er das Potenzial, zu einem nützlichen Werkzeug zu reifen.

Diesen Dienst kann man ebenfalls mit den kostenlosen Developer-Subs nutzen, während ein vergleichbarer Dienst meines Wissens für AlmaLinux und Rocky Linux nicht existiert.

Fazit

Ob die genannten Mehrwerte tatsächlich zum Tragen kommen, muss jeder für sich und seine Organisation selbst beurteilen. Sie sollten jedoch vor einer Entscheidung in die Überlegungen mit einbezogen werden.

Die Nutzung von Red Hat Insights ist in unserem Rechtsraum schwierig, bis nahezu unmöglich. Dort wo der Dienst genutzt werden kann, stellt er ein gutes Werkzeug dar, welches dem Admin die tägliche Arbeit erleichtert.

Ich werde ein kleines Heimlabor aus RHEL-Servern aufbauen und diese in Red Hat Insights integrieren. Vielleicht gewinne ich dadurch Erkenntnisse, die sich auch auf den Betrieb im Rechenzentrum übertragen lassen.

Der Image Builder ist als SaaS in seiner jetzigen Form nur von geringem Nutzen für mich. Ich hoffe, dass meine RFEs akzeptiert und umgesetzt werden, um diesen Service und dessen Nutzen weiter auszubauen.

Ein Blick auf AlmaLinux, RHEL und Rocky Linux

05. Juni 2022 um 05:00

In diesem Artikel möchte ich einen Blick auf die Linux Distributionen AlmaLinux und Rocky Linux werfen und sie ihrem Upstream-Projekt Red Hat Enterprise Linux (RHEL) gegenüber stellen. Dabei interessieren mich insbesondere die folgenden Punkte:

  1. Wer betreibt das Projekt bzw. steht hinter dem Projekt?
  2. Wie finanziert sich das Projekt? Gibt es ein funktionierendes Geschäftsmodell?
  3. Welchen Eindruck hinterlässt die Dokumentation?
  4. Wie lange wird ein Major-Release unterstützt?
  5. Wie viele Tage liegen zwischen einem RHEL-Release und den Releases der beiden Projekte?
  6. Wie handhaben die Projekte Sicherheits-Updates?

Aus Gründen der Transparenz weise ich darauf hin, dass ich Mitglied der Red Hat Accelerators Community und System-Administrator diverser RHEL-Server bin. Dieser Text gibt ausschließlich meine persönlichen Ansichten und nicht die von Red Hat oder die meines Arbeitgebers wieder. Zwar können diese in einzelnen Punkten übereinstimmen, müssen es aber nicht.

Was haben beide Projekte gemeinsam?

Sowohl AlmaLinux als auch Rocky Linux sind nach eigener Aussage:

  • Produktionsreife Betriebssysteme
  • Binärkompatibel zu RHEL
  • Werden aus den RHEL-Quelltexten übersetzt
  • Für den Nutzer kostenlos
  • Bieten 10 Jahre Unterstützung für ein Major-Release

Auch Red Hat bietet 10 Jahre Support auf seine RHEL-Major-Releases (Login erforderlich), an die sich eine Extended Life Phase anschließen kann. Selbstverständlich ist auch RHEL nach eigener Aussage reif für den produktiven Einsatz. Im Unterschied zu AlmaLinux und Rocky Linux kann RHEL jedoch nur zusammen mit einer kostenpflichtigen Subskription sinnvoll betrieben werden. Je nach Größe der Umgebung und Anzahl RHEL-Instanzen können hier beträchtliche Kosten anfallen.

Wer steht hinter den Distributionen?

Bei AlmaLinux handelt es sich um ein Community-Projekt, welches von der Firma CloudLinux Inc. gestartet wurde, welche das Projekt auch mit 1 Mio. USD pro Jahr unterstützt. Entwickelt und gesteuert wird das Projekt durch die Community, zu deren Unterstützung die gemeinnützige AlmaLinux OS Foundation gegründet wurde.

Neben der jährlichen Zuwendung von CloudLinux finanziert sich das Projekt durch diverse Sponsoren.

Die Firma CloudLinux Inc. verfügt selbst über mehr als 10 Jahre Erfahrung in der Pflege eines RHEL-Klons sowie den Betrieb der dazu notwendigen Infrastruktur. Nach eigener Aussage möchte CloudLinux Inc. durch die Unterstützung des Projekts den eigenen Bekanntheitsgrad steigern und hofft auf neue Kunden für das kommerzielle CloudLinux OS und KernelCare.

RHEL wird von der Firma Red Hat entwickelt, welches eines der weltweit erfolgreichsten Open Source Unternehmen ist. Haupteinnahmequelle des Unternehmens ist der Vertrieb von Subskriptionen für RHEL und weitere Produkte aus dem Portfolio. Eine Subskription berechtigt zum Bezug von Software-Aktualisierungen und umfasst kommerziellen Support.

Rocky Linux ist ein Community-Projekt, welches vom CentOS-Mitbegründer Gregory Kurtzer gegründet wurde. Das Projekt verfolgt die gleichen Ziele wie das ursprüngliche CentOS, welches zugunsten von CentOS Stream aufgegeben wurde.

Ähnlich wie AlmaLinux finanziert sich das Projekt durch verschiedene Sponsoren. Wie die Webseite Linuxnews am 16. Mai 2022 berichtete, konnte sich Rocky Linux eine Finanzierung in Höhe von 26 Mio. USD von Google sichern.

Hilfe, Unterstützung und Dokumentation

Wie oben bereits beschrieben haben alle Distributionen ein Unterstützungszeitraum von 10 Jahren. Das heißt, dass ein Major-Release wie AlmaLinux/RHEL/Rocky Linux 8 für einen Zeitraum von 10 Jahren Aktualisierungen in Form von Bug-/Security-Fixes und ausgewählter Verbesserungen erhält. AlmaLinux und Rocky Linux profitieren hier von der Vorarbeit, welche Red Hat für RHEL geleistet hat.

Hilfe für individuelle Probleme, Sorgen, Nöte und Anträge erhält man bei AlmaLinux und Rocky Linux in Foren, Chats und auf Mailinglisten. Darüber hinaus kann man kommerziellen Support für AlmaLinux bei TuxCare einkaufen. Rocky Linux listet CIQ und OpenLogic by Perforce als Support-Anbieter. Red Hat unterhält mit der Red Hat Customer Portal Community ebenfalls einen Bereich mit Disussions-Forum. Darüber hinaus bietet Red Hat im Rahmen seiner Subskriptionen Support direkt vom Hersteller.

Für alle drei Distributionen gibt es also sowohl freie/kostenlose Support-Angebote, als auch kommerzielle Support-Verträge, entweder direkt vom Hersteller oder durch Drittanbieter.

Ob man kommerziellen Support benötigt oder der Community-Support ausreicht, hängt von verschiedenen Faktoren wie z.B. den eigenen Fähigkeiten und nicht zuletzt von der eigenen Risikofreudigkeit ab. Ich persönlich habe die Erfahrung gemacht, dass in Enterprise- bzw. Provider-Umgebungen Probleme auftreten können, die im Hobbykeller, im Verein oder im SoHo ausbleiben und völlig unbekannt sind. In diesen Fällen darf man sich nicht wundern, wenn sich in den Community-Foren niemand findet, der helfen kann. Hier kann ein kommerzieller Support vorteilhaft sein, der auf diese Umgebungen ausgerichtet ist und über mehr Erfahrung in diesem Bereich verfügt.

Ich persönlich bin mit dem Red Hat Support für RHEL zufrieden. Er hat mir schon einige Male bei Problemen und Fragestellungen geholfen, wo ich im Forum vermutlich ohne Antwort geblieben wäre. Die Support-Qualität bei AlmaLinux und Rocky Linux kann ich mangels Erfahrung nicht beurteilen.

Softwareunterstützung

Alle drei betrachteten Distributionen sind zueinander binärkompatibel. Das bedeutet, dass Anwendungen, die unter RHEL ausgeführt werden können, in aller Regel auch unter AlmaLinux und Rocky Linux ausgeführt werden können und umgekehrt.

Software-Hersteller führen in ihren Systemvoraussetzungen häufig unterstützte Betriebssysteme auf. Hier finden sich manchmal nur die kommerziellen Enterprise-Betriebssysteme wie RHEL oder SuSE Linux Enterprise Server (SLES). In solch einem Fall kann es passieren, dass der Software-Hersteller den Support ablehnt, wenn man seine Anwendung auf einem RHEL-Klon ausführt statt auf dem Original. Evtl. verlangt der Hersteller auch nur, dass das Problem unter einem offiziell unterstützten Betriebssystem nachgestellt wird. Dieser Punkte sollte bei der Auswahl einer Distribution mit bedacht werden.

Dokumentation

Red Hat bietet für RHEL eine ausführliche Produkt-Dokumentation und eine umfassende Wissensdatenbank. Zwar ist auch hier nicht alles perfekt, doch hat man dies auch schon deutlich schlechter gesehen. Um die Dokumentation kontinuierlich zu verbessern, bietet Red Hat allen Kunden und Nutzern die Möglichkeit, Dokumentations-Feedback direkt in der Dokumentation zu geben. Aus eigener Erfahrung kann ich berichten, dass gefundene Fehler meist innerhalb weniger Tage behoben werden.

Bei AlmaLinux habe in hinsichtlich Dokumentation auf den ersten Blick nur ein Wiki gefunden, welches auf mich hinsichtlich Gliederung und Umfang einen enttäuschenden Eindruck macht.

Rocky Linux hat ebenfalls ein Wiki und einen gesonderten Bereich für Dokumentation. Auch hier lässt mich die Gliederung etwas verstört und hilflos zurück. Zwar finden sich auf den ersten Blick mehr Anleitungen als bei AlmaLinux, an die RHEL-Dokumentation reicht sie jedoch keinesfalls heran.

Die schwache bis mangelhafte Dokumentation bei AlmaLinux bzw. Rocky Linux mag nicht so sehr ins Gewicht fallen, da man in vielen Fällen einfach die RHEL-Doku zurate ziehen kann. Auch hier profitieren die beiden Projekte wieder von der Vorarbeit des Originals.

Release-Zyklen

Seit RHEL 8 hat sich Red Hat feste Release-Zyklen auferlegt, welche alle 6 Monate ein Minor- bzw. Point-Release und alle 3 Jahre ein Major-Release vorsehen. Da AlmaLinux und Rocky Linux als Downstream-Projekte aus den RHEL-Quelltexten gebaut werden, erscheinen deren Releases stets nach der Veröffentlichung eines RHEL-Release. Die folgende Tabelle gibt einen kleinen Überblick, wann welche Distribution ein Minor-Release veröffentlicht hat.

ReleaseAlmaLinuxRHELRocky Linux
8.42021-05-262021-05-182021-06-21
8.52021-11-122021-11-092021-11-15
8.62022-05-122022-05-102022-05-16
9 beta2022-04-192021-11-03N/A
9.0 GA2022-05-262022-05-182022-07-14
Zeitpunkt der Veröffentlichungen

Bisher folgen die Releases von AlmaLinux und Rocky Linux in der Regel wenige Tage auf das RHEL-Release. Beim Major-Release 9 hing RockyLinux ca. 1,5 Monate hinterher.

Bereitstellung von Sicherheits-Updates

Alle drei Projekte stellen Produkt-Errata im Internet bereit:

Dabei werden Errata in Bugfix-, Enhancement- und Security-Advisory unterschieden.

Während Bugs und fehlende Funktionalität störend und ärgerlich sein können, stellt die schnelle Verfügbarkeit von Sicherheits-Updates einen kritischen Faktor dar, um Sicherheitslücken zeitnah schließen zu können. Nach Aussage von AlmaLinux werden Errata bei AlmaLinux und Rocky Linux mit einem Geschäftstag Verzögerung in Bezug auf das RHEL-Errata-Release veröffentlicht. Ob beide Projekte dies durchhalten können, werde ich in der Zukunft stichprobenartig kontrollieren.

Mein Open-Source-Projekt Ansible: Patch-Management für Red Hat Systeme nutzt die Red Hat Security Advisories, um sogenannte Patch-Sets zu definieren, welche zu bestimmten Stichtagen installiert werden. Es ist darauf angewiesen, dass Errata-Informationen als Meta-Informationen in den Paket-Repositorien verfügbar sind. Bei CentOS fehlten diese, sodass mein Patch-Management für CentOS nicht nutzbar ist.

Daher bin ich sehr erfreut, dass AlmaLinux und Rocky Linux diese Meta-Informationen ebenfalls in ihren Repositorien bereitstellen. Prinzipiell sollte mein Patch-Management auch mit diesen beiden Distributionen funktionieren. Ein Test steht jedoch noch aus.

Zusammenfassung

Mit AlmaLinux und Rocky Linux gibt es zwei von einer Gemeinschaft entwickelte binärkompatible RHEL-Klone, welche von unterschiedlichen Unternehmen unterstützt und gesponsort werden. Erste Unternehmen bieten kommerziellen Support für diese Distributionen an.

Mir fällt positiv auf, dass beide Projekte in ihren Repos Errata-Informationen wie z.B. ALSA oder RLSA bereitstellen. Dies erleichtert die gezielte Installation von sicherheitsrelevanten Aktualisierungen. Hier bieten beide Projekte mehr, als es CentOS in der Vergangenheit tat.

Die Dokumentation scheint bei beiden Projekten keine große Rolle zu spielen. Ich empfinde sie als unübersichtlich und lückenhaft. Hier kann man jedoch vermutlich auf die Dokumentation des Originals (RHEL) zurückgreifen, welche sich mit sehr geringer Transferleistung auch für AlmaLinux und Rocky Linux nutzen lässt.

Auf den ersten Blick scheint es sich sowohl bei AlmaLinux als auch bei Rocky Linux um zwei solide Distributionen für alle jene zu handeln, die sich RHEL nicht leisten können oder wollen.

[Fixed] Defektes Plugin: Subscribe to Comments Reloaded

12. Mai 2022 um 09:35

Update 13.05.2022: Das Plug-in ist in Version 220513 repariert und wurde wieder deaktiviert. Danke an BobaWebDev für den schnellen Fix.

Liebe Leser*innen, leider ist das Plugin Subscribe to Comments Reloaded in der installierten Version 220512 offenbar defekt. Daher habe ich das Plugin vorerst deaktiviert.

Der Fehler wurde bereits in GitHub-Issue 707 gemeldet. Ich hoffe, dass der Fehler bald durch den Anbieter behoben wird.

Die Kommentarfunktion kann weiterhin wie gewohnt genutzt werden. Nur könnt ihr euch aktuell nicht über neue Kommentare benachrichtigen lassen.

Nextcloud im Container – Teil 6: Updates

18. April 2022 um 05:00

Herzlich willkommen zu Teil 6 meiner Reihe Nextcloud im Container. Dieser Teil behandelt das Thema Updates. Zum Verständnis empfehle ich, zuerst Teil 1 und Teil 2 zu lesen.

Nun wünsche ich euch viel Spaß beim Lesen und gute Unterhaltung.

Gedanken zum Update

Meine Nextcloud-Instanz läuft in einem Podman-Pod. Das sieht im Terminal wie folgt aus:

$ podman pod ps
POD ID        NAME    STATUS   CREATED       INFRA ID      # OF CONTAINERS
e84bec6108d1  nc_pod  Running  2 months ago  5e52555c5060  3

Dieser Pod besteht aus den folgenden drei Container-Instanzen:

$ podman ps
CONTAINER ID  IMAGE                                  COMMAND               CREATED       STATUS         PORTS                    NAMES
5e52555c5060  k8s.gcr.io/pause:3.2                                         2 months ago  Up 7 days ago  127.0.0.1:40671->80/tcp  e84bec6108d1-infra
c6571aa338ce  docker.io/library/mariadb:10.5.7       mysqld                2 months ago  Up 7 days ago  127.0.0.1:40671->80/tcp  nc_mariadb
21739d36eef1  docker.io/library/nextcloud:23-apache  apache2-foregroun...  2 months ago  Up 7 days ago  127.0.0.1:40671->80/tcp  nextcloud

Diese Container-Instanzen sind zustandslos und ephemeral (engl. für kurzlebig, vergänglich oder flüchtig). Persistent zu speichernde Daten werden außerhalb der Container-Instanzen gespeichert. Diese Eigenschaften erlauben es, Container einfach entfernen und durch neue Instanzen ersetzen zu können.

Um die Nextcloud zu aktualisieren, wird in dieser Umgebung also nicht die Anwendung innerhalb des Containers aktualisiert. Stattdessen werden die Container-Instanzen entfernt und Container-Images mit aktuelleren Versionen der Anwendung und Datenbank instanziiert.

Der Update-Prozess

Die aktuell laufenden Versionen von Nextcloud und MariaDB sind obigen Codeblock zu entnehmen. Diese Images wurden durch die beiden folgenden Zeilen in der Datei {role_path}/defaults/main.yml definiert:

MARIADB_IMAGE: docker.io/library/mariadb:10.5.7
NC_IMAGE: docker.io/library/nextcloud:23-apache

Hier kann man nun die gewünschten Versionen der zu verwendenden Container-Images eintragen. Alternativ kann man die Default-Werte auch durch entsprechende Einträge in {role_path}/vars/main.yml überschreiben. Die Einträge sehen dann bspw. wie folgt aus:

MARIADB_IMAGE: docker.io/library/mariadb:10.5.9
NC_IMAGE: docker.io/library/nextcloud:23.0.3-apache

Nun kann das Playbook mit der Ansible-Rolle aus Teil 2 dieser Reihe erneut ausgeführt werden:

$ ansible-playbook -i hosts deploy_nextcloud.yml --ask-vault-pass                                                 
Vault password:                                                                                                   
                                                                                                                  
PLAY [localhost] **************************************************************************************************
                                                                                                                  
TASK [Gathering Facts] *******************************************************************************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Main folder, needed for updating] *************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for installed/modified apps] ***********************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for local configuration] ***************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for the actual data of Nextcloud] ******************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for the MySQL data files] **************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Create the podman-pod(1)] *********************************
changed: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Create MariaDB container] *********************************
changed: [localhost]                                                                                               
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Wait for DB to initilize] *********************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Create Nextcloud container] *******************************
changed: [localhost]                                                                                               
                                                                                                                  
PLAY RECAP *******************************************************************************************************
localhost                   : ok=10   changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Nun kann man sich auf dem Zielsystem davon überzeugen, dass die neuen Container-Images zur Instanziierung verwendet wurden:

$ podman ps
CONTAINER ID  IMAGE                                      COMMAND               CREATED         STATUS             PORTS                    NAMES
5e52555c5060  k8s.gcr.io/pause:3.2                                             2 months ago    Up 7 days ago      127.0.0.1:40671->80/tcp  e84bec6108d1-infra
248f87e1135b  docker.io/library/mariadb:10.5.9           mysqld                35 seconds ago  Up 36 seconds ago  127.0.0.1:40671->80/tcp  nc_mariadb
59ac1aad168c  docker.io/library/nextcloud:23.0.3-apache  apache2-foregroun...  10 seconds ago  Up 11 seconds ago  127.0.0.1:40671->80/tcp  nextcloud

Fertig. Schon kann Nextcloud in Version 23.0.3 mit einer MariaDB 10.5.9 genutzt werden.

Fazit

Mit diesem Artikel habe ich das letzte noch offene Ziel Nr. 5 „Konfiguration und Test automatischer durch Ansible gesteuerter Updates“ erreicht. Der Update-Prozess folgte dem Container-Paradigma, die Container komplett zu ersetzen und nicht die Anwendung innerhalb der Container zu aktualisieren.

Es handelte sich im dokumentierten Fall um Updates auf ein neues Patch-Release, bei denen das Risiko für Fehler ohnehin sehr gering ist. Ob das Update auch bei Minor- bzw. Major-Releases so gut funktioniert, muss sich noch zeigen. Ich werde diesen Artikel aktualisieren, wenn es Erkenntnisse dazu gibt.

Mit diesem Artikel endet meine Reihe „Nextcloud im Container“. Ich hoffe, ich habe euch damit ein wenig unterhalten und konnte euer Wissen durch die ein oder andere Information erweitern.

Quellen und weiterführende Links

  1. Nextcloud im Container – Teil 1: Der Plan
  2. Nextcloud im Container – Teil 2: Die Ansible-Rolle
  3. Nextcloud im Container — Teil 3: Mit Reverse-Proxy
  4. Nextcloud im Container — Teil 4: Hier und da klemmt es
  5. Nextcloud im Container – Teil 5: Backup und Restore
  6. ansible_role_deploy_nextcloud_with_mariadb_pod auf GitHub
  7. Semantic Versioning 2.0.0

Wie kann man Hostnamen ohne Programme aus dem Paket bind-utils auflösen?

11. April 2022 um 05:00

Den allermeisten IT-Systemadministratoren werden die Programme dig, host und nslookup aus dem Paket bind-utils bekannt sein. Sie dienen der Auflösung von Hostnamen. Mir sind jedoch wiederholt Systeme begegnet, auf denen diese Programme nicht zur Verfügung stehen. Dies führte mich zur Eingangsfrage, wie ich Hostnamen auflösen kann, ohne das Paket bind-utils nachinstallieren zu müssen.

Die Antwort liefert der folgende Codeblock. Das Programm getent(1) stammt aus der glibc, welche auf jedem Linux-System vorhanden ist. Voraussetzung ist lediglich, dass das System für die Nutzung von DNS-Resolvern konfiguriert ist (z.B. /etc/resolv.conf).

$ getent hosts beispiel.de
23.21.157.88    beispiel.de

$ getent ahosts my-it-brain.de
2a02:c207:3005:4920::1 STREAM my-it-brain.de
2a02:c207:3005:4920::1 DGRAM  
2a02:c207:3005:4920::1 RAW    
144.91.83.52    STREAM 
144.91.83.52    DGRAM  
144.91.83.52    RAW

Für weitere Abfragemöglichkeiten verweise ich auf die Manpage getent(1).

Wie kann ich von der Shell aus prüfen, ob ein entfernter TCP-Port erreichbar ist?

04. April 2022 um 06:00

Diese Frage habe ich mir selbst schon mehrmals gestellt. Und mindestens genauso oft wurde sie mir schon von anderen gestellt. Als Antwort kommen hier meist zuerst telnet oder netcat (nc) in den Sinn. Doch in einer RHEL-Minimal-Installation sind diese beiden Programme nicht enthalten und müssen erst nachinstalliert werden. Was tut man in diesem Fall (ohne den Paketmanager zu starten)?

Ich möchte in diesem Artikel mehrere Antworten auf die Eingangsfrage festhalten. Dabei beginne ich mit denen, die ich auf stackoverflow gefunden habe.

Bash und timeout

Von RHEL 6 aufwärts sollte das Programm timeout in der Minimal-Installation enthalten sein. In Fedora 35 und Debian 11 ist es ebenfalls enthalten. Es stammt aus dem Paket coreutils, in dem es meines Wissens spätestens seit Version 8.22 enthalten ist.

Kommando

$ timeout $TIMEOUT_SECONDS bash -c "</dev/tcp/${HOST}/${PORT}"; echo $?

Beispiele

Erfolgreicher Verbindungstest

$ HOST=beispiel.de
$ PORT=443
$ timeout 5 bash -c "</dev/tcp/${HOST}/${PORT}"; echo $?
0

Selbstverständlich funktioniert auch folgender Befehl:

$ timeout 5 bash -c "</dev/tcp/beispiel.de/443"; echo $?
0

Fehlgeschlagener Verbindungstest

Jetzt nutze ich einen Port, der nicht erreichbar ist:

$ timeout 5 bash -c "</dev/tcp/beispiel.de/4433"; echo $?
124

Nutzung von nc

Sollte nc bereits installiert sein, kann man auch dieses Programm für einen Verbindungstest nutzen:

Kommando

$ nc -w $TIMEOUT_SECONDS -v $HOST $PORT </dev/null; echo $?

Beispiele

Erfolgreicher Verbindungstest

$ HOST=beispiel.de
$ PORT=443
$ nc -w 2 -v $HOST $PORT </dev/null; echo $?
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 23.21.157.88:443.
Ncat: 0 bytes sent, 0 bytes received in 0.54 seconds.
0

Fehlgeschlagener Verbindungstest

$ nc -w 2 -v beispiel.de 4433 </dev/null; echo $?
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: TIMEOUT.
1

Zusammenfassung

Mit timeout und nc habe ich meine beiden Favoriten festgehalten. Mit beiden lässt sich die Erreichbarkeit von entfernten TCP-Ports testen (die von lokalen TCP-Ports übrigens auch).

Falls ihr noch weitere Möglichkeiten kennt, mit Bordmitteln, die in der Minimal-Installation einer Distribution enthalten sind, um einen Verbindungstest durchzuführen, schreibt sie mir gern in die Kommentare. Ich nehme sie dann gern mit in den Artikel auf.

Nextcloud im Container – Teil 5: Backup und Restore

21. März 2022 um 06:00

Hallo und herzlich willkommen zu Teil 5 von Nextcloud im Container. In diesem Artikel geht es um Backup und Restore. Die vorhergehenden Teile dieser Serie sind weiter unten verlinkt.

Backup

Mit dem Thema Backup und Restore im Containerland habe ich mich bereits in einem früheren Artikel beschäftigt. Da ich mit dem Ansatz von dort nicht ganz zufrieden bin, gehe ich in diesem Wochenend-Projekt einen anderen Weg.

Für Nextcloud existiert die App Backup, welche im Folgenden zum Einsatz kommt. Diese wird über die App-Verwaltung in den Admin-Einstellungen der Nextcloud aktiviert.

Die englischsprachige Dokumentation (siehe [5]) macht einen soliden und verständlichen Eindruck. Ich habe mich von dieser durch die Konfiguration führen lassen.

Einstellungen der Nextcloud-Sicherung
Standard-Einstellungen der Nextcloud-Sicherungs-App

Ich habe die im obigen Bild dargestellten Einstellungen wie folgt geändert:

  1. Zeitintervall zwischen zwei vollständigen Wiederherstellungspunkten: 7 Tage
  2. Die Verschlüsselung für Wiederherstellungspunkte habe ich deaktiviert
  3. Richtlinie für die lokalen App-Daten: 4

Nun kann man über die entsprechende Schaltfläche einen vollständigen Wiederherstellungspunkt erstellen. Alternativ kann man auch warten, bis das Zeitfenster für den ersten vollständigen Wiederherstellungspunkt erreicht ist.

Ich habe die Sicherung einige Tage mitlaufen lassen. Wie der folgende Screenshot zeigt, wurden ein Full-Backup und zwei darauf folgende differenzielle Backups erzeugt, bevor ein neues Full-Backup erstellt wurde.

Backup-History on Sun 13th Mar
Backup-Historie am 13.03.2022

Hierbei ist zu beachten, dass die Backups in der gleichen Container-Instanz liegen wie die Live- bzw. Produktions-Daten. Damit ist es noch kein richtiges Backup. Denn wenn diese Instanz komplett zerstört wird, ist auch das Backup weg. Die Backups müssen daher unbedingt an einem anderen Ort gespeichert werden. Ich habe mich entschieden, diesem Thema einen eigenen Artikel zu widmen und es in Teil 6 Desaster-Recovery zu behandeln.

Wiederherstellung einer einzelnen Datei

An dieser Stelle möchte ich festhalten, wie eine einzelne Datei wiederhergestellt werden kann. Mit der Wiederherstellung einer kompletten Nextcloud-Instanz beschäftige ich mich in einem folgenden Artikel.

Wiederherstellungspunkte auflisten

Um vorhandene Wiederherstellungspunkte auflisten zu können, wechselt man auf dem Container-Host zuerst in die Container-Instanz mit der laufenden Nextcloud:

$ podman exec -u 33 -it nextcloud bash
www-data@nc_pod:~/html$

Durch ‚-u 33′ betritt man den Container mit der UID des Users www-data, welcher berechtigt ist, die folgenden Kommandos auszuführen. In obigem Beispiel heißt die Container-Instanz schlicht ’nextcloud‘. Für weitere Informationen zum Kommando siehe podman-exec(1). Die Restore-Points können nun wie folgt aufgelistet werden:

www-data@nc_pod:~/html$ ./occ backup:point:list
- retreiving data from local
 > found RestoringPoint 20220305001002-full-3ebDDoh2yinj55C
 > found RestoringPoint 20220307235028-differential-W558tuHGTnCbwgx
 > found RestoringPoint 20220310234002-differential-1NHETYudPSx6Yxc
 > found RestoringPoint 20220312001002-full-i1SY9VxDZRL8n1O
Ausgabe gekürzt.

Einen Wiederherstellungspunkt nach einer Datei durchsuchen

Angenommen, die Bilddatei Frog.jpg ist abhandengekommen. Nun kann ein Wiederherstellungspunkt wie folgt nach dem Dateinamen durchsucht werden:

www-data@nc_pod:~/html$ ./occ backup:file:search --point 20220305001002-full-3ebDDoh2yinj55C Frog.jpg

- searching in 20220305001002-full-3ebDDoh2yinj55C (2022-03-05 00:10:02)
 1/1 [============================] 100%
   > found tronde/files/Photos/Frog.jpg (447.02 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Photos/Frog.jpg (447.02 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
 1/1 [============================] 100%
   > found core/skeleton/Photos/Frog.jpg (447.02 KB) in nextcloud/nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5
 1/1 [============================] 100%

In dem angegebenen Wiederherstellungspunkt existiert die Datei Frog.jpg insgesamt dreimal. Es wird jeweils der komplette Pfad angezeigt.

Wiederherstellung von bob/files/Photos/Frog.jpg

Nun soll die Datei Frog.jpg für den Benutzer Bob aus dem oben verwendeten Wiederherstellungspunkt wiederhergestellt werden. Da meine Wiederherstellungspunkte komprimiert gespeichert werden, dient der erste Befehl im folgenden Codeblock jedoch erstmal dazu, einen Wiederherstellungspunkt zu entpacken.

www-data@nc_pod:~/html$ ./occ backup:point:unpack 20220305001002-full-3ebDDoh2yinj55C
Unpacking Restoring Point 20220305001002-full-3ebDDoh2yinj55C
 > lock and set status to unpacking
 > Browsing RestoringData data
   > Unpacking RestoringChunk data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e: proceeding
     * copying parts to temp files
       - 00001-Ur86KyoXRnp8Evt: /tmp/phpkzVs4Y
     * merging parts into single file: /tmp/phpyLjmxZ
     * Extracting /tmp/phpyLjmxZ: /tmp/phpmJG1O1
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData nextcloud
   > Unpacking RestoringChunk nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5: proceeding
     * copying parts to temp files
       - 00001-EUEeVME26dbQyf6: /tmp/phpVfSAt3
     * merging parts into single file: /tmp/phpc3kVw0
     * Extracting /tmp/phpc3kVw0: /tmp/phpF1kt52
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData apps
   > Unpacking RestoringChunk apps-14864f48-87a9-4c61-82f5-33defc3de2d2: proceeding
     * copying parts to temp files
       - 00001-65lDdATRHTQde5b: /tmp/phpDngG62
     * merging parts into single file: /tmp/php9U5X7Y
     * Extracting /tmp/php9U5X7Y: /tmp/phpXGwXP0
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData config
   > Unpacking RestoringChunk config-a8809c3e-5259-49b4-b2cb-2dbeb755949f: proceeding
     * copying parts to temp files
       - 00001-HMfvJewXGLEckCH: /tmp/phpPePsK1
     * merging parts into single file: /tmp/phpVlxXA1
     * Extracting /tmp/phpVlxXA1: /tmp/phpBUIDm0
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData apps-custom_apps-84a585e6
   > Unpacking RestoringChunk apps-custom_apps-84a585e6-c4e2adaa-eb06-4318-8196-62aea73b89c8: proceeding
     * copying parts to temp files
       - 00001-pXttjhVm9cRU6DU: /tmp/phpATT8t2
     * merging parts into single file: /tmp/php8HMcR1
     * Extracting /tmp/php8HMcR1: /tmp/phpmHRKS1
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData sqldump
   > Unpacking RestoringChunk sqldump-d0ebed1c-81dd-418b-a8ba-cecca8d92c00: proceeding
     * copying parts to temp files
       - 00001-QYOIpn2zOj43TsL: /tmp/phpcEUqQZ
     * merging parts into single file: /tmp/phpOwn0g1
     * Extracting /tmp/phpOwn0g1: /tmp/phpiJxrh3
     - storing chunk in appdata ok
     * removing old chunk parts
 > removing status packed
 > unlocking restoring point

www-data@nc_pod:~/html$ ./occ backup:point:restore 20220305001002-full-3ebDDoh2yinj55C --file bob/files/Photos/Frog.jpg --data data                                           
   > restoring bob/files/Photos/Frog.jpg (447.02 KB) from /data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e (rewind: 8 days, 20 hours, 51 minutes and 20 seconds): ok

Die allgemeine Befehlsform für die Wiederherstellung lautet:

backup:point:restore [--force] [--do-not-ask-data] [--do-not-ask-sql] [--file FILE] [--chunk CHUNK] [--data DATA] [--] <pointId>

Es ist nicht gut dokumentiert, wie man den korrekten Wert für --data herausfindet. Ich habe diesen durch Ausprobieren gefunden. Dann war der Restore jedoch kein Problem mehr. Wie man die korrekten Werte für --data bzw. --chunk sicher identifiziert, muss ich noch herausfinden. Falls hier jemand einen Tipp für mich hat, freue ich mich über euren Kommentar oder eine E-Mail.

Um Speicherplatz zu sparen, komprimiere ich den genutzten Wiederherstellungspunkt wieder und verlasse den Container:

www-data@nc_pod:~/html$ ./occ backup:point:pack 20220305001002-full-3ebDDoh2yinj55C
Packing Restoring Point 20220305001002-full-3ebDDoh2yinj55C
 > lock and set status to processing
 > Browsing RestoringData data
   > Packing RestoringChunk data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e: proceeding
     * copying chunk to temp file /tmp/phpsyGIxw: ok
     * compressing /tmp/phpsyGIxw: /tmp/phpjuZY7z
     * spliting /tmp/phpjuZY7z in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e.zip
 > Browsing RestoringData nextcloud
   > Packing RestoringChunk nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5: proceeding
     * copying chunk to temp file /tmp/php6f6Kfx: ok
     * compressing /tmp/php6f6Kfx: /tmp/phpz3Ndtz
     * spliting /tmp/phpz3Ndtz in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5.zip
 > Browsing RestoringData apps
   > Packing RestoringChunk apps-14864f48-87a9-4c61-82f5-33defc3de2d2: proceeding
     * copying chunk to temp file /tmp/phpf19OHA: ok
     * compressing /tmp/phpf19OHA: /tmp/phpZMcpmw
     * spliting /tmp/phpZMcpmw in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file apps-14864f48-87a9-4c61-82f5-33defc3de2d2.zip
 > Browsing RestoringData config
   > Packing RestoringChunk config-a8809c3e-5259-49b4-b2cb-2dbeb755949f: proceeding
     * copying chunk to temp file /tmp/phpykt9Rw: ok
     * compressing /tmp/phpykt9Rw: /tmp/phpxik6bx
     * spliting /tmp/phpxik6bx in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file config-a8809c3e-5259-49b4-b2cb-2dbeb755949f.zip
 > Browsing RestoringData apps-custom_apps-84a585e6
   > Packing RestoringChunk apps-custom_apps-84a585e6-c4e2adaa-eb06-4318-8196-62aea73b89c8: proceeding
     * copying chunk to temp file /tmp/php1oAWAA: ok
     * compressing /tmp/php1oAWAA: /tmp/phpOtyrjA
     * spliting /tmp/phpOtyrjA in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file apps-custom_apps-84a585e6-c4e2adaa-eb06-4318-8196-62aea73b89c8.zip
 > Browsing RestoringData internal
   > Packing RestoringChunk app.zip: already packed
   > Packing RestoringChunk restore.php: already packed
 > Browsing RestoringData sqldump
   > Packing RestoringChunk sqldump-d0ebed1c-81dd-418b-a8ba-cecca8d92c00: proceeding
     * copying chunk to temp file /tmp/phpFfW2zy: ok
     * compressing /tmp/phpFfW2zy: /tmp/phpvXl8qy
     * spliting /tmp/phpvXl8qy in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file sqldump-d0ebed1c-81dd-418b-a8ba-cecca8d92c00.zip
 > removing status processing, adding status packed
 > unlocking restoring point
www-data@nc_pod:~/html$ exit
exit

Wiederherstellung eines Ordners inkl. Inhalt

Manchmal ist es erforderlich, statt einzelner Dateien ganze Ordner inkl. Inhalt wiederherzustellen. Wie dies funktioniert, demonstriere ich im Folgenden für den Ordner Documents des Benutzers Bob. Dazu wechsle ich zuerst wieder in den Container, wie in diesem Abschnitt bereits beschrieben wurde.

TL;DR: Die Wiederherstellung eines Ordners inkl. dessen Inhalts ist aktuell nicht möglich (siehe [6]). Wer es eilig hat, kann direkt zum Fazit springen.

Während ich im vorherigen Abschnitt einen ausgewählten Wiederherstellungspunkt nach einer Datei durchsucht habe, liste ich diesmal alle Wiederherstellungspunkte bis zum 15.03.2022 auf, welche den gesuchten Pfad enthalten:

www-data@nc_pod:~/html$ ./occ backup:file:search bob/files/Documents --until 2022-03-15

- searching in 20220305001002-full-3ebDDoh2yinj55C (2022-03-05 00:10:02)
 1/1 [============================] 100%
   > found bob/files/Documents/Example.md (1.07 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Documents/Nextcloud flyer.pdf (365.24 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Documents/Readme.md (136 B) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Documents/Welcome to Nextcloud Hub.docx (24.56 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
 1/1 [============================] 100%

- searching in 20220307235028-differential-W558tuHGTnCbwgx (2022-03-07 23:50:28)
 1/1 [============================] 100%   no result

- searching in 20220310234002-differential-1NHETYudPSx6Yxc (2022-03-10 23:40:02)
 1/1 [============================] 100%   no result

- searching in 20220312001002-full-i1SY9VxDZRL8n1O (2022-03-12 00:10:02)
 1/1 [============================] 100%
   > found bob/files/Documents/Example.md (1.07 KB) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
   > found bob/files/Documents/Nextcloud flyer.pdf (365.24 KB) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
   > found bob/files/Documents/Readme.md (136 B) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
   > found bob/files/Documents/Welcome to Nextcloud Hub.docx (24.56 KB) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
 1/1 [============================] 100%

- searching in 20220314234502-differential-suwLdJJegRlO4XI (2022-03-14 23:45:02)
 1/1 [============================] 100%   no result

Es muss der vollständige Pfad zum gesuchten Verzeichnis angegeben werden. Wildcards wie z.B. */Documents oder "*/Documents" funktionieren genauso wenig wie reguläre Ausdrücke. Dies kann die Suche nach einem Ordner schonmal schwierig gestalten. Denn welcher Anwender kennt schon den vollständigen Pfad zu dem Ordner, der vermisst wird?

Im obigen Codeblock ist zu sehen, dass der Pfad zum Ordner lediglich als Bestandteil der Pfade zu den darin enthaltenen Dateien angezeigt wird. Dies und die Erkenntnis, keine Suchmuster verwenden zu können, lassen bei mir erste Zweifel aufkommen, ob das Verzeichnis inkl. Inhalt wiederhergestellt werden kann, oder ob ich jede enthaltene Datei einzeln wiederherstellen muss.

Doch bevor ich mich an der Wiederherstellung versuchen kann, muss ich zuerst den ausgewählten Wiederherstellungspunkt entpacken. Wie dies aussieht, kann dem Abschnitt über die Dateiwiederherstellung entnommen werden.

Der folgende Codeblock zeigt dann auch, dass der Ordner nicht wiederhergestellt werden kann:

www-data@nc_pod:~/html$ ./occ backup:point:restore 20220312001002-full-i1SY9VxDZRL8n1O --file bob/files/Documents --data data
 
In ChunkService.php line 925:
                                                        
  [OCA\Backup\Exceptions\ArchiveFileNotFoundException]  
                                                        

backup:point:restore [--force] [--do-not-ask-data] [--do-not-ask-sql] [--file FILE] [--chunk CHUNK] [--data DATA] [--] <pointId>

Eine Wiederherstellung ist offenbar nur auf Dateiebene möglich (siehe [6]). Das ist sehr schade, da dies die Wiederherstellung deutlich verkompliziert. Vor allem da Wildcards ebenfalls nicht funktionieren.

Fazit

In diesem Artikel habe ich das Thema Backup und Recovery aufgegriffen. Ich habe kurz beschrieben, wie eine Sicherung mit der Nextcloud-Backup-App konfiguriert werden kann. Nachdem einige Wiederherstellungspunkte erstellt wurden, habe ich exemplarisch eine Datei wiederhergestellt.

Da die Wiederherstellungspunkte komprimiert gespeichert werden, musste der ausgewählte Wiederherstellungspunkt vor dem Restore erst dekomprimiert werden, was einige Zeit gedauert hat. Hier muss jeder für sich selbst entscheiden, ob man sich die kürzere Zeit zur Wiederherstellung mit einem erhöhten Speicherbedarf durch unkomprimierte Wiederherstellungspunkte erkaufen möchte oder nicht.

Die zu Beginn des Artikels noch gelobte Dokumentation offenbarte schnell Lücken und ich muss mein Urteil revidieren. Sie ist im besten Fall ausreichend.

Der Versuch, einen Ordner inkl. Inhalt wiederherzustellen, endete hingegen in einer Enttäuschung. Dies ist aktuell offenbar nicht möglich, da nur der Datei-Restore oder die Wiederherstellung der gesamten Nextcloud-Instanz unterstützt wird. Diese Erfahrung lässt mich ein wenig enttäuscht und unzufrieden zurück.

Wie sichert ihr eure Nextcloud? Welche Restore-Fälle habt ihr damit bereits durchgespielt? Ich freue mich auf eure Kommentare und darauf euch zu Teil 6: Desaster-Recovery Updates wieder begrüßen zu dürfen.

Update 27.03.2022

Der obige Text wurde dahingehend angepasst, dass nun deutlich wird, dass die Wiederherstellung ganzer Verzeichnisse inkl. deren Inhalt aktuell nicht unterstützt wird (siehe [6]). Nicht mehr zutreffende Textstellen habe ich gestrichen.

Entgegen meiner ursprünglichen Planung wird es keinen Artikel zum Thema Desaster-Recovery geben, da es mir aktuell nicht möglich ist, die Restore-Points auf externem Speicher abzulegen (siehe [7] und [8]).

Damit ist die Backup-App in ihrem aktuellen Zustand (Version 1.0.6; Stand 27.03.2022) in meinen Augen unbrauchbar. Das ist schade, da mir die Idee, die Backup&Recovery-Funktion als App direkt zu integrieren, vom Grundsatz her gut gefällt.

Bis auf Weiteres verfahre ich daher so, wie analog im Artikel Backup und Restore im Kanboard-Container-Land beschrieben. Das dort beschriebene Verfahren lässt sich auch hier anwenden und genügt meinen Anforderungen.

Quellen und weiterführende Links

  1. Nextcloud im Container – Teil 1: Der Plan
  2. Nextcloud im Container – Teil 2: Die Ansible-Rolle
  3. Nextcloud im Container – Teil 3: Mit Reverse-Proxy
  4. Nextcloud im Container – Teil 4: Hier und da klemmt es
  5. Nextcloud im Container – Teil 6: Updates
  6. Nextcloud Backup App auf GitHub
  7. How to restore a folder with all files in it? #211
  8. S3 upload still failes with OOM for 1.0.6 #237
  9. Nothing being uploaded to external storage (WebDAV) #238

Nextcloud im Container — Teil 4: Hier und da klemmt es

07. März 2022 um 06:00

Herzlich Willkommen zu Teil 4 meines Wochenend-Projekts „Nextcloud im Container“. Diesem gingen die folgenden Teile voraus:

  1. Der Plan
  2. Die Ansible-Rolle
  3. NGINX als Reverse-Proxy

Nach Teil 3 habe ich die Zwei-Faktor-Authentisierung über TOTP für meine Nutzerkonten aktiviert, die Bookmark-, Calendar- und Contact-App installiert bzw. aktiviert, ein paar Kalendertermine erstellt und ein paar Dateien hochgeladen. Nichts Wichtiges. Lediglich ein paar Daten, die ich nach einem Backup zerstören kann, um anschließend den Restore-Prozess zu testen. Zuvor möchte ich aber noch ein paar Dinge festhalten, die mir bisher aufgefallen sind.

Background jobs: Cron does not run

In den Grundeinstellungen der Nextcloud werden Hintergrund-Aufgaben konfiguriert. Diese sind laut des dortigen Hinweises wichtig, um die optimale Geschwindigkeit zu erreichen:

Um die optimale Geschwindigkeit zu erreichen ist es wichtig, dass die Hintergrund-Aktivitäten richtig konfiguriert sind. Für größere Installationen ist ‚Cron‘ die empfohlene Einstellung. Weitere Informationen findest Du in der Dokumentation.

Grundeinstellungen in den Nextcloud-Einstellungen

Die Überschrift ist der Titel des GitHub-Issues #1695. Dieser beschäftigt sich damit, dass Cron in der Container-Instanz nicht läuft. Halt genau so, wie Cron dies bei mir auch nicht tut.

Der Benutzer beryl03, welcher den Issue eröffnet hat, beschreibt, dass Cron in der Container-Instanz nicht verfügbar ist und er in der Dokumentation keinen Hinweis darauf gefunden hat. Um das Problem zu mitigieren hat beryl03 einen Cronjob auf seinem Container-Host konfiguriert, welcher sich mit der Container-Instanz verbindet und darin die Datei cron.php ausführt. Welch elender Workaround. Aber immerhin gibt es einen. Denn die Hintergrund-Aufgaben mit AJAX auszuführen, scheitert leider ebenfalls. Schade, so habe ich mir das tatsächlich nicht vorgestellt.

Im Verlauf von Issue #1695 wird darauf hingewiesen, dass zur Verwendung von Cron ein weiterer Container benötigt wird (siehe [3]). Dies wird in den Beispielen zu den Compose-Dateien beschrieben (siehe [4]). Da ich Podman und Ansible statt Docker-Compose verwende, habe ich mir diese Beispiele natürlich nicht angesehen. Das ist dem Projekt nicht anzulasten, da ich mich ja bewusst für einen anderen Weg entschieden habe. Doch denke ich, dass man das Thema Hintergrund-Aufgaben innerhalb der Projekt-Dokumentation als auch in der Nextcloud-Dokumentation etwas ausführlicher behandeln könnte und sollte. Doch wie gehe ich nun mit dem Problem um, dass meine Hintergrund-Aufgaben nicht ausgeführt werden?

Docker-Compose mit Podman nutzen

Tatsächlich habe ich einen Artikel gefunden, welcher beschreibt, wie man Docker-Compose ab Podman 3.0 nutzen kann. Allerdings bietet dieser nur eine Lösung für den Fall, dass man Podman als User root bzw. mit Root-Rechten ausführt. Da Podman bei mir rootless läuft, kommt die Lösung für mich nicht in Frage.

Nach etwas weiterer Recherche habe ich einen RFE gefunden, welcher diese Funktionalität auch für rootless-Podman fordert. Die gute Nachricht lautet, dass diese Funktion mit Podman 3.2 veröffentlicht wurde. Pech für mich, dass unter Debian stable lediglich Podman 3.0.1 in den Quellen verfügbar ist.

Ein Workaround ist besser als gar keine Lösung

Tatsächlich erscheint mir aktuell der Workaround von beryl03 (siehe [1]) der beste Weg zu sein, um die Hintergrund-Aufgaben ausführen zu lassen. Dazu führe ich auf meinem Container-Host folgenden Befehl aus:

$ podman exec -u 33 -t nextcloud php -f /var/www/html/cron.php

Damit wird das Skript cron.php innerhalb der Container-Instanz mit der Nextcloud ausgeführt. Mit -u 33 wird die UID von www-data innerhalb der Container-Instanz angegeben. Für eine genaue Erklärung des Befehls und seiner Optionen siehe podman-exec(1).

Hintergrund-Aufgaben wurden erfolgreich ausgeführt.
Die Hintergrund-Aufgaben wurden nun erfolgreich ausgeführt

Da ich nicht gern lange Befehle in die Crontab schreibe, erstelle ich ein kurzes Skript namens nextcloud_cron.sh, welches obigen Befehl aufnimmt und welches ich alle 5 Minuten von Cron ausführen lasse. Damit werde ich sich noch sehr lange arbeiten, denn nicht umsonst sagen manche: „Nichts hält so lange, wie ein gutes Improvisorium.“

Fazit von Teil 4

Ich hoffe, die Artikelserie hat euch bis hierhin ein wenig unterhalten. Wer nach einer einfachen Lösung gesucht hat, bei der man ein bis zwei Container-Images aus dem Regal nimmt, ein paar Variablen mit Werten füllt, sie auf einen Container-Host provisioniert, ausführt und fertig ist, wird sicher gemerkt haben, dass er diese Lösung in dieser Artikelreihe nicht findet.

Auch ich habe mir zu Beginn nicht vorgestellt, dass es so hakelig werden würde. Schließlich soll mit Containern doch alles einfacher werden, nicht wahr? Warum mache ich also weiter und lasse das ganze Wochenend-Projekt nicht einfach fallen? Neugier, Sturheit, eine nutzbare Nextcloud-Instanz und auch ein bisschen Spaß bilden die Antwort auf vorstehende Frage. Und deshalb mache ich auch weiter. In Teil 5 wird es um Backup und Restore gehen.

Wie betreibt ihr eure Nextcloud? Mit Container oder ohne? Unter Docker, K3s, K8s, Podman, OpenShift oder einer noch ganz anderen Lösung? Lasst es mich gern in den Kommentaren wissen. Habt ihr über eure Erfahrungen in eurem eigenen Blog geschrieben, lasst mir gern einen Link hier. Macht es gut, bis nächste Woche.

Quellen und weiterführende Links

  1. Background jobs: Cron does not run #1695
  2. AJAX Background Jobs Fail After a Period of Inactivity #1442
  3. https://github.com/nextcloud/docker/issues/1695#issuecomment-1042602441
  4. https://github.com/nextcloud/docker/blob/master/.examples/docker-compose/insecure/mariadb/apache/docker-compose.yml
  5. Using Podman and Docker Compose. Podman 3.0 now supports Docker Compose to orchestrate containers. Enable Sysadmin. 2021-01-07.
  6. [RFE]Make docker-compose work with rootless podman #9169

Nextcloud im Container — Teil 3: Mit Reverse-Proxy

28. Februar 2022 um 06:00

In diesem Teil beschreibe ich die Konfiguration von NGINX als Reverse-Proxy für mein Wochenend-Projekt „Nextcloud im Container“. Und was so langweilig klingt, wie lediglich die Direktive proxy_pass in die NGINX-Konfiguration einzufügen, dauerte dann doch überraschend lange.

Darüber hinaus dokumentiere ich die Konfiguration von fail2ban, um die Anmeldemaske der Nextcloud vor Brute-Force-Angriffen zu schützen.

Falls ihr Teil 1 und Teil 2 dieser Reihe noch nicht kennt, empfehle ich euch, diese Teile zuerst zu lesen.

Die Fehlschläge

Bevor ich zur aktuellen Konfig komme, möchte ich kurz die Fehlschläge auf dem Weg dorthin festhalten.

Nextcloud im Unterverzeichnis nicht so einfach wie gedacht

Auf meinem Server läuft bereits ein Dienst, welcher unter der URL https://www.example.com/service1 erreichbar ist. Mir gefiel die Idee, die Nextcloud unter einer URL wie https://www.example.com/service2 erreichbar zu machen.

Die Dokumentation des Container-Repos [3] ist in meinen Augen zu kurz geraten und reicht für eine erfolgreiche Konfiguration längst nicht aus.

Nachdem ich einiges über die Generierung von Back-to-URLs und Redirects gelesen und im Nextcloud-Forum gestöbert hatte, ließ ich die Idee fallen. Ich warte mal ab, wie sich Issue #401 entwickelt. Vielleicht greife ich die Idee später nochmal auf.

Falsche Back-to-URLs und Protokoll-Probleme

Im nächsten Versuch, sollte Nextcloud unter einer eigenen Domain wie nextcloud.example.com erreichbar sein. Doch auch hier klemmte es zunächst. Zuerst stolperte ich in den bekannten Untrusted-Domain-Fehler.

access-through-untrusted-domain-error.png
Bekannte Fehlermeldung, wenn trusted_domains in config.php nicht korrekt gesetzt ist.

Diesen Fehler konnte ich schnell abstellen, da ich lediglich vergessen hatte die Variable NEXTCLOUD_TRUSTED_DOMAINS mit einem Wert zu belegen. Als Wert ist der FQDN einzutragen, unter dem die Nextcloud erreichbar sein soll. Dieser ist explizit anzugeben, da der Apache-Prozess innerhalb des Nextcloud-Containers den FQDN, auf den der NGINX-Reverse-Proxy lauscht und welcher im HTTP-Header übertragen wird, nicht kennt.

Des Weiteren musste ich noch die Variablen NEXTCLOUD_OVERWRITEPROTOCOL und NEXTCLOUD_OVERWRITECLIURL in vars/main.yml mit Werten belegen, um die entsprechenden Umgebungsvariablen für die Container-Instanz zu setzen (vgl. [3]). Dies ist notwendig, da die Anwendung im Nextcloud-Container andernfalls Back-to-URLs für das HTTP-Protokoll generiert. Versucht der Browser diese aufzurufen, erscheint ein Fehler oder man landet in einer endlosen Redirect-Schleife.

Nachdem ich die Nextcloud mit Hilfe der Ansible-Rolle [2] erneut deployt habe, war der Login-Screen erreichbar:

login-screen.png
Nextcloud-Login-Screen

Der Weg hierher war für meinen Geschmack aufwändiger als er hätte sein müssen. Nur durch Lektüre und Recherche der Quellen unter [4], [5] und [6] konnte ich mich von Problem zu Problem hangeln und diese lösen. Hier ist in meinen Augen noch Raum für Verbesserungen.

Die aktuell funktionierende Konfiguration

Meine NGINX-vHost-Konfiguration habe ich mir mithilfe von [4], [5] und [6] zusammengesucht und bin bei folgender Konfig gelandet (es werden nur relevante Abschnitte wiedergegeben):

server {
	listen 80;
	listen [::]:80;
	server_name nextcloud.example.com;
	root /var/www/nextcloud.example.com;
	index index.html;
[...]
	return 301 https://$server_name$request_uri;
	location ^~ /.well-known/acme-challenge/ {
	default_type "text/plain";
	root /var/www/nextcloud.example.com;
	}
	location = /.well-known/acme-challenge/ {
	return 404;
	}
}
server {
    # Listen on Port 443
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name nextcloud.example.com;
[...]
    root /var/www/nextcloud.example.com;
[...]
	location ^~ /.well-known/acme-challenge/ {
	default_type "text/plain";
	root /var/www/nextcloud.example.com;
	}
	location = /.well-known/acme-challenge/ {
	return 404;
	}

        location / {
	proxy_set_header Host $host;
	proxy_set_header X-Forwarded-Proto $scheme;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	add_header Front-End-Https on;
       	proxy_pass http://127.0.0.1:40231;
        }

	location /.well-known/carddav{
		return 301 $scheme://$host/remote.php/dav;
	}

	location /.well-known/caldav {
		return 301 $scheme://$host/remote.php/dav;
	}
}

Ob dies eine gute Konfiguration ist, mag ich nicht beurteilen. Sie funktioniert zumindest. Die Nextcloud ist erreichbar und über die Weboberfläche können weitere Einstellungen vorgenommen, Nutzerkonten erstellt und Apps installiert werden.

Fail2ban für Nextcloud

Nun steht die Nextcloud mit ihrer Login-Maske im Wind. Sie stellt damit ein schönes Ziel für Brute-Force-Angriffe dar. Um es den Angreifern nicht zu leicht zu machen, habe ich für den Nextcloud-Admin keinen einfach zu erratenen Namen wie Admin, Nextcloud-Admin oder meinen Namen verwendet. Um Angreifer weiter auszubremsen, konfiguriere ich fail2ban. Wie dies installiert wird, schlagt bitte in der Dokumentation eurer Distribution nach. Ich gehe hier nicht auf die Installation ein.

Damit fail2ban fehlgeschlagene Anmeldeversuche erkennt, habe ich die Datei /etc/fail2ban/filter.d/filter.d/jk_nextcloud.conf mit folgendem Inhalt erstellt:

[Definition]
_groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)
failregex = ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
            ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"

Dieser Filter wird in der Datei /etc/fail2ban/jail.d/jk_nextcloud.local referenziert:

[jk_nextcloud]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = jk_nextcloud
maxretry = 3
bantime = 86400
findtime = 43200
logpath = /home/tronde/.local/share/containers/storage/volumes/nc_data/_data/nextcloud.log

Wenn innerhalb der findtime von 43200 Sekunden von einer IP-Adresse mehr als drei fehlgeschlagene Anmeldeversuche (maxretry) registriert werden, wird die entsprechede IP-Adresse für 86400 Sekunden (bantime) gesperrt. Eine Statusabfrage ist wie folgt möglich:

$ sudo fail2ban-client status jk_nextcloud
[sudo] password for tronde: 
Status for the jail: jk_nextcloud
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	5
|  `- File list:	/home/tronde/.local/share/containers/storage/volumes/nc_data/_data/nextcloud.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	1
   `- Banned IP list:

Es ist zu erkennen, dass aktuell keine IP-Adresse gesperrt ist. In der Vergangenheit wurde jedoch bereits eine IP gebannt.

Für mich stellt fail2ban einen Baustein zur Sicherung der Nextcloud bereit. Zusätzlich werde ich eine Zwei-Faktor-Authentisierung konfigurieren, um die Sicherheit weiter zu steigern.

Zusammenfassung

An diesem Punkt meines Wochenend-Projekts kann ich eine Nextcloud-Instanz mit meiner Ansible-Rolle deployen und hinter einem Reverse-Proxy nutzen. Bisher schützt fail2ban vor Brute-Force-Angriffen. Zukünftig wird jedoch eine Zwei-Faktor-Authentisierung diesen Schutz verstärken.

Eine lauffähige Konfiguration zu erstellen, hat dabei länger gedauert, als ich mir vorgestellt habe. Dabei hat mir missfallen, dass dies nicht mit der Dokumentation allein gelang, sondern das Forum [5] und die Internetsuchmaschine meines geringsten Misstrauens zurate gezogen werden mussten. Damit ist dieses Projekt jedoch bei weitem kein Einzelfall.

307 offene Issues (Stand 05.02.2022) zeugen davon, dass hier noch längst nicht alles rund läuft. Von der Idee, dass es sich hierbei um ein Fire-and-Forget-Projekt handeln könnte, verabschiede ich mich lieber. So bin ich auch gleich in das unter [7] beschriebene Problem getreten. Erfreulicher Weise hat die dort beschriebene Lösung funktioniert, so dass meine Cloud mir jetzt E-Mails senden kann. Ich werde mir Gedanken machen, wie die entsprechenden Abschnitte in der Dokumentation verbessert werden können und dem Projekt einen Pull-Request senden. Mal schauen wie man darauf reagiert.

Damit sind die in Teil 1 formulierten Ziele 1-3 erreicht. In Teil 4 beschäftige ich mich mit einigen weiteren Steinen, über die ich gestolpert bin, bevor ich mich dann in Teil 5 dem Thema Backup & Recovery widme.

Quellen und weiterführende Links

  1. Nextcloud im Container — Teil 1: Der Plan
  2. Nextcloud im Container — Teil 2: Die Ansible-Rolle
  3. Using the apache image behind a reverse proxy and auto configure server host and protocol
  4. https://github.com/nextcloud/docker
  5. https://help.nextcloud.com/
  6. https://docs.nextcloud.com/server/latest/admin_manual/
  7. Email settings gets wrong senEmail settings gets wrong sender domain, fails due to not RFC compliant

Nextcloud im Container – Teil 2: Die Ansible-Rolle

21. Februar 2022 um 06:00

In Teil 1 dieser Artikelserie habe ich mein Ansinnen ausführlich beschrieben. Dieser Teil widmet sich der Entwicklung einer Ansible-Rolle zum Deployment des Nextcloud-Apache-Container-Images.

In den folgenden Abschnitten beschreibe ich die Einrichtung eines Python Virtual Environments, die Installation von Ansible in dem zuvor erstellten Environment und die Installation der Ansible-Collection containers.podman, bevor ich mich abschließend der eigentlichen Ansible-Rolle widme.

Python Virtual Environments für Ansible

Zur Einrichtung habe ich mich an den englischsprachigen Artikel „How to set up and use Python virtual environments for Ansible“ von Gineesh Madapparambath gehalten. Die notwendigen Schritte werden hier kurz und bündig dokumentiert.

[t14s ~]$ python3 --version
Python 3.9.7

[t14s ~]$ mkdir python-venv
[t14s ~]$ cd !$
cd python-venv

[t14s python-venv]$ python3 -m venv ansible-core2.x
[t14s python-venv]$ source ansible-core2.x/bin/activate
(ansible-core2.x) [jkastning@t14s python-venv]$ python3 -m pip install --upgrade pip
Requirement already satisfied: pip in ./ansible-core2.x/lib/python3.9/site-packages (21.0.1)
Collecting pip
  Downloading pip-21.3.1-py3-none-any.whl (1.7 MB)
     |████████████████████████████████| 1.7 MB 2.3 MB/s 
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 21.0.1
    Uninstalling pip-21.0.1:
      Successfully uninstalled pip-21.0.1
Successfully installed pip-21.3.1

(ansible-core2.x) [t14s python-venv]$ python3 -m pip install ansible-core
Collecting ansible-core
[...]

(ansible-core2.x) [t14s python-venv]$ ansible --version
ansible [core 2.11.6] 
  config file = None
  configured module search path = ['/home/tronde/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/tronde/python-venv/ansible-core2.x/lib64/python3.9/site-packages/ansible
  ansible collection location = /home/tronde/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/tronde/python-venv/ansible-core2.x/bin/ansible
  python version = 3.9.7 (default, Aug 30 2021, 00:00:00) [GCC 11.2.1 20210728 (Red Hat 11.2.1-1)]
  jinja version = 3.0.2
  libyaml = True

Damit ist die Installation von ansible-core abgeschlossen. Im folgenden Code-Block wird geprüft, ob Ansible sich grundsätzlich mit dem Zielsystem verbinden und dort einen Python-Interpreter identifizieren kann.

(ansible-core2.x) [t14s python-venv]$ ansible -i hosts --private-key ~/.ssh/ansible_id_rsa -m ping example.com
example.com | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Installation der Ansible-Collection containers.podman

Um Podman auf dem Zielsystem konfigurieren zu können, wird die genannte Ansible-Collection benötigt, welche mit folgendem Befehl installiert werden kann. Der Code-Block zeigt zusätzlich die Ausgabe während der Installation.

(ansible-core2.x) [t14s ansible-core2.x]$ ansible-galaxy collection install containers.podman
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/containers-podman-1.8.2.tar.gz to /home/tronde/.ansible/tmp/ansible-local-8729oh0om8w3/tmp7tv2yrae/containers-podman-1.8.2-9rw3fd1y
Installing 'containers.podman:1.8.2' to '/home/tronde/.ansible/collections/ansible_collections/containers/podman'
containers.podman:1.8.2 was installed successfully

Ansible-Rolle: Deployment von Nextcloud und MariaDB als Pod

Nextcloud benötigt für den Betrieb eine Datenbank. Hierfür könnte man eine integrierte SQLite nutzen. Dies wird jedoch nur für kleine Umgebungen empfohlen. Während der Entstehung dieses Artikels wird MariaDB als Datenbank-Backend vom Nextlcoud-Projekt empfohlen. Daher habe ich mich entschieden, das Nextcloud-Image zusammen mit einem MariaDB-Container zu deployen. Dazu greife ich auf die beiden folgenden Container-Repositorien zurück:

Das Grundgerüst bzw. die Verzeichnisstruktur für die Ansible-Rolle wurde erstellt mit:

$ ansible-galaxy role init --offline ansible_role_deploy_nextcloud_with_mariadb_pod

Die aktuelle Version der Ansible-Rolle ist auf GitHub zu finden. Ich werde ihre Bestandteile hier im Einzelnen vorstellen.

Die Variablen in defaults/main.yml

In der Datei defaults/main.yml habe ich Standardwerte für Variablen definiert, die geeignet sind, eine funktionsfähige Nextcloud-Instanz zu initialisieren. Die Bezeichner der Variablen sind dabei der Dokumentation der verwendeten Container-Repositorien entnommen.

In Zeile 4-7 und 10 werden die Namen für Podman-Volumes definiert, welche die persistent zu speichernden Daten aufnehmen werden.

     1	---
     2	# defaults file for ansible_role_deploy_nextcloud_with_mariadb_pod
     3	# Podman volumes for Nextcloud
     4	NC_HTML: nc_html
     5	NC_APPS: nc_apps
     6	NC_CONFIG: nc_config
     7	NC_DATA: nc_data
     8	
     9	# Podman volume for MariaDB
    10	MYSQL_DATA: mysql_data

Die Zeilen 13-17 definieren Variablen für die MariaDB-Instanz, wie z.B. Namen der Datenbank, Benutzername und Passwörter für diese Datenbank und den DB-Host. Diese werden neben dem MariaDB-Container auch von dem Nextcloud-Container benötigt, um eine Verbindung zur Datenbank herstellen zu können.

    12	# MySQL/MariaDB vars
    13	MYSQL_DATABASE: nc_db
    14	MYSQL_USER: nextcloud
    15	MYSQL_PASSWORD: ToPSeCrEt2021!
    16	MYSQL_ROOT_PASSWORD: ToPSeCrEt2021!
    17	MYSQL_HOST: 127.0.0.1
    18	
    19	# Vars for MariaDB container
    20	MARIADB_CONMON_PIDFILE: /tmp/mariadb_conmon.pid
    21	MARIADB_IMAGE: docker.io/library/mariadb:10.5.7
    22	MARIADB_NAME: nc_mariadb

Zeile 20-22 definiert Variablen, die für den MariaDB-Container benötigt werden. Hier wird z.B. die Version des Container-Images (MARIADB_IMAGE) und ein Name für die Container-Instanz (MARIADB_NAME) festgelegt.

Die folgenden Zeilen widmen sich den Variablen für den Nextcloud-Container. Dort werden in den Zeilen 25 u. 26 Benutzername und Passwort für den Nextcloud-Admin definiert, gefolgt von einigen Variablen, welche bei Nutzung eines Reverse-Proxy benötigt werden und SMTP-Variablen, welche der Nextcloud den Mailversand ermöglichen.

    24	# Nextcloud vars
    25	NEXTCLOUD_ADMIN_USER: nc_admin
    26	NEXTCLOUD_ADMIN_PASSWORD: VSnfD2021!
    27	NEXTCLOUD_OVERWRITEPROTOCOL: ""
    28	NEXTCLOUD_OVERWRITECLIURL: ""
    29	NEXTCLOUD_TRUSTED_DOMAINS: ""
    30	
    31	# SMTP vars
    32	SMTP_HOST: smtp.example.com
    33	SMTP_SECURE: tls # ssl to use SSL, or tls zu use STARTTLS
    34	SMTP_PORT: 587 # (25, 465 for SSL, 587 for STARTTLS)
    35	SMTP_AUTHTYPE: LOGIN
    36	SMTP_NAME: bob@example.com
    37	SMTP_PASSWORD: MailSecret1!
    38	MAIL_FROM_ADDRESS: no-reply@example.com
    39	MAIL_DOMAIN: "@example.com"

Bei den SMTP-Variablen handelt es sich um Beispiel-Werte. Diese müssen an die konkrete Umgebung angepasst werden.

Es folgen nun noch ein paar Variablen, welche dem Pod und dem Nextcloud-Container einen Namen geben, sowie die Version des zu verwendenden Nextcloud-Container-Images festlegen.

    41	# Vars for podman-pod(1)
    42	POD_NAME: nc_pod
    43	POD_PORT: 127.0.0.1:40231:80
    44	POD_INFRA_CONMON_PIDFILE: /tmp/nc_pod_infra.pid
    45	
    46	# Vars for Nextcloud container
    47	NC_CONMON_PIDFILE: /tmp/nc_conmon.pid
    48	NC_IMAGE: docker.io/library/nextcloud:23-apache
    49	NC_NAME: nextcloud

Durch POD_PORT: 127.0.0.1:40231:80 wird definiert, dass der Port 40231 an das Loopback-Interface gebunden und mit Port 80 des Pods verknüpft wird. Mit dieser Einstellung ist die Nextcloud-Instanz nur von dem Host aus erreichbar, auf dem sie ausgebracht wurde. Möchte man sie auch von anderen Hosts aus erreichbar machen, kann man entweder den Teil mit 127.0.0.1: weglassen oder einen Reverse-Proxy wie z.B. NGINX verwenden. Ich empfehle an dieser Stelle letzteres.

Hinweis: In defauts/main.yml stehen Passwörter im Klartext. Diese sind mit der Veröffentlichung der Ansible-Rolle allgemein bekannt und sollten gegen solche ersetzt werden, die geheimgehalten werden. Dies kann z.B. geschehen, in dem man die entsprechenden Variablen in vars/main.yml oder host_vars/hostname neu definiert. Es bietet sich an, diese zusätzlich mit Ansible-Vault zu verschlüsseln.

Die Tasks in tasks/main.yml

Im vorstehenden Abschnitt wurden die Variablen definiert, welche für die nun folgenden Tasks benötigt werden. Diese sind in tasks/main.yml definiert und werden im folgenden wieder abschnittsweise erläutert.

     1	---
     2	# tasks file for ansible_role_deploy_nextcloud_with_mariadb_pod
     3	- name: Main folder, needed for updating
     4	  containers.podman.podman_volume:
     5	    state: present
     6	    name: "{{ NC_HTML }}"
     7	    recreate: no
     8	    debug: no
     9	
    10	- name: Volume for installed/modified apps
    11	  containers.podman.podman_volume:
    12	    state: present
    13	    name: "{{ NC_APPS }}"
    14	    recreate: no
    15	    debug: no
    16	
    17	- name: Volume for local configuration
    18	  containers.podman.podman_volume:
    19	    state: present
    20	    name: "{{ NC_CONFIG }}"
    21	    recreate: no
    22	    debug: no
    23	
    24	- name: Volume for the actual data of Nextcloud
    25	  containers.podman.podman_volume:
    26	    state: present
    27	    name: "{{ NC_DATA }}"
    28	    recreate: no
    29	    debug: no
    30	
    31	- name: Volume for the MySQL data files
    32	  containers.podman.podman_volume:
    33	    state: present
    34	    name: "{{ MYSQL_DATA }}"
    35	    recreate: no
    36	    debug: no

Die ersten Zeilen enthalten Tasks, durch welche die Podman-Volumes zur persistenten Datenspeicherung auf dem Zielsystem erstellt werden. Diese Tasks sind, wie für Ansible üblich, deklarativ und idempotent. Existiert ein Volume bereits, liefert der entsprechende Task ein ‚OK‘ zurück, da keine Aktionen erforderlich sind.

Die folgenden Zeilen erstellen den Podman-Pod und fügen ihm einen Nextcloud- sowie einen MariaDB-Container hinzu. Die Dokumentation der verwendeten Module findet sich in Punkt 5 und 6 im Abschnitt Quellen und weiterführende Links.

    38	- name: Create the podman-pod(1)
    39	  containers.podman.podman_pod:
    40	    debug: no
    41	    infra: yes
    42	    infra_conmon_pidfile: "{{ POD_INFRA_CONMON_PIDFILE }}"
    43	    publish: "{{ POD_PORT }}"
    44	    name: "{{ POD_NAME }}"
    45	    state: started
    46	
    47	- name: Create MariaDB container
    48	  containers.podman.podman_container:
    49	    debug: yes
    50	    conmon_pidfile: "{{ MARIADB_CONMON_PIDFILE }}"
    51	    image: "{{ MARIADB_IMAGE }}"
    52	    image_strict: yes
    53	    pod: "{{ POD_NAME }}"
    54	    recreate: yes
    55	    state: started
    56	    name: "{{ MARIADB_NAME }}"
    57	    env:
    58	      MYSQL_USER: "{{ MYSQL_USER }}"
    59	      MYSQL_PASSWORD: "{{ MYSQL_PASSWORD }}"
    60	      MYSQL_ROOT_PASSWORD: "{{ MYSQL_ROOT_PASSWORD }}"
    61	      MYSQL_DATABASE: "{{ MYSQL_DATABASE }}"
    62	    volume: "{{ MYSQL_DATA }}:/var/lib/mysql:Z"
    63	
    64	- name: Wait for DB to initilize
    65	  wait_for:
    66	    timeout: 20
    67	
    68	- name: Create Nextcloud container
    69	  containers.podman.podman_container:
    70	    debug: no 
    71	    conmon_pidfile: "{{ NC_CONMON_PIDFILE }}"
    72	    image: "{{ NC_IMAGE }}"
    73	    image_strict: yes
    74	    pod: "{{ POD_NAME }}"
    75	    recreate: yes
    76	    state: started
    77	    name: "{{ NC_NAME }}"
    78	    env:
    79	      MYSQL_DATABASE: "{{ MYSQL_DATABASE }}"
    80	      MYSQL_USER: "{{ MYSQL_USER }}"
    81	      MYSQL_PASSWORD: "{{ MYSQL_PASSWORD }}"
    82	      MYSQL_HOST: "{{ MYSQL_HOST }}"
    83	      NEXTCLOUD_ADMIN_USER: "{{ NEXTCLOUD_ADMIN_USER }}"
    84	      NEXTCLOUD_ADMIN_PASSWORD: "{{ NEXTCLOUD_ADMIN_PASSWORD }}"
    85	      NEXTCLOUD_TRUSTED_DOMAINS: "{{ NEXTCLOUD_TRUSTED_DOMAINS }}"
    86	      SMTP_HOST: "{{ SMTP_HOST }}"
    87	      SMTP_SECURE: "{{ SMTP_SECURE }}"
    88	      SMTP_PORT: "{{ SMTP_PORT }}"
    89	      SMTP_AUTHTYPE: "{{ SMTP_AUTHTYPE }}"
    90	      SMTP_NAME: "{{ SMTP_NAME }}"
    91	      SMTP_PASSWORD: "{{ SMTP_PASSWORD }}"
    92	      MAIL_FROM_ADDRESS: "{{ MAIL_FROM_ADDRESS }}"
    93	      MAIL_DOMAIN: "{{ MAIL_DOMAIN }}"
    94	      OVERWRITEPROTOCOL: "{{ NEXTCLOUD_OVERWRITEPROTOCOL }}"
    95	      OVERWRITECLIURL: "{{ NEXTCLOUD_OVERWRITECLIURL }}"
    96	    volume:
    97	      - "{{ NC_HTML }}:/var/www/html:Z"
    98	      - "{{ NC_APPS }}:/var/www/html/custom_apps:Z"
    99	      - "{{ NC_CONFIG }}:/var/www/html/config:Z"
   100	      - "{{ NC_DATA }}:/var/www/html/data:Z"

In Zeile 64-66 habe ich einen Task definiert, der einfach nur 20 Sekunden wartet. Dies wurde erforderlich, da ich Laufzeitprobleme feststellen konnte, wenn der Nextcloud-Container startet, bevor die Datenbank im MariaDB-Container initialisiert war. Dieses Konstrukt ist nicht schön und ich bin für Verbesserungsvorschläge offen.

Zwischenfazit

Die Erstellung der Ansible-Rolle hat länger gedauert, als angenommen. Dies liegt nur zum Teil in meiner spärlichen Freizeit begründet. Einen größeren Einfluss darauf hatte die Dokumentation zum Nextcloud-Repository. Diese geht davon aus, dass man ein Dockerfile bzw. Docker-Compose verwendet. So war noch etwas Internet-Recherche erforderlich, um den Pod letztendlich ans Laufen zu bringen.

Dieser Artikel beschäftigte sich mit den Tag-1-Aufgaben, an deren Ende eine Nextcloud-Instanz ausgebracht wurde, welche an einen Reverse-Proxy angebunden werden kann.

Im nächsten Artikel gehe ich auf die Konfiguration des NGINX-Reverse-Proxy ein. Hierbei habe ich einige Überraschungen erlebt, welche mich an der Reife des Projekts [2] zweifeln lassen.

Quellen und weiterführende Links

  1. Nextcloud System Requirements — https://docs.nextcloud.com/server/latest/admin_manual/installation/system_requirements.html
  2. Nextcloud (Official Image) — https://hub.docker.com/_/nextcloud
  3. MariaDB (Official Image) — https://hub.docker.com/_/mariadb
  4. GitHub Tronde/ansible_role_deploy_nextcloud_with_mariadb_pod
  5. podman_pod – Manage Podman pods
  6. podman_container – Manage podman containers

Nextcloud im Container – Teil 1: Der Plan

14. Februar 2022 um 06:00

Dies ist der Beginn meines zweiten Container-Projekts. Nach Kanboard im Container möchte ich diesmal eine Nextcloud-Instanz als Container, zusammen mit einem Datenbank-Container, in einem Podman-Pod betreiben.

Da ein einzelner Artikel vermutlich zu lang wird, teile ich das Projekt in mehrere Artikel auf. Wie viele es genau werden, kann ich jetzt noch nicht sagen. Am Ende der Reihe werde ich hier eine Übersicht einführen und die einzelnen Teilen entsprechend miteinander verbinden.

In diesem ersten Teil geht es um meine Motivation, das eigentliche Ziel und den groben Plan.

Was Leser dieser Reihe erwartet

Ihr könnt mich durch diese Reihe begleiten und euch von meinen Erlebnissen und Erkenntnissen unterhalten lassen. Dabei dürft ihr nicht annehmen, dass es sich bei dem von mir beschriebenen Vorgehen um eine gute Praxis handelt. Hier gilt eher: Der Weg ist das Ziel.

Ihr seid herzlich eingeladen, die Artikel zu kommentieren und über das Vorgehen und Alternativen dazu zu diskutieren. Gern in der Kommentarsektion unter den jeweiligen Beiträgen oder als Artikel in euren eigenen Blogs.

Ich plane die Artikel im Wochenrhythmus, wenigstens monatlich, zu veröffentlichen. Bitte verzeiht, wenn es etwas unregelmäßig wird. Dies ist ein Hobby, dem nur begrenzt Zeit zur Verfügung steht.

Motivation

Bei Linux-Containern handelt es sich um eine Technologie, die gekommen ist, um zu bleiben. Sie hat bereits in vielen Branchen Fuß gefasst und immer mehr Projekte bieten ihre Anwendungen zusätzlich oder ausschließlich in Form von Containern an.

Als Sysadmin mittleren Alters werden mich Linux-Container sicher noch viele Jahre begleiten. Um praktische Erfahrungen mit dem Betrieb zu sammeln, möchte ich einige private Projekte in Containern betreiben.

Beruflich arbeite ich überwiegend mit RHEL. Red Hat engagiert sich stark in den Projekten Ansible und Podman, welche ich auch unter anderen Distributionen, wie z.B. Debian, einsetze. Ich möchte das Projekt als Chance nutzen, mein Wissen auch in diesen Werkzeugen zu festigen und auszubauen.

Ich spiele schon seit einiger Zeit mit dem Gedanken, wieder eine eigene Nextcloud-Instanz zu betreiben. Da auf dem zur Verfügung stehenden Server bereits eine Nextcloud-Instanz läuft und ich meine Anwendung von der bestehenden Instanz getrennt und möglichst losgelöst vom Betriebssystem betreiben möchte, habe ich mich entschieden, Nextcloud im Container zu betreiben.

Ziele

Ziel dieses Projekts sind das Deployment und der Betrieb einer Nextcloud-Instanz als Podman-Pod. Im Einzelnen sollen folgende Ziele erreicht werden:

  1. Entwicklung eines wiederverwendbaren Verfahrens zum Deployment einer Nextcloud im Container
  2. Persistente Speicherung von Konfigurations- und inhaltlichen Daten im Dateisystem des Hosts
  3. Konfiguration eines Reverse-Proxies (NGINX) für den Zugriff auf die Nextcloud-Instanz
  4. Konfiguration von Backup und Restore für Konfiguration und Inhalte der Nextcloud-Instanz
  5. Konfiguration und Test automatischer durch Ansible gesteuerter Updates

Umgebung

Für die Umsetzung des Projekts steht mir ein Virtual Private Server (VPS) mit genügend Ressourcen zur Verfügung. Dieser wird in einem Rechenzentrum in Deutschland betrieben. Auf diesem sind Debian Bullseye, NGINX, ein OpenSSH-Server, Podman 3.0.1 (rootless) und Python 3.9.2 installiert. Damit erfüllt dieses System die Voraussetzungen, um mit Ansible konfiguriert zu werden und Container ausführen zu können.

Ansible selbst läuft in meiner privaten Arbeitsumgebung auf meinem Debian-PC und einem Fedora-35-Notebook.

Methodik und verwendete Werkzeuge

Zu Beginn habe ich mich etwas in der Nextcloud-Dokumentation und den verfügbaren Nextcloud-Images belesen. Besagte Dokumentation sowie die der verwendeten Werkzeuge sind im folgenden Abschnitt verlinkt.

Um die oben formulierten Ziele zu erreichen, werde ich in einem Python Virtual Environment eine Ansible-Version installieren, mit der ich die Collection containers.podman nutzen kann. Hiermit werde ich eine Ansible-Rolle entwickeln, die ich wiederverwenden kann, um Nextcloud-Instanzen in einer rootless-Podman-Umgebung zu deployen. Die Ansible-Rolle wird anschließend auf meinem GitHub-Account veröffentlicht.

Die Konfiguration von NGINX und acme.sh für die TLS-Zertifikate erfolgt manuell.

Quellen und weiterführende Links

In diesem Abschnitt liste ich Links zu Artikeln und Dokumentationen auf, welche ich im Vorfeld gelesen habe und deren Kenntnis ich für die Umsetzung als nützlich erachte. Zur besseren Übersicht gliedere ich diese in die Unterabschnitte Hintergrundwissen, Dokumentation und Eigene Artikel.

Die weiteren Artikel dieser Reihe

Hintergrundwissen

Dokumentation

Eigene Artikel

Meine benutzerdefinierten Thunderbird-Einstellungen

07. Februar 2022 um 06:00

In Dirks Artikel „Wechsel auf Thunderbird“ wurden einige interessante und gute Einstellungen für den Donnervogel genannt, die ich in mein Setup übernommen habe. Um diese nicht jedes Mal in Dirks Logbuch nachschlagen zu müssen, dokumentiere ich sie hier kurz.

Alle Einstellungen werden unter Edit / Preferences / General / Config Editor vorgenommen.

Damit der Donnervogel standardmäßig alle Ordner auf eingehende E-Mails prüft, wird mail.server.default.check_all_folders_for_new auf true geändert. Um global die Thread-Ansicht zu aktivieren, wird mailnews.default_view_flags auf 1 gesetzt. Wer gern per Tastatur mit der Taste ’n‘ zur nächsten ungelesenen Nachricht geht, freut sich evtl. wenn dies auch ohne Nachfrage Ordner-übergreifend funktioniert. Dafür wird mailnews.nav_crosses_folders auf 0 gesetzt.

Danke Dirk, vinzv, Bad Penguin und Jens für den Artikel und eure Tipps.

Wie ich zum Journaling gekommen bin

31. Januar 2022 um 06:00

TL;DR: Dirk ist dafür verantwortlich.

Denn in seinem Linkdump bin ich über den Artikel „Journaling: Schreib dich erfolgreich und glücklich“ von Eric Kubitz gestolpert. Der Text hat grundsätzlich mein Interesse an der Methode geweckt. Jedoch zweifelte ich, ob mir das tägliche Ausfüllen vorgedruckter Bücher nicht schnell langweilig wird. Zum Glück hatte Dirk einen Tipp für mich parat.

Dirk hat mir diesen Kurs auf Udemy empfohlen, welcher Ende 2021 noch für knapp 10 Euro zu kaufen war. Für den Preis kann man in meinen Augen nicht viel falsch machen und so buchte ich den Kurs.

Der Kurs führt in englischer Sprache in die Methode „Journaling“ ein, stellt diverse Prompts vor und gibt Tipps, wie man beginnt und durchhält. Der Trainer spricht klar und deutlich (habe ich auch schon schlechter gehört). So konnte ich dem Kurs gut folgen. Für den günstigen Preis ist es in Ordnung, dass auch ein paar Minuten Marketing dabei sind. Mir hat der Kurs gut gefallen.

Für mich ist das Journal wie ein Logbuch. Ich führe es morgens zwischen Frühstück und dem Öffnen meines Posteingangs. Ich halte darin Dinge fest, an denen ich gerade arbeite, was ich kürzlich gelernt habe, wofür ich dankbar bin, worauf ich mich freue und was die wichtigsten Aufgaben des Tages sind, die ich erledigen muss.

Zu Weihnachten bekam ich ein Logbuch geschenkt, das geradezu Lust darauf macht, hineinzuschreiben.

my-journal
Dies ist mein erstes Journal. Und ja, ich mag schöne Einbände.

Ungefähr seit Weihnachten schreibe ich regelmäßig. Es wird langsam zu einem Ritual, auf das ich mich freue. Es hilft mir, den vorangegangenen Tag zu reflektieren und mich auf die dringendsten und wichtigsten Aufgaben des Tages zu fokussieren. Zudem hilft es, die positiven Dinge in den Vordergrund zu holen. Denn wenn man ein wenig darüber nachdenkt, findet man in fast jedem Tag auch Dinge, die gut waren. Leider spielt unser Gehirn uns gern einen Streich und fokussiert sich auf die negativen Dinge, die schiefgelaufen sind bzw. genervt haben. Journaling hilft, dies zu ändern und auf positive Gedanken zu kommen.

Was bleibt mir mehr zu sagen als: „Danke, Dirk.“

Es ist ein Fehler, das Hobby zum Beruf zu machen

24. Januar 2022 um 06:00

Träumt nicht jeder davon, sein Hobby zum Beruf zu machen, um damit das Geld für den Lebensunterhalt zu verdienen? Auf den ersten Blick erscheint dies erstrebenswert. Arbeitet man in einer Vollzeitstelle doch ca. 40 Stunden in der Woche (manchmal sogar etwas mehr). So nimmt die Arbeit einen großen Teil des eigenen Lebens ein. Da ist es doch schön, wenn man diese Zeit mit einer Tätigkeit ausfüllen kann, die einem Freude bereitet. Zum Beispiel mit dem liebsten Hobby eben.

Leider macht Arbeit nicht immer Spaß. Sind 1-2 miese Tage schnell vergessen, können längere, spaßbefreite Phasen ganz schön schlauchen, die Stimmung senken und die Freude an der Ausübung des Hobbys vermiesen.

Übt man sein liebstes Hobby als Beruf aus, wird die Grenze zwischen Arbeitszeit und Freizeit recht schnell recht dünn. Es droht die Gefahr, die Arbeit mit in die Freizeit zu nehmen, nicht abschalten zu können und den Kopf nicht mehr wirklich freizubekommen, da man seine Gedanken nicht auf etwas anderes lenken kann. Stattdessen arbeitet man, unter Umständen unbewusst, daheim an der Lösung für ein Problem auf der Arbeit weiter. Dies ist dumm. Denn:

  1. In der Regel wird man für diese Arbeit nicht bezahlt und
  2. macht es unter Umständen krank, wenn man nicht abschalten und zur Ruhe kommen kann.

Doch was tun, wenn die Erkenntnis zu spät kommt und man sein Hobby bereits seit Jahren als Beruf ausübt? Nun spontan fallen mir dazu drei mögliche Lösungen ein:

  1. Den Arbeitgeber wechseln
  2. Den Beruf wechseln
  3. Das Hobby wechseln

Punkt 1 kann man je nach Lage auf dem Arbeitsmarkt in Erwägung ziehen. Doch sollte man die Flinte nicht zu schnell ins Korn werfen. Denn das Gras sieht auf der anderen Weide meist grüner aus. Erst wenn man darauf steht, erkennt man nach einer gewissen Zeit, dass auch hier Unkraut wächst.

Ob man lieber Punkt 2 oder Punkt 3 verfolgt, mag davon abhängen, wie wichtig das eigene Hobby ist. Ich denke, man ist gut beraten, auf ein anderes Hobby ausweichen zu können. So kann man Kraft schöpfen, um berufliche Herausforderungen besser meistern zu können. Läuft es im Job wieder gut und man hat Spaß an der Arbeit, kann man sich ja auch wieder in der Freizeit mit ähnlichen Themen beschäftigen, ohne dabei gleich wieder schlechte Laune zu bekommen.

Mein Ausweich-Hobby ist das Lesen. Mit einem guten Buch kann ich in eine andere Welt eintauchen und abschalten.

Wie ist es mit euch? Habt ihr evtl. ähnliche Erfahrungen gemacht? Falls ja, wie geht ihr damit um? Wie trennt ihr berufliches und privates, wenngleich Hobby und Beruf verschmelzen? Ich bin auf eure Erfahrungen und Tipps gespannt.

Labor-Umgebung mit Ansible in KVM erstellen

17. Januar 2022 um 06:00

Inspiriert durch die Artikel von Ricardo Geradi [1] und Alex Callejas [3] schreibe ich diesen, um zu erklären, wie mithilfe von Ansible eine Labor-Umgebung bestehend aus einer oder mehreren virtuellen Maschinen (VMs) auf einem KVM-Hypervisor provisioniert werden kann.

Dabei handelt es sich weniger um ein Tutorial, sondern mehr um eine exemplarische Beschreibung einer möglichen Vorgehensweise, die euch als Vorlage für die eigene Umgebung dienen kann.

Ich gehe nicht darauf ein, wie KVM oder Ansible installiert werden. Hierzu verweise ich auf die Dokumentation der jeweiligen Projekte und der verwendeten Linux-Distributionen.

Motivation

Um Anwendungen zu testen, benötigt man in der Regel ein Betriebssystem, auf welchem diese ausgeführt werden können. Ein Betriebssystem läuft dieser Tage meist innerhalb einer virtuellen Maschine (VM). Um bei Tests stets gleiche Rahmenbedingungen zu haben, wird empfohlen, für jeden Test eine neue VM mit einer definierten Konfiguration zu provisionieren, die geplanten Tests durchzuführen, die Ergebnisse zu sichern und die VM zu dekommissionieren.

Möchte man Infrastrukturdienste testen, werden häufig gleich mehrere VMs benötigt. Diese werden auch als Labor-Umgebung bezeichnet.

Um nicht unnötig Zeit mit der Provisionierung der VMs zu verlieren — immerhin möchte man ja seine Anwendungen bzw. Dienste testen — bietet es sich an, diesen Prozess zu automatisieren.

Doch warum mit Ansible und nicht mit [hier Lieblings-Werkzeug eurer Wahl einsetzen]?

Viele Wege führen nach Rom. Und es gibt vermutlich ähnlich viele Werkzeuge, um eine Labor-Umgebung in KVM zu provisionieren. Ich habe mich in diesem Fall für Ansible entschieden, da:

  • Ich fast täglich damit arbeite.
  • Mit ansible-galaxy role init erstellte Rollen meiner bescheidenen Meinung nach (mbMn) eine schöne Struktur zur Organisation des Codes vorgeben.
  • Mit ansible-vault ein Werkzeug dabei ist, um Dateien mit sensiblen Informationen zu verschlüsseln und diese im weiteren Verlauf einfach zu nutzen.
  • Ich meine YAML-Dateien nächstes Jahr leichter lesen und verstehen kann als meine Shell-Skripte.
  • Ich in einem zukünftigen Artikel zeigen möchte, wie man mit Ansible eine Labor-Umgebung in einem VMware vSphere Cluster provisioniert.

Umgebung

KVM-Hypervisor: Debian 11 Bullseye

Die .qcow2-Image-Dateien für die VMs werden auf dem KVM-Hypervisor im Verzeichnis /var/lib/libvirt/images/ vorgehalten.

Getestete Ansible Versionen:

  • ansible 2.10.8 ( auf Debian 11 Bullseye)
  • ansible [core 2.12.1] (auf Fedora 35)

Die Verzeichnisstruktur für meine Ansible-Umgebung entspricht der aus dem Artikel Linux-Benutzerkonten mit Ansible verwalten, wie sie im dortigen Abschnitt Vorbereitung beschrieben ist.

Die im Laufe dieses Artikels provisionierte Labor-Umgebung wird aus einer RHEL-7 und einer RHEL-8-VM bestehen. Selbstverständlich ist es möglich, durch einfache Anpassungen weitere VMs sowie andere Linux-Distributionen zu provisionieren.

Vorarbeit

Ricardo Geradi [1] und Alex Callejas [3] beziehen in ihren Artikeln die qcow2-Images, welche sie als Vorlage (engl. Template) für weitere VMs verwenden, aus diversen Internet-Quellen. Ich bin kein Freund davon, mir Images aus dem Netz zu laden und zu nutzen, für die es keine ordentliche Dokumentation gibt, mit welchen Paketen und Einstellungen diese erzeugt wurden.

Wer kauft schon gern die Katze im Sack? Daher erstelle ich mir meine Vorlagen selbst. Dazu führe ich für jede Distribution, für die ich eine Vorlage erstellen möchte, eine manuelle Installation durch. Um die Vorlagen unter all den anderen VMs leicht identifizieren zu können, gebe ich ihnen Namen wie z.B.:

  • rhel7-template
  • rhel8-template
  • debian11-template

Dabei hinterlege ich beim User root bereits den SSH-Public-Key, den ich später mit Ansible verwenden möchte, um diese Systeme weiter zu konfigurieren. Dies tue ich zwar bisher. Es ist für die Verwendung der hier beschriebenen Rolle nicht erforderlich.

Möchte ich eine Vorlage aktualisieren, fahre ich die dazugehörige VM hoch, führe ein Paket-Update durch, fahre die VM wieder herunter und bin fertig. Dies mache ich in der Regel alle paar Monate, wenn mir das Paket-Update bei neu provisionierten VMs zu lange dauert und spätestens nach Erscheinen eines neuen Minor-Release.

Die Ansible-Rolle

Eine Ansible-Rolle wird mit dem Befehl ansible-galaxy role init role_name initialisiert. In meinem Fall sieht dies wie folgt aus:

$ ansible-galaxy role init kvm_provision_lab
- Role kvm_provision_lab was created successfully
$ tree kvm_provision_lab
kvm_provision_lab
├── defaults
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
└── vars
    └── main.yml

In obiger Ausgabe fehlen die Verzeichnisse Files und Handlers. Diese hatte ich bereits gelöscht, da sie nicht benötigt werden. Die erstellte Verzeichnisstruktur kann, je nach verwendeter Version von ansible-galaxy, leicht unterschiedlich aussehen. Benötigt werden in diesem Beispiel nur die oben dargestellten Verzeichnisse und Dateien. Streng genommen können das Verzeichnis meta und die Datei README.md ebenfalls entfernt werden, wenn man nicht vorhat, die Rolle zu veröffentlichen. Ich behalte beide bei und nutze die Dateien zur Dokumentation der Rolle.

Variablen

Es ist gute Praxis alle Variablen, die von einer Ansible-Rolle verarbeitet werden, in der Datei defaults/main.yml zu dokumentieren und mit Standardwerten zu versehen. Genannte Datei hat hier folgenden Inhalt:

$ cat -n defaults/main.yml 
     1	---
     2	libvirt_pool_dir: "/var/lib/libvirt/images"
     3	vm_root_pass: "123456"
     4	ssh_key: "/path/to/ssh-pub-key"
     5	
     6	guests:
     7	  test:
     8	    vm_ram_mb: 512
     9	    vm_vcpus: 1
    10	    vm_net: default
    11	    os_type: rhel7
    12	    file_type: qcow2
    13	    base_image_name: rhel7-template
    14	    vm_template: "rhel7-template"
    15	    second_hdd: false
    16	    second_hdd_size: ""
    17	  test2:
    18	    vm_ram_mb: 512
    19	    vm_vcpus: 1
    20	    vm_net: default
    21	    os_type: rhel8
    22	    file_type: qcow2
    23	    base_image_name: rhel8-template
    24	    vm_template: "rhel8-template"
    25	    second_hdd: true
    26	    second_hdd_size: "100M"

In Zeile 2-4 werden Variablen definiert, die unabhängig von einzelnen VMs für die gesamte Rolle gelten. Dies sind der Speicherort für Image-Dateien, das Passwort für den Root-Benutzer der VMs, sowie der Pfad zu dem SSH-Public-Key, welcher beim Root-Benutzer hinterlegt werden soll.

In Zeile 6 beginnt ein sogenanntes Ansible-Dictionary (siehe [6]) namens guests. Es enthält als Keys die Namen der VMs (hier test und test2) und ordnet diesen diverse Variablen als Werte zu (z.B. vm_ram_mb). Die hierfür gewählten Strings müssen gültige Ansible-Variablen sein (siehe [7]).

Die einzelnen Variablen kurz erklärt:

  • vm_ram_mb gibt die Größe des Gast-Arbeitsspeichers in Megabyte (MB) an.
  • vm_vcpus spezifiziert die Anzahl CPUs der VM.
  • vm_net bezeichnet das KVM-Netzwerk, mit dem die VM verbunden wird.
  • os_type wird aktuell noch nicht verwendet.
  • file_type gibt den Typ der Image-Datei an.
  • base_image_name verweist auf den Namen der zu verwendenden Vorlage, die zuvor manuell installiert wurde.
  • vm_template referenziert eine Jinja2-Template-Datei, welche wir uns im nächsten Abschnitt anschauen werden.
  • second_hdd kann auf true oder false gesetzt werden und bestimmt, ob einer VM eine zweite Festplatte hinzugefügt werden soll.
  • second_hdd_size gibt die Größe der zweiten Festplatte in Megabyte (MB) an.

Führt man diese Rolle mit einem Playbook aus, ohne eigene Variablen zu definieren, werden also zwei VMs mit den Namen test und test2 sowie den obigen Parametern erstellt.

Um die Rolle möglichst flexibel einsetzen und wiederverwenden zu können, werden die gewünschten Labor-Umgebungen in separaten Dateien definiert. Für mein RHEL-Lab habe ich die benötigten Variablen in die Datei vars/rhel_lab.yml geschrieben, welche ich mit ansible-vault create vars/rhel_lab.yml erstellt habe. So bleiben mein gewähltes Passwort sowie Pfad zu und Name von meinem SSH-Public-Key vor neugierigen Blicken geschützt. Der Inhalt der Datei entspricht vom Aufbau her jedoch dem aus obigem Code-Block der defaults/main.yml. Wie die Datei rhel_lab.yml genutzt wird, wird in Abschnitt „Das Playbook“ erläutert.

Templates

In der KVM-Terminologie wird eine VM auch als Gast-Domain (engl. guest domain) bezeichnet. Die Definition der Gast-Domain kann in Form einer XML-Datei erfolgen. In diesem Abschnitt werde ich zeigen, wie man die Konfiguration einer bestehenden VM in eine XML-Datei schreibt, um diese anschließend als Template für neue VMs zu benutzen.

Im Vorfeld habe ich die VMs rhel7-template und rhel8-template manuell installiert. Diese werde ich nun nutzen, um daraus Jinja2-Templates abzuleiten, welche ich innerhalb der Rollen-Verzeichnisstruktur im Verzeichnis templates ablege. Der folgende Codeblock zeigt den Befehl exemplarisch für das rhel7-template:

$ sudo virsh dumpxml rhel7-template >templates/rhel7-template.xml.j2

Das rhel8-template.xml.j2 wird auf die gleiche Weise erzeugt. Der Inhalt wird im Folgenden auszugsweise dargestellt:

<domain type='kvm'>
  <name>rhel8-template</name>
  <uuid>cb010068-fe32-4725-81e8-ec24ce237dcb</uuid>
  <metadata>
    <libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
      <libosinfo:os id="http://redhat.com/rhel/8-unknown"/>
    </libosinfo:libosinfo>
  </metadata>
  <memory unit='KiB'>2097152</memory>
  <currentMemory unit='KiB'>2097152</currentMemory>
  <vcpu placement='static'>1</vcpu>
[...]
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/rhel8-template.qcow2'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='hdb' bus='ide'/>
      <readonly/>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
[...]
    <interface type='network'>
      <mac address='52:54:00:0c:8d:05'/>
      <source network='default'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
[...]
  </devices>
</domain>

Die Template-Dateien sind zu bearbeiten, um aktuell statisch konfigurierte Werte durch Variablen zu ersetzen. Die zu bearbeitenden Zeilen sehen anschließend wie folgt aus:

  • <name>{{ item.key }}</name>
  • <memory unit='MiB'>{{ item.value.vm_ram_mb }}</memory>
  • <vcpu placement='static'>{{ item.value.vm_vcpus }}</vcpu>
  • <source file='{{ libvirt_pool_dir }}/{{ item.key }}.qcow2'/>
  • <source network='{{ item.value.vm_net }}'/>

Darüber hinaus sind weitere Zeilen, welche für jede VM einmalig sind, aus den Template-Dateien zu löschen:

  • <uuid>...</uuid>
  • <mac address='...'/>

In der fertigen rhel8-template.xml.j2-Datei sieht es dann wie folgt aus:

<domain type='kvm'>
  <name>{{ item.key }}</name>
  <metadata>
    <libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
      <libosinfo:os id="http://redhat.com/rhel/8-unknown"/>
    </libosinfo:libosinfo>
  </metadata>
  <memory unit='MiB'>{{ item.value.vm_ram_mb }}</memory>
  <vcpu placement='static'>{{ item.value.vm_vcpus }}</vcpu>
[...]
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='{{ libvirt_pool_dir }}/{{ item.key }}.qcow2'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </disk>
[...]
    <interface type='network'>
      <source network='{{ item.value.vm_net }}'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
[...]
  </devices>
</domain>

Solltet ihr zu diesem Abschnitt noch Fragen haben, weil z.B. etwas unverständlich ist, stellt diese bitte in den Kommentaren oder meldet euch per E-Mail. Ich werde den Abschnitt dann je nach Bedarf ergänzen.

Tasks

Als Nächstes stelle ich die Tasks vor, welche von dieser Rolle ausgeführt werden. Dabei beginne ich mit dem Inhalt der Datei tasks/main.yml, deren Inhalt ich nach dem folgenden Codeblock erläutern werde.

$ cat -n tasks/main.yml 
     1	---
     2	# tasks file for kvm_provision_lab
     3	- name: Ensure requirements are in place
     4	  apt:
     5	    name:
     6	      - libguestfs-tools
     7	      - python3-libvirt
     8	    state: present
     9	  become: yes
    10	
    11	- name: Get VMs list
    12	  community.libvirt.virt:
    13	    command: list_vms
    14	  register: existing_vms
    15	  changed_when: no
    16	
    17	- name: Copy base image
    18	  copy:
    19	    dest: "{{ libvirt_pool_dir }}/{{ item.key }}.{{ item.value.file_type }}"
    20	    src: "{{ libvirt_pool_dir }}/{{ item.value.base_image_name }}.{{ item.value.file_type }}"
    21	    force: no
    22	    remote_src: yes
    23	    mode: 0660
    24	    group: libvirt-qemu
    25	  register: copy_results
    26	  with_dict: "{{ guests }}"
    27	
    28	- name: Create VMs if not exist
    29	  include_tasks: create_vms.yml
    30	  when: "item.key not in existing_vms.list_vms"
    31	  with_dict: "{{ guests }}"

Der Task in Zeile 3-9 stellt sicher, dass die notwendigen Voraussetzungen erfüllt sind, um sich mit libvirt verbinden zu können. Der Paketname libguestfs-tools existiert unter CentOS Stream, Debian und RHEL. Unter Fedora heißt das Paket guestfs-tools. Der Name muss an die entsprechende Distribution angepasst werden.

In Zeile 11-15 wird das Modul community.libvirt.virt verwendet, um die Liste der bereits existierenden VMs abzurufen und in der Variablen existing_vms zu speichern. Diese wird später genutzt, um nur dann eine VM zu provisionieren, wenn nicht bereits eine VM mit dem gleichen Namen existiert. Es ist quasi ein schmutziger Trick, um der Rolle ein wenig Idempotenz einzuhauchen. Da mit diesem Task nur Informationen abgefragt werden, jedoch keinerlei Änderungen vorgenommen werden, setzt man changed_when: no.

Das Copy-Modul in Zeile 17-26 kopiert die qcow2-Image-Dateien an den vorgesehenen Zielort und setzt Zugriffsrechte entsprechend. Zeile 19 sorgt dafür, dass die Zieldatei den Namen der neuen VM beinhaltet. Da das Copy-Modul bereits idempotent arbeitet, werden die Dateien nur kopiert, wenn das Ziel nicht bereits existiert. Das Ergebnis des Kopiervorgangs wird in copy_results gespeichert.

Der letzte Task führt die Task-Datei create_vms.yml für die VMs aus, die nicht bereits existieren. Dafür sorgt die Bedingung when: "item.key not in existing_vms.list_vms", die diesem Task zu Idempotenz verhilft. Das Playbook selbst hat folgenden Inhalt:

$ cat -n tasks/create_vms.yml 
     1	---
     2	- name: Configure the image
     3	  command: |
     4	    virt-customize -a {{ libvirt_pool_dir }}/{{ item.key }}.qcow2 \
     5	    --hostname {{ item.key }} \
     6	    --root-password password:{{ vm_root_pass }} \
     7	    --ssh-inject 'root:file:{{ ssh_key }}' \
     8	    --uninstall cloud-init --selinux-relabel
     9	  when: copy_results is changed
    10	
    11	- name: Define VMs
    12	  community.libvirt.virt:
    13	    command: define
    14	    xml: "{{ lookup('template', '{{ item.value.vm_template }}.xml.j2') }}"
    15	
    16	- name: Create second disk images if needed
    17	  command: |
    18	    qemu-img create -f {{ item.value.file_type }} \
    19	    {{ libvirt_pool_dir }}/{{ item.key }}-vdb.{{ item.value.file_type }} {{ item.value.second_hdd_size }}
    20	  become: yes
    21	  when: item.value.second_hdd|bool == true
    22	
    23	- name : Attach second disk image to domain
    24	  command: |
    25	    virsh attach-disk {{ item.key }} \
    26	    --source "{{ libvirt_pool_dir }}/{{ item.key }}-vdb.{{ item.value.file_type }}" \
    27	    --target vdb \
    28	    --persistent
    29	  become: yes
    30	  when: item.value.second_hdd|bool == true
    31	
    32	- name: Ensure VMs are startet
    33	  community.libvirt.virt:
    34	    name: "{{ item.key }}"
    35	    state: running
    36	  register: vm_start_results
    37	  until: "vm_start_results is success"
    38	  retries: 15
    39	  delay: 2

Der Task in Zeile 2-9 konfiguriert den Inhalt der qcow2-Image-Datei. Die Bedingung when: copy_results is changed stellt sicher, dass dies nur passiert, wenn die Image-Datei zuvor an ihren Zielort kopiert wurde. Damit wird sichergestellt, dass nicht eine bereits vorhandene Image-Datei einer existierenden VM nochmals verändert wird. Der Task konfiguriert den Hostnamen, setzt das Root-Passwort und hinterlegt den SSH-Public-Key.

Der nächste Task ab Zeile 11 definiert/erstellt die neue VM aus den XML-Template-Dateien.

Die beiden Tasks in den Zeilen 16-30 fügen einer VM eine zweite Festplatte hinzu, wenn dies in defaults/main.yml bzw. vars/rhel_lab.yml entsprechend definiert wurde.

Der letzte Task sorgt schließlich dafür, dass die neu erstellten VMs eingeschaltet werden.

Das Playbook

Im Vergleich zu den Dateien mit den einzelnen Tasks fällt das Playbook eher kurz aus:

 cat -n kvm_provision_rhel_lab.yml 
     1	---
     2	- name: Provision RHEL lab VMs
     3	  hosts: localhost
     4	  vars_files:
     5	    - roles/kvm_provision_lab/vars/rhel_lab.yml
     6	  tasks:
     7	    - name: Run role kvm_provision_lab
     8	      include_role:
     9	        name: kvm_provision_lab

In Zeile 3 ist der KVM-Hypervisor anzugeben, auf dem die Rolle ausgeführt werden soll. Dies kann, wie in meinem Fall, der gleiche Host wie der Ansible-Control-Node sein.

In Zeile 4 und 5 wird die Datei geladen, welche die Variablen für die zu erstellende Laborumgebung enthält. Ohne diese Anweisung werden die Werte aus defaults/main.yml verwendet.

Abschließend wird die Ansible-Rolle inkludiert. Dies ist auch schon alles.

Zusammenfassung

Das Schreiben dieses Artikels hat deutlich länger gedauert als die Erstellung der eigentlichen Ansible-Rolle zur Erstellung einer Laborumgebung unter KVM.

Die einzelnen Abschnitte beschreiben das Vorgehen und die Bestandteile der Rolle im Detail. Ich hoffe, damit deren Funktionsweise deutlich gemacht zu haben.

Ich kann nun meine Labor-Umgebungen in Dateien wie rhel_lab.yml, debian_lab.yml, etc. definieren und die Rolle dazu verwenden, diese zu provisionieren. So steht mir in kurzer Zeit eine frische Testumgebung bereit. Und zwar jedes Mal aufs neue, wenn ich sie benötige.

Wenn euch dieser Artikel dabei hilft, eigene Labor-Umgebungen mithilfe von Ansible zu provisionieren freut mich dies umso mehr.

Quellen und weiterführende Links

  1. Build a lab in 36 seconds with Ansible. Ricardo Gerardi (Red Hat, Sudoer). Enable Sysadmin. 2021-10-22.
  2. 8 Linux virsh subcommands for managing VMs on the command line. Ricardo Gerardi (Red Hat, Sudoer). Enable Sysadmin. 2021-09.09.
  3. Build a lab in five minutes with three simple commands. Alex Callejas (Red Hat). Enable Sysadmin. 2021-08-20.
  4. Ansible Create KVM Guests
  5. community.libvirt.virt – Manages virtual machines supported by libvirt
  6. Ansible Dictionary Variables. URL: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#dictionary-variables
  7. Creating valid variable names. URL: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#creating-valid-variable-names

Meine privaten Arbeitsmittel Anfang 2022

10. Januar 2022 um 06:00

Ich selbst lese gern, womit andere Blogger dienstlich und privat arbeiten. Heute schreibe ich auf, wie dies Anfang 2022 bei mir aussieht. Dieser Artikel ist für euch. Viel Spaß beim Lesen.

Smartphone

Mein nahezu ständiger Begleiter ist das Smartphone Sony Xperia XZ2 Compact. Dieses nutze ich bereits seit Mai 2018 und es wird mir hoffentlich noch ein paar Jahre gute Dienste leisten. Als potenziellen Nachfolger habe ich ein Fairphone ins Auge gefasst. Ich nutzte das Gerät:

  • Um Bilder und Videos von meiner entzückenden Familie zu machen (meine Frau bestand auf diesen sehr wichtigen Hinweis)
  • Zum Telefonieren
  • Als Terminkalender
  • Für Chat und Kurznachrichten mit Matrix über Element (dienstlich), SMS und Threema (bevorzugt)
  • Zur E-Mail-Kommunikation mit K-9-Mail
  • Die Internetrecherche mit Firefox, Firefox Klar und dem Tor Browser
  • Zum Konsum von RSS-Feeds mit Feedly
  • Nutzung diverser sozialer Netzwerkwerke wie Facebook, LinkedIn, Mastodon, Twitter und XING
  • Mit bestimmt drei Dutzend weiteren Apps

Tablet

Vom Telefonieren und den Kurznachrichten abgesehen verwende ich für die gleichen Zwecke wie oben seit Mitte 2019 auch ein Samsung T830 Galaxy Tab S4 Wi-Fi Tablet. Durch seine 10,5 Zoll (ca. 27 cm) Bildschirmdiagonale, das geringe Gewicht und mit der App ReadEra eignet es sich hervorragend zum Lesen von PDF-Dateien und E-Books. Darüber hinaus nutze ich auf dem Tablet häufig den Android-Terminal-Emulator Termux. Zusammen mit der Tastatur-Hülle von Fintie dient es mir regelmäßig als Laptop-Ersatz. Dabei finde ich besonders das Preis-/Leistungs-Verhältnis der Tastatur-Hülle unschlagbar gut. Ich habe zuvor nicht wirklich daran geglaubt so gut auf einer doch sehr kleinen und günstigen Tastatur schreiben zu können.

Die Bedienung mit dem S-Pen ist nicht ganz so gut wie die von Apple, doch durchaus gut zu nutzen. Allerdings bin ich wieder dazu übergegangen längere Notizen und Gedanken mit Tinte in einem Notizbuch aus Papier festzuhalten. Das Schreiben mit der Hand auf Papier gefällt mir gut und ist eine Abwechslung zum ständigen Tippen.

Auf dem Tablet habe ich sicher nochmal ein Dutzend mehr Apps, als auf dem Smartphone. Doch möchte ich hier nicht alle auflisten. Die wichtigsten habe ich, glaube ich, genannt.

Laptop

In 2021 neu hinzugekommen ist ein Lenovo ThinkPad T14s (AMD). Auf diesem läuft aktuell Fedora 35. Zu den meistgenutzten Anwendungen zählen:

  • Thunderbird für E-Mail, Kalender und Aufgaben
  • Chromium für Videokonferenzen
  • Firefox für den Rest vom Web
  • Das Gnome-Terminal
  • Der beste Editor überhaupt: Vim
  • Rambox als Sammelecke für:
    • Element
    • Threema
    • Slack
    • XING
    • LinkedIn
    • Feedly
    • Mastodon
  • Lokale Instanz von LanguageTool, um auf Rechtschreibung und Grammatik aufzupassen
  • TeX Live zum Erstellen von allem, was mal in PDF oder auf Papier gebannt werden soll; als Editor nutze ich Vim.

An Letztem schätze ich, dass ich die ganzen Chat-, Nachrichten und Sozialen-Medien in einem separaten Fenster habe, wo ich sie insgesamt oder selektiv stumm schalten kann.

Insgesamt macht das T14s soviel Spaß, dass ich das Tablet tatsächlich nur noch fast ausschließlich zum Lesen verwende und sämtliche Schreib-, Programmier- und Recherche-Arbeiten an diesem Gerät verrichte.

Desktop/Server-PC

In meinem häuslichen Arbeitszimmer steht noch ein PC der Marke Eigenbau unter dem Schreibtisch. Ein Debian Bullseye verwaltet darin die folgenden Komponenten:

  • Motherboard: MSI MS-7C56/B550-A PRO
  • CPU: AMD Ryzen 5 PRO 4650G with Radeon Graphics
  • 32 GB RAM
  • 240 GB SSD und 1 TB HDD
working-space
Mein Arbeitsplatz 2022, mit höhenverstellbarem Schreibtisch, privater Workstation und Kabelmonster unter der Arbeitsplatte sowie dazugehöriger Ein- und Ausgabegeräte darauf (links im Bild). Rechts davon meine dienstlichen Arbeitsmittel.

Von Rambox abgesehen verwende ich auf diesem Gerät die gleichen Anwendungen wie auf dem Laptop. Zusätzlich dient mir dieser Rechner als KVM-/QEMUHypervisor. Die darin betriebenen virtuellen Maschinen dienen mir als Heimlabor, Entwicklungs- und Test-Umgebung. Produktive Dienste hoste ich darauf aktuell nicht.

Sonstige Geräte im Netzwerk

Seit nunmehr über 15 Jahren halten mein Netzwerk-Drucker und -Scanner Brother DCP-540CN und ich uns gegenseitig die Treue. Die Tintenpatronen sind seit Jahren für sehr geringe Preise zu bekommen und das Gerät verrichtet zuverlässig seinen Dienst. Die lange Laufzeit ist in meinen Augen ein Beweis für die Qualität dieses Gerätes. Zum Scannen unter Linux verwende ich die Anwendung XSane. Über die Jahre hat die Einrichtung unter verschiedenen Distributionen und Releases immer mal wieder etwas gehakt. Doch insgesamt bin ich wirklich sehr zufrieden mit dem Gerät und der Unterstützung unter Linux.

Nicht ganz so lange begleitet mich die Synology Diskstation DS213air. Ausschlaggebend für den Kauf war damals die integrierte WLAN-Unterstützung. Seit einigen Jahren nehmen die zu einem RAID-1 verbundenen Toshiba DT01ACA300 3 TB HDDs verschiedenste Daten auf. Das NAS dient als:

  • Backup-Ziel für diverse weitere Geräte im LAN
  • Netzwerk-Speicher für gemeinsam genutzte Dateien
  • Host für einige Git-Repos
  • Audio-, Foto- und Video-Station

Die Daten, die nicht bereits Backups darstellen, werden auf eine direkt angeschlossene 2,5 Zoll USB-HDD sowie mit einem Reverse-SSH-Tunnel offsite gesichert.

Dann gibt es da noch einen Pi-Hole auf einem Raspberry Pi 2 Model B, welcher für DHCP und DNS verantwortlich ist. Dann gibt es da noch einen Pi der ersten Generation. Dieser hostet FHEM und ruft einige Parameter meiner PV-Anlage ab.

Übrigens sind keine Geräte in meinem LAN aus dem Internet erreichbar. Auch nicht per VPN. Es steht also keine Tür offen, durch die der Schmutz aus dem Internet hereinwehen kann. Den Datenverkehr, der das LAN verlässt, möchte ich zukünftig ebenfalls limitieren. Hier suche ich noch nach einer geeigneten Lösung.

Irgendetwas as a Service

Diesen WordPress-Blog betreibe ich selbst auf einem Virtual Private Server (VPS) von Contabo. Als Betriebssystem kommt Debian Bullseye zum Einsatz und NGINX ist der Webserver meiner Wahl. Auf diesem System läuft auch eine Rootless-Podman-Installation, welche ich nutze, um mich mit Linux-Containern vertraut zu machen.

Um E-Mails, Termine und Aufgaben kümmert sich bereits seit einigen Jahren Mailbox.org. Die genannten Dienste nutze ich auf meinen Endgeräten in den Programmen Thunderbird, K-9-Mail sowie mit den Apps:

  • OX Sync App
  • OX Tasks
  • OX Drive

Maibox.org nutze ich ebenfalls für gelegentliche Videokonferenzen im Webbrowser mit 3-5 Teilnehmern.

Meine Domain und die dazugehörige DNS-Verwaltung liegen seit Jahren bei ClouDNS. Die Zonen-Updates sind schnell und ich hatte bisher noch nie Probleme mit dem Dienst.

Und ich bin noch einer der Ewig-gestrigen, die TeamDrive 3 auf Laptop, Tablet und PC nutzen. Der dazugehörige TeamDrive Personal Server läuft ebenfalls auf meinem VPS.

Zusammenfassung

Wie ihr gelesen habt, nutze ich mit wenigen Ausnahmen alte bis uralte Hardware, welche jedoch noch tadellos ihren Dienst verrichtet und meinen Ansprüchen voll und ganz genügt.

Grundsätzlich suche ich meine Geräte danach aus, dass sie meine Anforderungen erfüllen und den Eindruck erwecken, möglichst lange Unterstützung (Garantie, Updates, Ersatzteilverfügbarkeit, etc.) zu erhalten. Das muss kein Dogma sein. So gönne ich mir auch gerne mal etwas Neues wie z.B. das T14s. Deshalb landen die älteren Modelle T410 und X201 nicht auf dem Müll, sondern werden einem neuen Verwendungszweck zugeführt.

Ich hoffe, der Artikel hat euch ein wenig unterhalten. Lasst mich gern in einem Kommentar wissen, ob und wie euch diese Art von Artikeln gefallen. Kommt gut in die neue Woche.

❌
❌