Lese-Ansicht

Es gibt neue verfügbare Artikel. Klicken Sie, um die Seite zu aktualisieren.

Demonstration von Ansible Lint am Beispiel einer Ansible Collection

In diesem Beitrag erkläre ich kurz, was Ansible Lint ist und demonstriere dessen Anwendung am Beispiel meiner Ansible Collection tronde/nextcloud.

Er richtet sich primär an Personen, die mit Ansible Lint noch nicht vertraut sind. Linting-Profis werden vermutlich keine neuen Erkenntnisse gewinnen.

Was ist Ansible Lint und wofür ist es gut?

Lint (englisch für „Fussel“) ist eine Software zur statischen Code-Analyse. Davon abgeleitet hat sich das Verb linten (englisch to lint) für das Durchführen der statischen Code-Analyse etabliert.

https://de.wikipedia.org/wiki/Lint_(Programmierwerkzeug)

Dem obigen Zitat und der Projektdokumentation folgend, ist Ansible Lint dementsprechend ein Werkzeug zur statischen Code-Analyse von Ansible Playbooks, Roles und Collections. Mit der Anwendung dieses Werkzeugs auf die eigenen Ansible-Inhalte kann sichergestellt werden, dass diese gängigen Konventionen und Standards entsprechen.

Wie wird Ansible Lint installiert?

Die Dokumentation beschreibt verschiedene Installationsverfahren. Ich habe ansible-lint als Bestandteil der Ansible Development Tools (ADT) installiert. Dies ist ein Werkzeugkasten mit weiteren Programmen wie z.B. `ansible-core` und Ansible Molecule, welche ich für die Entwicklung meiner Ansible Collection nutze.

Auf meiner Fedora Workstation habe ich die ADT wie folgt installiert:

~]$ mkdir venv
~]$ cd venv
venv]$ ]$ python -m venv adt
venv]$ source adt/bin/activate
(adt) venv]$ pip install pip --upgrade
Requirement already satisfied: pip in ./adt/lib64/python3.12/site-packages (23.3.2)
Collecting pip
  Using cached pip-24.2-py3-none-any.whl.metadata (3.6 kB)
Using cached pip-24.2-py3-none-any.whl (1.8 MB)
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 23.3.2
    Uninstalling pip-23.3.2:
      Successfully uninstalled pip-23.3.2
Successfully installed pip-24.2
(adt) venv]$  pip install ansible-dev-tools
Collecting ansible-dev-tools
  Using cached ansible_dev_tools-24.7.2-py3-none-any.whl.metadata (11 kB)
… Ausgabe gekürzt
(adt) venv]$ adt --version
ansible-builder                          3.1.0
ansible-core                             2.17.3
ansible-creator                          24.7.1
ansible-dev-environment                  24.7.0
ansible-dev-tools                        24.7.2
ansible-lint                             24.7.0
ansible-navigator                        24.8.0
ansible-sign                             0.1.1
molecule                                 24.8.0
pytest-ansible                           24.8.0
tox-ansible                              24.8.0

Ansible Lint liefert eine ganze Reihe von Profilen mit, welche Autoren unterstützen, die Code-Qualität schrittweise zu verbessern. Der Befehl ansible-lint --list-profiles gibt die verfügbaren Profile mit einer Beschreibung aus.

Ich werde im Folgenden mit dem Profil shared arbeiten, welches für Autoren gedacht ist, die ihre Collection auf https://galaxy.ansible.com veröffentlichen möchten.

Eine Collection linten

Bevor es zur Sache geht, wechsel ich in das Projektverzeichnis meiner Ansible Collection und erstelle einen neuen Branch, mit dem Befehl git checkout -b lint. Die in meinem Repo vorhandene Datei .ansible-lint-ignore lösche ich, da ich im folgenden alle Fehler und Regelverstöße sehen möchte. Zu Beginn stellt sich mein Arbeitsverzeichnis wie folgt dar:

(adt) nextcloud]$ git status
On branch lint
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    .ansible-lint-ignore

no changes added to commit (use "git add" and/or "git commit -a")

Das Programm ansible-lint besitzt mit der Option --fix die Fähigkeit, Fehler automatisch zu korrigieren und auch YAML-Dateien neu zu formatieren. Der folgende Code-Block umfasst die gekürzte Ausgabe, wenn das Kommando ansible-lint --profile=shared --fix im Arbeitsverzeichnis ausgeführt wird.

(adt) nextcloud]$ ansible-lint --profile=shared --fix
WARNING  Listing 37 violation(s) that are fatal
galaxy[no-changelog]: No changelog found. Please add a changelog file. Refer to the galaxy.md file for more info.
galaxy.yml:1

var-naming[pattern]: Variables names should match ^[a-z_][a-z0-9_]*$ regex. (NC_HTML) (vars: NC_HTML)
roles/backup_restore_nextcloud/defaults/main.yml:4

…

risky-file-permissions: File permissions unset or incorrect.
roles/backup_restore_nextcloud/tasks/main.yml:18 Task/Handler: Copy backup files to container host

no-changed-when: Commands should not change things if nothing needs doing.
roles/backup_restore_nextcloud/tasks/main.yml:40 Task/Handler: Import tarball contents into an existing podman volume

no-changed-when: Commands should not change things if nothing needs doing.
roles/backup_restore_nextcloud/tasks/main.yml:54 Task/Handler: Export podman volumes to tarballs

var-naming[pattern]: Variables names should match ^[a-z_][a-z0-9_]*$ regex. (MYSQL_DATABASE) (vars: MYSQL_DATABASE)
roles/deploy_nextcloud_with_mariadb_pod/defaults/main.yml:13

…

Read documentation for instructions on how to ignore specific rule violations.

Modified 6 files.
                     Rule Violation Summary                      
 count tag                    profile rule associated tags       
    33 var-naming[pattern]    basic   idiom                      
     1 risky-file-permissions safety  unpredictability           
     1 galaxy[no-changelog]   shared  metadata                   
     2 no-changed-when        shared  command-shell, idempotency 

Failed: 37 failure(s), 0 warning(s) on 25 files. Profile 'shared' was required, but 'min' profile passed.

Obige Ausgabe:

  • Benennt Funde mit Pfadangabe und Zeilennummer
  • Führt 37 Fehler auf, inkl. der Regeln, die nicht eingehalten werden; Beispiele
    • galaxy[no-changelog]: No changelog found. Please add a changelog file. Refer to the galaxy.md file for more info.
    • risky-file-permissions: File permissions unset or incorrect.
    • no-changed-when: Commands should not change things if nothing needs doing.
    • var-naming[pattern]: Variables names should match ^[a-z_][a-z0-9_]*$ regex.
  • Informiert, dass ansible-lint Änderungen an 6 Dateien vorgenommen hat

