Serverautomatisierung mit Ansible in der Praxis
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.
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:
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 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.