Vier Stunden. Genau so lange hat mich der monatliche Update-Abend gekostet: drei Server, drei ssh-Sitzungen, dreimal apt upgrade, dreimal auf dpkg warten. Eine Kiste wollte neu starten, die andere meldete vollen Disk, und auf der dritten lief nach dem Update ein Dienst nicht mehr. Kurz vor elf war ich fertig, müde und mit dem Gefühl, einen Abend für etwas ausgegeben zu haben, das ein Computer allein hätte erledigen können.
Heute sieht das anders aus. Ich tippe einen Befehl, stelle die Kaffeetasse daneben, und zwölf Minuten später melden alle drei Server: alles aktuell, alles grün. Dazwischen liegen fünf kleine Playbooks, die ich an zwei Abenden geschrieben habe und seitdem jeden Monat unverändert nutze. Kein Cluster, keine Agenten, keine 200-Seiten-Doku. Nur SSH, ein Inventar-File und Tasks, die ich einmal formuliert habe.
Dieser Artikel zeigt die fünf Playbooks, die bei mir tatsächlich laufen: Setup, Host-Keys, Updates, Benutzer, Backups. Inklusive der Fehler, die ich unterwegs gemacht habe, damit du sie nicht wiederholst.
Warum Ansible, wenn man allein arbeitet?
Ansible klingt nach Enterprise, nach Tower und Teams und Ticketing. Dabei ist es für eine einzelne Person das beste Werkzeug, das ich kenne, und genau dafür ist es gebaut: Die Zielsysteme brauchen nichts außer SSH und Python. Keinen Agenten, der installiert, gepflegt und aktualisiert werden will. Du schreibst ein Playbook auf deinem Laptop, und Ansible führt es auf allen Servern aus, mit derselben Reihenfolge, denselben Parametern, denselben Ergebnissen.
Der wichtigste Grund ist für mich aber ein anderer: Ein Playbook ist dokumentierte Erinnerung. Früher wusste ich irgendwann nicht mehr, welche Pakete ich auf welcher Kiste nachinstalliert hatte und warum. Jetzt steht es in einem File. Wenn ich mich in sechs Monaten frage, warum der Server so eingerichtet ist, wie er ist, gibt mir das Playbook die Antwort. Und weil Playbooks idempotent sind, kann ich sie gefahrlos immer wieder laufen lassen: Ein Task, der nichts zu tun hat, tut nichts.
Das Grundgerüst: ansible.cfg und Inventar
Mein Setup besteht aus zwei kleinen Dateien. Die erste, ansible.cfg, sagt Ansible, wo das Inventar liegt und dass die Host-Keys geprüft werden sollen:
[defaults]
inventory = inventory
host_key_checking = true
retry_files_enabled = false
Die zweite Datei ist das Inventar. Drei Gruppen wären Overkill, eine reicht:
[servers]
vps1.example.net
nas01.local
mail01.example.net
[all:vars]
ansible_user = admin
Mehr braucht es nicht. Die Variablen unter [all:vars] gelten für alle Hosts, und weil ich mich überall als admin anmelde, muss ich das nicht pro Server wiederholen. Den Rest des Setups erledigt das erste Playbook.
setup.yml: Ein neuer Server in 15 Minuten
Früher habe ich einen frischen Server von Hand eingerichtet: Benutzer anlegen, sudo konfigurieren, SSH-Keys kopieren, fail2ban, ufw, ein paar Pakete. Zwei Stunden, wenn alles glatt lief, und bei jedem Server habe ich leicht andere Entscheidungen getroffen. Heute läuft setup.yml, und jeder Server sieht am Ende gleich aus. Das ist der Punkt: Standardisierung ist nicht langweilig, sie ist Versicherung. Wenn alle Kisten gleich sind, funktionieren die anderen vier Playbooks überall ohne Sonderfälle.
Das Playbook ist bewusst simpel: apt-Update am Anfang, Basis-Pakete, Benutzer mit SSH-Key, fail2ban und ufw mit den Ports, die ich brauche. Nichts Exotisches, aber alles, was nötig ist, damit ein Server produktiv werden kann. Danach wandert er ins Inventar, und die Update-Routine übernimmt.
update.yml: Der Morgenkaffee-Lauf
Das ist das Playbook, das mir am meisten Zeit zurückgibt. Vorher: vier Stunden an einem Abend, verteilt auf drei Terminals, mit Unterbrechungen. Heute:
---
- name: Systeme aktuell halten
hosts: servers
become: true
tasks:
- name: Paketquellen aktualisieren
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Pakete upgraden
ansible.builtin.apt:
upgrade: dist
autoremove: true
register: update_result
notify: Dienste neu laden
- name: Reboot-Status prüfen
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Server neu starten
ansible.builtin.reboot:
reboot_timeout: 300
when: reboot_required.stat.exists
handlers:
- name: Dienste neu laden
ansible.builtin.service:
name: "{{ item }}"
state: reloaded
loop:
- nginx
- postfix
Ein paar Details, die man erst nach dem dritten Fehler schätzt. cache_valid_time verhindert, dass Ansible bei jedem Lauf die Paketlisten neu lädt, wenn sie jünger als eine Stunde sind. autoremove räumt alte Kernel und verwaiste Abhängigkeiten weg, die sich sonst über Monate ansammeln. Und der Reboot-Task startet eine Kiste nur dann neu, wenn /var/run/reboot-required existiert. Genau dieser Check hat mir den ersten Wochenend-Einsatz erspart, als ein Kernel-Update auf dem Mailserver wartete und ich es übersehen hatte.
Der Handler mit der Loop ist mein Standard-Rezept: Dienste, die ein Update überleben sollen, werden nur neu geladen, wenn ein Task sie benachrichtigt. Läuft ein Playbook ohne Änderungen durch, passiert nichts. Das macht den Lauf am Morgen so entspannt: Ich starte update.yml, trinke Kaffee, und wenn ich zurückkomme, steht die Zusammenfassung da. Zwölf Minuten statt vier Stunden, jeden Monat.
hostkeys.yml: Kein SSH-Albtraum mehr
Irgendwann habe ich einen Server neu installiert, und beim nächsten ssh meldete sich der alte Bekannte: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED. Erster Impuls: Key löschen und weitermachen. Genau das ist die Falle. Wenn sich ein Host-Key wirklich geändert hat, weil jemand den Server übernommen hat, merkst du den Unterschied nicht mehr.
Mein Kompromiss: Host-Keys werden zentral eingesammelt und in die known_hosts geschrieben. Nach einer Neuinstallation hole ich die frischen Keys mit einem Playbook-Lauf statt mit ssh-keyscan von Hand:
---
- name: Host-Keys einsammeln und verteilen
hosts: localhost
connection: local
tasks:
- name: ed25519 Keys einsammeln
ansible.builtin.command: ssh-keyscan -t ed25519 {{ item }}
loop: "{{ groups['servers'] }}"
register: keys
changed_when: false
- name: known_hosts aktualisieren
ansible.builtin.known_hosts:
name: "{{ item.item }}"
key: "{{ item.stdout }}"
loop: "{{ keys.results }}"
host_key_checking bleibt an, damit Ansible selbst keine Keys akzeptiert, die nicht in der known_hosts stehen. Das Playbook ist der einzige Weg, auf dem neue Keys ins System kommen. Seitdem ist mir der Warnhinweis genau einmal begegnet: bei meinem eigenen Neuaufbau, und ich wusste, warum.
users.yml: Benutzer mit password_hash
Benutzer anlegen war früher Copy-and-paste aus einer Notiz: useradd, dann mkpasswd -m sha-512, dann das Ergebnis in die Passwort-Zeile. Beim dritten Server habe ich dann doch lieber ein Playbook daraus gemacht, denn Ansible hat dafür etwas Besseres: den password_hash-Filter. Das Passwort wird nie als Klartext gespeichert, der Filter erzeugt den Hash direkt auf dem Kontroll-Rechner:
---
- name: Benutzer einrichten
hosts: servers
become: true
vars_prompt:
- name: new_password
prompt: "Passwort für den neuen Benutzer"
private: true
no_log: true
tasks:
- name: Benutzer anlegen
ansible.builtin.user:
name: "{{ new_user }}"
password: "{{ new_password | password_hash('sha512') }}"
groups: sudo
append: true
shell: /bin/bash
Das Passwort frage ich per vars_prompt ab, mit no_log, damit es weder im Output noch im Log landet. Wer es noch sauberer will, legt das Passwort verschlüsselt in ein ansible-vault-File. Der Punkt ist: Das Ergebnis ist auf allen drei Servern identisch, und der Hash wird bei jedem Lauf neu gegen die Datei geprüft. Kein Server driftet mehr vom anderen ab.
Wenn ich prüfen will, ob der Hash in der /etc/shadow wirklich zu dem Passwort gehört, das ich vergeben habe, nehme ich den Hash-Generator auf bitcalc.net: Ein Vergleichs-Hash aus demselben Passwort muss identisch sein. Für Checksummen von heruntergeladenen Dateien nutze ich denselben Generator, bevor setup.yml Pakete installiert, die nicht aus den Repos kommen.
backup.yml: Die Konfiguration ist das Kapital
Das fünfte Playbook ist das unspektakulärste und das, das ich am wenigsten missen will: Es sichert die Konfigurationen. /etc von allen drei Servern, die Cron-Tabs, die Liste der installierten Pakete. Nicht die Daten, die liegen woanders, sondern der Zustand, der einen Server zu dem macht, was er ist.
Das waren früher pro Monat gut zwei Stunden Handarbeit: Verzeichnisse zusammenpacken, per scp rüberziehen, irgendwo ablegen. Heute ist es ein Playbook-Lauf mit zehn Minuten Laufzeit, und die Backups liegen auf einer Kiste außerhalb des eigentlichen Setups. Wer schon einmal einen Server nach einem Festplatten-Tod aus dem Gedächtnis neu aufgebaut hat, weiß, wie viel das wert ist.
Fakten und Checksummen mit den bitcalc-Tools
Zwei Werkzeuge von bitcalc.net begleiten mich bei der Ansible-Arbeit. Der erste Moment ist immer derselbe: ansible -m setup liefert die Fakten eines Servers als riesiges JSON-Dokument. Unformatiert ist das eine einzige Zeile mit tausend verschachtelten Klammern. Der JSON Formatter macht daraus einen lesbaren Baum, in dem ich gezielt nachschauen kann, welche Kernel-Version läuft oder welches Interface welche IP hat.
Der zweite Moment ist die Prüfung: Bevor setup.yml etwas installiert, das nicht aus den Paketquellen kommt, vergleiche ich die Checksumme der heruntergeladenen Datei mit der offiziellen Angabe. Der Hash-Generator rechnet SHA-256 direkt im Browser, die Datei verlässt den Rechner nicht.
setup.yml · Erstsetup: Benutzer, SSH-Keys, fail2ban, ufw (2 h → 15 min)
hostkeys.yml · Host-Keys einsammeln und verteilen (30 min → 2 min)
update.yml · apt-Update inkl. Reboot-Erkennung (4 h → 12 min)
users.yml · Benutzer mit password_hash (20 min → 5 min)
backup.yml · Konfigurationen sichern (1 h → 10 min)
Der Morgenkaffee-Befehl:
ansible-playbook -i inventory update.ymlFazit
Fünf Playbooks, drei Server, zwölf Minuten am Morgen. Der größte Gewinn ist nicht die eingesparte Zeit, so schön sie ist. Es ist das ruhige Gewissen: Der Zustand der Server steht in Files, ist reproduzierbar und wird von einem Werkzeug geprüft, das nicht müde wird und nichts vergisst.
Fang klein an. Nimm update.yml, schreib es in einer Stunde und lass es nächsten Monat laufen. Der Rest ergibt sich von selbst, sobald du einmal gemerkt hast, wie gut sich ein Dienstagabend ohne apt upgrade anfühlt.