Mit git diff verschaffe ich mir einen Überblick, welche Änderungen ansible-lint vorgenommen hat. Dies sind in meinem Fall:

  • Quoting von Strings
  • Einrückung von Kommentaren

Als Nächstes sehe ich mir die übrigen Fehler der Reihe nach an. Die Dokumentation beinhaltet eine Übersicht mit Beschreibungen der einzelnen Regeln. Dies ist nützlich, wenn der kurze Text in der Ausgabe von ansible-lint nicht ausreichend ist.

galaxy[no-changelog]: No changelog found. Please add a changelog file.

Unter Galaxy: Changelog Details finden sich Hinweise, wie dieser Fehler zu beheben ist. Ich erstelle die leere Datei CHANGELOG.md im Wurzelverzeichnis meiner Collection und der Fehler ist abgestellt.

Natürlich werde ich diese Datei zukünftig nutzen, um die wichtigsten Änderungen zu dokumentieren. ;-)

risky-file-permissions: File permissions unset or incorrect.

Auch hier habe ich kurz in der Dokumentation unter risky-file-permissions nachgesehen. Den Fehler habe ich abgestellt, indem ich den Parameter mode: 0600 zum Task hinzugefügt habe.

Dies war ein Flüchtigkeitsfehler, wie er häufig passieren kann, wenn man seinen Code nicht konsequent überprüft. Ohne den Dateimode explizit zu setzen, kann dies zu unvorhersehbaren bzw. überraschenden Verhalten führen.

no-changed-when: Commands should not change things if nothing needs doing.

An zwei Stellen bin ich leider nicht herumgekommen, das ansible.builtin.command Modul zu verwenden, da kein natives Modul für diese Aufgabe existiert. Betrachtet man sich die beiden Tasks fällt auf, dass diese jedes Mal Daten verarbeiten werden. Sie sind nicht idempotent. Im Ergebnis können sie erfolgreich sein oder fehlschlagen, aber sie werden immer Daten verarbeiten und dadurch ändern.

 41     - name: Import tarball contents into an existing podman volume
 42       ansible.builtin.command:
 43         cmd: |
 44           podman volume import
 45           {{ item }} {{ backup_restore_nextcloud_import_path }}/{{ item }}.tar
 46       loop:
 47         - "{{ NC_HTML }}"
 48         - "{{ NC_APPS }}"
 49         - "{{ NC_CONFIG }}"
 50         - "{{ NC_DATA }}"
 51         - "{{ MYSQL_DATA }}"
 52            
 53 # I need to use the command module as the volume module lacks the functionality
 54 # to export podman volumes.
 55 - name: Export podman volumes to tarballs
 56   ansible.builtin.command:
 57     cmd: podman volume export {{ item }} --output {{ backup_restore_nextcloud_export_path }}/{{ item }}.tar
 58   loop:    
 59     - "{{ NC_HTML }}"
 60     - "{{ NC_APPS }}"
 61     - "{{ NC_CONFIG }}"
 62     - "{{ NC_DATA }}"
 63     - "{{ MYSQL_DATA }}"
 64   tags:    
 65     - never
 66     - backup

Um herauszufinden, wie ich ansible-lint zufriedenstellen kann, schaue ich wieder in der Doku unter no-changed-when nach. Nach der dortigen Beschreibung ist mein Fehler, dass ich den Rückgabewert des Kommandos nicht behandel. Daher registriere ich nun eine Variable je Task, die die Task-Ausgabe aufnimmt und prüfe den Rückgabewert. Ist der Rückgabewert gleich 0 wird der Task-Status auf changed gesetzt, ist der Rückgabewert ungleich 0 wird der Status entsprechend auf failed gesetzt. Das ganze sieht nun wie folgt aus:

 41     - name: Import tarball contents into an existing podman volume
 42       ansible.builtin.command:
 43         cmd: |
 44           podman volume import
 45           {{ item }} {{ backup_restore_nextcloud_import_path }}/{{ item }}.tar
 46       register: __import_tar_output
 47       changed_when: __import_tar_output.rc == 0
 48       failed_when: __import_tar_output.rc != 0
 49       loop:
 50         - "{{ NC_HTML }}"
 51         - "{{ NC_APPS }}"
 52         - "{{ NC_CONFIG }}"
 53         - "{{ NC_DATA }}"
 54         - "{{ MYSQL_DATA }}"
 55  
 56 # I need to use the command module as the volume module lacks the functionality
 57 # to export podman volumes.
 58 - name: Export podman volumes to tarballs
 59   ansible.builtin.command:
 60     cmd: podman volume export {{ item }} --output {{ backup_restore_nextcloud_export_path }}/{{ item }}.tar
 61   register: __import_tar_output
 62   changed_when: __import_tar_output.rc == 0
 63   failed_when: __import_tar_output.rc != 0
 64   loop:
 65     - "{{ NC_HTML }}"
 66     - "{{ NC_APPS }}"
 67     - "{{ NC_CONFIG }}"
 68     - "{{ NC_DATA }}"
 69     - "{{ MYSQL_DATA }}"
 70   tags:
 71     - never
 72     - backup

Collection-intern verwendete Variablen leite ich mit zwei Unterstrichen (‚_‘) ein, um mir zu verdeutlichen, dass diese nicht durch den Nutzer gesetzt werden und daher auch nicht im README.md oder defaults/main.yml dokumentiert sind.

var-naming[pattern]: Variables names should match ^[a-z_][a-z0-9_]*$ regex.

Hier brauche ich nicht weiter nachzuschlagen. Ich verstoße gegen diese Regel, da ich meine Variablen-Namen großgeschrieben habe. Die Ausgabe von ansible-lint zeigt dies deutlich:

var-naming[pattern]: Variables names should match ^[a-z_][a-z0-9_]*$ regex. (NC_HTML) (vars: NC_HTML)
roles/backup_restore_nextcloud/defaults/main.yml:4

Diese Meldungen lassen sich mit folgendem Bash-Einzeiler abstellen:

$ for text in $(cut -d':' -f 1 roles/deploy_nextcloud_with_mariadb_pod/defaults/main.yml | grep -v '^$\|^#\|---'); do find roles -type f -iname "*.yml" | xargs sed -i -e "s/$text/\L&/g"; done

Aus verschiedenen Gründen hebe ich mir die Überarbeitung für später auf und nutze die Meldung, um zu demonstrieren, wie man ansible-lint dazu bringt, bestimmte Fehler zu ignorieren.

