Serverautomatisierung mit Ansible in der Praxis

|
Der Server ist eingebaut, das Betriebssystem installiert. Doch welche Pakete sollten noch gleich installiert, welche Einstellungen für den Webserver gesetzt sein? Wer sich diese Fragen stellt, sollte sich dringend mit Konfigurationsmanagement auseinandersetzen. Denn dieses ermöglicht einen deklarativen Ansatz, um Server zuverlässig einzurichten. Die Installation von Paketen oder das Ablegen von Konfigurationsdateien wird dadurch vereinfacht, da das Konfigurationsmanagementsystem diese Aufgaben übernimmt.

Die meisten Konfigurationsmanagementsysteme basieren auf dem Client-Server-Modell. In diesem werden die einzurichtenden Hosts (Clients) bei einem zentralen Host (Server) angemeldet. Dieser schickt dann die Anweisungen zum Client, der den Host dementsprechend einrichtet. So arbeiten zum Beispiel Saltstack oder Puppet standardmäßig. Alternativ gibt es jedoch auch Möglichkeiten, ohne einen zentralen Server Konfigurationsmanagement zu betreiben. Die bekannteste Software dafür ist Ansible von Red Hat, auf das ich in diesem Artikel näher eingehen möchte.

Die Funktionsweise von Ansible

Im Gegensatz zum Client-Server-Modell muss auf den einzurichtenden Hosts keine besondere Software außer Python und ein SSH Server installiert sein. Denn Ansible nutzt zum Erreichen der Server keinen eigenständigen Dienst, sondern verlässt sich auf den Zugriff per SSH. Dadurch verringert sich die Einstiegshürde immens: Auf frisch installierten Servern muss im besten Fall nur ein SSH-Schlüssel abgelegt und so kein weiterer Dienst installiert werden, der zusätzlich im zentralen Konfigurationsmanagement authentifiziert werden müsste.

Ansible Logo

Die Möglichkeiten von Ansible sind dank einer umfassenden Liste an Modulen sehr vielfältig. Für den Einstieg sind jedoch die rudimentären Module, zum Beispiel für die Paketinstallation oder Dateiablage, völlig ausreichend. Wer tiefer in Ansible einsteigt, wird später auch AWS Infrastruktur verwalten, MySQL Datenbanken und Benutzer einrichten oder Netzwerkhardware bedienen können. Um ein einfaches Backup zu konfigurieren, reichen jedoch schon eine Hand voll einfach zu bedienender Module.

Praxisbeispiel Restic Backup

Im Folgenden schauen wir uns ein sogenanntes Ansible Playbook an, das auf einem Ubuntu 18.04 Server ein Backup mit Restic einrichtet. Mit einem Playbook lässt sich übrigens eine gesamte Infrastruktur steuern oder auch nur ein einzelner Server.. Für dieses Beispiel gehen wir davon aus, dass der Server bereits so konfiguriert ist, dass der root Benutzer sich per SSH-Schlüssel passwortlos anmelden kann.

Zuerst wird ein Inventar angelegt. In diesem werden alle per Ansible zu verwaltenden Server eingetragen und Gruppen zugewiesen, über die später Rollen an diese verteilt werden. Die Rollen definieren, welche Module auf dem Server ausgeführt werden sollen. Der Dateiname des Inventars kann freigewählt werden, lautet jedoch meistens „inventory“.

[beispiel]
beispielserver.example.org ansible_user=root ansible_ssh_private_key_file=<Pfad_zum_SSH_Key>

Mithilfe des Inventars wird unser Beispiel-Server definiert und der „beispiel“-Gruppe hinzugefügt. Im gleichen Schritt wird auch festgelegt, dass für die Verbindung der „root“-Benutzer und ein SSH-Schlüssel verwendet werden sollen.

Nun wird eine Rolle namens „backup“ angelegt, mit der Restic auf den Server installiert und konfiguriert wird.

> mkdir -p roles/backup/{tasks,templates,files}

Dieser Befehl legt in dem Ordner „roles/backup“ die weiteren Ordner „tasks“, „templates“ und „files“ ab. In „tasks“ werden die auszuführenden Module definiert, in „templates“ und „files“ werden abzulegende Dateien gespeichert, wobei die Dateien in „templates“ per Jinja dynamisch bei der Ausführung des Playbooks angepasst werden können (mehr dazu später).

