commit dfb7279a8c73ac847741615b4fa3c9a2c043d0d9 Author: jonnybravo Date: Mon Sep 14 11:36:01 2026 +0200 first diff --git a/Beispiel/README.md b/Beispiel/README.md new file mode 100644 index 0000000..1b688b0 --- /dev/null +++ b/Beispiel/README.md @@ -0,0 +1,86 @@ +# Ansible-Beispiel: Rolle, Variablen & Templating (localhost) + +Ein vollständiges Beispiel-Projekt, das alle Konzepte aus der Präsentation +demonstriert: + +- **Rollen-Struktur** (`roles/webserver/`) +- **Variablen-Vergabe** mit Präzedenz (defaults → vars → group_vars → host_vars) +- **Templating** mit Jinja2 (`.j2`-Dateien, Schleifen, Filter) +- **FQCN** (`ansible.builtin.*`) +- **Facts** (`ansible_distribution`, `ansible_hostname`, …) +- **Handler** (`notify` → Dienst neu starten) + +--- + +## Verzeichnisstruktur + +``` +Beispiel/ +├── ansible.cfg # Ansible-Konfiguration +├── inventory.yml # Ziel-Hosts (localhost) +├── site.yml # Haupt-Playbook +├── group_vars/ +│ └── all.yml # Variablen für ALLE Hosts +├── host_vars/ +│ └── localhost.yml # Host-spezifische Variablen +└── roles/ + └── webserver/ + ├── tasks/main.yml # Aufgaben + ├── handlers/main.yml # Neustart-Aktionen (notify) + ├── templates/ + │ ├── nginx.conf.j2 # Jinja2-Template + │ └── index.html.j2 # Template mit Schleife + Filter + ├── defaults/main.yml # Standard-Variablen (niedrigste Priorität) + ├── vars/main.yml # Rollen-Variablen (höhere Priorität) + └── meta/main.yml # Rollen-Metadaten +``` + +--- + +## Ausführen + +```bash +# Trockenlauf — zeigt nur, was passieren würde +ansible-playbook site.yml --check + +# Echt ausführen (benötigt sudo wegen Paket-Installation) +ansible-playbook site.yml + +# Variable per CLI überschreiben (HÖCHSTE Priorität!) +ansible-playbook site.yml -e "nginx_port=9090" +``` + +--- + +## Variablen-Präzedenz im Beispiel + +Dieselbe Variable (`nginx_port`) ist an mehreren Stellen definiert — wer gewinnt? + +| Priorität | Ort | Wert | +|-----------|------------------------------|-----------------| +| 1 (höchste) | Extra-Variablen `-e` | `9090` (wenn gesetzt) | +| 2 | `host_vars/localhost.yml` | `8080` | +| 3 | `group_vars/all.yml` | — | +| 4 | `roles/.../vars/main.yml` | `nginx_user` | +| 5 (niedrigste) | `roles/.../defaults/main.yml` | `80` (Default) | + +→ Ergebnis: `nginx_port` wird **8080**, weil `host_vars` Vorrang vor `defaults` +hat (die Default `80` wird überschrieben). + +--- + +## Für Manjaro / Arch Linux anpassen + +Das Beispiel nutzt `apt` (Debian/Ubuntu). Auf Manjaro ersetzt Du in +`roles/webserver/tasks/main.yml` das Modul: + +```yaml +- name: Pakete installieren + community.general.pacman: + name: "{{ packages }}" + state: present + update_cache: true +``` + +und passt die Paketnamen (`nginx`, `git`, `curl` bleiben gleich) sowie +`nginx_user` ggf. an. diff --git a/Beispiel/ansible.cfg b/Beispiel/ansible.cfg new file mode 100644 index 0000000..664d353 --- /dev/null +++ b/Beispiel/ansible.cfg @@ -0,0 +1,12 @@ +[defaults] +# Inventory-Datei (relativ zum Projektordner) +inventory = inventory.yml + +# Kein SSH-Host-Key-Check (praktisch für lokale Tests) +host_key_checking = False + +# Python-Interpreter automatisch erkennen +interpreter_python = auto_silent + +# Retry-Dateien nicht anlegen +retry_files_enabled = False diff --git a/Beispiel/group_vars/all.yml b/Beispiel/group_vars/all.yml new file mode 100644 index 0000000..78fcbbc --- /dev/null +++ b/Beispiel/group_vars/all.yml @@ -0,0 +1,6 @@ +--- +# group_vars/all.yml +# Variablen für ALLE Hosts in der Gruppe "all" (mittlere Priorität). + +app_name: "Meine Ansible-Demo" +admin_email: admin@example.com diff --git a/Beispiel/host_vars/localhost.yml b/Beispiel/host_vars/localhost.yml new file mode 100644 index 0000000..6395c23 --- /dev/null +++ b/Beispiel/host_vars/localhost.yml @@ -0,0 +1,5 @@ +--- +# host_vars/localhost.yml +# Host-spezifische Variablen — überschreiben group_vars und Rollen-Defaults. + +nginx_port: 8080 # überschreibt die Default 80 aus defaults/main.yml diff --git a/Beispiel/inventory.yml b/Beispiel/inventory.yml new file mode 100644 index 0000000..966edb5 --- /dev/null +++ b/Beispiel/inventory.yml @@ -0,0 +1,8 @@ +--- +# Inventory — definiert die Ziel-Hosts +# Format: YAML (alternativ auch INI möglich) + +all: + hosts: + localhost: + ansible_connection: local # läuft direkt auf diesem Rechner (kein SSH) diff --git a/Beispiel/roles/webserver/defaults/main.yml b/Beispiel/roles/webserver/defaults/main.yml new file mode 100644 index 0000000..7457cbc --- /dev/null +++ b/Beispiel/roles/webserver/defaults/main.yml @@ -0,0 +1,12 @@ +--- +# roles/webserver/defaults/main.yml +# Standard-Variablen — NIEDRIGSTE Priorität, leicht überschreibbar. + +nginx_port: 80 +domain: example.com +web_root: /var/www/html + +packages: + - nginx + - git + - curl diff --git a/Beispiel/roles/webserver/handlers/main.yml b/Beispiel/roles/webserver/handlers/main.yml new file mode 100644 index 0000000..38a87d0 --- /dev/null +++ b/Beispiel/roles/webserver/handlers/main.yml @@ -0,0 +1,9 @@ +--- +# roles/webserver/handlers/main.yml +# Handler — laufen NUR, wenn eine Task sie per "notify" auslöst. +# (Bsp.: erst nach einer Änderung an der Konfiguration neu starten.) + +- name: nginx neu starten + ansible.builtin.service: + name: nginx + state: restarted diff --git a/Beispiel/roles/webserver/meta/main.yml b/Beispiel/roles/webserver/meta/main.yml new file mode 100644 index 0000000..36b3e3f --- /dev/null +++ b/Beispiel/roles/webserver/meta/main.yml @@ -0,0 +1,13 @@ +--- +# roles/webserver/meta/main.yml +# Rollen-Metadaten: Name, Beschreibung, Lizenz, Abhängigkeiten. + +galaxy_info: + role_name: webserver + author: Dein Name + description: "Beispiel-Rolle: installiert & konfiguriert nginx (mit Templating)" + license: MIT + min_ansible_version: "2.10" + +# Abhängigkeiten zu anderen Rollen (hier: keine) +dependencies: [] diff --git a/Beispiel/roles/webserver/tasks/main.yml b/Beispiel/roles/webserver/tasks/main.yml new file mode 100644 index 0000000..c7faa07 --- /dev/null +++ b/Beispiel/roles/webserver/tasks/main.yml @@ -0,0 +1,39 @@ +--- +# roles/webserver/tasks/main.yml +# Die Aufgaben der Rolle. Jede Task ruft genau ein Modul auf (per FQCN). + +- name: Betriebssystem ausgeben (Facts nutzen) + ansible.builtin.debug: + msg: "Läuft auf {{ ansible_distribution }} {{ ansible_distribution_version }}" + +- name: Pakete installieren + ansible.builtin.apt: + name: "{{ packages }}" + state: present + update_cache: true + notify: nginx neu starten + +- name: Webverzeichnis anlegen + ansible.builtin.file: + path: "{{ web_root }}" + state: directory + mode: '0755' + +- name: Konfiguration aus Template rendern + ansible.builtin.template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + mode: '0644' + notify: nginx neu starten + +- name: Startseite aus Template rendern (mit Schleife & Filter) + ansible.builtin.template: + src: index.html.j2 + dest: "{{ web_root }}/index.html" + mode: '0644' + +- name: Dienst aktivieren und starten + ansible.builtin.service: + name: nginx + state: started + enabled: true diff --git a/Beispiel/roles/webserver/templates/index.html.j2 b/Beispiel/roles/webserver/templates/index.html.j2 new file mode 100644 index 0000000..eefaac2 --- /dev/null +++ b/Beispiel/roles/webserver/templates/index.html.j2 @@ -0,0 +1,20 @@ + + + + + {{ app_name }} + + +