Um Regeln für ausgewählte Dateien zu ignorieren, spezifiziert man den jeweiligen Dateinamen und den Namen der Regel in der Datei .ansible-lint-ignore, welche im Wurzelverzeichnis der Collection erstellt wird:

nextcloud]$ cat .ansible-lint-ignore 
roles/deploy_nextcloud_with_mariadb_pod/defaults/main.yml var-naming[pattern]
roles/backup_restore_nextcloud/defaults/main.yml var-naming[pattern]

Zweiter Durchgang mit ansible-lint

Damit habe ich alle Probleme, die im ersten Durchlauf von ansible-lint gefunden wurden, adressiert. Ein zweiter Durchlauf zeigt das Ergebnis meiner Arbeit:

(adt) nextcloud]$ ansible-lint --profile=shared
WARNING  Listing 33 violation(s) marked as ignored, likely already known
var-naming[pattern]: Variables names should match ^[a-z_][a-z0-9_]*$ regex. (NC_HTML) (vars: NC_HTML) (warning) # ignored
roles/backup_restore_nextcloud/defaults/main.yml:4

…Ausgabe gekürzt

WARNING  Listing 1 violation(s) that are fatal
yaml[octal-values]: Forbidden implicit octal value "0600"
roles/backup_restore_nextcloud/tasks/main.yml:22

Read documentation for instructions on how to ignore specific rule violations.

                Rule Violation Summary                 
 count tag                profile rule associated tags 
     1 yaml[octal-values] basic   formatting, yaml     

Failed: 1 failure(s), 33 warning(s) on 27 files. Profile 'shared' was required, but 'min' profile passed.

Die ignorierten Regelverstöße werden als Warnung weiterhin ausgegeben, nehmen jedoch keinen Einfluss auf die abschließende Bewertung. Dafür habe ich einen neuen Fehler (yaml[octal-values]) eingebaut. Nach dem aktuellen Regelwerk erfordern oktale Werte ein explizites Quoting, um als Strings verarbeitet zu werden.

Nachdem ich das Problem mit mode: '0600' behoben habe, endet ein weiterer Lauf von ansible-lint schlussendlich mit:

Passed: 0 failure(s), 33 warning(s) on 27 files. Profile 'shared' was required, but 'production' profile passed.

Damit erfüllt meine Collection aktuell sogar die Anforderungen des nächst höheren Profils production; allerdings nur, weil ich einige Regeln bewusst ignoriere. Daher ist aktuell noch nicht sichergestellt, dass meine Collection tatsächlich bei einem Import auf Ansible Galaxy akzeptiert wird.

Zusammenfassung

  • Ansible Lint ist ein Werkzeug zur statischen Analyse von Ansible Playbooks, Roles und Collections
  • Das Werkzeug unterstützt Autoren dabei, gängige Konventionen und Standards einzuhalten und die Qualität des eigenen Codes auf einem Mindest-Niveau zu halten
  • Ansible Lint bietet mehrere Profile für verschiedene Anwendungsfälle
  • Regeln können bei Bedarf ignoriert werden, was zwar das Ergebnis des Linting beeinflusst, die Qualität jedoch nicht steigert
  • Linting sollte fester Bestandteil des eigenen Entwicklungsworkflows sein und stets nach Änderungen durchgeführt werden

Ich persönlich führe ansible-lint gern in einem eigenständigen Schritt aus. Es besteht jedoch auch die Möglichkeit, dies in den verwendeten Editor, die genutzte IDE oder Molecule zu integrieren und bei Änderungen automatisch laufen zu lassen.

Ich freue mich, wenn euch dieser Artikel gefallen hat.

Quellen und weiterführende Links

Meine zweite Ansible Collection

Dies ist ein Erfahrungsbericht zur Migration meiner Ansible-Rolle „Nextcloud im Container“ in eine Ansible Collection. Er umfasst die Migration der bestehenden Rolle und die Ergänzung der neuen Collection um eine weitere Rolle, welche sich um die Aufgaben Backup und Restore kümmert.

Wer es ganz eilig hat, kann direkt zur Zusammenfassung springen. Allen anderen wünsche ich viel Spaß bei der Lektüre.

Für grundlegende Informationen zu Ansible Collections siehe:

Randnotiz: Dass ihr zu meiner ersten Ansible Collection hier im Blog nicht fündig werdet, liegt darin begründet, dass meine Arbeit daran eingeschlafen ist.

Die Entwicklung der hier beschriebenen Collection könnt ihr auf Codeberg.org verfolgen. Die Repo-URL lautet: https://codeberg.org/Tronde/nextcloud

Motivation

Für mein Wochenendprojekt „Nextcloud im Container“ habe ich eine Ansible-Rolle entwickelt und auf Github veröffentlicht, die dem Zweck dient, Nextcloud mit einer MariaDB in einer rootless Podman-Umgebung zu installieren.

Wie ihr in Teil 5 der Artikel-Serie nachlesen könnt, war ich mit der Lösung für Backup und Restore nicht ganz zufrieden. Ich möchte dieses Thema erneut angehen und mithilfe einer Ansible-Rolle lösen.

Backup und Restore beschreiben jedoch andere Anwendungsfälle als die Bereitstellung, weswegen ich den dafür notwendigen Quelltext nicht mit in die bestehende Rolle pressen möchte. Da Backup und Restore für mich jedoch zum Betrieb dazugehören, möchte ich alle Ansible-Rollen, die ich zum Betrieb meines Nextcloud-Setups benötige, in einer Collection zusammenfassen.

Migration der bestehenden Rolle in eine Collection

Ich orientiere mich hierzu an der englischsprachigen Dokumentation „Migrating a role to a collection“. Da ich bereits einen Galaxy Namespace besitze, nutze ich diesen auch für die Erstellung der Collection. Der folgende Codeblock zeigt die verwendeten Befehle.

]$ ansible-galaxy collection init tronde.nextcloud
- Collection tronde.nextcloud was created successfully

]$ mkdir tronde/nextcloud/roles/deploy_nextcloud_with_mariadb_pod && rsync -a ../roles/ansible_role_deploy_nextcloud_with_mariadb_pod/ tronde/nextcloud/roles/deploy_nextcloud_with_mariadb_pod/

]$ tree -L 3 tronde/nextcloud/
tronde/nextcloud/
├── docs
├── galaxy.yml
├── meta
│   └── runtime.yml
├── plugins
│   └── README.md
├── README.md
└── roles
    └── deploy_nextcloud_with_mariadb_pod
        ├── defaults
        ├── meta
        ├── README.md
        ├── tasks
        ├── tests
        └── vars

]$ rm -rf tronde/nextcloud/roles/deploy_nextcloud_with_mariadb_pod/.git