Unter „files“ wird nun eine Datei namens „restic-backup.service“ mit folgendem Inhalt erstellt:

[Unit]
Description=Restic backup
[Service]
Type=oneshot
ExecStartPre=-/usr/bin/restic init || true
ExecStart=/usr/bin/restic backup --one-file-system $BACKUP_PATHS
ExecStartPost=/usr/bin/restic forget --keep-daily $RETENTION_DAYS --keep-weekly $RETENTION_WEEKS --keep-monthly $RETENTION_MONTHS --keep-yearly $RETENTION_YEARS
EnvironmentFile=/etc/restic/restic.conf

Dazu noch eine zweite Datei namens „restic-backup.timer“:

[Unit]
Description=Restic backup timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target

Diese beiden Dateien sorgen auf dem Server dafür, dass Restic täglich ausgeführt wird.

Im „templates Ordner“ wird noch eine Datei namens „restic.conf.j2“ angelegt, die das Template für die Konfigurationsdatei des Backups darstellt:

BACKUP_PATHS="{{ backup_paths|default("/home") }}"
RESTIC_REPOSITORY="{{ backup_destination|default("/mnt/backup") }}"
RESTIC_PASSWORD="{{ backup_restic_password|default("backup") }}"
RETENTION_DAYS={{ backup_retention_days|default(7) }}
RETENTION_WEEKS={{ backup_retention_weeks|default(4) }}
RETENTION_MONTHS={{ backup_retention_months|default(6) }}
RETENTION_YEARS={{ backup_retention_years|default(3) }}

Die Werte innerhalb der geschweiften Klammern stellen Ansible-Variablen dar, die von der Jinja Template Engine ersetzt werden und durch welche die Konfiguration für jeden Server individuell gestaltet werden kann. Wichtig ist die Angabe eines Standardwertes mit „|default()“, da die Ausführung von Ansible ansonsten scheitert, sollte die Variable nicht anderweitig gesetzt sein. Keine Sorge, das Restic Passwort wird in einem späteren Schritt noch auf einen sicheren Wert geändert.

Der wichtigste Teil findet sich in der „tasks/main.yml“ wieder, in der im YAML-Format die auszuführenden Aufgaben eingetragen werden:

---
- name: install restic # Menschenlesbarer Name für diese Aufgabe
  ansible.builtin.package: # Name des Moduls, das ausgeführt werden soll
    name: restic # Name des zu verwaltenden Pakets
    state: present # Welchen Zustand das Paket haben soll

- name: create restic configuration directory
  ansible.builtin.file: # Allgemeines Dateiverwaltungsmodul
    dest: /etc/restic # Welche Datei oder Ordner verwaltet werden soll
    state: directory # Welchen Zustand die Datei haben soll

- name: create restic configuration file
  ansible.builtin.template: # Kopiert Dateien aus dem templates Ordner und wendet die Jinja Template Engine über diese an, wodurch z.B. die Variablen in den geschweiften Klammern ersetzt werden
    src: restic.conf.j2 # Name der Datei im templates Ordner
    dest: /etc/restic/restic.conf # Ort auf dem Server, wo die Datei hinkopiert werden soll
    owner: root # Welchem Benutzer die Datei gehören soll
    group: root # Welcher Gruppe die Datei gehören soll
    mode: 0600 # Welche Berechtigungen die Datei bekommen soll

- name: install restic-backup.service
  ansible.builtin.copy: # Kopiert eine Datei aus dem files Verzeichnis auf den Server
    src: restic-backup.service
    dest: /etc/systemd/system/restic-backup.service
    owner: root
    group: root
    mode: 0600

- name: install restic-backup.timer
  ansible.builtin.copy:
    src: restic-backup.timer
    dest: /etc/systemd/system/restic-backup.timer
    owner: root
    group: root
    mode: 0600

- name: enable restic-backup.timer
  ansible.builtin.service: # Verwaltet Dienste, z.B. mit SystemD oder SysV
    name: restic-backup.timer
    state: started # Startet den Timer bei Ausführung des Playbooks
    enabled: yes # Aktiviert den Autostart des Timers beim Neustart

