Server-Deployment mit Ansible: reproduzierbare Debian-Server
Mehrere Server, gleiche Konfiguration, kein Handbuch-Gefrickel: Mit Ansible richtest du Debian-Server reproduzierbar ein – agentenlos über SSH und mit idempotenten Playbooks.
Warum Ansible?
Teil 5 richtet Container ein – aber was ist, wenn du mehrere Server gleich oder wiederholt konfigurieren musst? Ansible automatisiert Debian-Server reproduzierbar: gleiche Konfiguration, dokumentiert im Git-Repo, ohne Agenten auf den Zielsystemen.
Konzept: Control Node, Inventory & Playbooks
Ansible arbeitet agentenlos über SSH: Ein Control-Rechner (dein Laptop oder ein Admin-Server) führt Playbooks aus, die Zielserver müssen nur SSH und Python mitbringen. Wichtig ist die Idempotenz: Ein Playbook kann beliebig oft laufen, ohne Zustände doppelt zu setzen.
Vorbereitung
Ansible wird nur auf dem Control-Rechner installiert – die Debian-Zielserver brauchen nichts:
sudo apt install ansible # Debian-Paket
ansible --version
mkdir -p ~/ansible/{inventories,playbooks}
cd ~/ansibleVoraussetzung: SSH-Key-Zugang zu den Zielservern (ohne Passwort, siehe Debian-Teil 1).
Inventory anlegen
In inventories/hosts tragen wir die Server ein:
[server]
web1 ansible_host=10.0.0.10 ansible_user=lena
vpn1 ansible_host=10.0.0.11 ansible_user=lena
[server:vars]
ansible_python_interpreter=/usr/bin/python3
Erste Befehle (Ad-hoc)
Erreichbarkeit und sudo testen:
ansible -i inventories/hosts -m ping all
ansible -i inventories/hosts -m apt -a "name=htop state=present" -b --become-user=root serverDas -b aktiviert become (sudo) – für Administrationsaufgaben Pflicht.
Das erste Playbook
Datei playbooks/basis.yml – Updates, Basis-Pakete und SSH-Härtung in einem Rutsch:
---
- name: Basis-Server einrichten
hosts: server
become: true
tasks:
# Kombiniert: Aktualisierung und Installation spart massiv Zeit
- name: Basis-Pakete installieren und Cache aktualisieren
ansible.builtin.apt:
name:
- htop
- ufw
- fail2ban
state: present
update_cache: true
# Blockinfile ist wesentlich robuster und sauberer als eine lineinfile-Schleife
- name: SSH-Härtung konfigurieren (Drop-in)
ansible.builtin.blockinfile:
path: /etc/ssh/sshd_config.d/99-ansible.conf
create: true
mode: '0644'
block: |
PermitRootLogin no
PasswordAuthentication no
notify: SSH sanft neu laden
handlers:
# reloaded verhindert das harte Trennen der aktuellen SSH-Sitzung
- name: SSH sanft neu laden
ansible.builtin.service:
name: ssh
state: reloadedAusführen:
ansible-playbook -i inventories/hosts playbooks/basis.yml --check # Probe
ansible-playbook -i inventories/hosts playbooks/basis.ymlSecrets mit Ansible Vault
Passwörter gehören nicht ins Playbook. Mit Vault verschlüsseln wir sie:
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-playbook --ask-vault-pass -i inventories/hosts playbooks/basis.ymlPush-Variante mit Semaphore (Web-UI)
Bisher haben wir Playbooks per Terminal gestartet – die Push-Variante. Wer das im Team machen will oder History, Zeitpläne und Zugriffskontrolle braucht, hängt ein Web-Interface davor: Semaphore (Open Source) verwaltet Projekte, Inventories, SSH-Keys und stößt Playbooks per Klick, Zeitplan, Webhook oder API an.
ansible-pull) holen sich die Zielserver die Playbooks selbst. Semaphore ist der komfortablere Push-Weg für Teams.Semaphore mit Docker Compose betreiben
Semaphore braucht eine Datenbank (hier MariaDB), um Projekte und Verlauf zu speichern. Passwörter schreiben wir nicht direkt in die docker-compose.yml – so gibt der Artikel keine (Demo-)Config mit echten Werten weiter, und wer sie kopiert, übernimmt keine unsicheren Standard-Passwörter. Stattdessen liest Docker Compose die Werte automatisch aus der Datei .env im selben Ordner wie die docker-compose.yml. Diese erstellst du einmalig mit eigenen, sicheren Passwörtern:
# .env (im selben Ordner wie die docker-compose.yml – NIE ins Git-Repo!)
DB_ROOT_PASS=dein_sicheres_root_passwort
DB_PASS=dein_sicheres_db_passwort
ADMIN_USER=admin
ADMIN_PASS=dein_sicheres_admin_passwort
ADMIN_NAME=Admin
ADMIN_EMAIL=admin@meine.domain
Die docker-compose.yml referenziert die Variablen nur noch per ${…} – so bleibt sie frei von Secrets und darf bedenkenlos im Git-Repo liegen:
services:
semaphore:
image: semaphoreui/semaphore:latest
container_name: semaphore
restart: unless-stopped
depends_on:
- db
ports:
- "127.0.0.1:50600:3000"
environment:
- SEMAPHORE_DB_USER=semaphore
- SEMAPHORE_DB_PASS=${DB_PASS}
- SEMAPHORE_DB_HOST=db
- SEMAPHORE_DB_PORT=3306
- SEMAPHORE_DB=semaphore
- SEMAPHORE_ADMIN=${ADMIN_USER}
- SEMAPHORE_ADMIN_PASSWORD=${ADMIN_PASS}
- SEMAPHORE_ADMIN_NAME=${ADMIN_NAME}
- SEMAPHORE_ADMIN_EMAIL=${ADMIN_EMAIL}
- SEMAPHORE_TZ=Europe/Berlin
volumes:
# WICHTIG: Verhindert, dass SSH-Keys und Repositories bei Updates gelöscht werden
- ./semaphore-data:/var/lib/semaphore
db:
image: mariadb:11
container_name: semaphore_db
restart: unless-stopped
environment:
- MARIADB_DATABASE=semaphore
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASS}
- MARIADB_USER=semaphore
- MARIADB_PASSWORD=${DB_PASS}
volumes:
- ./semaphore-db:/var/lib/mysql
.env automatisch – genau darum gehört sie ins .gitignore (.env eintragen), während die docker-compose.yml mit den Platzhaltern gefahrlos versioniert werden kann.Danach erreichbar unter http://127.0.0.1:50600. Beim ersten Login mit den SEMAPHORE_ADMIN_*-Zugangsdaten aus deiner .env anmelden.
Im Projekt dann:
- Repository: dein Ansible-Ordner als Git-Repo (z. B. Codeberg) – Semaphore klont es beim Lauf.
- Inventory: die Datei
inventories/hostsaus dem Repo wählen. - Key: den SSH-Key zum Zielserver im Key-Store hinterlegen.
- Playbook:
playbooks/basis.yml– per Klick, Cron-artigem Zeitplan oder Webhook ausführen.
FAQ
| Problem | Lösung |
|---|---|
| „unreachable“ | SSH-Key prüfen, Hostname/IP im Inventory korrigieren, ansible -m ping isoliert testen. |
| sudo-Passwort wird abgefragt | -b --ask-become-pass oder Key-basiertes sudo (NOPASSWD) einrichten. |
| Playbook ist nicht idempotent | Module statt command nutzen; --check vor jedem Lauf. |
| Andere Distribution | Inventar-Gruppen je Distro anlegen oder ansible_facts["os_family"] verwenden. |
| Python fehlt auf dem Zielserver | ansible_python_interpreter=/usr/bin/python3 setzen (Debian 13 hat es). |
| Rollout über viele Server dauert | In ansible.cfg forks = 10 erhöhen. |
| Semaphore: Hosts unreachable | SSH-Key im Key-Store hinterlegen, Erreichbarkeit aus dem Container testen (docker exec semaphore ssh …). |
| Semaphore: Playbook nicht gefunden | Repository-URL, Branch und Dateipfad im Projekt prüfen; Semaphore klont beim Lauf frisch. |
| Semaphore: Zeitplan feuert nicht | SEMAPHORE_TZ prüfen; Cron läuft in der Container-Zeitzone. |
Fazit
Mit Ansible wird aus „ich habe den Server irgendwann eingerichtet“ ein reproduzierbares, dokumentiertes Setup – die perfekte Ergänzung zu Teil 5: erst der Container-Standard, dann die Automatisierung darüber.
② Idempotente Playbooks: immer wieder ausführbar
③
--check vor jedem Lauf, Vault für Secrets④ Playbooks gehören ins Git-Repo