Im letzten Schritt entferne ich das .git-Verzeichnis, da ich die Collection als Ganzes in einem Repository verwalten möchte, statt alle Rollen einzeln zu versionieren und dann in die Collection einzufügen.

Anschließend aktualisiere ich die Dateien galaxy.yml und README.md. In der Rolle habe ich ein Playbook zum Testen verwendet, welches im Pfad deploy_nextcloud_with_mariadb_pod/tests/test.yml liegt. Um konform mit der Struktur einer Ansible Collection zu sein, erstelle ich im Wurzelverzeichnis meiner Collection das Verzeichnis playbooks und kopiere das Test-Playbook hier hinein:

]$ tree playbooks/
playbooks/
└── deploy_nextcloud_with_mariadb_pod.yml

An dieser Stelle halte ich zunächst inne und überlege mir, welche Tools ich noch benötige, um meine Collection entwickeln und testen zu können.

Ansible Development Tools

Ansible Development Tools (ADT) ist ein Projekt mit dem Ziel, einen Werkzeugkasten mit allen wichtigen Werkzeugen zur Entwicklung von Ansible Content zu bieten. Genau so etwas habe ich gesucht. Das probiere ich gleich aus.

Um mir mein System nicht zu verhunzen, installiere ich die ADT in ein Python Virtual Environment und lasse mir anzeigen, welche Werkzeuge ADT mitbringt:

]$ python -m venv adt
]$ . adt/bin/activate
(adt) ]$ pip install --upgrade pip
(adt) ]$ pip install ansible-dev-tools
(adt) ]$ pip install molecule-podman
(adt) ]$ adt --version
ansible-builder                          3.1.0
ansible-core                             2.17.1
ansible-creator                          24.7.0
ansible-dev-environment                  24.7.0
ansible-dev-tools                        24.7.1
ansible-lint                             24.7.0
ansible-navigator                        24.7.0
ansible-sign                             0.1.1
molecule                                 24.7.0
pytest-ansible                           24.7.0
tox-ansible                              24.7.0

Bei molecule-podman handelt es sich um einen Molecule-Treiber, welcher benötigt wird um Molecule-Tests in Podman-Containern ausführen zu können. Ich habe dieses Paket mitinstalliert, da ich mir vorstellen kann, dies in naher Zukunft zu nutzen.

ansible-lint

Ansible Lint ist das erste Werkzeug aus der ADT-Sammlung, welches ich verwende, um den Quelltext meiner Collection zu prüfen. Dazu wird das Kommando einfach im Wurzelverzeichnis der Collection ausgeführt. Anschließend können die gefundenen Fehler behoben werden.

Da es den Rahmen dieses Artikels sprengen würde, werde ich dem Thema einen eigenen Text widmen.

molecule

Ansible Molecule eignet sich zum Testen von Ansible-Rollen und Collections. Ich nutze meine Ansible Collection, um mich mit diesem Werkzeug vertraut zu machen.

Zwar gibt es auch hier erst eine Lernkurve, doch wird mir eine durchdachte Test-Konfiguration bei der weiteren Entwicklung sicher zugutekommen. Ich orientiere mich zu Beginn am Ansible Molecule Getting Started Guide.

Zu Beginn möchte ich meine in die Collection migrierte Rolle testen. Dazu bearbeite ich die Datei tronde/nextcloud/extensions/molecule/default/converge.yml. Sie enthält schließlich folgenden Inhalt:

extensions]$ cat molecule/default/converge.yml 
---
- name: Converge
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Test collection role deploy_nextcloud_with_mariadb_pod
      ansible.builtin.import_role:
        name: tronde.nextcloud.deploy_nextcloud_with_mariadb_pod

Und mit folgendem Befehl teste ich, ob meine Nextcloud-Instanz erfolgreich deployt wird:

extensions]$ molecule converge
…
PLAY RECAP *********************************************************************
localhost                  : ok=10   changed=9    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

extensions]$ podman ps
CONTAINER ID  IMAGE                                    COMMAND               CREATED             STATUS             PORTS                    NAMES
48438d1bd884  quay.io/centos/centos:stream8            bash -c while tru...  7 minutes ago       Up 7 minutes                                instance
104c525a6b50  localhost/podman-pause:5.1.1-1717459200                        About a minute ago  Up About a minute  127.0.0.1:40671->80/tcp  e34ddf4d4203-infra
f27842b12d56  docker.io/library/mariadb:10.11.2        mariadbd              About a minute ago  Up About a minute  127.0.0.1:40671->80/tcp  nc_mariadb
9ee391a57fbd  docker.io/library/nextcloud:25-apache    apache2-foregroun...  13 seconds ago      Up 13 seconds      127.0.0.1:40671->80/tcp  nextcloud

Das Ergebnis ist positiv und kann durch Aufruf von http://127.0.0.1:40671 im Webbrowser überprüft werden. Ein Login mit den Default-Werten aus tronde/nextcloud/roles/deploy_nextcloud_with_mariadb_pod/defaults/main.yml bestätigt das erfolgreiche Deployment.

Es handelt sich hierbei noch nicht um ein adäquates Test-Setup; doch für den Moment ist es ausreichend. Ich werde mich noch weiter einarbeiten müssen, um das Test-Setup verbessern zu können. Vermutlich wird auch dazu dann ein weiterer Artikel folgen.

Falls sich jemand wundert, es ist durchaus Absicht, dass die Nextcloud nur via 127.0.0.1 und per HTTP erreichbar ist. Ich betreibe diese hinter einem Reverse-Proxy. Details können in Nextcloud im Container — Teil 3: Mit Reverse-Proxy nachgelesen werden.

Backup und Restore für die Nextcloud

Um die Konfiguration und die in der Nextcloud gespeicherten Daten sichern und wiederherstellen zu können, werde ich eine Offline-Sicherung der verwendeten Podman Volumes durchführen. Der Ablauf für die Sicherung sieht wie folgt aus:

  1. Den Pod ˋnc_podˋ inkl. aller darin enthaltenen Container stoppen
  2. Alle zum Setup gehörenden Podman Volumes in Tarballs exportieren
  3. Diese Tarballs vom managed Node auf den Control Node sichern
  4. Den ˋnc_podˋ wieder starten

Die Wiederherstellung läuft sinngemäß andersherum ab:

  1. Die Tarballs werden vom Control Node auf den Managed Node kopiert
  2. Der Pod ˋnc_podˋ inkl. aller darin enthaltenen Container wird gestoppt
  3. Die Podman Volumes aus Schritt 1 werden importiert
  4. Der nc_pod wird wieder gestartet
  5. Die zur Wiederherstellung auf den Managed Node kopierten Tarballs werden wieder entfernt

Um diese neue Rolle der Collection hinzuzufügen, navigiere ich in das Rollenverzeichnis tronde/nextcloud/roles/ und erstelle dort das Grundgerüst für die Rolle: ansible-galaxy role init backup_nextcloud

Die Struktur sieht bei mir wie folgt aus, wobei nicht benötigte Verzeichnisse noch nicht entfernt wurden:

ansible_collections]$ tree -L 2 tronde/nextcloud/roles/backup_restore_nextcloud/
tronde/nextcloud/roles/backup_restore_nextcloud/
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

9 directories, 8 files

Den aktuellen Entwicklungsstand könnt ihr unter dieser URL einsehen: https://codeberg.org/Tronde/nextcloud/src/branch/main/roles/backup_restore_nextcloud

Das dortige README.md enthält wie bei dedizierten Rollen die Dokumentation zu dieser Rolle. Um doppelte Texte zu vermeiden, verweise ich im Collection-README auf die jeweiligen README-Dateien in den einzelnen Rollen.

Getestet wird diese Rolle ebenfalls mit Molecule. Zu den Tests mit Molecule werde ich noch mehr schreiben, sobald ich dies etwas verfeinert habe.

Fallen mir noch weitere Anwendungsfälle ein, können diese nun einfach als weitere Rollen der Collection hinzugefügt werden.

Ansible-Variablen definieren

Jede Rolle in dieser Collection ist für sich alleine nutzbar. Somit definiert jede Rolle die Eingangs-Variablen in der defaults/main.yml der jeweiligen Rolle.

Bei der Entwicklung achte ich darauf, dass Variablen, die z.B. den Namen eines Podman Volumes enthalten, in allen Rollen gleich geschrieben werden. Damit ist es möglich, diese Variablen an anderer Stelle zu definieren und in den verschiedenen Rollen nutzbar zu machen. So können an individuelle Deployments angepasste Variablen in Playbooks, group_vars, host_vars, etc. definiert werden.

Wo Variablen definiert werden können und welche Präzedenz diese besitzen, kann in der offiziellen Doku nachgelesen werden: Variable precedence: Where should I put a variable?

Zusammenfassung

Eine Ansible-Rolle ohne Plug-ins in eine Ansible Collection zu überführen, ist mit dem kurzen Abschnitt aus der Dokumentation schnell erledigt.

Weitere Rollen können der Collection hinzugefügt werden, indem man sie im Verzeichnis roles der Collection-Verzeichnisstruktur wie gewohnt mit ansible-galaxy role init <Rollenname> erstellt und mit Leben füllt. Sorgfalt ist und bleibt bei der Benennung von Variablen und deren Präzedenz geboten, um nicht wahnsinnig zu werden. Dies ist nicht wirklich schwierig, doch muss ich fast jedes Mal in der Dokumentation nachschlagen, um sicher zu sein.

Die Ansible Development Tools (ADT) sind eine praktische Sammlung von Werkzeugen zur Entwicklung von Rollen und Collections für Ansible. Für mich sind aktuell Ansible Lint und Ansible Molecule die wichtigsten Werkzeuge aus dieser Sammlung. Wobei gerade die Dokumentation von Molecule auf dem Weg zu meinem Ziel, nämlich meine Collections in verschiedenen rootless Podman Containern mit nested Podman und Ansible zu testen, leider viele Fragen offen lässt. Grundsätzlich lassen sich Testpläne damit realisieren und vereinfachen mein bisheriges Testsetup bestehend aus diversen Skripts und virtuellen Maschinen. Und sie bieten Stoff für weitere Einträge in diesem Blog.

Die weitere Entwicklung meiner Nextcloud Collection findet auf Codeberg statt. Ihr findet sie dort unter der URL: https://codeberg.org/Tronde/nextcloud

Wie wird im Ansible Automation Controller eine neue Inventory Source hinzugefügt?

In diesem Überblick beschreibe ich am Beispiel der Proxmox inventory source, wie eine eigene Inventory Source im Ansible Automation Controller hinzugefügt werden kann.

Die folgenden Schritte wurden mit der Ansible Automation Platform 2.4 getestet. Die einzelnen Schritte sollten in gleicher Weise auch in Ansible AWX ausgeführt werden können.

Um diesem Text folgen zu können, werden Kenntnisse im Umgang mit Ansible und Git auf der Kommandozeile vorausgesetzt.

Der Text verweist, wo möglich, auf bestehende Dokumentation. Es handelt sich bei diesem Text nicht um ein klassisches Tutorial. Er dient mir als Gedächtnisstütze und mag euch eine Anregung sein, bzw. im besten Fall die Wissenslücken schließen, die sich mit der Dokumentation allein nicht schließen lassen.

Ausgangssituation

Abschnitt 18.4.5.1. Inventory sources im Automation Controller User Guide führt die in der Ansible Automation Platform (AAP) unterstützten Inventory Sources auf. Möchte man nun bspw. Proxmox Virtual Environment (PVE), Microsoft Active Directory oder Cisco DNA Center als Quelle für sein Inventar benutzen, wird man auf den ersten Blick nicht fündig.

Für das Beispiel in diesem Text werden Hosts aus der Bestandsliste eines PVE als Inventory Source hinzugefügt. Die dabei verwendete Vorgehensweise kann auch für andere Inventory Plugins verwendet werden. Die Entwicklung von Inventory Plugins ist jedoch nicht Gegenstand dieses Textes. Hierzu wird auf die Dokumentation unter „Developing dynamic inventory“ verwiesen.

Mein Kollege Steffen Scheib hat mir geholfen, das Proxmox-Plugin zu konfigurieren, wofür ich ihm an dieser Stelle nochmal ganz herzlich danke. Es liegt auf meiner Arbeitsstation als Ansible Project in folgender Verzeichnisstruktur vor:

]$ tree proxmox_inventory/
proxmox_inventory/
├── collections
│   └── requirements.yml
├── inventory
│   └── inventory.proxmox.yml
└── vault_password_file

3 directories, 3 files

Mit Ausnahme der Datei vault_password_file wurden alle Dateien und Verzeichnisse in Git aufgenommen. Ich verwende einen einfachen Git-Server in meiner Laborumgebung, auf welchen ich meine lokalen Repositorys pushe. Der Automation Controller synchronisiert das Projekt aus dem Git-Repo, um es als Inventory Source verfügbar zu machen.

Die Vorgehensweise im Überblick

  1. Ansible Credential für Source Control erstellen
  2. Ein Ansible Projekt hinzufügen
  3. Einen Custom Credential Type erstellen
  4. Ein Ansible Inventory hinzufügen