Die rudimentäre „backup“-Rolle ist hiermit fertig. Weitere hinzuzufügende Features wären zum Beispiel das Ermöglichen einer Exclude-Liste oder eine genauere Steuerung, wann das Backup ausgeführt werden soll. Für diese Präsentation ist die Rolle jedoch ausreichend.

Um die Rolle unserem Server hinzuzufügen, wird auf der Ebene des Inventars nun eine „site.yml“ angelegt:

- hosts: beispiel
  roles:
    - backup

Diese Datei ordnet den Servern oder Servergruppen ihre Rollen zu. In diesem Fall erhält die „beispiel“-Gruppe die Rolle „backup“.

Zu diesem Zeitpunkt lässt sich das Playbook bereits ausführen, das Restic Repository würde jedoch noch mit dem unsicheren Standardpasswort angelegt werden. Um dieses zu ändern, machen wir uns zwei Features von Ansible zunutze: Hostvariablen und Ansible Vault. Erstere ermöglichen, Variablen für bestimmte Hosts anzupassen, letzterer ermöglicht, Geheimnisse sicher in das Playbook abzulegen.

Um Hostvariablen anzulegen, wird zuerst ein weiterer Ordner im Hauptverzeichnis des Playbooks kreiert.

> mkdir host_vars

Nun wird ein sicheres Passwort mit Ansible Vault verschlüsselt, so dass es von Ansible beim Ausführen des Playbooks gelesen werden kann.

> ansible-vault encrypt_string --ask-vault-pass 'sicherespassword' --name 'backup_restic_password'

New Vault password:
Confirm New Vault password:
backup_restic_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          37303063333032323138633962666265663033303036343835386661353931653361306436386632
          3534373866373536356564313431636637386433656664360a663663613563313062633836363865
          39376238613838396462353932316237383732346435323132343266326632373265346538626330
          3039383965383234630a666138663232333563306536663839613863346237613839633632353164
          66653633376432386534343838643136356331333834323434386261393737346539
Encryption successful

Zuletzt wird die von ansible-vault ausgegebene Zeichenfolge in die Datei "host_vars/ beispielserver.example.org" hinterlegt.

backup_restic_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          37303063333032323138633962666265663033303036343835386661353931653361306436386632
          3534373866373536356564313431636637386433656664360a663663613563313062633836363865
          39376238613838396462353932316237383732346435323132343266326632373265346538626330
          3039383965383234630a666138663232333563306536663839613863346237613839633632353164
          6665363337643238653434383864313635633133383432343438626139373734653

Die Dateistruktur sollte nun folgendermaßen aussehen:

Dateistruktur

Nun fehlt nur noch das Ausführen des Playbooks. Da per ansible-vault verschlüsselte Variablen eingesetzt werden, muss das Vault Passwort hier eingegeben werden.

> ansible-playbook -i inventory --ask-vault-pass site.yml

Sollte alles geklappt haben, quittiert ansible-playbook dies mit folgender Meldung:

ansible-playbook Erfolgsmeldung

Ansible macht das Backup einfach

Dank des erstellten Ansible Playbooks wird die Einrichtung von Backups auf neuen Servern stark vereinfacht. Um dem Backup weitere Hosts hinzuzufügen, müssen diese nur in das Inventar eingetragen werden, woraufhin eine weitere Ausführung des Playbooks Restic auf den neu hinzu gekommenen Hosts einrichtet. Für ein vollwertiges Backup muss natürlich noch der Pfad des Backups von „/mnt/backup“ auf einen sinnvollen Wert geändert werden. Restic unterstützt viele Protokolle, wie S3 oder SFTP, die es ermöglichen, das Backup auf einen anderen Host zu übertragen oder in der Cloud zu sichern. Mit den in diesem Artikel vorgestellten Konzepten kann die Rolle beliebig erweitert werden, um diese konfigurieren zu können. Wer keine Rollen selbst schreiben möchte, kann sich alternativ auch an den vielen von der Community bereitgestellten / verfassten Rollen bedienen.

Ähnliche Artikel:

Webentwicklung

Automatisierte Tests dank Gitlab CI

Testen ist bei der Entwicklung das A und O. Umso besser, dass es mit Gitlab CI eine Möglichkeit des automatisierten Testens gibt. Details erfährst du im Blog.

Kommentar hinzufügen