LU10c - Provisioning mit Ansible (idempotent konfigurieren)

Ziel: Du kannst Provisioning verstehen und anwenden: Du konfigurierst VMs automatisiert mit Ansible (idempotent), und du kannst Ansible-Provisioning sinnvoll von Shell-Skripten und Dockerfiles abgrenzen.

Übersicht

Thema Kernaussage
Provisioning „Maschine nach dem Boot automatisch einrichten“ (Pakete, Konfig, Services, Users, Files).
Shell Provisioner Schnell, aber oft „fragil“ (nicht idempotent, schwer wartbar).
Ansible Konfiguration als Code: deklarativ, idempotent, gut strukturierbar (Rollen/Tasks).
Idempotenz „Mehrfach ausführen → gleicher Zustand, ohne Chaos“.
Handler „Nur bei Änderung Service neu starten/reload“.
Playbook Sammlung von Tasks, die auf Hosts angewendet werden.

Warum Provisioning?

Ohne Provisioning passiert das hier:

Mit Provisioning:

Merke: Vagrant erstellt VMs. Provisioning macht daraus „fertige Nodes“ für dein verteiltes System.

Shell vs. Ansible (kurzer Vergleich)

Kriterium Shell-Skript Ansible
Lesbarkeit ok, aber schnell unübersichtlich sehr gut (Tasks/Module)
Wiederholbarkeit oft problematisch idempotent
Fehlerhandling selber bauen eingebaut (Return-Codes, Changed-Status)
Strukturierung eher „Script-Spaghetti“ Rollen, Variablen, Templates
Lerntransfer Script-Skills DevOps/IaC-Transfer sehr hoch

Kernkonzepte in Ansible

Inventory (Hosts-Liste)

Welche Maschinen gibt es und wie heissen sie?

Beispiel (klassisch):

[db]
192.168.56.11
 
[api]
192.168.56.12
 
[proxy]
192.168.56.13

Playbook

Ein YAML-Dokument, das beschreibt, was auf welchen Hosts passieren soll.

Task

Ein einzelner Schritt, z.B. „nginx installieren“.

Module

„Bausteine“ für typische Dinge: `apt`, `copy`, `template`, `service`, `user`, `lineinfile`…

Handler

Wird nur ausgelöst, wenn sich etwas ändert (z.B. Config geändert → nginx reload).

Idempotenz ist das Killer-Feature: Ein Playbook kann man „einfach nochmal laufen lassen“, ohne alles kaputt zu machen.

Ansible in Vagrant einbinden

Es gibt zwei gängige Wege:

Variante A: ansible (Host-basiert)

Variante B: ansible_local (Guest-basiert)

In Klassen mit gemischten Betriebssystemen ist ansible_local oft robuster.

Beispiel-Projektstruktur

projekt/
├─ Vagrantfile
└─ provision/
   ├─ playbook.yml
   ├─ group_vars/
   │  ├─ all.yml
   └─ templates/
      └─ nginx.conf.j2

Beispiel: Vagrantfile mit ansible_local

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/jammy64"
 
  nodes = {
    "db"    => "192.168.56.11",
    "api"   => "192.168.56.12",
    "proxy" => "192.168.56.13"
  }
 
  nodes.each do |name, ip|
    config.vm.define name do |node|
      node.vm.hostname = name
      node.vm.network "private_network", ip: ip
    end
  end
 
  # Ansible läuft in der VM (ansible_local)
  config.vm.provision "ansible_local" do |ansible|
    ansible.playbook = "provision/playbook.yml"
    ansible.install = true
  end
end

Beispiel: Playbook (Basis + Rollen light)

Dieses Playbook zeigt typische Patterns:

# provision/playbook.yml
- name: Multi-Node Setup fuer verteilte Systeme
  hosts: all
  become: true

  vars:
    cluster_hosts:
      - { name: "db",    ip: "192.168.56.11" }
      - { name: "api",   ip: "192.168.56.12" }
      - { name: "proxy", ip: "192.168.56.13" }

  tasks:
    - name: Pakete installieren (Basis)
      ansible.builtin.apt:
        name:
          - curl
          - ca-certificates
          - net-tools
          - python3
          - python3-pip
        update_cache: true
        state: present

    - name: /etc/hosts fuer Cluster-Namen pflegen
      ansible.builtin.lineinfile:
        path: /etc/hosts
        line: "{{ item.ip }} {{ item.name }}"
        state: present
      loop: "{{ cluster_hosts }}"

- name: Proxy Node konfigurieren
  hosts: proxy
  become: true

  tasks:
    - name: nginx installieren
      ansible.builtin.apt:
        name: nginx
        update_cache: true
        state: present

    - name: nginx config deployen
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: reload nginx

    - name: nginx aktivieren und starten
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Beispiel: Nginx Template (Reverse Proxy)

# provision/templates/nginx.conf.j2
server {
  listen 80;
 
  location / {
    proxy_pass http://api:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

Was hier wichtig ist: Der Proxy spricht api:8000 (Name, nicht IP). Das funktioniert, weil wir via Ansible `/etc/hosts` gesetzt haben. (Später kann man das durch „echte“ Service Discovery ersetzen.)

Brücke zu Dockerfile / Docker-Compose

Konzept Docker-Welt VM/Ansible-Welt
Artefakt Image VM/Box + Provisioning
„Build“ Dockerfile → Image (optional) Packer → Image / oder Vagrant + Provisioning
Konfiguration ENV, COPY, RUN apt, template, service, systemd
Reproduzierbarkeit sehr hoch hoch (wenn idempotent)
Runtime Container VM

Wenn du Ansible sauber machst, lernen die Lernenden „Konfiguration als Code“ – das ist in der Praxis extrem gefragt, egal ob VM, Cloud oder Kubernetes.

Debugging & Qualität

Playbook testen

# in der VM (ansible_local) typischerweise:
sudo ansible-playbook /vagrant/provision/playbook.yml
 
# mehr Details:
sudo ansible-playbook /vagrant/provision/playbook.yml -vv

Häufige Fehler

Profi-Pattern (optional)


Kevin Maurizi