Ansible Credential für Source Control erstellen

Das Proxmox Inventory Plugin befindet sich in einem Git-Repository, auf welches mit SSH-Key-Authentifizierung zugegriffen werden kann. Damit auch der Automation Controller auf dieses Repository zugreifen kann, wird ein Credential vom Typ Source Control erstellt.

Beipsiel für einen Source Control Credential Typ im Ansible Automation Controller

Der SSH-Private-Key wurde von meinem Host hochgeladen und verschlüsselt im Automation Controller gespeichert. Der Key lässt sich in der GUI nicht wieder sichtbar machen, lediglich ersetzen.

Ein Ansible Projekt hinzufügen

Der Dokumentation folgend, wird ein Projekt hinzugefügt:

Beispiel einer ausgefüllten Maske im Automation Controller, zum Hinzufügen eines Projekts.

Wenn alles passt, wird das Projekt nach dem Speichern erfolgreich synchronisiert:

Dieses Projekt wird in einem späteren Schritt zur Erstellung des Inventory benötigt.

Einen Custom Credential Type erstellen

Bevor ich auf die Erstellung selbst eingehe, möchte ich kurz beschreiben, warum dieser Schritt notwendig ist.

Folgender Codeblock zeigt meine Datei inventory.proxmox.yml welche einige mit Ansible Vault verschlüsselte Werte enthält:

]$ cat inventory/inventory.proxmox.yml 
---
plugin: 'community.general.proxmox'
url: 'https://pve.example.com'
user: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          30623661316338386633623162303036346562346238386162633263636164636338393532613565
          3332616265353962326139363533313261623739643765640a623032613034613139653162356266
          34646464323233313964663939643631313539353736313364333433643136306632633065633664
          3234346635396563350a656334353632643830353534386636306365656261356436613662623163
          31663535363264356537336531393731633164613733316537383433653334643433
token_id: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          62356662336534646661353666356263363734666231643932393430336639363032303266636432
          3762343235633335613663393838343532326230353130380a616161313830373265306137346562
          61613662333764393565316362623838633332376366373161646237363163663039613863393439
          3165616664626633390a396465343430373837343662373634653634643138613131633034306432
          62623438366166353765366339323263393833396133653866343833663335663766
token_secret: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66386338643463373837666164396332306261366634396630306430663937613963346164636433
          3362396566343932393234353439383932316436396437380a336365393038373566383534623136
          30353332383464356664393666383664636536666531663463623232353136353636363366653431
          3234616531623537630a393530643437376363653438643036636436316636616265316361623661
          35313832613063633662363531346164306638373538393164373663633335333863646430663232
          6339343164633865636239356538326438333937366134613738

validate_certs: false

# fail if a variable is not resolvable
strict: true

# facts are required to retrieve proxmox_vmtype
want_facts: true

# only allow qemu VMs
filters:
  - "proxmox_vmtype == 'qemu'"

Auf der Kommandozeile meines Hosts kann ich den Inhalt des Dynamic Inventory wie folgt anzeigen lassen:

ansible-inventory -i inventory/inventory.proxmox.yml --list --vault-password-file vault_password_file

Die Datei vault_password_file befindet sich jedoch nicht im Git, da sie das Passwort im Klartext enthält. Aus diesem Grund möchte ich die Datei auch nicht auf dem Automation Controller ablegen. Irgendwie müssen auf dem Automation Controller jedoch Credentials hinterlegt werden, um die Ansible Vault encrypted_strings zu entschlüsseln. Die Lösung steckt in diesem Kommentar auf Github. Im Automation Controller User Guide gibt es dazu Chapter 11. Custom credential types.

Fertig sieht das dann so aus:

Quelle: https://github.com/ansible/awx/issues/4089#issuecomment-1632066592

Das Schlüssel-Wert-Paar secret: true stellt sicher, dass das Passwort verschlüsselt gespeichert wird. Es kann danach im Automation Controller nicht mehr im Klartext angezeigt werden. Nachdem der neue Credential Typ erstellt ist, kann dieser instanziiert werden:

Beispiel des neuen Custom Credential Typs ‚encrypted_vault_password‘

Das Vault Passwort wird in das entsprechende Formularfeld kopiert. Es ist standardmäßig nicht sichtbar und wird wie oben bereits erwähnt, verschlüsselt gespeichert. Mit diesem Credential verfügt der Automation Controller nun über die notwendigen Informationen, um das Proxmox Inventory auszulesen.

Ein Ansible Inventory hinzufügen

Zuerst wird ein Inventory nach Dokumentation erstellt. Anschließend wird diesem eine Inventory Source hinzugefügt.

Die Formularfelder sind dabei wie folgt zu befüllen:

  • Name: Kann frei vergeben werden
  • Source: Sourced from a Project
  • Credential: Hier wird das im vorangegangenen Schritt erstellte Credential ausgewählt
  • Project: Hier wird das in obigen Abschnitt erstellte Projekt ausgewählt
  • Inventory file: Kann in diesem Fall auf `/ (project root)` gesetzt werden
Eine Inventory Source mit Proxmox als Beispiel

Nach dem Speichern wird die Inventory Source durch Klick auf ‚Sync‘ synchronisiert:

In dieser Ansicht wurde die Inventory Source bereits erfolgreich synchronisiert

Und wir haben 17 Hosts in unserem Inventory:

Damit endet dieser kurze Überblick auch schon. Ich wünsche euch viel Freude bei der Inventarpflege.

Beispiele für Ansibles ‚var is defined‘

In diesem Beitrag gebe ich einige Beispiele mit Erklärungen zu Ansible Conditionals, welche mir in der Dokumentation fehlen. Dieser Artikel dient mir zur Erinnerung und mag Einsteigern helfen, ein besseres Verständnis der Bedingung is defined zu gewinnen.

Wird in einem Task eine Variable verwendet, welche nicht definiert ist, bricht Ansible die Verarbeitung eines Playbooks mit einem Fehler ab. Der folgende Code-Block zeigt ein einfaches Playbook mit anschließender Ausgabe des Playbooklaufs, um dies zu verdeutlichen. Der Task beinhaltet dabei bereits eine Bedingung. Er soll nur dann ausgeführt werden, wenn die Variable my_var den Wert Hello world! enthält.

]$ cat test_conditionals.yml 
---
- name: Play around with conditionals
  hosts: localhost
  gather_facts: false

  tasks:
    - name: >-
        When all conditionals are met, print the variable value using
        ansible.builtin.debug
      when:
        - my_var == "Hello world!"
      ansible.builtin.debug:
        var: my_var

]$ ansible-playbook -i hosts test_conditionals.yml 