{{ app_name }}

+

Betriebssystem: {{ ansible_distribution }} {{ ansible_distribution_version }}

+ +

Installierte Pakete:

+ + +

Port: {{ nginx_port }}

+ + diff --git a/Beispiel/roles/webserver/templates/nginx.conf.j2 b/Beispiel/roles/webserver/templates/nginx.conf.j2 new file mode 100644 index 0000000..d7f861d --- /dev/null +++ b/Beispiel/roles/webserver/templates/nginx.conf.j2 @@ -0,0 +1,21 @@ +# {{ ansible_managed }} <- von Ansible erzeugt, nicht manuell ändern! + +user {{ nginx_user }}; +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + server { + listen {{ nginx_port }}; + server_name {{ domain }}; + + root {{ web_root }}; + index index.html; + + # Fact des Zielsystems wird hier aufgelöst: + access_log /var/log/nginx/{{ ansible_hostname }}.access.log; + } +} diff --git a/Beispiel/roles/webserver/vars/main.yml b/Beispiel/roles/webserver/vars/main.yml new file mode 100644 index 0000000..5e430ee --- /dev/null +++ b/Beispiel/roles/webserver/vars/main.yml @@ -0,0 +1,6 @@ +--- +# roles/webserver/vars/main.yml +# Rollen-Variablen — HÖHERE Priorität als defaults, nicht leicht überschreibbar. +# Eignet sich für Werte, die fest zur Rolle gehören. + +nginx_user: www-data diff --git a/Beispiel/site.yml b/Beispiel/site.yml new file mode 100644 index 0000000..62394b7 --- /dev/null +++ b/Beispiel/site.yml @@ -0,0 +1,13 @@ +--- +# site.yml — Haupt-Playbook +# Ruft die Rolle "webserver" für localhost auf. + +- name: Webserver auf localhost konfigurieren + hosts: localhost + gather_facts: true # Facts sammeln (OS, IP, Hostname, …) + become: true # mit sudo ausführen (für Paket-Installation) + + roles: + - role: webserver + vars: # Playbook-Variablen (überschreibt Defaults) + domain: localhost diff --git a/ansible_praesentation.pptx b/ansible_praesentation.pptx new file mode 100644 index 0000000..c778fdb Binary files /dev/null and b/ansible_praesentation.pptx differ diff --git a/assets/ansible_logo.svg b/assets/ansible_logo.svg new file mode 100644 index 0000000..07a76db --- /dev/null +++ b/assets/ansible_logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/ansible_mark_red.png b/assets/ansible_mark_red.png new file mode 100644 index 0000000..aad2537 Binary files /dev/null and b/assets/ansible_mark_red.png differ diff --git a/assets/ansible_mark_white.png b/assets/ansible_mark_white.png new file mode 100644 index 0000000..5858c8a Binary files /dev/null and b/assets/ansible_mark_white.png differ diff --git a/assets/architektur.png b/assets/architektur.png new file mode 100644 index 0000000..6370462 Binary files /dev/null and b/assets/architektur.png differ diff --git a/create_presentation.py b/create_presentation.py new file mode 100644 index 0000000..954e81a --- /dev/null +++ b/create_presentation.py @@ -0,0 +1,969 @@ +#!/usr/bin/env python3 +""" +Moderne Ansible-Präsentation — vollständiger Generator (v3). + +19 Folien (16:9) mit den Ansible-Brandfarben (Rot #EE0000, Anthrazit #1A1918). +Fonts: Carlito (Body) + DejaVu Sans Mono (Code) — auf dem Zielsystem installiert +und von OnlyOffice korrekt gerendert (kein Font-Substitution-Bruch). + +Behandelt: + - Was ist Ansible? / Aufbau & Architektur + - YAML-Syntax, Playbooks, Module, Ad-hoc-Befehle + - Jinja2 (Templating) und Gather Facts + - Variablen-Auflösung (Präzedenz) + - FQCN (Fully Qualified Collection Name) + - Collections + - Rollen (Roles) + - Praxisbeispiel + Quellen + +Benötigt: pip install python-pptx matplotlib Pillow +Bilder: Ansible-Logo (assets/ansible_mark_*.png, Quelle: Wikimedia Commons) + Architektur-Diagramm wird automatisch erzeugt (assets/architektur.png) +""" + +import os +import re +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib.patches import FancyBboxPatch + +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from pptx.dml.color import RGBColor + +# --------------------------------------------------------------------------- +# Pfade & Farben +# --------------------------------------------------------------------------- +BASE = os.path.dirname(os.path.abspath(__file__)) +ASSETS = os.path.join(BASE, "assets") +os.makedirs(ASSETS, exist_ok=True) + +LOGO_RED = os.path.join(ASSETS, "ansible_mark_red.png") +LOGO_WHITE = os.path.join(ASSETS, "ansible_mark_white.png") +ARCH = os.path.join(ASSETS, "architektur.png") + +BLACK = RGBColor(0x1A, 0x19, 0x18) +RED = RGBColor(0xEE, 0x00, 0x00) +DARKRED = RGBColor(0xB0, 0x00, 0x00) +WHITE = RGBColor(0xFF, 0xFF, 0xFF) +LIGHT = RGBColor(0xF7, 0xF7, 0xF7) +CARD = RGBColor(0xFF, 0xFF, 0xFF) +GRAY = RGBColor(0x55, 0x55, 0x55) # dunkler für bessere Lesbarkeit +MIDGRAY = RGBColor(0x8A, 0x8A, 0x8A) +BORDER = RGBColor(0xDD, 0xDD, 0xDD) +CODE_BG = RGBColor(0x1E, 0x1E, 0x1E) +CODE_CMT= RGBColor(0x8B, 0xC3, 0x7C) # grün für Kommentare +CODE_KEY= RGBColor(0x7E, 0xC8, 0xFF) # hellblau für Schlüssel +CODE_MOD= RGBColor(0xFF, 0x6B, 0x6B) # rot für Module/FQCN + +# Fonts: auf dem Zielsystem installiert (OnlyOffice-kompatibel) +FONT = "Carlito" +FONT_MONO = "DejaVu Sans Mono" + + +# --------------------------------------------------------------------------- +# Architektur-Diagramm (matplotlib) +# --------------------------------------------------------------------------- +def make_architecture_diagram(path): + C_RED, C_BLACK, C_GRAY = "#EE0000", "#1A1918", "#6E6E6E" + fig, ax = plt.subplots(figsize=(11, 5.0), dpi=170) + ax.set_xlim(0, 11); ax.set_ylim(0, 5.0); ax.axis("off") + + def box(x, y, w, h, fc, ec, lw=1.5, r=0.12): + p = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad=0.06,rounding_size={r}", + fc=fc, ec=ec, lw=lw) + ax.add_patch(p); return p + + box(0.5, 0.7, 4.3, 3.7, "#1A1918", C_RED, lw=2.5) + ax.text(2.65, 3.95, "CONTROL NODE", ha="center", va="center", + color="white", fontsize=15, fontweight="bold") + ax.text(2.65, 3.45, "Ansible installiert", ha="center", va="center", + color=C_RED, fontsize=10.5, fontweight="bold") + items = ["Playbooks (YAML)", "Inventory", "Module & Plugins", "Rollen & Collections"] + for i, it in enumerate(items): + y = 2.85 - i * 0.5 + box(0.95, y - 0.18, 3.4, 0.36, "#2B2B2B", "none") + ax.text(2.65, y, it, ha="center", va="center", color="white", fontsize=10.5) + + ax.annotate("", xy=(5.75, 2.6), xytext=(4.9, 2.6), + arrowprops=dict(arrowstyle="-|>", color=C_RED, lw=2.6)) + ax.text(5.32, 3.05, "SSH", ha="center", va="center", color=C_RED, + fontsize=12, fontweight="bold") + ax.text(5.32, 2.1, "agentenlos", ha="center", va="center", color=C_GRAY, fontsize=8.5) + + box(5.95, 0.7, 4.6, 3.7, "#F7F7F7", C_BLACK, lw=1.5) + ax.text(8.25, 3.95, "MANAGED NODES", ha="center", va="center", + color=C_BLACK, fontsize=15, fontweight="bold") + ax.text(8.25, 3.45, "Ziel-Systeme — kein Agent nötig", ha="center", va="center", + color=C_GRAY, fontsize=10) + nodes = [("Web-Server", "Linux"), ("Datenbank", "Linux"), ("App-Server", "Windows")] + xs = [6.6, 8.25, 9.9] + for (name, os_), x in zip(nodes, xs): + box(x - 0.62, 1.0, 1.24, 1.5, "white", C_RED, lw=2, r=0.1) + ax.text(x, 1.95, name, ha="center", va="center", color=C_BLACK, + fontsize=10, fontweight="bold") + ax.text(x, 1.4, os_, ha="center", va="center", color=C_GRAY, fontsize=9) + + fig.savefig(path, bbox_inches="tight", facecolor="white", transparent=False) + plt.close(fig) + return path + + +# --------------------------------------------------------------------------- +# pptx-Helfer +# --------------------------------------------------------------------------- +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +BLANK = prs.slide_layouts[6] + + +def new_slide(bg=None): + s = prs.slides.add_slide(BLANK) + if bg is not None: + r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height) + r.fill.solid(); r.fill.fore_color.rgb = bg + r.line.fill.background(); r.shadow.inherit = False + return s + + +def rect(slide, x, y, w, h, fill, line=None, line_w=1.0, rounded=False, radius=0.08): + shp_type = MSO_SHAPE.ROUNDED_RECTANGLE if rounded else MSO_SHAPE.RECTANGLE + shp = slide.shapes.add_shape(shp_type, Inches(x), Inches(y), Inches(w), Inches(h)) + if fill is None: + shp.fill.background() + else: + shp.fill.solid(); shp.fill.fore_color.rgb = fill + if line is None: + shp.line.fill.background() + else: + shp.line.color.rgb = line; shp.line.width = Pt(line_w) + shp.shadow.inherit = False + if rounded: + try: + shp.adjustments[0] = radius + except Exception: + pass + return shp + + +def text(slide, x, y, w, h, runs, size=14, bold=False, color=BLACK, + align=PP_ALIGN.LEFT, font=FONT, anchor=MSO_ANCHOR.TOP, leading=1.0, + space_after=0): + box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) + tf = box.text_frame + tf.word_wrap = True + tf.vertical_anchor = anchor + tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0 + + if isinstance(runs, str): + runs = [runs] + + for i, item in enumerate(runs): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = align + p.line_spacing = leading + if space_after: + p.space_after = Pt(space_after) + if isinstance(item, str): + item = {"t": item} + elif isinstance(item, tuple): + item = {"t": item[0], **item[1]} + r = p.add_run(); r.text = item["t"] + r.font.size = Pt(item.get("size", size)) + r.font.bold = item.get("bold", bold) + r.font.italic = item.get("italic", False) + r.font.name = item.get("font", font) + r.font.color.rgb = item.get("color", color) + return box + + +def header(slide, kicker, title, dark=False): + text(slide, 0.55, 0.42, 12.2, 0.3, kicker.upper(), + size=11, bold=True, color=RED, font=FONT) + text(slide, 0.55, 0.75, 12.2, 0.75, title, + size=30, bold=True, color=WHITE if dark else BLACK) + logo = LOGO_RED if not dark else LOGO_WHITE + if os.path.exists(logo): + slide.shapes.add_picture(logo, Inches(12.35), Inches(0.25), Inches(0.62), Inches(0.76)) + + +def footer(slide, num): + text(slide, 0.55, 7.08, 9.0, 0.3, "Ansible — moderne Automatisierung", + size=8, color=MIDGRAY) + text(slide, 12.2, 7.08, 0.6, 0.3, str(num), size=9, color=MIDGRAY, align=PP_ALIGN.RIGHT) + + +def _set_run(r, text, font_size, color, bold=False): + r.text = text + r.font.size = Pt(font_size) + r.font.name = FONT_MONO + r.font.color.rgb = color + r.font.bold = bold + + +def _style_kv(p, text, font_size): + if ":" in text: + key, colon, rest = text.partition(":") + is_mod = ("ansible." in key) or ("community." in key) + _set_run(p.add_run(), key, font_size, + CODE_MOD if is_mod else CODE_KEY, bold=is_mod) + _set_run(p.add_run(), colon + rest, font_size, WHITE) + else: + _set_run(p.add_run(), text, font_size, WHITE) + + +def _style_code_line(p, line, font_size): + """Syntax-Einfärbung unter Erhalt der Einrückung.""" + m = re.match(r'^(\s*)(.*)$', line) + lead, rest = m.group(1), m.group(2) + if lead: + _set_run(p.add_run(), lead, font_size, WHITE) + if rest.startswith("#"): + _set_run(p.add_run(), rest, font_size, CODE_CMT) + return + # FQCN (namespace.collection.modul) -> rot, fett + if re.match(r'^[A-Za-z0-9_]+\.[A-Za-z0-9_]+\.[A-Za-z0-9_]+$', rest): + _set_run(p.add_run(), rest, font_size, CODE_MOD, bold=True) + return + if rest.startswith("- "): + _set_run(p.add_run(), "- ", font_size, CODE_MOD) + _style_kv(p, rest[2:], font_size) + return + _style_kv(p, rest, font_size) + + +def code_block(slide, x, y, w, lines, font_size=11, title=None, title_color=RED): + """Dunkler Code-Block mit automatischer Höhe (kein Überlauf).""" + line_h = font_size * 1.32 / 72.0 + pad_top = 0.18 + pad_bot = 0.16 + title_h = 0.38 if title else 0.0 + n = len(lines) if lines else 1 + h = title_h + n * line_h + pad_top + pad_bot + + rect(slide, x, y, w, h, CODE_BG, line=BORDER, line_w=0.75, rounded=True, radius=0.04) + if title: + rect(slide, x, y, w, 0.32, CODE_BG, line=BORDER, line_w=0.75, rounded=True, radius=0.14) + text(slide, x + 0.25, y + 0.035, w - 0.5, 0.25, title, + size=9.5, bold=True, color=title_color, font=FONT_MONO) + y += title_h + tb = slide.shapes.add_textbox(Inches(x + 0.22), Inches(y + pad_top), + Inches(w - 0.44), Inches(h - pad_top - pad_bot)) + tf = tb.text_frame; tf.word_wrap = True + tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0 + for i, line in enumerate(lines): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.line_spacing = 1.05 + if line.strip() == "": + _set_run(p.add_run(), " ", font_size, WHITE) + continue + _style_code_line(p, line, font_size) + return h + + +def card(slide, x, y, w, h, icon, title, body, accent=RED): + rect(slide, x, y, w, h, CARD, line=BORDER, line_w=1.0, rounded=True, radius=0.06) + if icon: + circ = slide.shapes.add_shape(MSO_SHAPE.OVAL, Inches(x + 0.2), Inches(y + 0.2), + Inches(0.44), Inches(0.44)) + circ.fill.solid(); circ.fill.fore_color.rgb = accent + circ.line.fill.background(); circ.shadow.inherit = False + text(slide, x + 0.2, y + 0.2, 0.44, 0.44, icon, size=14, bold=True, + color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE) + text(slide, x + 0.78, y + 0.2, w - 0.95, 0.4, title, size=15, bold=True, color=BLACK) + else: + text(slide, x + 0.22, y + 0.2, w - 0.44, 0.4, title, size=15, bold=True, color=BLACK) + text(slide, x + 0.22, y + 0.72, w - 0.44, h - 0.85, body, size=11, color=GRAY, leading=1.05) + + +def kv_rows(slide, x, y, w, rows, key_w, name_size=11, desc_size=10.5, gap=0.42): + """Monospace-Schlüssel + Beschreibung in Zeilen. rows = [(key, desc), ...]""" + yy = y + for key, desc in rows: + text(slide, x, yy, key_w, 0.3, key, size=name_size, bold=True, + color=DARKRED, font=FONT_MONO) + text(slide, x + key_w, yy, w - key_w, 0.4, desc, size=desc_size, color=GRAY, leading=1.0) + yy += gap + + +# --------------------------------------------------------------------------- +# Diagramm erzeugen +# --------------------------------------------------------------------------- +make_architecture_diagram(ARCH) + + +# =========================================================================== +# FOLIE 1 — Titel (dunkel) +# =========================================================================== +s = new_slide(BLACK) +rect(s, 9.0, 0, 4.333, 7.5, RED, rounded=False) +if os.path.exists(LOGO_WHITE): + s.shapes.add_picture(LOGO_WHITE, Inches(0.9), Inches(1.35), Inches(2.1), Inches(2.59)) +text(s, 0.9, 4.15, 7.5, 0.4, "MODERNE IT-AUTOMATISIERUNG", size=14, bold=True, color=RED) +text(s, 0.9, 4.55, 8.0, 1.3, "Ansible", size=60, bold=True, color=WHITE) +text(s, 0.9, 5.75, 7.5, 0.4, "Agentenlos · Idempotent · Deklarativ · YAML", + size=16, color=RGBColor(0xD8, 0xD8, 0xD8)) +text(s, 0.9, 6.55, 7.5, 0.4, + "Was es ist · Aufbau · Syntax · Ad-hoc · Jinja2 · Facts · FQCN · Collections · Rollen", + size=12, color=RGBColor(0x9A, 0x9A, 0x9A)) +text(s, 9.5, 1.6, 3.3, 0.3, "INHALT", size=12, bold=True, color=WHITE) +agenda = [ + "1. Was ist Ansible?", + "2. Aufbau & Architektur", + "3. YAML, Playbooks & Ad-hoc", + "4. Module, Jinja2 & Facts", + "5. Variablen & Auflösung", + "6. FQCN & Collections", + "7. Rollen & Praxis", +] +text(s, 9.5, 2.15, 3.3, 4.0, agenda, size=14, color=WHITE, leading=1.5) + + +# =========================================================================== +# FOLIE 2 — Was ist Ansible? +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Grundlagen", "Was ist Ansible?") +rect(s, 0.55, 1.55, 8.0, 1.15, BLACK, rounded=True, radius=0.08) +text(s, 0.85, 1.75, 7.4, 0.75, + "Ansible ist ein Open-Source-Tool zur Automatisierung von IT-Aufgaben — " + "Provisionierung, Konfigurationsmanagement und Anwendungs-Deployment.", + size=15, color=WHITE, leading=1.15) +card(s, 0.55, 3.0, 4.0, 1.8, "A", "Agentenlos", + "Kein Agent auf Zielsystemen nötig. Kommunikation über SSH (Linux) bzw. WinRM (Windows).") +card(s, 4.75, 3.0, 4.0, 1.8, "P", "Push-basiert", + "Der Control Node stößt Änderungen aktiv an. Kein Pull-Mechanismus, kein ständiger Dienst.") +card(s, 0.55, 5.0, 4.0, 1.8, "D", "Deklarativ", + "Du beschreibst den gewünschten Ziel-Zustand in YAML — nicht die einzelnen Schritte.") +card(s, 4.75, 5.0, 4.0, 1.8, "I", "Idempotent", + "Mehrfaches Ausführen führt zum selben Ergebnis — keine doppelten Änderungen.") +if os.path.exists(LOGO_RED): + s.shapes.add_picture(LOGO_RED, Inches(9.4), Inches(1.9), Inches(1.7), Inches(2.09)) +text(s, 9.15, 4.1, 3.6, 0.3, "KEY FACTS", size=11, bold=True, color=RED) +facts = [("Red Hat / IBM", "Open-Source-Projekt"), + ("Python", "Implementiert in Python"), + ("4000+", "Module verfügbar"), + ("2012", "Erstveröffentlichung")] +yy = 4.5 +for big, small in facts: + text(s, 9.15, yy, 1.8, 0.5, big, size=22, bold=True, color=BLACK) + text(s, 10.95, yy + 0.08, 2.2, 0.5, small, size=11, color=GRAY, leading=1.0) + yy += 0.62 +footer(s, 2) + + +# =========================================================================== +# FOLIE 3 — Aufbau & Architektur +# =========================================================================== +s = new_slide(WHITE) +header(s, "Architektur", "Wie ist Ansible aufgebaut?") +if os.path.exists(ARCH): + from PIL import Image as _Img + _dw, _dh = _Img.open(ARCH).size + _ar = _dw / _dh + _w = 8.1 + _h = _w / _ar + s.shapes.add_picture(ARCH, Inches(0.55), Inches(1.55), Inches(_w), Inches(_h)) +text(s, 8.95, 1.6, 3.9, 0.3, "KOMPONENTEN", size=11, bold=True, color=RED) +comp = [ + ("Control Node", "Rechner, auf dem Ansible installiert ist; enthält Playbooks & Inventory."), + ("Managed Nodes", "Zielsysteme, die über SSH/WinRM erreicht werden."), + ("Inventory", "Liste aller Hosts und Gruppen (INI oder YAML)."), + ("Module", "Bausteine, die eine Aufgabe ausführen (z.B. copy, apt)."), +] +yy = 2.0 +for t, d in comp: + text(s, 8.95, yy, 3.9, 0.3, t, size=13, bold=True, color=BLACK) + text(s, 8.95, yy + 0.28, 3.9, 0.9, d, size=10.5, color=GRAY, leading=1.05) + yy += 1.22 +footer(s, 3) + + +# =========================================================================== +# FOLIE 4 — Kernkonzepte +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Architektur", "Vier Kernkonzepte") +card(s, 0.55, 1.6, 6.0, 2.4, "1", "Playbooks", + "YAML-Dateien, die beschreiben, was auf welchen Hosts passieren soll. " + "Ein Playbook enthält ein oder mehrere Plays mit Tasks (Aufgaben).") +card(s, 6.85, 1.6, 6.0, 2.4, "2", "Inventory", + "Die Host-Liste. Definiert Server, Gruppen und Variablen. Formate: INI oder YAML. " + "Auch dynamisch (Cloud, VMware) möglich.") +card(s, 0.55, 4.3, 6.0, 2.4, "3", "Module", + "Kleine Programme, die eine konkrete Aktion ausführen: Paket installieren, Datei kopieren, " + "Dienst starten. Werden über FQCN referenziert.") +card(s, 6.85, 4.3, 6.0, 2.4, "4", "Rollen & Collections", + "Rollen bündeln Tasks/Templates/Variablen zu wiederverwendbaren Einheiten. " + "Collections verteilen Module, Rollen & Plugins.") +footer(s, 4) + + +# =========================================================================== +# FOLIE 5 — YAML-Syntax +# =========================================================================== +s = new_slide(WHITE) +header(s, "Syntax", "YAML — die Sprache von Ansible") +text(s, 0.55, 1.6, 5.6, 0.3, "GRUNDREGELN", size=11, bold=True, color=RED) +rules = [ + ("Schlüssel: Wert", "Einfache Zuweisungen, getrennt durch Doppelpunkt."), + ("Einrückung", "Struktur über Leerzeichen (2 Spaces, KEINE Tabs)."), + ("Listen", "Aufzählungen mit Bindestrich (-)."), + ("Lesbar", "YAML ist menschenlesbar, kein Code-Zwang."), +] +yy = 2.0 +for t, d in rules: + text(s, 0.55, yy, 5.6, 0.3, t, size=13, bold=True, color=BLACK) + text(s, 0.55, yy + 0.28, 5.6, 0.7, d, size=11, color=GRAY, leading=1.05) + yy += 1.05 +code_block(s, 6.5, 1.6, 6.3, [ + "# Inventory (hosts.yml)", + "all:", + " hosts:", + " webserver:", + " ansible_host: 192.168.1.10", + " database:", + " ansible_host: 192.168.1.20", + "", + "# Schlüssel-Wert", + "name: \"Mein Webserver\"", + "paket: nginx", + "", + "# Liste", + "pakete:", + " - nginx", + " - git", + " - curl", +], font_size=11) +footer(s, 5) + + +# =========================================================================== +# FOLIE 6 — Playbook & Tasks +# =========================================================================== +s = new_slide(WHITE) +header(s, "Syntax", "Ein Playbook in der Praxis") +text(s, 0.55, 1.55, 5.6, 1.4, + [("Ein Playbook ", {}), + ("definiert, was passieren soll. ", {}), + ("plays", {"bold": True, "color": RED}), + (" wirken auf ", {}), + ("hosts", {"bold": True, "color": RED}), + (", jede ", {}), + ("task", {"bold": True, "color": RED}), + (" ruft genau ein ", {}), + ("Modul", {"bold": True, "color": RED}), + (" auf.", {})], + size=12.5, color=BLACK, leading=1.2) +code_block(s, 6.5, 1.55, 6.3, [ + "---", + "- name: Webserver konfigurieren", + " hosts: webserver", + " become: true", + "", + " tasks:", + " - name: nginx installieren", + " ansible.builtin.apt:", + " name: nginx", + " state: present", + "", + " - name: Dienst starten", + " ansible.builtin.service:", + " name: nginx", + " state: started", + " enabled: true", +], font_size=10.5, title="site.yml") +footer(s, 6) + + +# =========================================================================== +# FOLIE 7 — Module +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Syntax", "Module — die Arbeitspferde") +text(s, 0.55, 1.6, 12.2, 0.8, + "Ein Modul ist ein kleines, eigenständiges Programm, das eine Aufgabe auf dem " + "Zielsystem ausführt. Ansible bringt hunderte Module mit — von Paketverwaltung " + "über Datei-Operationen bis Cloud-Anbindung.", + size=13, color=BLACK, leading=1.2) +mods = [ + ("apt / dnf", "Pakete installieren (Debian/RedHat)"), + ("copy / file", "Dateien kopieren & Rechte setzen"), + ("service", "Dienste starten / stoppen / aktivieren"), + ("template", "Jinja2-Templates rendern"), + ("user / group", "Benutzer & Gruppen verwalten"), + ("command / shell", "Beliebige Befehle ausführen"), +] +for i, (t, d) in enumerate(mods): + col = i % 3; row = i // 3 + card(s, 0.55 + col * 4.2, 2.6 + row * 1.75, 3.95, 1.5, "•", t, d) +text(s, 0.55, 6.15, 12.2, 0.6, + [("Wichtig: Module werden heute über den ", {}), + ("FQCN", {"bold": True, "color": RED}), + (" referenziert (z.B. ", {}), + ("ansible.builtin.apt", {"font": FONT_MONO, "color": DARKRED}), + (").", {})], + size=12, color=BLACK) +footer(s, 7) + + +# =========================================================================== +# FOLIE 8 — Ad-hoc-Befehle +# =========================================================================== +s = new_slide(WHITE) +header(s, "Syntax", "Ad-hoc-Befehle — Ansible ohne Playbook") +text(s, 0.55, 1.55, 5.7, 1.25, + [("Mit dem ", {}), + ("ansible", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + ("-CLI lassen sich einzelne Module direkt ausführen — ohne Playbook. Ideal " + "für schnelle Checks und einmalige Aktionen.", {})], + size=12.5, color=BLACK, leading=1.2) +text(s, 0.55, 2.85, 5.7, 0.3, "AUFBAU", size=11, bold=True, color=RED) +code_block(s, 0.55, 3.2, 5.7, [ + "ansible -m ", + " -a \"\"", +], font_size=11, title="Kommando") +text(s, 0.55, 4.55, 5.7, 0.3, "WICHTIGE OPTIONEN", size=11, bold=True, color=RED) +kv_rows(s, 0.55, 4.95, 2.6, [ + ("-m", "Modul"), + ("-a", "Argumente"), + ("-i", "Inventory"), +], key_w=0.55, gap=0.4) +kv_rows(s, 3.4, 4.95, 2.6, [ + ("-b", "become (sudo)"), + ("-u", "Benutzer"), +], key_w=0.55, gap=0.4) +code_block(s, 6.6, 1.55, 6.2, [ + "# Erreichbarkeit testen", + "ansible all -m ping", + "", + "# Facts anzeigen", + "ansible webserver -m setup", + "", + "# Paket installieren", + "ansible webserver -m apt", + " -a \"name=nginx state=present\"", + "", + "# Dienst neu starten", + "ansible webserver -m service", + " -a \"name=nginx state=restarted\"", +], font_size=10, title="Beispiele") +footer(s, 8) + + +# =========================================================================== +# FOLIE 9 — Jinja2 (Templating) +# =========================================================================== +s = new_slide(WHITE) +header(s, "Syntax", "Jinja2 — dynamische Inhalte") +text(s, 0.55, 1.55, 12.2, 0.7, + "Jinja2 ist die Template-Engine von Ansible. Damit werden Variablen aufgelöst " + "und Dateien dynamisch erzeugt — in Playbooks, aber vor allem in Templates (.j2).", + size=13, color=BLACK, leading=1.15) +card(s, 0.55, 2.4, 4.0, 1.7, "1", "Variable auflösen", + [("{{ variable }}", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (" setzt den Wert einer Variablen an die Stelle im Text ein.", {})]) +card(s, 4.75, 2.4, 4.0, 1.7, "2", "Filter anwenden", + [("{{ wert | filter }}", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (" transformiert Werte (z.B. ", {}), + ("upper", {"font": FONT_MONO}), + (", ", {}), + ("default", {"font": FONT_MONO}), + (").", {})]) +card(s, 8.95, 2.4, 3.85, 1.7, "3", "Template-Dateien", + [("Dateien mit Endung ", {}), + (".j2", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (" werden vom Modul ", {}), + ("template", {"font": FONT_MONO}), + (" gerendert.", {})]) +code_block(s, 0.55, 4.35, 6.0, [ + "# Variablen auflösen", + "msg: \"Hallo {{ benutzer }}\"", + "", + "# Filter anwenden", + "name: \"{{ name | upper }}\"", + "port: \"{{ port | default(80) }}\"", +], font_size=10.5, title="Variablen & Filter") +code_block(s, 6.85, 4.35, 6.0, [ + "# nginx.conf.j2", + "server {", + " listen {{ nginx_port }};", + " server_name {{ domain }};", + " root {{ web_root }};", + "}", +], font_size=10.5, title="nginx.conf.j2") +footer(s, 9) + + +# =========================================================================== +# FOLIE 10 — Gather Facts +# =========================================================================== +s = new_slide(WHITE) +header(s, "Facts", "Gather Facts — Systeminformationen") +text(s, 0.55, 1.5, 12.2, 0.7, + [("Ansible sammelt beim Start automatisch ", {}), + ("Facts", {"bold": True}), + (" (Systeminfos) über jeden Host — über das Modul ", {}), + ("setup", {"font": FONT_MONO, "color": DARKRED}), + (", standardmäßig ", {}), + ("gather_facts: true", {"font": FONT_MONO, "color": DARKRED}), + (".", {})], + size=13, color=BLACK, leading=1.15) + +# Links: Facts als 2-Spalten-Raster (Name oben, Beschreibung darunter) +text(s, 0.55, 2.4, 6.6, 0.3, "WAS MAN AUSLESEN KANN", size=11, bold=True, color=RED) +facts = [ + ("ansible_os_family", "Betriebssystem-Familie"), + ("ansible_distribution", "z.B. Ubuntu, Debian"), + ("ansible_hostname", "Hostname des Systems"), + ("ansible_default_ipv4.address", "IP-Adresse"), + ("ansible_memtotal_mb", "Arbeitsspeicher (MB)"), + ("ansible_processor_vcpus", "CPU-Kerne"), +] +for i, (name, desc) in enumerate(facts): + col = i % 2 + row = i // 2 + cx = 0.55 + col * 3.3 + cy = 2.9 + row * 1.3 + text(s, cx, cy, 3.2, 0.35, name, size=11.5, bold=True, + color=DARKRED, font=FONT_MONO) + text(s, cx, cy + 0.38, 3.2, 0.5, desc, size=11, color=GRAY, leading=1.0) + +# Rechts: ein einzelner, klarer Code-Block (Zugriff + Custom Facts) +code_block(s, 7.25, 2.4, 5.55, [ + "# Zugriff im Playbook", + "- name: OS anzeigen", + " ansible.builtin.debug:", + " msg: \"{{ ansible_distribution }}\"", + "", + "# Fakten deaktivieren (schneller)", + "gather_facts: false", + "", + "# Eigene Facts setzen:", + "# Datei: /etc/ansible/facts.d/app.fact", + "# Abruf: {{ ansible_local.app.version }}", +], font_size=11, title="Zugriff & Custom Facts") +footer(s, 10) + + +# =========================================================================== +# FOLIE 11 — Variablen & Auflösung (Präzedenz) +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Variablen", "Variablen definieren & auflösen") +text(s, 0.55, 1.55, 5.7, 0.3, "WO KOMMEN SIE HER?", size=11, bold=True, color=RED) +sources = [ + ("Inventory", "host_vars / group_vars"), + ("Playbook", "vars: / vars_files:"), + ("Rollen", "vars/ & defaults/"), + ("Facts", "automatisch gesammelt"), + ("Laufzeit", "register & set_fact"), + ("CLI", "Extra-Variablen (-e)"), +] +yy = 2.0 +for t, d in sources: + text(s, 0.55, yy, 2.2, 0.3, t, size=12.5, bold=True, color=BLACK) + text(s, 2.9, yy, 3.3, 0.3, d, size=11, color=GRAY, font=FONT_MONO) + yy += 0.55 +text(s, 6.85, 1.55, 6.0, 0.3, "PRÄZEDENZ — WER GEWINNT?", size=11, bold=True, color=RED) +text(s, 6.85, 1.9, 6.0, 0.3, "Oben gewinnt — von höchster zu niedrigster Priorität:", + size=10, color=GRAY) +prec = [ + ("Extra-Variablen", "ansible-playbook ... -e key=wert"), + ("Task-/Block-Variablen", "vars: direkt an der Aufgabe"), + ("set_fact / register", "Werte aus der Laufzeit"), + ("Rollen-Variablen", "roles/.../vars/main.yml"), + ("Playbook-Variablen", "vars: im Play"), + ("host_vars / group_vars", "aus dem Inventory"), + ("Rollen-Defaults", "roles/.../defaults/main.yml"), +] +yy = 2.25 +for i, (t, d) in enumerate(prec, 1): + rect(s, 6.85, yy, 0.4, 0.4, RED, rounded=True, radius=0.3) + text(s, 6.85, yy + 0.03, 0.4, 0.34, str(i), size=13, bold=True, + color=WHITE, align=PP_ALIGN.CENTER) + text(s, 7.4, yy + 0.02, 3.6, 0.34, t, size=12, bold=True, color=BLACK) + text(s, 11.0, yy + 0.02, 1.85, 0.34, d, size=9, color=GRAY, font=FONT_MONO) + yy += 0.5 +text(s, 6.85, yy + 0.1, 6.0, 0.6, + [("Faustregel: ", {"bold": True, "color": RED}), + ("je spezifischer die Quelle, desto höher die Priorität.", {})], + size=11, color=GRAY) +footer(s, 11) + + +# =========================================================================== +# FOLIE 12 — FQCN (Definition) +# =========================================================================== +s = new_slide(WHITE) +header(s, "FQCN", "FQCN — Fully Qualified Collection Name") +rect(s, 0.55, 1.55, 12.2, 0.95, LIGHT, line=BORDER, line_w=1.0, rounded=True, radius=0.1) +text(s, 0.85, 1.77, 11.6, 0.55, + [("FQCN = ", {"bold": True, "color": RED}), + ("eindeutige, vollständige Referenz auf ein Modul oder Plugin.", {})], + size=16, color=BLACK) +text(s, 0.85, 2.75, 11.6, 0.6, "namespace · collection · modul", + size=28, bold=True, color=BLACK) +code_block(s, 0.55, 3.6, 12.2, [ + "# FQCN = namespace.collection.modul", + "", + "ansible.builtin.copy", + "", + "# ansible -> namespace (Anbieter)", + "# builtin -> collection (Paket)", + "# copy -> modul (die Funktion)", +], font_size=12, title="Beispiel") +text(s, 0.55, 6.15, 12.2, 0.5, + [("NICHT verwechseln mit ", {"bold": True}), + ("FQDN", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (" (Fully Qualified Domain Name = Hostname)! Der FQCN macht eindeutig, " + "aus welcher Collection ein Modul stammt.", {})], + size=12.5, color=BLACK) +footer(s, 12) + + +# =========================================================================== +# FOLIE 13 — FQCN (Beispiele & Warum) +# =========================================================================== +s = new_slide(WHITE) +header(s, "FQCN", "FQCN in der Praxis") +text(s, 0.55, 1.6, 6.5, 0.4, "GÄNGIGE FQCN-BEISPIELE", size=11, bold=True, color=RED) +ex = [ + ("ansible.builtin.copy", "Datei kopieren"), + ("ansible.builtin.apt", "Paket installieren"), + ("ansible.builtin.service", "Dienst verwalten"), + ("ansible.builtin.template", "Template rendern"), + ("ansible.posix.firewalld", "Firewall (Collection)"), + ("community.general.git_config", "Git (Community)"), +] +yy = 2.1 +for fqcn, d in ex: + rect(s, 0.55, yy, 3.7, 0.5, LIGHT, line=BORDER, line_w=0.75, rounded=True, radius=0.14) + text(s, 0.8, yy + 0.1, 3.4, 0.3, fqcn, size=11, bold=True, + color=DARKRED, font=FONT_MONO) + text(s, 4.45, yy + 0.1, 2.6, 0.3, d, size=11.5, color=BLACK) + yy += 0.62 +text(s, 7.35, 1.6, 5.4, 0.4, "WARUM FQCN?", size=11, bold=True, color=RED) +rect(s, 7.35, 2.1, 5.45, 4.35, LIGHT, line=BORDER, line_w=1.0, rounded=True, radius=0.05) +text(s, 7.65, 2.35, 4.9, 3.9, [ + ("Seit Ansible 2.10 sind Module in ", {}), + ("Collections", {"bold": True}), + (" gebündelt.\n\n", {}), + ("Der FQCN macht eindeutig, ", {}), + ("welches Modul", {"bold": True}), + (" gemeint ist.\n\n", {}), + ("Er vermeidet ", {}), + ("Namenskollisionen", {"bold": True}), + (" — mehrere Collections können ein gleichnamiges Modul anbieten.\n\n", {}), + ("Faustregel: ", {"bold": True, "color": RED}), + ("immer den FQCN verwenden, nie die Kurzform.", {}), +], size=12.5, color=BLACK, leading=1.25) +footer(s, 13) + + +# =========================================================================== +# FOLIE 14 — Collections (Definition) +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Collections", "Was sind Collections?") +rect(s, 0.55, 1.6, 12.2, 1.1, BLACK, rounded=True, radius=0.08) +text(s, 0.85, 1.8, 11.6, 0.75, + "Eine Collection ist das Verteilungsformat für Ansible-Inhalte. Sie bündelt " + "Module, Plugins, Rollen, Playbooks und Dokumentation in einem Paket.", + size=15, color=WHITE, leading=1.15) +cols = [ + ("Module", "Aktions-Bausteine (z.B. apt, firewalld)"), + ("Plugins", "Erweiterungen: Inventory, Connection, Filter"), + ("Rollen", "Wiederverwendbare Aufgaben-Sammlungen"), + ("Playbooks", "Fertige Abläufe als Vorlage"), + ("Dokumentation", "Beschreibung & Beispiele (docs/)"), + ("Test", "Tests & CI für die Collection"), +] +for i, (t, d) in enumerate(cols): + col = i % 3; row = i // 3 + card(s, 0.55 + col * 4.2, 3.0 + row * 1.6, 3.95, 1.35, "•", t, d) +footer(s, 14) + + +# =========================================================================== +# FOLIE 15 — Collections (Struktur & Galaxy) +# =========================================================================== +s = new_slide(WHITE) +header(s, "Collections", "Collections nutzen") +text(s, 0.55, 1.55, 5.8, 0.3, "BEZUGSQUELLE", size=11, bold=True, color=RED) +text(s, 0.55, 1.9, 5.8, 0.85, + [("Ansible Galaxy", {"bold": True}), + (" (galaxy.ansible.com) ist der zentrale Hub — vergleichbar mit einem " + "Paket-Manager für Ansible-Inhalte.", {})], + size=12, color=BLACK, leading=1.15) +text(s, 0.55, 2.85, 5.8, 0.3, "INSTALLATION", size=11, bold=True, color=RED) +code_block(s, 0.55, 3.25, 5.8, [ + "# Installation", + "ansible-galaxy collection", + " install ansible.posix", + "", + "# requirements.yml", + "collections:", + " - name: ansible.posix", + " version: '>=1.5.0'", +], font_size=11, title="galaxy") +text(s, 6.85, 1.55, 5.9, 0.3, "NAMENSSCHEMA", size=11, bold=True, color=RED) +text(s, 6.85, 1.9, 5.9, 0.45, + [("namespace", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (" . ", {"color": BLACK, "bold": True}), + ("name", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (" → z.B. ", {}), + ("ansible.posix", {"font": FONT_MONO, "color": DARKRED, "bold": True})], + size=14) +text(s, 6.85, 2.5, 5.9, 0.3, "AUFBAU EINER COLLECTION", size=11, bold=True, color=RED) +code_block(s, 6.85, 2.9, 5.9, [ + "ansible-posix/", + " plugins/modules/", + " plugins/inventory/", + " roles/", + " playbooks/", + " docs/", + " galaxy.yml", +], font_size=11.5, title="ansible.posix") +footer(s, 15) + + +# =========================================================================== +# FOLIE 16 — Rollen (Konzept) +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Rollen", "Wie funktionieren Rollen?") +rect(s, 0.55, 1.6, 12.2, 1.05, BLACK, rounded=True, radius=0.08) +text(s, 0.85, 1.78, 11.6, 0.75, + "Eine Rolle ist eine wiederverwendbare, in sich geschlossene Einheit von " + "Aufgaben. Sie kapselt Tasks, Variablen, Templates und Handler zu einer " + "logischen Funktion — z.B. „installiere & konfiguriere einen Webserver“.", + size=14.5, color=WHITE, leading=1.15) +points = [ + ("Wiederverwendbar", "Einmal schreiben, in vielen Playbooks nutzen."), + ("Gekapselt", "Eigene Variablen, Templates & Dateien pro Rolle."), + ("Teilbar", "Über Collections & Galaxy mit anderen teilen."), + ("Strukturiert", "Klare Verzeichnis-Struktur (tasks, handlers, …)."), +] +for i, (t, d) in enumerate(points): + col = i % 2; row = i // 2 + card(s, 0.55 + col * 6.2, 3.0 + row * 1.8, 5.95, 1.55, "✓", t, d) +footer(s, 16) + + +# =========================================================================== +# FOLIE 17 — Rollen (Verzeichnisstruktur) +# =========================================================================== +s = new_slide(WHITE) +header(s, "Rollen", "Die Verzeichnisstruktur einer Rolle") +text(s, 0.55, 1.55, 5.5, 0.3, "ANATOMIE", size=11, bold=True, color=RED) +text(s, 0.55, 1.9, 5.5, 0.9, + "Eine Rolle folgt einem festen Aufbau. Jede Datei hat eine klar definierte " + "Aufgabe — Ansible lädt sie automatisch.", + size=12, color=BLACK, leading=1.15) +expl = [ + ("tasks/main.yml", "Hauptaufgaben der Rolle"), + ("handlers/main.yml", "Neustart-/Trigger-Aktionen"), + ("templates/", "Jinja2-Templates (.j2)"), + ("files/", "Statische Dateien (Copy-Quelle)"), + ("vars/main.yml", "Variablen (hohe Priorität)"), + ("defaults/main.yml", "Standard-Variablen (niedrig)"), + ("meta/main.yml", "Abhängigkeiten & Infos"), +] +yy = 2.6 +for f, d in expl: + text(s, 0.55, yy, 3.0, 0.3, f, size=11, bold=True, color=DARKRED, font=FONT_MONO) + text(s, 3.7, yy, 2.6, 0.5, d, size=10.5, color=GRAY, leading=1.0) + yy += 0.55 +code_block(s, 6.7, 1.55, 6.1, [ + "roles/webserver/", + " tasks/", + " main.yml", + " handlers/", + " main.yml", + " templates/", + " files/", + " vars/", + " main.yml", + " defaults/", + " main.yml", + " meta/", + " main.yml", +], font_size=11.5, title="roles/webserver/") +footer(s, 17) + + +# =========================================================================== +# FOLIE 18 — Praxisbeispiel +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Praxis", "Alles zusammen: Playbook mit Rolle") +text(s, 0.55, 1.55, 5.3, 0.3, "ZUSAMMENSPIEL", size=11, bold=True, color=RED) +text(s, 0.55, 1.9, 5.3, 2.8, + [("Das Playbook referenziert die Rolle ", {}), + ("webserver", {"font": FONT_MONO, "color": DARKRED, "bold": True}), + (". FQCN-Module (", {}), + ("ansible.builtin.*", {"font": FONT_MONO, "color": DARKRED}), + (") arbeiten im Hintergrund.\n\n", {}), + ("1.", {"bold": True, "color": RED}), (" Rolle wird geladen.\n", {}), + ("2.", {"bold": True, "color": RED}), (" Aufgaben & Variablen aus der Rolle gelten.\n", {}), + ("3.", {"bold": True, "color": RED}), (" Idempotenz verhindert doppelte Arbeit.", {})], + size=12, color=BLACK, leading=1.25) +code_block(s, 6.15, 1.55, 6.6, [ + "---", + "- name: Webserver ausrollen", + " hosts: webserver", + " become: true", + "", + " roles:", + " - webserver", + "", + " vars:", + " nginx_port: 8080", +], font_size=11, title="deploy.yml") +footer(s, 18) + + +# =========================================================================== +# FOLIE 19 — Zusammenfassung & Quellen +# =========================================================================== +s = new_slide(LIGHT) +header(s, "Fazit", "Zusammenfassung") +takeaways = [ + ("Was", "Agentenloses, deklaratives Automatisierungs-Tool (SSH, YAML)."), + ("Aufbau", "Control Node, Managed Nodes, Inventory, Module, Playbooks."), + ("Syntax", "YAML + Jinja2 ({{ variable }}) für dynamische Inhalte."), + ("Ad-hoc", "ansible -m für schnelle Einzelaktionen."), + ("Facts", "Systeminfos werden automatisch gesammelt (gather_facts)."), + ("Variablen", "Auflösung nach Präzedenz — Extra-Vars gewinnen, Defaults verlieren."), + ("FQCN", "Eindeutige Modul-Referenz: namespace.collection.modul."), + ("Collections", "Pakete mit Modulen, Rollen & Plugins (Galaxy)."), + ("Rollen", "Wiederverwendbare, gekapselte Aufgaben-Einheiten."), +] +yy = 1.6 +for t, d in takeaways: + text(s, 0.75, yy, 2.0, 0.4, t, size=13, bold=True, color=RED) + text(s, 2.85, yy + 0.02, 6.7, 0.5, d, size=11.5, color=BLACK, leading=1.05) + yy += 0.52 +rect(s, 9.8, 1.7, 2.95, 4.6, LIGHT, line=BORDER, line_w=1.0, rounded=True, radius=0.06) +if os.path.exists(LOGO_RED): + s.shapes.add_picture(LOGO_RED, Inches(10.35), Inches(1.95), Inches(1.2), Inches(1.48)) +text(s, 10.05, 3.6, 2.4, 0.3, "QUELLEN", size=11, bold=True, color=RED, align=PP_ALIGN.CENTER) +text(s, 10.05, 3.95, 2.5, 1.9, + ["docs.ansible.com", "galaxy.ansible.com", "github.com/ansible", + "Logo: Wikimedia Commons"], + size=9.5, color=GRAY, leading=1.4) +footer(s, 19) + + +# --------------------------------------------------------------------------- +# Speichern +# --------------------------------------------------------------------------- +OUT = os.path.join(BASE, "ansible_praesentation.pptx") +prs.save(OUT) +print(f"OK — gespeichert: {OUT}") +print(f"Folien: {len(prs.slides)}") diff --git a/handout.docx b/handout.docx new file mode 100644 index 0000000..8441ee4 Binary files /dev/null and b/handout.docx differ diff --git a/handout.md b/handout.md new file mode 100644 index 0000000..ee4dae7 --- /dev/null +++ b/handout.md @@ -0,0 +1,231 @@ +# Ansible — Syntax & Variablen-Handout + +Kompakte Referenz zu YAML-Syntax, Ad-hoc-Befehlen, Jinja2, Facts, Variablen-Auflösung und FQCN. + +--- + +## 1. YAML-Grundsyntax + +| Element | Syntax | Beispiel | +|--------------------|-----------------|--------------------------| +| Schlüssel-Wert | `key: wert` | `name: webserver` | +| Liste | `- eintrag` | `- nginx` | +| Verschachtelung | Einrückung | 2 Leerzeichen (KEINE Tabs) | +| Kommentar | `# text` | `# Dies ist ein Kommentar` | +| Zeichenkette | mit/ohne Quotes | `name: "Mein Server"` | + +Einrückung mit 2 Leerzeichen (niemals Tabs!): + +```yaml +user: + name: admin + shell: /bin/bash + groups: + - sudo + - docker +``` + +--- + +## 2. Ad-hoc-Befehle (ohne Playbook) + +```bash +ansible -m -a "" +``` + +Beispiele: + +```bash +# Erreichbarkeit testen +ansible all -m ping + +# Facts anzeigen +ansible webserver -m setup + +# Paket installieren +ansible webserver -m apt -a "name=nginx state=present" + +# Dienst neu starten +ansible webserver -m service -a "name=nginx state=restarted" +``` + +Wichtige Optionen: + +| Option | Bedeutung | +|--------|----------------------| +| `-m` | Modul wählen | +| `-a` | Argumente übergeben | +| `-i` | Inventory angeben | +| `-b` | become (sudo) | +| `-u` | Benutzer | + +--- + +## 3. Jinja2 — Variablen auflösen + +Ansible nutzt die Template-Engine **Jinja2**. Variablen werden in doppelte +geschweifte Klammern gesetzt: + +```yaml +# Wert einsetzen +message: "Hallo {{ benutzer }}" +path: "/home/{{ benutzer }}/.config" + +# Verschachtelte Variablen +db_host: "{{ hostvars['db'].ansible_host }}" +``` + +### Filter + +```yaml +name: "{{ name | upper }}" # -> GROSSSCHREIBUNG +port: "{{ port | default(80) }}" # Fallback, wenn Variable leer +anzahl: "{{ liste | length }}" # Anzahl der Elemente +ip: "{{ ansible_host | ipaddr }}" # IP-Adresse validieren +``` + +### Template-Dateien (.j2) + +Dateien mit Endung `.j2` werden vom Modul `template` gerendert: + +```jinja +# nginx.conf.j2 +server { + listen {{ nginx_port }}; + server_name {{ domain }}; + root {{ web_root }}; +} +``` + +--- + +## 4. Gather Facts (Systeminformationen) + +Ansible sammelt automatisch **Facts** (Systeminformationen) über jeden Host — +über das Modul `setup`, standardmäßig `gather_facts: true`. + +Häufige Facts: + +| Fact | Bedeutung | +|---------------------------------|-------------------------------| +| `ansible_os_family` | OS-Familie (Debian, RedHat) | +| `ansible_distribution` | z.B. Ubuntu, CentOS, Debian | +| `ansible_distribution_version` | Version (z.B. 22.04) | +| `ansible_hostname` | Hostname des Systems | +| `ansible_default_ipv4.address` | IP-Adresse | +| `ansible_architecture` | CPU-Architektur (x86_64) | +| `ansible_memtotal_mb` | RAM in MB | +| `ansible_processor_vcpus` | Anzahl CPU-Kerne | + +Zugriff im Playbook: + +```yaml +- name: OS anzeigen + ansible.builtin.debug: + msg: "{{ ansible_distribution }} {{ ansible_distribution_version }}" +``` + +Eigene Facts (Custom Facts) setzen: + +```bash +# Datei anlegen (INI-Format): +# /etc/ansible/facts.d/app.fact +# Inhalt: +# [app] +# version=1.2.3 +# Abruf: {{ ansible_local.app.version }} +``` + +Facts deaktivieren (Performance): + +```yaml +gather_facts: false +``` + +--- + +## 5. Wo Variablen definiert werden + +| Quelle | Datei / Ort | Beispiel | +|------------|------------------------------------|---------------------------------| +| Inventory | `host_vars/`, `group_vars/` | `group_vars/webserver.yml` | +| Playbook | `vars:` / `vars_files:` | `vars: { port: 8080 }` | +| Rolle | `roles//vars/` + `defaults/`| `vars/main.yml` | +| Facts | automatisch gesammelt | `ansible_os_family` | +| Laufzeit | `register` / `set_fact` | `register: result` | +| CLI | Extra-Variablen | `-e "port=8080"` | + +--- + +## 6. Variablen-Präzedenz (Auflösungs-Reihenfolge) + +Ist dieselbe Variable an mehreren Stellen definiert, gewinnt die mit der +**höchsten** Priorität (oben = gewinnt): + +1. **Extra-Variablen** (CLI `-e`) — gewinnen immer +2. **Task-/Block-Variablen** (`vars:` direkt an der Aufgabe) +3. **set_fact / register** (Werte aus der Laufzeit) +4. **Rollen-Variablen** (`roles/.../vars/main.yml`) +5. **Playbook-Variablen** (`vars:` im Play) +6. **host_vars / group_vars** (aus dem Inventory) +7. **Rollen-Defaults** (`roles/.../defaults/main.yml`) — verlieren + +> **Faustregel:** Je spezifischer die Quelle, desto höher die Priorität. + +--- + +## 7. Module & FQCN + +FQCN = **Fully Qualified Collection Name** — `namespace.collection.modul`. + +```yaml +ansible.builtin.apt: + name: nginx + state: present +``` + +| FQCN | Zweck | +|-----------------------------------|---------------------------| +| `ansible.builtin.copy` | Datei kopieren | +| `ansible.builtin.apt` | Paket installieren (Debian) | +| `ansible.builtin.service` | Dienst verwalten | +| `ansible.builtin.template` | `.j2`-Template rendern | +| `ansible.posix.firewalld` | Firewall konfigurieren | +| `community.general.git_config` | Git-Einstellungen | + +--- + +## 8. Vollständiges Beispiel + +**Playbook** (`deploy.yml`): + +```yaml +- name: Webserver ausrollen + hosts: webserver + become: true + vars: + nginx_port: 8080 # überschreibt den Rollen-Default + roles: + - webserver +``` + +**Rollen-Defaults** (`roles/webserver/defaults/main.yml`) — niedrigste Priorität: + +```yaml +nginx_port: 80 +domain: example.com +``` + +**Rollen-Tasks** (`roles/webserver/tasks/main.yml`): + +```yaml +- name: nginx installieren + ansible.builtin.apt: + name: nginx + state: present + +- name: Konfiguration rendern + ansible.builtin.template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf +```