Red Hat Enterprise Linux 9.4 ist fertig
Red Hat hat sei Red Hat Enterprise Linux in Version 9.4 veröffentlicht. Neue Funktionen sollen vor allem die Komplexität des Hybrid Cloud Computing meistern helfen.
Red Hat hat sei Red Hat Enterprise Linux in Version 9.4 veröffentlicht. Neue Funktionen sollen vor allem die Komplexität des Hybrid Cloud Computing meistern helfen.
Dies ist der Folgeartikel, den ich in der Einführung in das Advanced Intrusion Detection Environment (AIDE) versprochen hatte. Es handelt sich hierbei um einen Proof of Concept (PoC), der zeigt, wie AIDE mithilfe einer Ansible-Rolle ferngesteuert werden kann. Die Einführung wird als bekannt vorausgesetzt.
Grundlegende Ansible-Kenntnisse, wie die Verwendung von Ansible-Rollen und das Ausführen von Playbooks werden ebenfalls als bekannt vorausgesetzt. Wer Ansible nicht kennt, sei an die offizielle Dokumentation verwiesen.
aide
ist auf den Zielsystemen installiertaide.conf
Durch die Speicherung der AIDE-Datenbanken und -Konfigurationsdateien auf dem ACN sind diese gegen Veränderung auf einem kompromittierten Host geschützt. Gegen Veränderungen auf dem ACN selbst sind die Dateien nur mit Unix-Dateiberechtigungen geschützt. Doch wenn der ACN kompromittiert ist, hat man eh ein ganz anderes Problem, als sich um AIDE Sorgen zu machen.
Meine Labor-Umgebung für diesen PoC besteht aus den vier Hosts:
ansible-core
)Der ACN kann sich via SSH zu den Zielsystemen (rhel{7,8,9}) verbinden und dort Programmcode mit erhöhten Rechten ausführen.
Die von mir für diesen PoC entwickelte Ansible-Rolle gibt es unter der URL: https://github.com/Tronde/aide
Die Rolle ist nicht idempotent. Sie ruft das Programm aide
auf den Zielsystemen mit verschiedenen Optionen auf und verarbeitet deren Ausgabe. Dazu macht die Rolle Gebrauch des Moduls ansible.builtin.command
.
Gesteuert wird die Rolle über Ansible-Tags. Wird die Rolle in einem Playbook ohne Angabe von Tags ausgeführt, werden keinerlei Veränderungen an den Zielsystemen vorgenommen.
Der folgende Code-Block zeigt ein Beispiel-Playbook zum Aufruf der Rolle. Die Tags und die Variable aide_db_fetch_dir
werden im Anschluss erläutert.
# SPDX-License-Identifier: MIT
---
- name: Example aide role invocation
hosts: targets
tasks:
- name: Include role aide
tags:
- install
- generate_config
- init
- check
- update
vars:
aide_db_fetch_dir: files
ansible.builtin.include_role:
name: aide
aide
auf den Zielsystemen installiert ist/etc/aide.conf
unter Nutzung von templates/aide.conf.j2
; das Template ist an die individuellen Bedürfnisse anzupassen; Details siehe nächster AbschnittDie Variable aide_db_fetch_dir
erwartet im Auslieferungszustand das Verzeichnis files
parallel zum Playbook. In diesem Verzeichnis werden Unterverzeichnisse für jeden Host erstellt, in denen die AIDE-Datenbank der verwalteten Systeme gespeichert wird. Soll ein anderer Speicherort verwendet werden, ist der Wert dieser Variablen entsprechend anzupassen. Die AIDE-Datenbanken werden mit dem Ansible-Module ansible.builtin.fetch
von den verwalteten Systemen geholt.
In diesem Abschnitt beschreibe ich die fünf Anwendungsfälle für den PoC. Alle Anwendungsfälle wurden gegen RHEL 7, RHEL 8 und RHEL 9 getestet. Für diesen Blog beschränke ich mich jedoch auf Tests gegen RHEL 9, um die Übersichtlichkeit der Ausgaben zu verbessern.
Es wird stets das Playbook aus dem Abschnitt Beschreibung der Ansible-Rolle aide verwendet und mit unterschiedlichen Tags ausgeführt.
Um AIDE nutzen zu können, muss es zuerst installiert sein. Dies wird mit folgendem Playbook-Aufruf festgestellt:
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags install
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Ensure required packages are installed] ***************************
changed: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Für diesen Anwendungsfall arbeitet die Rolle idempotent. Bei einer zweiten Ausführung werden keine weiteren Änderungen am System vorgenommen:
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags install
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Ensure required packages are installed] ***************************
ok: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Zusammen mit der Rolle wird die Datei templates/aide.conf.j2
ausgeliefert. Dabei handelt es sich um die Standardkonfigurationsdatei aus einer RHEL9-Installation, in welcher zusätzlich der Pfad /root/.ansible*
von der Überwachung ausgenommen wurde, um falsch positive Ergebnisse zu vermeiden.
Diese Datei ist an die individuellen Bedürfnisse anzupassen. Wer Hilfe zum Templating mit Jinja2 benötigt, findet in der Ansible-Dokumentation einen Einstieg.
Ausgerollt wird die Konfigurationsdatei dann wie folgt:
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags generate_config
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Generate /etc/aide.conf] ******************************************
changed: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Auch mit diesem Tag arbeitet die Rolle idempotent.
Wird dieser Schritt ausgelassen, wird in allen folgenden Anwendungsfällen die Standardkonfigurationsdatei verwendet, welche bei der Installation des Pakets aide
mitinstalliert wurde.
Um Integritäts-Checks durchführen zu können, muss zuerst die AIDE-Datenbank initialisiert werden. Dies geschieht mit dem folgenden Aufruf:
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags init
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Initialize AIDE database] *****************************************
changed: [rhel9]
TASK [aide : Fetch AIDE database] **********************************************
changed: [rhel9]
TASK [aide : Remove remote AIDE database file] *********************************
changed: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Nach der Initialisierung der AIDE-Datenbank wird diese auf den ACN kopiert und von den verwalteten Systemen entfernt. Dies hat den Hintergrund, dass es sich beim ACN um ein sehr gut gesichertes System handelt und die Datenbanken hier am besten vor einer Kompromittierung geschützt sind.
Wird der Standardwert der Variable aide_db_fetch_dir
verwendet, findet sich die AIDE-Datenbank jetzt im Pfad files/rhel9/var/lib/aide/aide.db.new.gz
. Dabei entspricht rhel9
in der Pfadangabe dem inventory_hostname
des jeweiligen Zielsystems.
Dieser Teil der Rolle ist nicht idempotent. Wird das Playbook erneut ausgeführt, wird eine neue AIDE-Datenbank erstellt, auf den ACN heruntergeladen und vom Zielsystem gelöscht.
Der nun folgende Code-Block zeigt den Playbook-Aufruf zur Integritätsprüfung. Hier wird zuerst die AIDE-Datenbank auf das Zielsystem kopiert, anschließend ein AIDE-Check ausgeführt. Da im folgenden Beispiel keine Änderungen detektiert wurden, besitzt der Task „[aide : Check against AIDE reference database]“ den Status „ok“.
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags check
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Copy AIDE reference database to remote] ***************************
changed: [rhel9]
TASK [aide : Check against AIDE reference database] ****************************
ok: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Dieser Teil der Rolle ist nicht idempotent. Bei jedem Aufruf wird ein neuer Integritäts-Check ausgeführt.
Ich habe die Datei /etc/hosts
auf dem Zielsystem manipuliert, um auch den Fall zu zeigen, wenn eine Änderung erkannt wurde.
Zu Beginn der folgenden Ausgabe ist zu erkennen, dass der Task „[aide : Copy AIDE reference database to remote]“ den Status „ok“ besitzt. Ansible hat erkannt, dass die AIDE-Datenbank bereits in unverändertem Zustand auf dem Zielsystem existiert, und hat sie deshalb nicht erneut übertragen. Der Task „[aide : Check against AIDE reference database]“ schlägt nun allerdings fehl (Status: „fatal“), da Veränderungen erkannt wurden. Die zugegeben etwas unübersichtliche Ausgabe enthält die Nachricht, dass die Datei /etc/hosts
verändert wurde.
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags check
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Copy AIDE reference database to remote] ***************************
ok: [rhel9]
TASK [aide : Check against AIDE reference database] ****************************
fatal: [rhel9]: FAILED! => {"changed": true, "cmd": ["aide", "--check"], "delta": "0:00:27.177397", "end": "2024-03-29 05:16:50.682795", "msg": "non-zero return code", "rc": 4, "start": "2024-03-29 05:16:23.505398", "stderr": "", "stderr_lines": [], "stdout": "Start timestamp: 2024-03-29 05:16:23 -0400 (AIDE 0.16)\nAIDE found differences between database and filesystem!!\n\nSummary:\n Total number of entries:\t45541\n Added entries:\t\t0\n Removed entries:\t\t0\n Changed entries:\t\t1\n\n---------------------------------------------------\nChanged entries:\n---------------------------------------------------\n\nf ... .C... : /etc/hosts\n\n---------------------------------------------------\nDetailed information about changes:\n---------------------------------------------------\n\nFile: /etc/hosts\n SHA512 : YobgpcvAMPey0QX1lK4K+5EFySF1xrB/ | 7nIivvNa5ozfhOqSFLmPIiu6g04Wbx1n\n 9FRzTCPNC93+13Y5/lm2inC4x4rydlf2 | iGNf0/QTgFjaMGug8HywxTiO2PREZRNS\n EcvonCf3pHuXj6lEmAjBnw== | 3qNEi4Qm6an5inSY72sjfA==\n\n\n---------------------------------------------------\nThe attributes of the (uncompressed) database(s):\n---------------------------------------------------\n\n/var/lib/aide/aide.db.gz\n MD5 : gMgRyMOExVAdOAvdgt4QDA==\n SHA1 : w7tmPKNvRYggY/JZ5wv+7ZdcSZM=\n RMD160 : CO0pK5tfg66MaO17YB8eaRuyyMw=\n TIGER : n8UbZJNt9gL672+pR9IPjoyhpAsUJ46O\n SHA256 : k8UHnv2CK4zYrfZN+bDp6SCcLkx21px6\n GNZlwySPKcY=\n SHA512 : DFw5wlBoJQOBCrs0ulvVxaMvoQk/oBEQ\n TkOmhfHAdevUWNAgCJ0KH0q26LsynEMj\n MWQpsGf7v12iACc4SP9ANA==\n\n\nEnd timestamp: 2024-03-29 05:16:50 -0400 (run time: 0m 27s)", "stdout_lines": ["Start timestamp: 2024-03-29 05:16:23 -0400 (AIDE 0.16)", "AIDE found differences between database and filesystem!!", "", "Summary:", " Total number of entries:\t45541", " Added entries:\t\t0", " Removed entries:\t\t0", " Changed entries:\t\t1", "", "---------------------------------------------------", "Changed entries:", "---------------------------------------------------", "", "f ... .C... : /etc/hosts", "", "---------------------------------------------------", "Detailed information about changes:", "---------------------------------------------------", "", "File: /etc/hosts", " SHA512 : YobgpcvAMPey0QX1lK4K+5EFySF1xrB/ | 7nIivvNa5ozfhOqSFLmPIiu6g04Wbx1n", " 9FRzTCPNC93+13Y5/lm2inC4x4rydlf2 | iGNf0/QTgFjaMGug8HywxTiO2PREZRNS", " EcvonCf3pHuXj6lEmAjBnw== | 3qNEi4Qm6an5inSY72sjfA==", "", "", "---------------------------------------------------", "The attributes of the (uncompressed) database(s):", "---------------------------------------------------", "", "/var/lib/aide/aide.db.gz", " MD5 : gMgRyMOExVAdOAvdgt4QDA==", " SHA1 : w7tmPKNvRYggY/JZ5wv+7ZdcSZM=", " RMD160 : CO0pK5tfg66MaO17YB8eaRuyyMw=", " TIGER : n8UbZJNt9gL672+pR9IPjoyhpAsUJ46O", " SHA256 : k8UHnv2CK4zYrfZN+bDp6SCcLkx21px6", " GNZlwySPKcY=", " SHA512 : DFw5wlBoJQOBCrs0ulvVxaMvoQk/oBEQ", " TkOmhfHAdevUWNAgCJ0KH0q26LsynEMj", " MWQpsGf7v12iACc4SP9ANA==", "", "", "End timestamp: 2024-03-29 05:16:50 -0400 (run time: 0m 27s)"]}
PLAY RECAP *********************************************************************
rhel9 : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
An dieser Stelle wurde gezeigt, dass sowohl unveränderte Systeme als auch Systeme mit Veränderungen erkannt und gemeldet werden. Dabei muss natürlich niemand die Standardausgabe beobachten. Stattdessen kann Logging für Ansible Ausgaben konfiguriert werden.
Dieser Anwendungsfall nimmt an, dass erfolgte Änderungen legitim sind und in die AIDE-Referenzdatenbank aufgenommen werden sollen. Dies geschieht wie folgt:
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags update
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Update AIDE database] *********************************************
changed: [rhel9]
TASK [aide : Fetch AIDE database] **********************************************
changed: [rhel9]
TASK [aide : Remove remote AIDE database file] *********************************
changed: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Nachdem die Referenzdatenbank aktualisiert wurde, wird diese wieder auf den ACN kopiert und vom Zielsystem entfernt.
Das folgende Beispiel zeigt, dass auf dem Zielsystem der AIDE-Check nun ohne Fehler absolviert wird:
[root@ansible-ctrl ansible]# ansible-playbook aide.yml --tags check
PLAY [Example aide role invocation] ********************************************
TASK [Gathering Facts] *********************************************************
ok: [rhel9]
TASK [Include role aide] *******************************************************
TASK [aide : Copy AIDE reference database to remote] ***************************
changed: [rhel9]
TASK [aide : Check against AIDE reference database] ****************************
ok: [rhel9]
PLAY RECAP *********************************************************************
rhel9 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ansible hat erkannt, dass die AIDE-Datenbank auf dem Zielhost nicht mit der aktuellen Referenzdatenbank übereinstimmt und hat letztere daher auf das Zielsystem übertragen. Die Überprüfung endet mit dem Status „ok“. Das System entspricht dem Soll-Zustand.
Der Proof of Concept hat gezeigt, dass AIDE mit der verwendeten Ansible-Rolle ferngesteuert genutzt werden kann. AIDE-Datenbank und Konfigurationsdatei werden dabei getrennt von den verwalteten Systemen gespeichert und sind daher gegen Veränderung bei Kompromittierung der Zielsysteme geschützt. Bei Bedarf, wenn Ansible Abweichungen des Ist- zum Soll-Zustand erkennt, werden diese Dateien auf die Zielsysteme übertragen.
Der größte Arbeitsaufwand steckt in der Erstellung einer oder mehrerer AIDE-Konfigurationsdateien, die optimal zur eigenen Umgebung passen und möglichst keine falsch positiven Ergebnisse erzeugen. Dieser Aufwand besteht jedoch auch, wenn man AIDE ohne Ansible einsetzt.
Einen Punkt hat dieser PoC unberücksichtigt gelassen. Es nützt natürlich nichts, wenn man die Ausgaben der Playbooks nur protokolliert, die Protokolle jedoch nicht analysiert, um entsprechende Alarme in Monitoring- oder Ticket-Systemen zu erzeugen. Dies sei den Anwendern zur selbstständigen Übung überlassen. ;-)
In diesem Artikel stelle ich euch die RHEL System Role nbde_client
vor, mit welcher sich Hosts für Network Bound Disk Encryption (NBDE) installieren lassen. Er ist Bestandteil einer losen Serie, in der ich eine Reihe von System Roles vorstelle, mit denen häufig anfallende Aufgaben in der Systemadministration erledigt werden können.
Wer sich zuerst über die genannten Begriffe informieren möchte, lese:
Für das folgende Beispiel verwende ich eine Umgebung, bestehend aus:
ansible-core
rhel-system-roles
/dev/sdc
in den Beispielen in diesem Text)Die Installation von RHEL sowie der genannten Pakete sind nicht Bestandteil dieses Artikels. Wer hierzu einen Einstieg sucht, findet entsprechende Dokumentation unter:
Durch die Installation des Pakets rhel-system-roles
existiert diese Rolle bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.nbde_client/
und die Dokumentation in /usr/share/doc/rhel-system-roles/nbde_client/README.md
. Letztere enthält verschiedene Beispiele für häufige Anwendungsfälle.
In meinem Labor betreibe ich zwei NBDE-Server (TANG-Server) rhel-hetz-tang1
und rhel-hetz-tang2
sowie zwei NBDE-Clients (Clevis-Clients) rhel-hetz-clevis1
und rhel-hetz-clevis2
. Die beiden NBDE-Clients besitzen jeweils ein LUKS-Device /dev/sdc
, welches aktuell durch eine LUKS-Passphrase gesichert ist.
Zukünftig sollen diese LUKS-Devices durch die Kommunikation mit einem NBDE-Server entschlüsselt werden. Die LUKS-Passphrase soll entfernt werden.
Damit wird zukünftig ein Neustart der Clients aus der Ferne ermöglicht. Gleichzeitig bleibt das verschlüsselte Gerät bei Diebstahl vor unbefugtem Zugriff geschützt.
Hinweis: Das folgende Playbook ist nicht idempotent. Um dies zu ändern, ist dem ersten Task eine Bedingung hinzuzufügen, damit dieser nur dann ausgeführt werden, wenn die Bedingung erfüllt ist.
Für dieses Beispiel ist die fehlende Idempotenz des Playbooks jedoch kein Problem, da grubby
das Argument nur dann hinzufügt, wenn es nicht bereits vorhanden ist.
---
- hosts: clevis
tasks:
- name: Configure ip address for interface during early boot
ansible.builtin.command:
cmd: grubby --update-kernel=ALL --args='GRUB_CMDLINE_LINUX_DEFAULT="net.ifnames=0 biosdevname=0 ip={{ ansible_default_ipv4.address }}::{{ ansible_default_ipv4.gateway }}:{{ ansible_default_ipv4.netmask }}::{{ ansible_default_ipv4.alias }}:none"'
- name: Enroll Clevis clients
include_role:
name: rhel-system-roles.nbde_client
vars:
nbde_client_bindings:
- device: /dev/sdc
encryption_password: "{{ luks_password }}"
password_temporary: true
slot: 2
servers:
- http://rhel-hetz-tang1.example.com
- http://rhel-hetz-tang2.example.com
luks_password
stecktluks_password
mit ansible-vault vor neugierigen Blicken zu schützenpassword_temporary: true
wird die LUKS-Passphrase aus dem jeweiligen Key-Slot gelöscht, nachdem das LUKS-Device für NBDE konfiguriert wurdeAchtung (I know, the warning comes after the spell): Wenn zur Laufzeit ein Fehler auftritt und der Key-Slot mit der LUKS-Passphrase bereits gelöscht wurde, die NBDE-Konfiguration jedoch nicht erfolgreich war, verliert man Zugriff auf das LUKS-Device. In meiner Labor-Umgebung bin ich das Risiko eingegangen. In der echten Welt, müsst ihr selbst entscheiden, ob ihr mehr Vorsicht walten lasst.
Zur Erstellung des Playbooks habe ich die Informationen aus /usr/share/doc/rhel-system-roles/nbde_client/README.md
und dem Kapitel 12.18. Using the nbde_client
System Role for setting up multiple Clevis clients genutzt. Bis ich festgestellt habe, dass ich auch noch den Task „Configure ip address for interface during early boot“ benötige, hat es ein wenig gedauert. Nun habe ich allerdings ein Playbook, dass ich zukünftig wiederverwenden kann.
In der erstellten Konfiguration, können die LUKS-Devices nur entschlüsselt werden, wenn mindestens einer der beiden Tang-Server im Netzwerk erreichbar ist. Wird ein so gesicherter Server gestohlen und sind die Tang-Server nicht aus dem Internet erreichbar, bleiben die Daten in der verschlüsselten Partition wie gewohnt geschützt. Es ist jedoch möglich den Server neuzustarten, ohne manuell die LUKS-Passphrase an der Konsole eingeben zu müssen.
In diesem Artikel stelle ich euch die RHEL System Role nbde_server
vor, mit welcher sich Tang-Server für Network Bound Disk Encryption (NBDE) installieren lassen. Er ist Bestandteil einer losen Serie, in der ich eine Reihe von System Roles vorstelle, mit denen häufig anfallende Aufgaben in der Systemadministration erledigt werden können.
Wer sich zuerst über die genannten Begriffe informieren möchte, lese zuerst:
Im folgenden Text verwende ich die Begriffe NBDE-Server und Tang-Server synonym. Bitte lasst euch dadurch nicht verwirren.
Für das folgende Beispiel verwende ich eine Umgebung, bestehend aus:
ansible-core
rhel-system-roles
Die Installation von RHEL sowie der genannten Pakete sind nicht Bestandteil dieses Artikels. Wer hierzu einen Einstieg sucht, findet entsprechende Dokumentation unter:
Durch die Installation des Pakets rhel-system-roles
existiert diese Rolle bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.nbde_server/
und die Dokumentation in /usr/share/doc/rhel-system-roles/nbde_server/README.md
. Letztere enthält verschiedene Beispiele für häufige Anwendungsfälle.
Ich möchte mit dieser Rolle Folgendes erreichen:
enforcing
Das Playbook ist recht übersichtlich. tang
bezeichnet eine Gruppe aus meinem Ansible-Inventory, welche die Systeme enthält, die ich als NBDE-Server konfigurieren möchte.
---
- name: Manage nbde server with selinux and firewall
hosts: tang
vars:
nbde_server_manage_firewall: true
nbde_server_manage_selinux: true
roles:
- rhel-system-roles.nbde_server
Nach der Anwendung der Rolle lauscht der Tang-Service auf Port 80/tcp
der Zielsysteme und ist aus dem Netzwerk erreichbar.
Leider läuft es dieses Mal nicht ganz so rund wie üblich. Der Task [redhat.rhel_system_roles.selinux : Set an SELinux label on a port]
schlägt auf dem RHEL 8 Host mit folgender Fehlermeldung fehl: „Failed to import the required Python library (libselinux-python)“
Das Problem und die Lösung beschreibt Red Hat in dem Solution Article: Ansible playbook fails with libselinux-python aren’t installed on RHEL8 (Login required)
Diesmal lief es nicht ganz so reibungslos wie gewohnt.
Letztendlich konnten die beiden NBDE-Server dennoch schneller konfiguriert werden, als wäre ich der manuellen Prozedur in Chapter 12. Configuring automated unlocking of encrypted volumes using policy-based decryption gefolgt.
Die Server sind damit aufgesetzt, nächste Woche beschreibe ich, wie die Clients konfiguriert werden.
Im siebten Teil meiner losen Reihe über die RHEL System Roles stelle ich die Rolle rhc vor, mit welcher sich RHEL-Systeme (Version >= 8.6) in der Hybrid Cloud Console, Insights und dem RHSM registrieren lassen.
Das Tool rhc
selbst habe ich bereits im Artikel Red Hat Remote Host Configuration ausführlich vorgestellt.
Möchte man ein oder mehrere RHEL-Systeme in der Hybrid Cloud Console registrieren, kann man dazu die RHEL System Role rhc verwenden.
Durch die Installation des Pakets rhel-system-roles
existiert die Rolle rhc bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.rhc/
und die Dokumentation in /usr/share/doc/rhel-system-roles/rhc/README.md
.
- name: Register systems
hosts: all
vars:
rhc_auth:
activation_keys:
keys: ["key-1", ...]
rhc_organization: "your-organization"
roles:
- rhel-system-roles.rhc
key-1
ist durch den eigenen Activation Key zu ersetzenyour-organization
ist durch die eigene Org-ID zu ersetzenMit dieser System Role ist es einfach möglich, eine große Anzahl Systeme in die Hybrid Cloud Console aufzunehmen. Dabei lässt sich konfigurieren, ob Funktionen wie Insights und Remediation Playbooks genutzt werden können.
Eine weitere tolle Rolle aus dem Paket rhel-system-roles
, die sich einfach zur Anwendung bringen lässt.
In diesem Artikel beschreibe ich die beiden Ansible-Variablen-Typen „List variables“ und „Dictionary variables“ sowie die Kombination beider Typen. Ich zeige mit einem einfachen Playbook, wie diese Variablen-Typen in einer Schleife (eng. loop) durchlaufen werden können.
Während der Text mir zur Übung und Erinnerung dient, hoffe ich, dass er für die Einsteiger unter euch eine hilfreiche Einführung bietet. Für weiterführende Informationen verlinke ich im Text direkt auf die Ansible-Dokumentation.
Eine Liste ist eine Variable mit einem Namen und einem bis mehreren Werten. Folgender Code-Block zeigt die List-Variable namens list
mit ihren zwei Werten:
list:
- Alice Cooper
- Bob Marley
Die Einrückung der Werte ist wichtig. Sie erhöht nicht nur die Lesbarkeit, sondern vermeidet auch Lint-Fehler bei der Anwendung von ansible-lint
. Bedauerlicherweise läuft euer Playbook auch, wenn ihr die Werte nicht einrückt, doch bitte ich euch, euch diesen schlechten Stil nicht anzugewöhnen.
Listen sind mit Arrays verwand. Sie besitzen einen Index, welcher bei 0 beginnt und für jedes Listen-Element (für jeden Wert) um 1 inkrementiert wird. Folgendes Beispiel zeigt, wie man das Listen-Element mit dem Wert „Alice Cooper“ der einfachen Variable favorit
zuweisen kann:
favorit: "{{ list[0] }}"
Ein Dictionary speichert Daten in Schlüssel-Wert-Paaren (excuse my German). Dabei darf der Wert eines Dictionary wiederum ein Dictionary sein.
Ein einfaches Dictionary sieht wie folgt aus:
Felder:
Feld1: 10ha
Feld2: 40ha
Möchte man z.B. auf den Wert des Schlüssels Feld2
aus dem Dictionary Felder
zugreifen, geht dies wie folgt:
mein_feld: "{{ Felder['Feld2'] }}"
# oder
mein_feld: "{{ Felder.Feld2 }}"
Die beiden folgenden Code-Blöcke zeigen zwei Beispiele für etwas komplexere Dictionaries, über die ich später iterieren werde:
dict:
Alice:
last_name: Cooper
job: singer
Bob:
last_name: Marley
job: singer
virtual_machines_with_params:
vm1:
cpu_count: 2
memory_mb: 2048
guest_os: rhel8
vm2:
cpu_count: 2
memory_mb: 1024
guest_os: rhel9
Auch hier ist die Einrückung sehr wichtig. Macht man dabei einen Fehler, fängt man sich einen Syntax-Fehler bei der Ausführung des Playbooks ein.
Beide zuvor beschriebenen Variablen-Typen können miteinander kombiniert werden. Folgendes Beispiel zeigt eine Liste von Dictionaries:
list_of_dicts:
- first_name: Alice
last_name: Cooper
job: singer
- first_name: Bob
last_name: Marley
job: singer
In Ansible und YAML spielt die Einrückung von Code eine sehr wichtige Rolle. Des Weiteren ist bei der Kombination von Variablen-Typen nicht alles erlaubt. Folgender Code-Block zeigt ein fehlerhaftes Beispiel und die Fehlermeldungen, die es generiert:
$ cat nonsense.yml
---
nonsense:
- first_name: Alice
last_name: Cooper
job: singer
- first_name: Bob
last_name: Marley
job: singer
$ ansible-lint nonsense.yml
WARNING Listing 1 violation(s) that are fatal
load-failure: Failed to load YAML file
nonsense.yml:1 mapping values are not allowed in this context
in "<unicode string>", line 4, column 15
Rule Violation Summary
count tag profile rule associated tags
1 load-failure min core, unskippable
Failed after : 1 failure(s), 0 warning(s) on 1 files.
Tipp: Um Fehler bei der Eingabe zu vermeiden, habe ich meinen Editor Vim mit folgenden Optionen konfiguriert: set ts=2 sts=2 sw=2 et ai cursorcolumn
Das folgende Playbook nutzt das Modul ansible.builtin.debug, um Werte der genannten Variablen-Typen auszugeben. Es zeigt dabei, wie diese Variablen in einer Schleife durchlaufen werden können.
Damit es übersichtlich bleibt, nutze ich Tags, um die Tasks im Playbook einzeln ausführen zu können.
$ cat output_dicts_and_lists.yml
---
- name: Output content of dicts_and_lists.yml
hosts: localhost
gather_facts: false
become: false
vars_files:
- dicts_and_lists.yml
tasks:
- name: Task 1 Ouput all vars in dicts_and_lists.yml
loop:
- "{{ list }}"
- "{{ dict }}"
- "{{ list_of_dicts }}"
ansible.builtin.debug:
var: item
tags:
- dicts_and_lists
- name: Task 2 Loop over some list
loop: "{{ list }}"
ansible.builtin.debug:
msg: "Name: {{ item }}"
tags:
- list
- name: Task 3 Loop over some dictionary
loop: "{{ dict | dict2items }}"
ansible.builtin.debug:
msg: "Firstname: {{ item.key }} Lastname: {{ item.value.last_name }}"
tags:
- dict
- name: Task 4 Loop over some list_of_dicts
loop: "{{ list_of_dicts }}"
ansible.builtin.debug:
msg: "Firstname: {{ item.first_name }} Lastname: {{ item.last_name }}"
tags:
- list_of_dicts
Dies ist der erste Task aus obigem Playbook. Er gibt die Werte der Variablen list
, dic
t und list_of_dicts
aus. Diese habe ich als Liste an loop
übergeben (siehe Loops in der Dokumentation).
$ ansible-playbook output_dicts_and_lists.yml --tags dicts_and_lists
…
PLAY [Output content of dicts_and_lists.yml] ***********************************
TASK [Task 1 Ouput all vars in dicts_and_lists.yml] ****************************
ok: [localhost] => (item=['Alice Cooper', 'Bob Marley']) => {
"ansible_loop_var": "item",
"item": [
"Alice Cooper",
"Bob Marley"
]
}
ok: [localhost] => (item={'Alice': {'last_name': 'Cooper', 'job': 'singer'}, 'Bob': {'last_name': 'Marley', 'job': 'singer'}}) => {
"ansible_loop_var": "item",
"item": {
"Alice": {
"job": "singer",
"last_name": "Cooper"
},
"Bob": {
"job": "singer",
"last_name": "Marley"
}
}
}
ok: [localhost] => (item=[{'first_name': 'Alice', 'last_name': 'Cooper', 'job': 'singer'}, {'first_name': 'Bob', 'last_name': 'Marley', 'job': 'singer'}]) => {
"ansible_loop_var": "item",
"item": [
{
"first_name": "Alice",
"job": "singer",
"last_name": "Cooper"
},
{
"first_name": "Bob",
"job": "singer",
"last_name": "Marley"
}
]
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
In der Ausgabe ist zu erkennen, dass Listen mit [ ]
und Dictonaries mit { }
umschlossen werden. Der folgende Code-Block zeigt zum Vergleich den Inhalt der Datei dicts_and_lists.yml
.
---
list:
- Alice Cooper
- Bob Marley
dict:
Alice:
last_name: Cooper
job: singer
Bob:
last_name: Marley
job: singer
list_of_dicts:
- first_name: Alice
last_name: Cooper
job: singer
- first_name: Bob
last_name: Marley
job: singer
Als Nächstes schauen wir uns die Ausgabe von Task 2 an, welcher lediglich die einzelnen Listenelemente nacheinander ausgibt.
$ ansible-playbook output_dicts_and_lists.yml --tags list
PLAY [Output content of dicts_and_lists.yml] ***********************************
TASK [Task 2 Loop over some list] **********************************************
ok: [localhost] => (item=Alice Cooper) => {
"msg": "Name: Alice Cooper"
}
ok: [localhost] => (item=Bob Marley) => {
"msg": "Name: Bob Marley"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Die Variable item
referenziert das jeweils aktuelle Element der Liste, welche mit loop
durchlaufen wird.
Um Scrollen zu vermeiden, zeigten die beiden folgenden Code-Blöcke noch einmal den entsprechenden Task aus obigem Playbook und das Dictionary, welches für dieses Beispiel benutzt wird. Der dritte Code-Block zeigt dann die dazugehörige Ausgabe.
- name: Task 3 Loop over some dictionary
loop: "{{ dict | dict2items }}"
ansible.builtin.debug:
msg: "Firstname: {{ item.key }} Lastname: {{ item.value.last_name }}"
tags:
- dict
Bevor ein Dictionary mit loop
verarbeitet werden kann, muss es in eine Liste transformiert werden. Dies geschieht mit Hilfe des Filters dict2items
.
In einigen älteren Playbooks sieht man statt loop
ein Lookup-Plugin in der Form with_dict: "{{ dict }}"
. Dies ist ebenfalls korrekt, heute jedoch nicht mehr gebräuchlich.
dict:
Alice:
last_name: Cooper
job: singer
Bob:
last_name: Marley
job: singer
$ ansible-playbook output_dicts_and_lists.yml --tags dict
PLAY [Output content of dicts_and_lists.yml] ***********************************
TASK [Task 3 Loop over some dictionary] *****************************************
ok: [localhost] => (item={'key': 'Alice', 'value': {'last_name': 'Cooper', 'job': 'singer'}}) => {
"msg": "Firstname: Alice Lastname: Cooper"
}
ok: [localhost] => (item={'key': 'Bob', 'value': {'last_name': 'Marley', 'job': 'singer'}}) => {
"msg": "Firstname: Bob Lastname: Marley"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Auch hier sind Dictionaries involviert, doch wird kein Filter dict2items
benötigt, da es sich bereits um eine Liste handelt, welche an loop
übergeben wird.
Die drei folgenden Code-Blöcke zeigen die verwendete Liste, den Task aus obigem Playbook und die Ausgabe.
list_of_dicts:
- first_name: Alice
last_name: Cooper
job: singer
- first_name: Bob
last_name: Marley
job: singer
- name: Task 4 Loop over some list_of_dicts
loop: "{{ list_of_dicts }}"
ansible.builtin.debug:
msg: "Firstname: {{ item.first_name }} Lastname: {{ item.last_name }}"
tags:
- list_of_dicts
$ ansible-playbook output_dicts_and_lists.yml --tags list_of_dicts
PLAY [Output content of dicts_and_lists.yml] ***********************************
TASK [Loop over some list_of_dicts] ********************************************
ok: [localhost] => (item={'first_name': 'Alice', 'last_name': 'Cooper', 'job': 'singer'}) => {
"msg": "Firstname: Alice Lastname: Cooper"
}
ok: [localhost] => (item={'first_name': 'Bob', 'last_name': 'Marley', 'job': 'singer'}) => {
"msg": "Firstname: Bob Lastname: Marley"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Auch hier ist wieder an den { }
zu erkennen, dass item
jeweils eine Liste enthält. Der Zugriff auf die Werte geschieht durch die Referenzierung des jeweiligen Schlüssel-Namens. So ist z.B. Alice der Wert des Schlüssels first_name
.
Jörn hat ein weiteres Beispiel beigesteuert. Da die Kommentarfunktion von WordPress den Code nicht sauber darstellt, spendiere ich Jörn eine eigene Überrschrift und baue sein Beispiel hier ein.
Jörns Datenstruktur sieht wie folgt aus:
dict_of_lists:
- name: foo
elems:
- foo one
- foo two
- foo three
- name: bar
elems:
- bar one
- bar two
- name: baz
elems:
- baz one
- baz two
- baz three
Jörn möchte nun zuerst auf alle Elemente (elems
) von foo
zugreifen, dann auf jene von bar
usw. Dazu nutzt Jörn das Lookup Plugin subelements.
Folgender Code-Block nutzt die Datenstruktur in einem Playbook, um die verschachtelten Elemente auszugeben. Aufruf und Ausgabe zeigt der darauf folgende Block.
---
- name: Lists in dicts
hosts: localhost
become: false
vars:
dict_of_lists:
- name: foo
elems:
- foo one
- foo two
- foo three
- name: bar
elems:
- bar one
- bar two
- name: baz
elems:
- baz one
- baz two
- baz three
tasks:
- name: Loop over lists in dicts
ansible.builtin.debug:
msg: "Name: {{ item.0.name }}, element {{ item.1 }}"
loop: "{{ dict_of_lists | subelements('elems') }}
Und hier nun der Playbook-Aufruf mit Ausgabe:
$ ansible-playbook dict_of_lists.yml
PLAY [Lists in dicts] ************************************************************************
TASK [Gathering Facts] ************************************************************************
ok: [localhost]
TASK [Loop over lists in dicts] ************************************************************************
ok: [localhost] => (item=[{'name': 'foo', 'elems': ['foo one', 'foo two', 'foo three']}, 'foo one']) => {
"msg": "Name: foo, element foo one"
}
ok: [localhost] => (item=[{'name': 'foo', 'elems': ['foo one', 'foo two', 'foo three']}, 'foo two']) => {
"msg": "Name: foo, element foo two"
}
ok: [localhost] => (item=[{'name': 'foo', 'elems': ['foo one', 'foo two', 'foo three']}, 'foo three']) => {
"msg": "Name: foo, element foo three"
}
ok: [localhost] => (item=[{'name': 'bar', 'elems': ['bar one', 'bar two']}, 'bar one']) => {
"msg": "Name: bar, element bar one"
}
ok: [localhost] => (item=[{'name': 'bar', 'elems': ['bar one', 'bar two']}, 'bar two']) => {
"msg": "Name: bar, element bar two"
}
ok: [localhost] => (item=[{'name': 'baz', 'elems': ['baz one', 'baz two', 'baz three']}, 'baz one']) => {
"msg": "Name: baz, element baz one"
}
ok: [localhost] => (item=[{'name': 'baz', 'elems': ['baz one', 'baz two', 'baz three']}, 'baz two']) => {
"msg": "Name: baz, element baz two"
}
ok: [localhost] => (item=[{'name': 'baz', 'elems': ['baz one', 'baz two', 'baz three']}, 'baz three']) => {
"msg": "Name: baz, element baz three"
}
PLAY RECAP ************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Das waren nun eine Menge Code-Blöcke. Mir hat es geholfen, dieses Thema noch einmal zu rekapitulieren. Listen können direkt an loop
übergeben werden. Dictionaries müssen zuerst den Filter dict2items
durchlaufen.
In diesem Text wurden noch nicht alle Fälle besprochen. So wurden nested lists und tiefer verschachtelte Dictionaries ausgespart, um den Artikel nicht noch mehr in die Länge zu ziehen.
Ich hoffe, der Text war auch für die Anfänger und Einsteiger unter euch hilfreich.
Willkommen zu Teil 6 meiner losen Reihe über die RHEL System Roles. In diesem Teil stelle ich euch die Rolle storage vor, mit welcher sich unpartitionierte Laufwerke und LVM-Volumes verwalten lassen.
Zuerst stelle ich meinen Anwendungsfall vor. Anschließend beschreibe ich, wie ich diesen mithilfe der RHEL System Role storage löse.
Während mir dieser Artikel zur Dokumentation dient, soll er euch den Einsatz von RHEL System Roles verdeutlichen.
Hinweis: Zum Zeitpunkt der Erstellung dieses Artikels unterstützt die Rolle die LVM-Konfiguration lediglich auf unpartitionierten Laufwerken, welche komplett als Physical Volume (PV) genutzt werden.
Wer sich an dieser Stelle fragt, was RHEL System Roles sind, wie man sie installiert und nutzt, dem empfehle ich am Anfang zu beginnen: Vorstellung der Red Hat Enterprise Linux (RHEL) System Roles.
Mit der Ansible-Rolle kvm_provision_lab (siehe [1,2]) provisioniere ich virtuelle Maschinen (VM) auf KVM/QEMU-Hypervisoren. In „Labor-Umgebung mit Ansible in KVM erstellen“ habe ich die Anwendung dieser Rolle bereits detailliert beschrieben. Eine VM wird darin als ein YAML-Dictionary nach folgendem Muster definiert:
test-vm1:
vm_ram_mb: 512
vm_vcpus: 1
vm_iftype: network
vm_net: default
os_type: rhel9
file_type: qcow2
base_image_name: rhel9-template
vm_template: "rhel9-template"
second_hdd: true
second_hdd_size: "2G"
Das Beispiel im Code-Block provisioniert eine VM mit einem zweiten Blocklaufwerk. Dieses wird in der VM als /dev/vdb
konfigruiert.
Um das zweite Laufwerk nutzen zu können, müssen zuerst eine Partitionstabelle und eine Partition erstellt und diese mit einem Dateisystem formatiert werden. Alternativ kann das Gerät auch für LVM verwendet werden.
Ich möchte aus /dev/vdb
ein PV für LVM machen, um es einer Volume Group (VG) vg_data hinzuzufügen und ein Logical Volume (LV) zu erstellen, welches die gesamte Speicherkapazität von /dev/vdb
nutzt.
Durch die Installation des Pakets rhel-system-roles
existiert diese Rolle storage bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.stroage/
und die Dokumentation in /usr/share/doc/rhel-system-roles/storage/README.md
. Aus letzterer stammt auch folgendes Beispiel:
Example Playbook
----------------
```yaml
- hosts: all
roles:
- name: rhel-system-roles.storage
storage_pools:
- name: app
disks:
- sdb
- sdc
volumes:
- name: shared
size: "100 GiB"
mount_point: "/mnt/app/shared"
#fs_type: xfs
state: present
- name: users
size: "400g"
fs_type: ext4
mount_point: "/mnt/app/users"
storage_volumes:
- name: images
type: disk
disks: ["mpathc"]
mount_point: /opt/images
fs_label: images
```
Da ich auf /dev/vdb ein LVM konfigurieren möchte, kopiere ich mir das Dictionary storage_pools
aus obigen Beispiel und passe es für mein Playbook an.
---
- hosts: test-vm1
roles:
- name: rhel-system-roles.storage
storage_pools:
- name: vg_data
disks:
- vdb
volumes:
- name: data1
size: "2 GiB"
mount_point: "/mnt"
fs_type: ext4
state: present
Obiges Playbook führt folgende Schritte auf dem Host test-vm1
durch:
/dev/vdb
wird als PV für LVM konfiguriert.vg_data
auf dem PV /dev/vdb
erstellt.vg_data
wird das LV data1
erstellt./mnt
eingehängt.Innerhalb des Gast-Betriebssystems lässt sich mit folgenden Kommandos prüfen, dass die Konfiguration wie gewünscht durchgeführt wurde.
[root@test-vm1 ~]# lsblk vdb
lsblk: vdb: not a block device
[root@test-vm1 ~]# lsblk /dev/vdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vdb 252:16 0 2G 0 disk
└─vg_data-data1 253:0 0 2G 0 lvm /mnt
[root@test-vm1 ~]# pvs
PV VG Fmt Attr PSize PFree
/dev/vdb vg_data lvm2 a-- <2.00g 0
[root@test-vm1 ~]# vgs
VG #PV #LV #SN Attr VSize VFree
vg_data 1 1 0 wz--n- <2.00g 0
[root@test-vm1 ~]# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
data1 vg_data -wi-ao---- <2.00g
[root@test-vm1 ~]# mount | grep mnt
/dev/mapper/vg_data-data1 on /mnt type ext4 (rw,relatime,seclabel)
[root@test-vm1 ~]# echo "Hallo Welt!" >/mnt/world.txt
[root@test-vm1 ~]# cat /mnt/world.txt
Hallo Welt!
Der betrachtete Anwendungsfall lässt sich mit der vorgestellten Ansible-Rolle schnell und einfach umsetzen. Man deklariert lediglich die Wunschkonfiguration im Ansible-Playbook und die Rolle kümmert sich um den Rest, wie die Installation der notwendigen Pakete auf den Zielsystemen.
Unterstützt werden als Zielsysteme aktuell EL 7-9 sowie Fedora. Damit ist sie für die Anwendung auf Debian bzw. darauf basierende Systeme nicht geeignet. Wie man auch für diese Systeme ein einfaches Playbook entwirft, um LVM für Blockgeräte zu konfigurieren, werde ich in einem folgenden Artikel zeigen.
Ich hoffe, dass euch auch die Vorstellung dieser Rolle gefallen hat und wünsche euch viel Spaß bei der Nutzung der RHEL System Roles.
Die AlmaLinux OS Foundation hat das zu Red Hat Enterprise Linux 9.2 binärkompatible AlmaLinux OS 9.2 mit dem Codenamen “Turquoise Kodkod” veröffentlicht.
Die neue Version enthalte Sicherheitsaktualisierungen wie die Systemrolle realmd, ein SCAP-Profil und Ansible-Inhalte für erweiterte Systemprüfungen, die die Verwaltung von Sicherheit und Compliance vereinfachen, heißt es in der Ankündigung. Erweiterungen der Webkonsole und neue Systemrollen sollen zudem die Automatisierung und Standardisierung von Systemen erleichtern. Bei Containern stellen die Anbieter eine einfachere Entwicklung und Verwaltung von containerisierten Deployments in Aussicht.
Zu den Modul-Updates zählen Python 3.11, Nginx 1.22 und PostgreSQL 15. Toolchain-Updates gibt es in Form von GCC 11.3.1, Glibc 2.34 und Binutils 2.35.2.
Neu vorgestellt hat die AlmaLinux OS Foundation das Projekt Elevate, das ein Upgrade zwischen den Hauptversionen der RHEL-basierten Distributionen von 7.x auf 8.x und von 8.x auf 9.x ermöglichen soll. Es kombiniere das Leapp-Framework von Red Hat mit einer von der Community erstellten Bibliothek und einem Service für Migrationsmetadaten.
Der Beitrag AlmaLinux OS 9.2 mit Projekt Elevate erschien zuerst auf Linux-Magazin.
In Teil 5 meiner losen Reihe über die RHEL System Roles stelle ich die Rolle firewall vor. Diese dient der Konfiguration des Regelwerks für firewalld. Möchte man bestimmte Regelsätze für alle bzw. eine Gruppe von Servern konfigurieren, erleichtert der Einsatz dieser Rolle die Arbeit und reduziert den administrativen Aufwand.
Im Folgenden stelle ich meinen Anwendungsfall vor. Anschließend beschreibe ich, wie ich diesen mithilfe der RHEL System Role löse.
Während mir dieser Artikel zur Dokumentation dient, soll er euch den Einsatz von RHEL Roles verdeutlichen.
Warnung: Die Firewall-Konfiguration eines Hosts über das Netzwerk birgt die Gefahr, sich selbst auszusperren. Wenn dies passiert, benötigt man höchstwahrscheinlich physischen Zugriff auf den verkonfigurierten Host.
Ich möchte sicherstellen, dass auf allen RHEL-Systemen in meiner Labor-Umgebung das Paket firewalld
installiert ist, der Service aktiviert ist und läuft. Darüber hinaus möchte ich sicherstellen, dass ausschließlich der SSH-Zugriff in der lokalen Hostfirewall freigegeben ist und alle übrigen Regeln entfernt werden.
Durch die Installation des Pakets rhel-system-roles
existiert diese Rolle bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.firewall/
und die Dokumentation in /usr/share/doc/rhel-system-roles/firewall/README.md
. Aus letzterer stammt auch folgendes Beispiel:
---
- name: Erase existing config and enable ssh service
hosts: myhost
vars:
firewall:
- previous: replaced
- service: 'ssh'
state: 'enabled'
roles:
- rhel-system-roles.firewall
Dieses Beispiel kann ich direkt in das folgende Playbook übernehmen.
Das Playbook ist kompakt und übersichtlich:
---
- name: Ensure firewalld is started with SSH-acess only
hosts: all
vars:
firewall:
- previous: replaced
- service: 'ssh'
state: 'enabled'
roles:
- rhel-system-roles.firewall
Der folgende Playbook-Lauf in meiner Labor-Umgebung zeigt, dass sich die RHEL System Role auch um die Installation, Aktivierung und den Start des Dienstes firewalld kümmert, wenn dies erforderlich ist.
[root@ansible-ctrl ansible]# ansible-playbook firewall_config.yml
PLAY [Ensure firewalld is started with SSH-acess only] ************************************
TASK [Gathering Facts] ********************************************************************
ok: [rhel7]
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel8]
TASK [rhel-system-roles.firewall : include_tasks] *****************************************
included: /usr/share/ansible/roles/rhel-system-roles.firewall/tasks/firewalld.yml for ansible-pctrl, rhel7, rhel8, rhel9
TASK [rhel-system-roles.firewall : Ensure ansible_facts used by role] *********************
ok: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]
TASK [rhel-system-roles.firewall : Install firewalld] *************************************
ok: [ansible-pctrl]
changed: [rhel9]
changed: [rhel8]
changed: [rhel7]
TASK [rhel-system-roles.firewall : Install python-firewall] *******************************
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel9]
ok: [rhel7]
TASK [rhel-system-roles.firewall : Install python3-firewall] ******************************
skipping: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]
TASK [rhel-system-roles.firewall : Enable and start firewalld service] ********************
ok: [ansible-pctrl]
changed: [rhel7]
changed: [rhel9]
changed: [rhel8]
TASK [rhel-system-roles.firewall : Check if previous replaced is defined] *****************
ok: [rhel7]
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel8]
TASK [rhel-system-roles.firewall : Get config files, checksums before and remove] *********
ok: [rhel9]
ok: [rhel7]
ok: [rhel8]
ok: [ansible-pctrl]
TASK [rhel-system-roles.firewall : Configure firewall] ************************************
ok: [rhel7] => (item={'service': 'ssh', 'state': 'enabled'})
ok: [rhel9] => (item={'service': 'ssh', 'state': 'enabled'})
ok: [rhel8] => (item={'service': 'ssh', 'state': 'enabled'})
ok: [ansible-pctrl] => (item={'service': 'ssh', 'state': 'enabled'})
TASK [rhel-system-roles.firewall : gather firewall config information] ********************
skipping: [ansible-pctrl] => (item={'service': 'ssh', 'state': 'enabled'})
skipping: [rhel9] => (item={'service': 'ssh', 'state': 'enabled'})
skipping: [rhel7] => (item={'service': 'ssh', 'state': 'enabled'})
skipping: [rhel8] => (item={'service': 'ssh', 'state': 'enabled'})
TASK [rhel-system-roles.firewall : update firewalld_config fact] **************************
skipping: [rhel7]
skipping: [ansible-pctrl]
skipping: [rhel9]
skipping: [rhel8]
TASK [rhel-system-roles.firewall : gather firewall config if no arguments] ****************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel9]
skipping: [rhel8]
TASK [rhel-system-roles.firewall : update firewalld_config fact] **************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel9]
skipping: [rhel8]
TASK [rhel-system-roles.firewall : Get config files, checksums after] *********************
ok: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]
TASK [rhel-system-roles.firewall : Calculate what has changed] ****************************
changed: [ansible-pctrl]
changed: [rhel7]
changed: [rhel9]
changed: [rhel8]
TASK [rhel-system-roles.firewall : Show diffs] ********************************************
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel7]
skipping: [rhel9]
PLAY RECAP ********************************************************************************
ansible-pctrl : ok=11 changed=1 unreachable=0 failed=0 skipped=6 rescued=0 ignored=0
rhel7 : ok=11 changed=3 unreachable=0 failed=0 skipped=6 rescued=0 ignored=0
rhel8 : ok=11 changed=3 unreachable=0 failed=0 skipped=6 rescued=0 ignored=0
rhel9 : ok=11 changed=3 unreachable=0 failed=0 skipped=6 rescued=0 ignored=0
Ich beginne, mich an dieser Stelle zu wiederholen. Auch diesmal war es möglich, mithilfe einer RHEL System Role einen einfachen Anwendungsfall schnell und unkompliziert zu lösen, ohne selbst eine Ansible-Rolle schreiben zu müssen. Ein einfaches Copy-Past-and-Modify genügte.
In meinen Augen ist es Red Hat gelungen, den System-Administratoren mit den RHEL System Roles etwas Arbeit abzunehmen und sie beim Einsatz von Ansible zu unterstützen.
Lasst euch überraschen, welche Rolle ich mir als nächstes herauspicke.
Wer diesen Blog regelmäßig liest, kann den Eindruck gewinnen, es sei mein Hobby, Ansible-Rollen zu schreiben, mit denen von mir genutzte Web-Anwendungen auf Servern bereitgestellt werden können. Dieses Mal habe ich es mit Seafile getan und möchte in diesem Beitrag darüber berichten.
Seafile ist eine Sync&Share- bzw. Private-Cloud-Lösung ähnlich wie Nextcloud, ownCloud oder TeamDrive. Auf mich erweckt Seafile den Eindruck, als wenn der Schwerpunkt jedoch auf der Synchronisation und dem Teilen von Dateien liegt und damit genau meinem Suchmuster entspricht.
Seafile gibt es in einer Community und einer Professional Edition. Die Professional Edition darf mit bis zu drei Benutzern kostenlos verwendet werden.
Für weiterführende Informationen wird auf die Seiten des Herstellers und den Wikipedia-Artikel verwiesen.
Nun, es gibt nicht das eine Ziel. Ich habe mit diesem kleinen Wochenendprojekt folgende Ziele verfolgt:
Zuerst habe ich mich informiert, ob es Container-Images für Seafile gibt und ob entsprechende Installationswege in der Dokumentation beschrieben sind. Meine Recherche förderte folgende Treffer zutage:
Ansible-Rollen haben eine einheitliche Struktur. Mit dem Befehl ansible-galaxy role init ansible_role_deploy_seafile_with_rootless_podman
habe ich das Grundgerüst für meine Rolle erstellt. Anschließend habe ich die notwendigen Dateien {defaults,meta,tasks,vars}/main.yml
mit Inhalt gefüllt und nicht benötigte Verzeichnisse wie handlers
gelöscht. Mir ist dabei wichtig, dass alle notwendigen Parameter über Variablen definiert werden, die in defaults/main.yml
zu finden sind. In vars/main.yml
befinden sich hingegen nur Variablen, welche intern von der Rolle verwendet werden und vom Benutzer nicht explizit gesetzt werden sollen. So lässt sich die Rolle leicht wiederverwenden, um verschiedene Seafile-Instanzen auf dem gleichen Host oder in unterschiedlichen Umgebungen zu deployen.
Bevor ich die Rolle zum ersten Mal auf meinen Server angewendet habe, habe ich sie mit yamllint und ansible-lint geprüft und vorhandene Warnungen und Fehler behoben. Allerdings lassen sich mit den Lint-Werkzeugen und der Option --syntax-check
nicht alle Fehler im Vorfeld finden. Da mir ein zweites Augenpaar fehlte, habe ich die letzten Tippfehler erst durch die Verwendung des Ansible Playbook Debugger gefunden.
Das Ergebnis findet ihr auf:
Unter allen drei URLs könnt ihr meine Rolle herunterladen. Es ist damit möglich, eine lauffähige Seafile Pro Instanz bereitzustellen. Ein Test auf Idempotenz und ob diese Rolle auch zur Aktualisierung einer bestehenden Umgebung genutzt werden kann, steht noch aus.
Ihr seid herzlich eingeladen, die Rolle bei Interesse zu testen. Ich freue mich über Rückmeldungen zur Rolle und Dokumentation (Readme.md).
Ich habe das Deployment bisher nur auf Debian Buster getestet. Daher freue ich mich besonders über Rückmeldungen, wenn ihr die Rolle erfolgreich auf anderen Plattformen angewendet habt. Dann kann ich die entsprechenden Angaben für Ansible Galaxy ergänzen.
Eure Rückmeldungen nehme ich in den Kommentaren zu diesem Beitrag, per E-Mail oder in meinem neuen Matrix-Kanal #my-it-brain:matrix.org
entgegen.
Ich interessiere mich für Themen rund um Ansible und Podman und frage mich, wie dies bei euch aussieht. Daher freue ich mich, wenn ihr in den Kommentaren oder gern auch per E-Mail und Chat folgende Fragen beantworten mögt:
Ich freue mich auf eure Antworten.
In Teil 4 meiner losen Reihe über die RHEL System Roles stelle ich die Ansible-Rolle sshd vor. Diese dient der Konfiguration des OpenSSH-Servers, einem der wichtigsten Dienste in Linux- und UNIX-Systemen.
Wer die ersten Teile dieser Reihe gelesen hat, ist inzwischen mit der grundsätzlichen Anwendung dieser Ansible-Rollen vertraut. Die Rolle sshd bildet hier keine Ausnahme. Wendet man die Rolle ohne weitere Konfiguration auf Ziel-Systeme an, konfiguriert sie den OpenSSH-Server entsprechend der Standard-Konfiguration des jeweiligen Betriebssystems. Es werden alle Optionen der sshd_config(5) unterstützt.
Ein Wort der Warnung: Mit dieser Rolle konfiguriert ihr den SSH-Dienst der Zielsysteme. Wenn dabei ein Fehler passiert, könnt ihr euch und euren Ansible-Controller aussperren und verliert ggf. den Zugriff auf die Systeme. Behaltet dies bitte im Hinterkopf und sorgt ggf. für alternative Zugänge, wie z.B. über eine lokale Konsole.
Bei der Konfiguration meiner Server ist mir persönlich wichtig, dass
.ssh/authorized_keys
des jeweiligen Benutzers nach dem SSH-Public-Key gesucht wird.Darüber hinaus möchte ich alle Git-bezogenen Umgebungsvariablen (GIT_*
) nutzen. Die übrigen Einstellungen möchte ich auf den Standard-Werten des jeweiligen Betriebssystems belassen.
Im Folgenden beschreibe ich, wie sich diese mit der RHEL System Role sshd umsetzen lässt.
Wie bei allen RHEL System Roles müssen auch hier die Pakete ansible-core
und rhel-system-roles
inkl. ihrer Abhängigkeiten auf dem Ansible-Controller installiert sein. Der Ansible-Controller muss die Ziel-Hosts über SSH erreichen können und über einen Benutzer mit sudo-Berechtigungen verfügen.
Es werden bereits zwei Beispiel-Playbooks mitgeliefert, die sich im Pfad /usr/share/doc/rhel-system-roles/sshd/
befinden. Diese heißen:
example-accept-env-playbook.yml
undexample-root-login-playbook.yml
.Aus diesen beiden Beispieldateien habe ich das folgende Playbook für meine Labor-Umgebung erstellt:
---
- hosts: all
tasks:
- name: Configure sshd to accept some useful environment variables
include_role:
name: rhel-system-roles.sshd
vars:
sshd:
PermitRootLogin: without-password
PasswordAuthentication: no
PubkeyAuthentication: yes
AuthorizedKeysFile: .ssh/authorized_keys
# there are some handy environment variables to accept
AcceptEnv:
LANG
LS_COLORS
EDITOR
GIT_*
Wie zu sehen ist, habe ich mich entschieden, noch ein paar weitere Umgebungsvariablen zu konfigurieren. Diese habe ich aus dem Beispiel example-accept-env-playbook.yml
übernommen.
Auch dieses Playbook habe ich in meiner Labor-Umgebung, bestehend aus einem RHEL8-Ansible-Controller und jeweils einem rhel{7..9}-Client laufen lassen. Mit den Optionen -C -D
ist die Ausgabe 707 Zeilen lang, weswegen der folgende Code-Block nur den Aufruf und das Ergebnis zeigt.
[root@ansible-ctrl ansible]# ansible-playbook sshd_config.yml -C -D
PLAY [all] ************************************************************************************************************
[...]
PLAY RECAP *******************************************************************************************************************************
ansible-pctrl : ok=20 changed=2 unreachable=0 failed=0 skipped=13 rescued=0 ignored=0
rhel7 : ok=20 changed=2 unreachable=0 failed=0 skipped=13 rescued=0 ignored=0
rhel8 : ok=20 changed=2 unreachable=0 failed=0 skipped=13 rescued=0 ignored=0
rhel9 : ok=21 changed=2 unreachable=0 failed=0 skipped=12 rescued=0 ignored=0
Die RHEL System Role sshd wurde kurz vorgestellt und genutzt, um meine bevorzugten Einstellungen für den OpenSSH-Dienst in meiner Labor-Umgebung zu konfigurieren. Alle Optionen in der sshd_config(5), welche ich nicht explizit über die Ansible-Rolle konfiguriert habe, werden auf die Standardwerte des Betriebssystems eingestellt. Es ist also ggf. Vorsicht geboten, wenn Systeme mit bestehender Konfiguration bearbeitet werden.
Selbstverständlich schützt ein einmaliger Playbook-Lauf nicht davor, dass ein Benutzer mit root-Berechtigungen lokale Änderungen an der Datei /etc/ssh/sshd_config
vornimmt. Dies mag vorübergehend für Tests auch so gewollt sein. Damit die Konfiguration nicht dauerhaft vom SOLL-Zustand abweicht, kann man das Playbook regelmäßig durch cron(8) ausführen lassen, um evtl. Abweichungen zu korrigieren.
In diesem dritten Teil meiner Serie über RHEL System Roles nutze ich die Rolle timesync, um die NTP-Pool-Zone de.pool.ntp.org für meine Hosts zu konfigurieren.
Ich möchte mit diesem Artikel zeigen, wie einfach die Nutzung der RHEL System Roles ist, um eine Gruppe von RHEL-Servern zu konfigurieren. Dabei muss ich mich nicht um Details wie die Frage kümmern, ob auf meinen Zielhosts ntpd oder chronyd für die Zeitsynchronisierung genutzt wird. Diese Aufgabe löst die Ansible-Rolle für mich.
Bevor ich fortfahre, habe ich eine Warnung: Diese Rolle ersetzt die Konfiguration auf den Zielsystemen. Alle zuvor dort getroffenen Einstellungen werden verloren gehen.
Man muss sich also entscheiden, ob man die Zeitsynchronisation komplett über diese Rolle steuern möchte oder gar nicht.
Auf dem Ansible-Controller müssen die Pakete ansible-core
und rhel-system-roles
installiert sein.
Ich möchte mehrere NTP-Server konfigurieren. Für diesen Anwendungsfall liefert die Rolle timesync bereits ein Beispiel mit, welches ich mittels Copy-Paste-and-Modify in mein Playbook übernehme.
[root@ansible-ctrl ]# cp /usr/share/doc/rhel-system-roles/timesync/example-multiple-ntp-servers-playbook.yml ansible/use_de_ntp_servers.yml
Das Playbook sieht nach der Anpassung wie folgt aus:
- hosts: all
vars:
timesync_ntp_servers:
- hostname: 0.de.pool.ntp.org
iburst: yes
- hostname: 1.de.pool.ntp.org
iburst: yes
- hostname: 2.de.pool.ntp.org
iburst: yes
- hostname: 3.de.pool.ntp.org
iburst: yes
roles:
- rhel-system-roles.timesync
Um zu sehen, wie die Datei /etc/chrony.conf
vor und nach dem Playbook-Lauf aussieht, lasse ich das Playbook zuerst mit den Optionen -C
(aktiviert Check-Mode) und -D
(zeigt die Änderungen an) laufen. So kann ich vorab prüfen, welche Änderungen vorgenommen werden, bevor es ernst wird. Die Ausgabe ist über 500 Zeilen lang. Ich habe sie auf Gist gepostet und hier eingebunden. Wer sich für die Ausgabe nicht interessiert, kann direkt zur Zusammenfassung springen.
Anschließend habe ich das Playbook ohne die Optionen -C
und -D
ausgeführt und meine Hosts wie gewünscht konfiguriert.
Mit der RHEL System Role timesync kann die Zeitsynchronisation verschiedener RHEL-Releases schnell und einfach konfiguriert werden, ohne Kenntnis über die konkrete Implementierung auf den Zielsystemen zu besitzen.
Gleichzeitig kann ein Blick in die Struktur der Rolle und den Inhalt der dazugehörigen Dateien Aufschluss darüber geben, wie Ansible-Rollen für mehrere RHEL-Major-Releases erstellt werden können. Man kann dies für die Erstellung eigener Rollen mit ein wenig Transferleistung wiederverwenden.
Dies ist Teil 2 meiner kleinen Serie zu den RHEL System Roles. Ich beschreibe hierin, wie die Ansible-Rolle selinux genutzt werden kann, um Einstellungen für SELinux auf mehreren/allen Hosts in der eigenen Infrastruktur zu konfigurieren.
Die Anforderung dies zu tun, lässt sich bspw. aus den IT-Grundschutzbausteinen SYS.1.3.A10, SYS.1.3.A16, SYS.2.3.A8 und SYS.2.3.A17 des BSI [2] ableiten.
Falls euch SELinux noch nichts sagt, schaut zuerst in meine Einführung in das grundlegende Konzept von SELinux [1].
In dem folgenden und zugegeben sehr einfachen Beispiel nutze ich ein Playbook, welches sicherstellt, dass SELinux auf allen Ziel-Hosts im Modus Enforcing läuft. Dieses Playbook kann ich dann bspw. durch cron(8) in regelmäßigen Abständen laufen lassen, um sicherzustellen, dass sich SELinux im gewünschten Modus befindet bzw. in diesen zurückversetzt wird.
Auf dem Ansible-Controller müssen die Pakete ansible-core
und rhel-system-roles
installiert sein.
Die Dokumentation zu dieser Ansible-Rolle befindet sich in /usr/share/doc/rhel-system-roles/selinux/README.md
. Darin enthalten ist auch ein Code-Beispiel, aus dem ich das folgende Playbook erstellt habe:
---
- name: Enforce SELinux Policy
hosts: all
vars:
selinux_policy: targeted
selinux_state: enforcing
roles:
- role: rhel-system-roles.selinux
become: true
Der erste Code-Block gibt die Ausgabe des Playbook-Laufs wieder. Der zweite Code-Block zeigt ein Ansible-Ad-Hoc-Kommando, mit dem ich kontrolliere, ob Ansible auf allen Ziel-Hosts im Enforcing-Modus läuft.
[root@ansible-ctrl ansible]# pwd
/root/ansible
[root@ansible-ctrl ansible]# ansible-playbook enfoce_selinux.yml
PLAY [Enforce SELinux Policy] **************************************************************************
TASK [Gathering Facts] *********************************************************************************
ok: [rhel7]
ok: [rhel8]
ok: [rhel9]
ok: [ansible-pctrl]
TASK [rhel-system-roles.selinux : Set ansible_facts required by role and install packages] *************
included: /usr/share/ansible/roles/rhel-system-roles.selinux/tasks/set_facts_packages.yml for ansible-pctrl, rhel7, rhel8, rhel9
TASK [rhel-system-roles.selinux : Ensure ansible_facts used by role] ***********************************
skipping: [rhel7]
skipping: [ansible-pctrl]
skipping: [rhel9]
skipping: [rhel8]
TASK [rhel-system-roles.selinux : Install SELinux python2 tools] ***************************************
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel9]
ok: [rhel7]
TASK [rhel-system-roles.selinux : Install SELinux python3 tools] ***************************************
skipping: [rhel7]
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel8]
TASK [rhel-system-roles.selinux : refresh facts] *******************************************************
ok: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]
TASK [rhel-system-roles.selinux : Install SELinux tool semanage] ***************************************
skipping: [rhel7]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel8]
TASK [rhel-system-roles.selinux : Set permanent SELinux state if enabled] ******************************
ok: [rhel7]
ok: [rhel9]
ok: [rhel8]
ok: [ansible-pctrl]
TASK [rhel-system-roles.selinux : Set permanent SELinux state if disabled] *****************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Set selinux_reboot_required] *****************************************
ok: [ansible-pctrl]
ok: [rhel9]
ok: [rhel7]
ok: [rhel8]
TASK [rhel-system-roles.selinux : Fail if reboot is required] ******************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Warn if SELinux is disabled] *****************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Drop all local modifications] ****************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Purge all SELinux boolean local modifications] ***********************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Purge all SELinux file context local modifications] ******************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Purge all SELinux port local modifications] **************************
skipping: [rhel7]
skipping: [ansible-pctrl]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Purge all SELinux login local modifications] *************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
TASK [rhel-system-roles.selinux : Set SELinux booleans] ************************************************
TASK [rhel-system-roles.selinux : Set SELinux file contexts] *******************************************
TASK [rhel-system-roles.selinux : Restore SELinux labels on filesystem tree] ***************************
TASK [rhel-system-roles.selinux : Restore SELinux labels on filesystem tree in check mode] *************
TASK [rhel-system-roles.selinux : Set an SELinux label on a port] **************************************
TASK [rhel-system-roles.selinux : Set linux user to SELinux user mapping] ******************************
TASK [rhel-system-roles.selinux : Get SELinux modules facts] *******************************************
ok: [rhel8]
ok: [rhel9]
ok: [ansible-pctrl]
ok: [rhel7]
TASK [rhel-system-roles.selinux : include_tasks] *******************************************************
skipping: [ansible-pctrl]
skipping: [rhel7]
skipping: [rhel8]
skipping: [rhel9]
PLAY RECAP *********************************************************************************************
ansible-pctrl : ok=8 changed=0 unreachable=0 failed=0 skipped=17 rescued=0 ignored=0
rhel7 : ok=7 changed=0 unreachable=0 failed=0 skipped=18 rescued=0 ignored=0
rhel8 : ok=8 changed=0 unreachable=0 failed=0 skipped=17 rescued=0 ignored=0
rhel9 : ok=8 changed=0 unreachable=0 failed=0 skipped=17 rescued=0 ignored=0
[root@ansible-ctrl ansible]#
[root@ansible-ctrl ansible]# ansible -m command -a'getenforce' all
rhel7 | CHANGED | rc=0 >>
Enforcing
rhel8 | CHANGED | rc=0 >>
Enforcing
ansible-pctrl | CHANGED | rc=0 >>
Enforcing
rhel9 | CHANGED | rc=0 >>
Enforcing
Mit einem sehr einfachen Beispiel habe ich gezeigt, wie die RHEL System Role SELinux genutzt werden kann, um sicherzustellen, dass SELinux auf allen Ziel-Hosts im Enforcing-Modus läuft.
Dazu habe ich keine Zeile Ansible-Code selbst geschrieben. Den Quelltext für mein Playbook habe ich per Copy-Paste-and-Modify aus der mitgelieferten Dokumentation übernommen. Anschließend habe ich die notwendige Variable definiert und das Playbook ausgeführt. Fertig.
Die Golem Karrierewelt haut in der Black Week einen raus: Unglaubliche 50 Prozent Rabatt auf alle teilnehmenden Produkte des Sortiments! Gültig bis Mittwoch, 30. November 2022!
Zu den rabattierten Angeboten gehört eine umfangreiche Liste an interaktiven Live-Workshops, die den Weg zum IT-Profi ebnen und Best Practices aufzeigen. Zentrale IT-Standards sind dabei selbstverständlich bestens vertreten. Ob Dauerbrenner im Cloudcomputing wie Kubernetes und Ansible, Admin- und Sicherheits-Kurse zur weit verbreiteten IT-Plattform Microsoft 365, oder das allgegenwärtige IT-Security-Thema.
Hinzu kommen Kurse zu Coding und Web Development. Die Bandbreite reicht von TypeScript, Angular, Vue.js über Python bis hin zu C++. Allen interaktiven Workshops gemeinsam ist, dass die Teilnehmer nach Abschluss eine Teilnahmebescheinigung erhalten.
In den E-Learning-Kursen der Golem Karrierewelt wird komplexe IT-Materie in leicht verdauliche Häppchen von fünf- bis zehnminütigen Videolektionen aufgesplittet. Die Teilnehmer können die Lektionen so oft wiederholen wie sie möchten – und wann immer es ihnen beliebt.
Die Bandbreite der Black-Week-Angebote reicht vom Einstieg in Microsoft Office über umfangreiche Pakete zur Systemadministration bis hin zur Linux-Befehlszeile für Anfänger:innen und Sicherheitstests mit Kali Linux. Der 50-prozentige Black-Week-Rabatt gilt auch für bereits preisreduzierte Trainings und Gruppenrabatte.
Wer – neben des Ausbaus seines IT-Know-hows – auch seine allgemeine persönliche Weiterentwicklung im Blick hat, findet in der Golem Karrierewelt eine Reihe hilfreicher Coachingangebote. Ein Highlight im Programm ist der User Experience Essentials Workshop!
Den 50-Prozent-Rabatt gibt’s ebenso auf alle One-on-One- und Gruppen-Coachings von Golem Shifoo. Als Themen werden Prozessoptimierung, Agile, Mitarbeiterkommunikation, Feedback im Team und vieles mehr angeboten. Alle Coaches verfügen über einen Background im IT-Business. Wer gleich sein Team personell verstärken möchte, kann dies im Golem-Stellenanzeigenportal ebenso zu Black-Week-Konditionen tun (50 Prozent Rabatt ausschließlich bei Buchung über das Portal).
War das Erlernen einer neuen Sprache schon mal einfacher als heute? Zu den Vorteilen des zeitgemäßen Lernens per modernen Onlineangeboten gesellt sich der unschlagbare 50-Prozent-Preisvorteil der Black Week. Die von Gymglish entwickelten Sprachkurse der Golem Karrierewelt zeichnen sich durch kurzweilige Rahmenerzählungen aus, die Neugierde auf weitere Lektionen wecken. Ob Englisch, Spanisch, Französisch, Italienisch oder Deutsch, alle Kurse können kostenlos 7 Tage lang ausprobiert werden. Bei Buchung bis zum 30. November gibt es 50 Prozent Rabatt auf den regulären Preis.
Wer von dem Rabatt profitieren möchte, sollte nicht zögern. Einen so günstigen Einstieg in die fachliche und persönliche Weiterentwicklung wie in der Black Week wird man voraussichtlich so bald nicht wieder finden. Jetzt zuschlagen und den 50-Prozent-Rabatt nutzen!*
*Die preisreduzierten Black-Week-Angebote der Golem Karrierewelt sind bis einschließlich Mittwoch, den 30. November 2022 gültig. Etwaige Rabattcodes sind von der Black Week ausgeschlossen.
Der Beitrag Anzeige: 50 Prozent auf Alles in der Golem Karrierewelt! erschien zuerst auf Linux-Magazin.
Um die Paketquellen von Red Hat für Red Hat Enterprise Linux (RHEL) nutzen zu können, wird eine sogenannte Software-Subskription benötigt. Diese bestimmt, auf welche Paketquellen ein System zugreifen und deren Inhalt konsumieren kann.
Das System der Subskriptionen befindet sich im Umbruch (siehe [1]). In diesem Artikel beschreibe ich, wie es bisher war, was sich durch die Aktivierung von Simple Content Access (SCA) [2] ändert und wie ich aktuell meine RHEL-Systeme registriere und deren System Purpose konfiguriere.
Der Text vermittelt dabei Wissen zum Red Hat Subscription Management (RHSM), Simple Content Access (SCA), Subscription Watch (SWatch), dem System Purpose und verlinkt relevante Quellen.
Aus Transparenz-Gründen möchte ich an dieser Stelle darauf hinweisen, dass ich Mitglied der Red Hat Accelerators bin (vgl. hier). Dieser Text spiegelt ausschließlich meine persönliche Meinung wider.
Eine Subskription berechtigt zur Nutzung bestimmter Paketquellen von Red Hat. Sie umfasst in der Regel eine gewisse Menge sogenannter Entitlements. Diese bestimmen, wie viele Systeme von einer Subskription abgedeckt werden.
Ein Beispiel: Eine Subskription für „Red Hat Enterprise Linux Server, Standard (Physical or Virtual Nodes)“ beinhaltet zwei Entitlements. Damit lassen sich ein physischer Server mit bis zu zwei CPU-Sockeln oder zwei virtuelle Maschinen (mit einer beliebigen Anzahl CPUs) zur Nutzung der Paketquellen berechtigen.
In der Regel werden RHEL-Systeme über den subscription-manager
beim Red Hat Subscription Management (RHSM) oder einem Satellite Server registriert. Anschließend werden sie mit einer Subskription verknüpft. Wie man dies lösen kann, habe ich 2019 in dem Artikel RHEL-System registrieren und Subskription hinzufügen beschrieben.
Das RHSM im Customer Portal bietet eine gute Übersicht über die vorhandenen Verträge, die Subskriptionen, deren Laufzeiten und verknüpfte Systeme. Man sieht hier auf einen Blick, ob man ausreichend Subskription hat, um all seine Systeme damit abdecken zu können.
Um ein System beim RHSM zu registrieren und mit einer Subskription zu verknüpfen, muss das System eine Verbindung ins Internet zum RHSM-Dienst aufbauen. Dies ist im Datacenter häufig nicht erwünscht. Für Systeme ohne Zugang zum Internet gibt es die optionale Offline-Registrierung [3], welche jedoch etwas umständlich ist und bei vielen Offline-Systemen nicht skaliert.
Registriert man die Systeme nicht, ist man dennoch zu einer ordentlichen Buchführung verpflichtet, um sicherzustellen, dass man nicht dauerhaft mehr Systeme einsetzt, als durch vorhandene Subskriptionen abgedeckt sind.
Läuft ein Subskriptionsvertrag ab, werden die Entitlements ungültig. Die damit verknüpften Systeme fangen an, sich beim Update-Versuch darüber zu beschweren und verweigern den Zugriff auf die Paketquellen. Das ist besonders ärgerlich, weil es nach meiner Erfahrung bei jeder Vertragsverlängerung passiert. Denn tatsächlich wird der Vertrag nicht verlängert. Es gibt einen neuen Vertrag mit der entsprechenden Anzahl Subskriptionen. Diese müssen dann manuell neu verknüpft werden, was jedes Mal manuellen Pflegeaufwand bedeutet.
Vermutlich um dem zuvor genannten Ärgernis entgegenzuwirken hat Red Hat die Funktion auto-attach entwickelt. Wird die mit einem registrierten System verknüpfte Subskription ungültig sucht auto-attach automatisch nach einer geeigneten freien Subskription und verknüpft diese mit dem jeweiligen System. Nun mag sich manch einer Fragen, wie auto-attach wohl entscheidet, wenn es mehrere Subskriptionen gibt, die prinzipiell geeignet sind. Nach meiner Erfahrung wählt auto-attach mit einer Wahrscheinlichkeit von >95 % die am wenigsten geeignete Subskription aus. In meinen Augen nervt es mehr, als das es hilft.
Das Verknüpfen von Subskriptionen ist für die Buchführung praktisch, für den Betrieb eher nervig. Teilweise stört es sogar Betriebsabläufe, wenn z.B. Updates vorübergehend nicht installiert werden können. Um dem zu begegnen, hat Red Hat Simple Content Access (SCA) [2] geschaffen.
Wird SCA im RHSM aktiviert, müssen Subskriptionen nicht mehr mit Systemen verknüpft werden. RHEL-Systeme, die im RHSM registriert sind, erkennen dies und setzen für den Zugriff auf die Paketquellen kein Entitlement mehr voraus.
Der Betrieb wird vereinfacht. Ein System muss nur noch registriert werden und kann sofort auf Inhalte der diversen Paketquellen zugreifen.
Unterbrechungen im Betriebsablauf bei Ablauf einer Subskription gehören der Vergangenheit an.
Auch auto-attach bereitet nun keinen Ärger mehr.
Die Buchführung wird aufwändiger, da RHSM mit aktivierten SCA nur noch begrenzt dafür taugt. Man muss sich nun einen anderen Weg überlegen, wie man den Überblick behält.
Subscription Watch [4, 5] ist ein SaaS-Dienst in der Hybrid Cloud Console [6], welcher den Kunden dabei unterstützen soll, im Blick zu behalten, wie viele Subskriptionen er besitzt und wie viele er konsumiert. Dabei ist es möglich, mehr zu konsumieren, als man besitzt. Wird dies angezeigt, kann man handeln und fehlende Subskriptionen nachkaufen.
Leider hat die Sache einen Haken. Es funktioniert nicht richtig. In meinem Fall kommt eine Mischung aus kostenpflichtigen Subskriptionen und der Developer Subscription for Teams zum Einsatz. Im Subscription Watch gibt es einen Bug, durch den mir angezeigt wird, ich würde mehr Subskriptionen nutzen, als ich im Bestand habe, obwohl dies nicht der Fall ist.
Ich habe zu dem Fall ein Support-Ticket, in dem das Verhalten reproduziert werden konnte und der Fehler bestätigt wurde. Nur eine Lösung gibt es noch nicht. Leider ist Subscription Watch im aktuellen Status damit nutzlos für mich.
Was der System Purpose ist, wird im Detail in [1] und [7] beschrieben. Red Hat empfiehlt den System Purpose zu pflegen, um u.a. Subscription Watch dabei zu helfen, die konsumierten Inhalte korrekt zu zählen. Das hat folgenden Hintergrund.
Sowohl mit der „Red Hat Enterprise Linux Server, Standard (Physical or Virtual Nodes)“ Subskription als auch mit der „Developer Subscription for Teams“ darf man das RHEL-8-BaseOS-Repo nutzen. Gezählt werden soll in Subscription Watch jedoch nur die kostenpflichtige Subskription (Erstgenannte). Mit Hilfe des System Purpose gibt man an, ob es sich um ein Produktionssystem oder ein Test-/Entwicklungs-System handelt und steuert darüber, ob ein System in Subscription Watch gezählt wird.
Funktionieren tut das Ganze leider (noch) nicht. Unter anderem ist der zuvor erwähnte Bug dafür verantwortlich. Ich pflege den System Purpose jedoch trotzdem, in der Hoffnung, dass es in der Zukunft funktionieren wird.
Ich habe dazu eine kleine Ansible-Rolle erstellt, welche folgende Struktur besitzt:
roles/register_syspurpose/
├── defaults
│ └── main.yml
├── README.md
└── tasks
└── main.yml
Das Readme.md enthält eine Beschreibung der notwendigen Variablen und ein Beispiel-Playbook, wie man diese Rolle nutzt:
register_syspurpose
===================
Register host to RHSM and set System Purpose.
Requirements
------------
* [community.general collection](https://galaxy.ansible.com/community/general)
You might already have installed this collection if you are using Ansible Engine 2.9 or the `ansible` package. It is not included in `ansible-core`. To check whether it is installed, run `ansible-galaxy collection list`.
To install it, use: `ansible-galaxy collection install community.general`.
To use it in a playbook, specify: `community.general.redhat_subscription`.
Role Variables
--------------
```yaml
register_syspurpose_activationkey: register-syspurpose # activationkey for access.redhat.com or Satellite
register_syspurpose_org_id: 123456 # Org ID on access.redhat.com or Satellite
register_syspurpose_role: "Red Hat Enterprise Linux Server"
# possible values are:
# Red Hat Enterprise Linux Server
# Red Hat Enterprise Linux Workstation
# Red Hat Enterprise Linux Compute Node
register_syspurpose_sla: "Self-Support"
# possible values are:
# Premium
# Standard
# Self-Support
register_syspurpose_usage: "Development/Test"
# possible values are:
# Development/Test
# Disaster Recovery
# Production
```
I got these values from the KB [syspurpose_usage: "Development/Test"](https://access.redhat.com/articles/5713081).
There might be other possible values out there. In case you know some valid
addtional values please sent a PR to add them to this documentation.
Dependencies
------------
None.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
~~~
- hosts: all
gather_facts: no
roles:
- register_syspurpose
~~~
License
-------
MIT.
Author Information
------------------
Joerg Kastning - "joerg (dot) kastning '@' uni-bielefeld (dot) de"
Auch die Tasks-Datei ist sehr übersichtlich:
---
# tasks file for register_syspurpose
- name: Register system to RHSM and set syspurpose attributes
redhat_subscription:
state: present
activationkey: "{{ register_syspurpose_activationkey }}"
org_id: "{{ register_syspurpose_org_id }}"
syspurpose:
role: "{{ register_syspurpose_role }}"
service_level_agreement: "{{ register_syspurpose_sla }}"
usage: "{{ register_syspurpose_usage }}"
sync: true
Um die Variablen mit Werten zu belegen, nutze ich group_vars. Ich verwende ein statisches Inventory, in dem ich Gruppen für die möglichen Kombinationen aus role, sla und usage definiert habe. So wird jeder neue Host der entsprechenden Gruppe hinzugefügt und anschließend bei der Provisionierung direkt registriert und korrekt konfiguriert. Detaillierte Informationen zum verwendeten Modul redhat_subscription
bietet die Dokumentation unter [8].
Ihr seht, es steckt keine Magie in der Rolle. But I like to Keep It Simple, Stupid.
Das Red Hat Subscription Management ist kompliziert, hat seine Macken, eine Geschichte und etliche Altlasten. Ein Team um Rich Jerrido hat es sich zur Aufgabe gemacht, das Subskriptions-System zu überarbeiten und alte Zöpfe abzuschneiden. Ich beneide das Team nicht um diese Aufgabe.
Das System befindet sich aktuell im Übergang (siehe [1]). Damit gehen Herausforderungen sowohl für Red Hat als auch dessen Kunden einher.
Das Red Hat die technischen Abhängigkeiten zwischen Betriebssystem und RHSM mit SCA abschafft, weiß ich zu schätzen. Schade, dass die Unterstützung bei der Buchführung dabei auf der Strecke bleibt.
Subscription Watch bietet mir auch in der nahen Zukunft keinen Nutzen. Um meinen Pflichten aus [9] nachzukommen, werde ich mir Red Hat Discovery näher ansehen. Meine Erfahrungen werde ich anschließend hier im Blog niederschreiben.
subscription-manager
commandIn 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.
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.
Um die Ansible-Rolle vsphere_provision_lab
nutzen zu können, benötigt man:
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.
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.
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.
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:
Diesen Block kann man nun für weitere VMs wiederholen. Wird keine zweite HDD benötigt, setzt man vm_second_hdd
auf false
.
---
- 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.
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:
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?
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.
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"
}
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
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.
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.
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.
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.
Um sein MacBook für die Entwicklung oder die Administration zu nutzen sind die MacPorts unverzichtbar. MacPorts ist eine Paketverwaltung, welche es ermöglicht grafische und kommandozeilenorientierte Programme unter macOS via Script zu installieren. Ich benötige die MacPorts, da macOS nicht nativ … Weiterlesen →
Der Beitrag macOS Catalina Installation MacPorts und ansible erschien zuerst auf Got tty.