PLAY [Play around with conditionals] *******************************************

TASK [When all conditionals are met, print the variable value using ansible.builtin.debug] ***
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'my_var == \"Hello world!\"' failed. The error was: error while evaluating conditional (my_var == \"Hello world!\"): 'my_var' is undefined. 'my_var' is undefined\n\nThe error appears to be in '/home/tronde/ansible/test_conditionals.yml': line 7, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n  tasks:\n    - name: >-\n      ^ here\n"}

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

Der Text 'my_var' is undefined in obiger Fehlerausgabe benennt den Grund des Scheiterns.

Nun gibt es Fälle, in denen Tasks, welche undefinierte Variablen enthalten, nicht zum Abbruch des gesamten Playbooks führen sollen. Stattdessen soll der betroffene Task einfach übersprungen werden. Dies erreicht man mit der Bedingung `is defined`. Der nächste Code-Block liefert wieder ein Beispiel dazu.

]$ cat test_conditionals.yml
---
- name: Play around with conditionals
  hosts: localhost
  gather_facts: false

  tasks:
    - name: >-
        When all conditionals are met, print the variable value using
        ansible.builtin.debug
      when:
        - my_var is defined
        - my_var == "Hello world!"
      ansible.builtin.debug:
        var: my_var

]$ ansible-playbook -i hosts test_conditionals.yml

PLAY [Play around with conditionals] *******************************************

TASK [When all conditionals are met, print the variable value using ansible.builtin.debug] ***
skipping: [localhost]

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

Der Task mit der undefinierten Variable wurde übersprungen (skipped) und das Playbook erfolgreich beendet.

Im nächsten Beispiel habe ich die Variable my_var im Playbook definiert und den passenden Wert zugewiesen.

]$ cat test_conditionals.yml
---
- name: Play around with conditionals
  hosts: localhost
  gather_facts: false
  vars:
    my_var: Hello world!

  tasks:
    - name: >-
        When all conditionals are met, print the variable value using
        ansible.builtin.debug
      when:
        - my_var is defined
        - my_var == "Hello world!"
      ansible.builtin.debug:
        var: my_var

]$ ansible-playbook -i hosts test_conditionals.yml

PLAY [Play around with conditionals] *******************************************

TASK [When all conditionals are met, print the variable value using ansible.builtin.debug] ***
ok: [localhost] => {
    "my_var": "Hello world!"
}

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

Das war auch schon alles für heute. Ihr seht, es ist keine Hexerei. Mir hilft es, die Bedeutung von is defined zu erinnern, wenn ich es einmal aufgeschrieben habe.

Zum Ende gibt es noch einen Code-Block, der zeigt, dass dies auch mit Dictionaries funktioniert:

]$ cat test_conditionals.yml
---
- name: Play around with conditionals
  hosts: localhost
  gather_facts: false
  vars:
    my_dict:
      my_var: Hello world!

  tasks:
    - name: >-
        When all conditionals are met, print the variable value using
        ansible.builtin.debug
      when:
        - my_dict.my_var is defined
        - my_dict.my_var == "Hello world!"
      ansible.builtin.debug:
        var: my_dict.my_var

]$ ansible-playbook -i hosts test_conditionals.yml

PLAY [Play around with conditionals] *******************************************

TASK [When all conditionals are met, print the variable value using ansible.builtin.debug] ***
ok: [localhost] => {
    "my_dict.my_var": "Hello world!"
}

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

In der Kategorie Ansible findet ihr weitere Artikel rund um Ansible. Viel Spaß beim Stöbern.

Proof of Concept: Mit Ansible das Advanced Intrusion Detection Environment (AIDE) steuern

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.

Welche Aufgaben sind mit Ansible zu lösen?

  • Das Paket aide ist auf den Zielsystemen installiert
  • Die optionale Generierung und Verteilung der Konfigurationsdatei aide.conf
  • Die Initialisierung der AIDE-Datenbank
  • Zentrale Speicherung der AIDE-Datenbanken aller verwalteter Hosts auf dem Ansible Control Node (ACN)
  • Durchführung von Integritäts-Checks
  • Aktualisierung der AIDE-Datenbanken und erneute Speicherung auf dem ACN

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.

Labor-Umgebung

Meine Labor-Umgebung für diesen PoC besteht aus den vier Hosts:

  • ansible-ctrl (RHEL 8 mit installiertem ansible-core)
  • rhel7
  • rhel8
  • rhel9

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

Beschreibung der Ansible-Rolle 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
  • install – Bei Angabe dieses Tags stellt die Rolle sicher, dass das Paket aide auf den Zielsystemen installiert ist
  • generate_config – Generiert die Datei /etc/aide.conf unter Nutzung von templates/aide.conf.j2; das Template ist an die individuellen Bedürfnisse anzupassen; Details siehe nächster Abschnitt
  • init – Hiermit wird die AIDE-Datenbank initialisiert, welche als Referenzdatenbank für zukünftige Checks dient
  • check – Führt einen Integritäts-Check unter Verwendung der Referenzdatenbank durch
  • update – Führt einen Integritäts-Check durch und erzeugt eine neue AIDE-Datenbank, welche zukünftig als Referenz dient

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

Nutzung für die verschiedenen Anwendungsfälle

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.

Anwendungsfall 1: Installation von AIDE

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

Anwendungsfall 2: Generierung der Datei /etc/aide.conf

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.

Anwendungsfall 3: Initialisierung der AIDE-Datenbank

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.

Anwendungsfall 4: Ausführung einer Integritätsprüfung

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.

Anwendungsfall 5: Update der AIDE-Datenbank

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.

Zusammenfassung

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

RHEL System Roles: nbde_client

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:

Umgebung

Für das folgende Beispiel verwende ich eine Umgebung, bestehend aus:

  • Einem Ansible-Controller (RHEL 9) mit den Paketen
    • ansible-core
    • rhel-system-roles
  • Jeweils einem RHEL 8 und RHEL 9 Server mit Minimalinstallation und einem LUKS-Gerät (/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:

Die Rolle

Durch die Installation des Pakets rhel-system-roles existiert diese Rolle bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.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.

Anwendungsfall

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.

Das Playbook

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
  • Der erste Task stellt sicher, dass das Netzwerkinterface aktiviert und mit einer IP-Adresse konfiguriert wird; dies ist notwendig, um den Tang-Server kontaktieren zu können, da in dem genutzten Netzwerk-Segment kein DHCP verfügbar ist; Solltet ihr ein Netzwerk-Segment nutzen, in dem DHCP zur Verfügung steht, kann der erste Task entfallen
  • Um das LUKS-Device für NBDE zu konfigurieren wird die LUKS-Passphrase benötigt, welche in der Variablen luks_password steckt
  • Ich empfehle die Variable luks_password mit ansible-vault vor neugierigen Blicken zu schützen
  • Durch password_temporary: true wird die LUKS-Passphrase aus dem jeweiligen Key-Slot gelöscht, nachdem das LUKS-Device für NBDE konfiguriert wurde

Achtung (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.

Fazit

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.

Quellen und weiterführende Links

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

RHEL System Roles: nbde_server

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.

Umgebung

Für das folgende Beispiel verwende ich eine Umgebung, bestehend aus:

  • Einem Ansible-Controller mit den Paketen (RHEL 9)
    • ansible-core
    • rhel-system-roles
  • Jeweils einem RHEL 8 und RHEL 9 Server mit Minimalinstallation

Die Installation von RHEL sowie der genannten Pakete sind nicht Bestandteil dieses Artikels. Wer hierzu einen Einstieg sucht, findet entsprechende Dokumentation unter:

Die Rolle

Durch die Installation des Pakets rhel-system-roles existiert diese Rolle bereits auf meinem System und muss nur noch konfiguriert werden. Die Rolle selbst findet man im Pfad /usr/share/ansible/roles/rhel-system-roles.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:

  • Installation von Tang auf den beiden Zielsystemen
  • Konfiguration von SELinux im Modus enforcing
  • Konfiguration der Host-Firewall

Das Playbook

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.

Probleme

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)

Fazit

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.

Quellen und weiterführende Links

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

RHEL System Roles: rhc

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

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

Anwendungsfall

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

Die Rolle

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

Das Playbook

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

Fazit

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

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

Weiterführende Quellen und Links

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

Mit Ansible über YAML Lists and Dictionaries iterieren

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

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

List

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

list:
  - Alice Cooper
  - Bob Marley

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

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

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

Dictionary

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

Ein einfaches Dictionary sieht wie folgt aus:

Felder:
  Feld1: 10ha
  Feld2: 40ha

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

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

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

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

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

List of Dictionaries

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

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

Syntaxfehler

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

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

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


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

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

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

Playbook

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

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

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

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

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

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

Task 1: Ouput all vars in dicts_and_lists.yml

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

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

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

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

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

---
list:
  - Alice Cooper
  - Bob Marley

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

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

Task 2: Loop over some list

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

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

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

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

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

Task 3: Loop over some dictionary

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

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

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

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

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

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

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

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

Task 4: Loop over some list_of_dicts

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

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

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

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

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

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

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

Bonusmaterial: Lists in Dicts

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

Jörns Datenstruktur sieht wie folgt aus:

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

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

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

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

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

Und hier nun der Playbook-Aufruf mit Ausgabe:

$ ansible-playbook dict_of_lists.yml

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

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

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

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

Fazit

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

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

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

RHEL System Roles: storage

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

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

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

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

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

Anwendungsfall

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

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

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

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

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

Die Rolle

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

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

```yaml
- hosts: all

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

```

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

Das Playbook

---
- hosts: test-vm1

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

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

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

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

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

Fazit

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

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

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

Quellen und weiterführende Links

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

AlmaLinux OS 9.2 mit Projekt Elevate

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.

RHEL System Roles: firewall

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

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

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

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

Anwendungsfall

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

Die Rolle

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

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

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

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

Das Playbook

Das Playbook ist kompakt und übersichtlich:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fazit

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

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

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

Quellen und weiterführende Links

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

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

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

Was ist Seafile?

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

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

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

Was ist das Ziel?

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

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

Die Vorgehensweise

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

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

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

Das Ergebnis

Das Ergebnis findet ihr auf:

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

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

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

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

Fragen an meine Leser*innen

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

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

Ich freue mich auf eure Antworten.

RHEL System Roles: sshd

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

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

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

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

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

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

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

Voraussetzungen

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

Das Playbook

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

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

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

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

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

Testlauf in Labor-Umgebung

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

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

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

Zusammenfassung

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

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

Quellen und weiterführende Links

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

RHEL System Roles: timesync

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

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

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

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

Voraussetzungen

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

Das Playbook

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

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

Das Playbook sieht nach der Anpassung wie folgt aus:

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

Testlauf in Labor-Umgebung

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

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

Zusammenfassung

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

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

Weiterführende Quellen und Links

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

RHEL System Roles: selinux

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

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

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

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

Voraussetzungen

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

Das Playbook

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

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

Testlauf in der Laborumgebung

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Zusammenfassung

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

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

Quellen und weiterführende Links

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

Anzeige: 50 Prozent auf Alles in der Golem Karrierewelt!

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!

Zehn Tage lang die Hälfte sparen

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.

Lernen, wann immer du möchtest

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.

Persönliche Weiterentwicklung per Coaching

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

Sprachenlernen zum halben Preis

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.

Wissen vertiefen – Horizont erweitern – Geld sparen

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.

Red Hat Enterprise Linux registrieren und System Purpose konfigurieren mit Ansible

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.

Subskriptions-Verwaltung ohne SCA

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.

Vorteile des RHSM

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.

Nachteile des RHSM

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.

Was ändert sich durch SCA?

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.

Vorteile

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.

Nachteile

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

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.

System Purpose

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.

Wie registriere ich meine Systeme heute?

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.

Fazit

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.

Quellen und weiterführende Links

  1. Transition of Red Hat’s subhttps://access.redhat.com/documentation/en-us/red_hat_subscription_management/2022scriptions 5ervices to console.redhat.com
  2. Simple Content Access (SCA)
  3. How to register and subscribe a system offline to the Red Hat Customer Portal?
  4. Subscription Watch
  5. Chapter 1. What is subscription watch?
  6. Red Hat Hybrid Cloud Console
  7. RHEL 8 Documentation: Chapter 12. Configuring System Purpose
  8. community.general.redhat_subscription module – Manage registration and subscriptions to RHSM using the subscription-manager command
  9. Red Hat Enterprise Linux subscription guide

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

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

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

Anwendungsfall

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

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

Der Vorgang der Provisionierung und Dekommissionierung soll automatisiert erfolgen.

Voraussetzungen

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

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

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

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

Variablen

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

vCenter-Variablen

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

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

Dictionary zur Spezifikation der VMs

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

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

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

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

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

Beispiel-Playbook

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

In diesem Beispiel werden die Variablen aus zwei Dateien geladen.

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

Wie kann man diese Rolle nutzen?

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

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

Fazit

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

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

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

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

macOS Catalina Installation MacPorts und ansible

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.

❌