
🌐 Auch auf: English · Français · Español
Teil 6 der Ansible-Tour, und der letzte. Teil 1 hat das Gerüst erklärt, Teil 2 den Server und die Plattform, die Teile 3 und 4 die beiden Anwendungen, Teil 5 die Sicherheit. In diesem Teil geht es darum, zu wissen, was los ist. Er beschreibt das Release v3.3.0.
Ein Server, den niemand beobachtet, fällt still aus. Das Setup hier beobachtet auf zwei Arten, und es hilft, sie auseinanderzuhalten:
| Art | Frage, die sie beantwortet | Rollen |
|---|---|---|
| Metriken | Wie geht es dem Server über die Zeit? Speicher, Platte, CPU, Datenbank, PHP – als Kurven | common_node_exporter, common_mysqld_exporter, common_kube_metrics, die Sidecar-Exporter (Teile 3 und 4), common_prometheus, common_grafana |
| Wachhund | Ist gerade etwas kaputt? Ein Dienst ausgefallen, ein Pod in einer Absturzschleife, ein fehlendes Backup, ein Zertifikat kurz vor dem Ablauf | common_monit |
Beide verschicken Mails, aber unterschiedlich: Grafana, wenn eine Metrik eine Schwelle überschreitet, Monit, wenn sich ein Zustand ändert. Und beide folgen der Regel aus Teil 5 – still, solange alles gesund ist, damit jede Mail etwas bedeutet.
📈 Wie Metriken fließen
Das Modell ist Pull: Programme namens Exporter bieten ihre Zahlen als Textseite über HTTP an, und Prometheus holt diese Seite alle 15 Sekunden ab, speichert jeden Wert mit Zeitstempel und behält ihn 30 Tage. Grafana fragt Prometheus und zeichnet die Kurven. Nichts schiebt Daten irgendwohin, und nichts verlässt den Server.
| Exporter | Läuft | Meldet | Prometheus erreicht ihn unter |
|---|---|---|---|
| Node Exporter | auf dem Host, beide Stacks | CPU, Speicher, Platten, Dateisysteme, Netzwerk, systemd | 127.0.0.1:9100 |
| mysqld_exporter | auf dem Host, nur Nextcloud | die MariaDB auf dem Host: Verbindungen, Abfragen, InnoDB | 127.0.0.1:9104 |
| mysqld_exporter | Sidecar im MariaDB-Pod, Blog | die MariaDB des Blogs | ein NodePort auf 127.0.0.1 |
| php-fpm_exporter | Sidecar im Anwendungs-Pod, beide | PHP-FPM-Worker: beschäftigt, untätig, Warteschlange, langsame Anfragen | ein NodePort auf 127.0.0.1 |
| cAdvisor | im Kubelet, beide Stacks | CPU, Speicher, Drosselung, OOM-Ereignisse pro Container | https://127.0.0.1:10250 mit Token |
| kube-state-metrics | Pod in kube-system, beide | Limits, Neustarts, Abbruchgründe aus der API | NodePort 30808 auf 127.0.0.1 |
Alles lauscht auf localhost oder auf NodePorts, die die Firewall von außen verwirft (Teil 5). Auch Prometheus bindet sich an localhost; nur Grafana ist von außen erreichbar, und nur über den Ingress unter /grafana/, mit eigenem Login.
🧰 Die Exporter: noch einmal das Muster des festgelegten Binarys
common_node_exporter und common_mysqld_exporter sind klein und fast identisch, und sie sind das deutlichste Beispiel für ein Muster, das Teil 2 eingeführt hat:
- Ein Systembenutzer ohne Login-Shell und ohne Home-Verzeichnis – der Prozess darf nicht als root laufen, und niemand meldet sich je als dieser Benutzer an.
- Die installierte Version lesen (
node_exporter --version | awk ..., mitchanged_when: false,check_mode: false,failed_when: false– auf einem frischen Host gibt es kein Binary, und die leere Antwort heißt „installieren“). - Herunterladen, entpacken, installieren, nur wenn sie abweicht von der Festlegung in
versions.yml, mit der Probelauf-Wachenot ansible_check_modean den Schritten, die die heruntergeladene Datei brauchen. - Eine systemd-Unit plus ein Sandbox-Drop-in, geschrieben mit
copyundcontent:, und zwei Handler in fester Reihenfolge: systemd neu laden, dann den Dienst neu starten.
In Schritt 3 steckt eine kleine Geschichte. Früher war das Archiv die Markierung für „schon installiert“: nur herunterladen, wenn /tmp/node_exporter.tar.gz fehlt. Aber /tmp wird bei jedem Reboot geleert – also lud jeder Lauf nach einem Reboot erneut herunter und meldete eine Änderung, obwohl die richtige Version installiert war. Die installierte Version mit der Festlegung zu vergleichen, ist der ehrliche Test.
Node Exporter hat keine Konfigurationsdatei; alle Einstellungen sind Kommandozeilen-Flags in der Unit. Die Rolle weist darauf hin, zusammen mit einer Warnung, die für jede Rolle gilt, die eine Datei mit content: schreibt: Kommentarzeilen im Inhalt sind Teil der Datei auf dem Server. Wer dort einen Kommentar ändert, ändert die Unit und startet den Dienst neu.
Der MariaDB-Exporter bringt eine Sache dazu: Zugangsdaten. Er liest sie aus einer Datei, die nur er lesen kann (Modus 0600, im Besitz seines eigenen Benutzers), geschrieben mit no_log: true. Der Benutzer, mit dem er sich anmeldet, ist das nur lesende Konto aus Teil 4 – mit eigenem Passwort, nicht mehr dem von root. Seine meta/argument_specs.yml ist diejenige, auf die die anderen Rollen als erklärtes Beispiel verweisen: Wenn die Rolle startet, prüft Ansible jede aufgeführte Option, Pflichtoptionen müssen existieren, jeder Wert muss zu seinem Typ passen, und no_log an einer Option hält sie aus der Fehlermeldung der Validierung heraus.
🗄️ common_prometheus: die Zeitreihen-Datenbank
Prometheus wird genauso installiert wie die Exporter – Systembenutzer, Verzeichnisse für die Datenbank, festgelegtes Binary, systemd-Unit, Sandbox-Drop-in. Der Kopfkommentar der Rolle ist der einsteigerfreundlichste im Repository: Er erklärt register, loop, Handler, den Check-Modus und den Unterschied zwischen template und copy von Grund auf. Jeder Task, der etwas ändert, das Prometheus liest, benachrichtigt denselben Handler, restart prometheus – ein neues Binary und eine neue Konfiguration heißen trotzdem ein Neustart.
Eine Scrape-Konfiguration, zwei Stacks
scrape_configs:
- job_name: "node"
static_configs:
- targets: ["127.0.0.1:9100"]
labels:
instance: "{{ inventory_hostname }}"
{% if stack == 'nextcloud' %}
- job_name: "mysqld_nextcloud"
static_configs:
- targets: ["127.0.0.1:9104"]
- job_name: "php_fpm_nextcloud"
static_configs:
- targets: ["127.0.0.1:<nodeport>"]
{% endif %}
{% if stack == 'wordpress' %}
- job_name: "mysqld_wordpress"
...
- job_name: "php_fpm_wordpress"
...
{% endif %}Wieder die Variable stack aus Teil 1. Bis die Jobs stackspezifisch wurden, wurden alle vier Anwendungs-Jobs auf jedem Server gerendert, und jeder Server zeigte zwei Ziele dauerhaft als „down“ – die Ports des anderen Stacks. Harmlos, aber Rauschen in genau der Ansicht, in der man nach echten Problemen sucht.
Alarmregeln ohne Mail
Das Template definiert auch Alarmregeln: Platte unter 20 und unter 10 Prozent frei, Speicher unter 10 Prozent, CPU über 90 Prozent für zehn Minuten, Node Exporter nicht erreichbar. Prometheus wertet sie aus und zeigt sie auf seiner Seite /alerts – verschickt aber keine Mail. Dafür bräuchte es eine zweite Komponente, Alertmanager, die bewusst nicht installiert ist. Die Mails kommen stattdessen von Grafana und Monit, und die Datei sagt das in ihren ersten Zeilen, damit niemand auf eine Mail wartet, die nicht kommen kann.
📊 common_grafana: Kurven, hinter einem Unterpfad, und Mail
Grafana ist der einzige Teil des Monitorings mit Weboberfläche, und die Rolle, die es konfiguriert, ist ein gutes Lehrstück dafür, mit einer Anwendung zu sprechen, für die es kein Ansible-Modul gibt. Ihre drei Dateien laufen nacheinander, weil die zweite und dritte ein laufendes Grafana brauchen:
install.yml: das Paket und ein paar ini-Zeilen
Grafana kommt aus seiner offiziellen Paketquelle, Beta-Versionen ausgeschlossen. Dann setzt lineinfile einzelne Zeilen in grafana.ini: Admin-Benutzer und -Passwort (aus dem Vault, mit no_log), kein erzwungener Passwortwechsel beim ersten Login, Lauschen auf allen Schnittstellen – weil der Ingress es aus dem Pod-Netz über den externen Service aus den Teilen 3 und 4 erreicht – und die beiden Einstellungen für den Betrieb unter einem Unterpfad, root_url und serve_from_sub_path. Ohne sie würde Grafana Links auf /login statt auf /grafana/login erzeugen.
- name: Grafana | Wait for API to become available
ansible.builtin.uri:
url: "http://127.0.0.1:3000/api/health"
status_code: 200
register: grafana_health
check_mode: false
until: grafana_health.status == 200
retries: 30
delay: 3Das Modul uri stellt HTTP-Anfragen vom Server aus – hier an Grafana selbst, auf localhost. Mit until fragt es den Health-Endpunkt ab, bis Grafana antwortet, höchstens 90 Sekunden lang.
dashboards.yml: prüfen, dann anlegen
Die Grafana-API hat keine eigene Idempotenz: Zweimal „Datenquelle anlegen“ legt zwei Datenquellen an. Die Rolle macht das mit einem Paar von Anfragen sicher:
- name: Grafana | Check if Prometheus datasource already exists
ansible.builtin.uri:
url: "http://127.0.0.1:3000/api/datasources/name/Prometheus"
user: "..."
password: "..."
force_basic_auth: true
status_code: [200, 404]
register: grafana_ds_check
- name: Grafana | Create Prometheus datasource
ansible.builtin.uri:
url: "http://127.0.0.1:3000/api/datasources"
method: POST
body_format: json
body: { name: Prometheus, type: prometheus, url: "http://127.0.0.1:9090", ... }
when: grafana_ds_check.status == 404status_code: [200, 404] erklärt beide Antworten für erwartet – „existiert“ und „existiert nicht“ –, sodass nur ein echter Fehler (401, 500) den Task scheitern lässt. Die Anlege-Anfrage läuft nur bei 404. Das ist „erst fragen, dann handeln“ für eine HTTP-API.
Die drei Dashboards (Host-Metriken, MySQL, PHP-FPM) kommen von grafana.com und werden über die API importiert. Das heruntergeladene JSON wird mit slurp zurückgelesen – Base64, daher | b64decode | from_json – und mit dem Filter combine mit einer festen ID zusammengeführt, sodass jeder Import dasselbe Dashboard überschreibt, statt eine Kopie hinzuzufügen. Der ganze Import-Block wird im Check-Modus übersprungen. Ein kleines Python-Skript passt danach die Variablen eines Dashboards an die Job-Namen dieses Setups an. Eine Einschränkung steht in der Rolle: uri meldet von sich aus nie „changed“, API-Schreibzugriffe erscheinen also nicht in der Zusammenfassung – die Paare aus Prüfen und Anlegen verhindern nur, dass sie sich wiederholen. Ein viertes Dashboard, Container-Ressourcen, wird nicht heruntergeladen: Sein JSON liegt im Repository (files/container-resources.json), trägt seine feste ID selbst und wird so, wie es ist, gesendet.
alerting.yml: Mail über dasselbe Relay
Für Mails braucht Grafana SMTP-Einstellungen, und die Rolle nutzt dasselbe Relay wie die eigenen Mails des Servers (Teil 2), es gibt also kein zweites Mailkonto. Die Einstellungen werden mit blockinfile geschrieben: Es verwaltet einen ganzen Block von Zeilen zwischen zwei Markierungskommentaren und ersetzt beim nächsten Lauf genau diesen Block. Dann startet meta: flush_handlers Grafana sofort neu, weil die nächsten Tasks das Alerting über das laufende Grafana konfigurieren, das dann schon wissen muss, wie es Mails verschickt. Nach dem Neustart folgt ein zweites Warten auf den Health-Endpunkt.
Dann, wieder mit Prüfen-dann-Anlegen: ein E-Mail-Kontaktpunkt, die Standard-Benachrichtigungsrichtlinie, die darauf zeigt, ein Ordner für die Regeln und die Regeln selbst – geschrieben von einem Python-Skript, das die Rolle auf dem Server ablegt und ausführt, mit den Zugangsdaten als Umgebungsvariablen nur für diesen einen Task. Die Regeln:
| Alarm | Schlägt an, wenn |
|---|---|
| Root-Partition | mehr als 80 % belegt, für 5 Minuten |
| Daten-Partition | mehr als 80 % belegt, für 5 Minuten |
| Speicher | mehr als 90 % belegt, für 5 Minuten |
| CPU | durchschnittliche Last über 85 %, für 5 Minuten |
„Für 5 Minuten“ ist wichtig: Eine einzelne Spitze – ein Backup, ein Lauf des Vorschau-Generators – weckt niemanden.
📦 common_kube_metrics: eine Kurve pro Container
Der Node Exporter sieht den Host als Ganzes. Ob der PHP-Container an seinem CPU-Limit gebremst wird, während der Host nichts zu tun hat, oder ein Pod auf sein Speicherlimit zukriecht, bleibt unsichtbar – am 16.10.2026 gab es keine einzige Messreihe pro Container. Seit Release v3.3.0 schließen zwei weitere Quellen diese Lücke:
- cAdvisor steckt im Kubelet, es gibt nichts zu installieren. Er meldet CPU, Speicher, Drosselung und OOM-Ereignisse jedes Containers unter
https://127.0.0.1:10250/metrics/cadvisor– aber nur an einen Aufrufer mit Token. Die Rolle legt einen ServiceAccountprometheus-scraperan, dessen einziges Rechtgetaufnodes/metricsist, dazu ein Secret vom Typservice-account-token.common_prometheusliest es mitk8s_info(mit Wiederholungen, bis der Token-Controller es gefüllt hat) und schreibt es nach/etc/prometheus/kubelet-token. - kube-state-metrics macht aus den Objekten des API-Servers Zahlen: Limits und Requests, Neustarts, den Grund, warum ein Container zuletzt endete. Es läuft in
kube-systemmit reinen Leserechten, ohne root und mit schreibgeschütztem Root-Dateisystem, hinter NodePort 30808 – nur lokal, wie die anderen Exporter.
- job_name: "kubelet_cadvisor"
scheme: https
metrics_path: /metrics/cadvisor
authorization:
credentials_file: /etc/prometheus/kubelet-token
tls_config:
insecure_skip_verify: true # K3s's self-signed kubelet certificate, 127.0.0.1 only
static_configs:
- targets: ["127.0.0.1:10250"]
metric_relabel_configs:
- source_labels: [container]
regex: "|POD" # pause containers and pod-level sums
action: dropDie Relabel-Regel verwirft etwa die Hälfte aller Messreihen, bevor sie gespeichert werden. Das vierte Grafana-Dashboard, Container-Ressourcen, setzt jeden Container ins Verhältnis zu seinem eigenen Limit – CPU-Anteil, Drosselung, Speicher, Neustarts, OOM-Kills –, und vier Mail-Alarme achten auf dasselbe: Speicher über 90 % vom Limit, Drosselung über 50 %, ein OOM-Kill, mehr als zwei Neustarts in 15 Minuten.
🐕 common_monit: der Wachhund
Monit gibt es wegen eines Vorfalls, der einen eigenen Beitrag hat: fail2ban war auf dem Blog gestorben, und zwei Tage lang merkte es niemand. Metriken halfen nicht – ein totes fail2ban zeigt sich nicht als Kurve. Monit prüft Zustände: Alle zwei Minuten führt es eine Liste von Prüfungen aus, und es mailt nur, wenn eine Prüfung von ok auf fehlgeschlagen wechselt oder zurück. Ein gesunder Server schickt nichts.
Die Rolle, Schritt für Schritt
assertauf die Mail-Einstellungen. Monit verschickt seine eigenen Mails über das Relay; ohne Zugangsdaten wäre es ein Wachhund, der nicht bellen kann. Besser, man scheitert am Anfang der Rolle mit einer Meldung, die sagt, welche Vault-Werte fehlen.- Herausfinden, welche Dienste es hier gibt. Dieselbe Rolle läuft auf beiden Stacks, aber nicht jeder Dienst existiert auf jedem Host (der MariaDB-Exporter nur auf Nextcloud). Also:
- name: Monit | Detect which candidate services exist on this host
ansible.builtin.command: "systemctl cat {{ item }}.service"
register: monit_unit_check
changed_when: false
failed_when: false
loop: "{{ monit_candidate_services }}"
- name: Monit | Build the list of present services
ansible.builtin.set_fact:
monit_present_services: >-
{{ monit_candidate_services
| zip(monit_unit_check.results)
| selectattr('1.rc', 'equalto', 0)
| map(attribute='0') | list }}systemctl cat gibt eine Unit-Datei aus und endet mit einem Code ungleich null, wenn die Unit nicht existiert – eine rein lesende Art zu fragen „ist das installiert?“. Der zweite Task ist ein kompaktes Stück Jinja: zip paart jeden Kandidatennamen mit seinem Ergebnis (Element 0 = Name, Element 1 = Ergebnis), selectattr('1.rc', 'equalto', 0) behält die Paare, deren Ergebnis den Exit-Code 0 hatte, und map(attribute='0') holt die Namen wieder heraus. Das Konfigurations-Template schreibt dann nur für diese eine Prüfung.
- Schritt 3 – Prüfskripte und Konfiguration aus
files/und zwei Templates. Die Skripte sind klein, und jedes beantwortet eine Frage mit seinem Exit-Code: Ist dieser Dienst aktiv? Ist die Firewall-Tabelle mit ihren Sperr-Sets geladen? Steckt ein Pod in einer Absturzschleife oder kann sein Image nicht ziehen (kurze „creating“-Zustände werden ignoriert)? Lesen die fail2ban-Jails das aktuelle Ingress-Log? Ist ein Zertifikat näher als drei Wochen am Ablauf? - Schritt 4 – prüfen, dann starten.
monit -tprüft die Konfiguration; die Rolle startet Monit und bestätigt, dass es aktiv ist – mit einemfailed_when, das die Ausgabe vonsystemctl is-activeliest.
Was Monit beobachtet
| Prüfung | Alarmiert, wenn | Warum diese Schwelle |
|---|---|---|
| System | Last oder Speicher drei Zyklen lang zu hoch | kurze Spitzen sind normal |
| Root-Dateisystem | mehr als 90 % Platz oder Inodes | die letzte Linie, nach den 80 % von Grafana |
| fail2ban | läuft nicht – Monit startet es neu; gibt nach drei Neustarts in kurzer Zeit auf und alarmiert | der Vorfall, mit dem alles anfing |
| Dienste | SSH, K3s, Node Exporter, Prometheus, Grafana und – auf Nextcloud – der MariaDB-Exporter nicht aktiv | nur die, die es auf diesem Host gibt |
| Firewall | die Tabelle oder ihre Sperr-Sets fehlen | eine Firewall ohne ihre Tabelle schützt nichts |
| Pods | Absturzschleife, Fehler beim Image-Pull, OOM | wartende Pods während eines Neustarts sind kein Fehler |
| fail2ban-Jails | ein Jail liest ein veraltetes Ingress-Log | das Problem der blinden Jails aus Teil 5 |
| Zertifikate | weniger als 21 Tage übrig | die Erneuerung beginnt bei 30 Tagen; 21 heißt, sie scheitert seit über einer Woche – früh genug, um es zu reparieren |
| Backup | das letzte erfolgreiche Backup älter als 3 Tage | das Backup wird von außen abgeholt; so erfährt der Server, dass es stattgefunden hat |
Zwei Prüfungen haben einen eigenen Zeitplan, in Cron-Syntax: Zertifikate und Backups einmal pro Stunde statt bei jedem Zyklus. Es bringt nichts, alle zwei Minuten eine TLS-Verbindung zu jedem Zertifikat aufzubauen. Die Backup-Prüfung ist ein hübscher Trick: Der externe Backup-Job aktualisiert nach jedem erfolgreichen Lauf eine Datei auf dem Server, und Monit alarmiert, wenn der Zeitstempel dieser Datei zu alt wird. Der Server kann das Backup nicht sehen, aber er kann sehen, dass ihm niemand von einem erzählt hat.
Die Zertifikatsschwelle hat eine Geschichte. Einmal blockierte eine Image-Richtlinie (Teil 3) still den Challenge-Solver von cert-manager, und kein Zertifikat ließ sich erneuern. Niemand merkte es, bis ein Zertifikat kurz vor dem Ablauf stand. Jetzt merkt Monit es drei Wochen vorher.
Ein Selbsttest, der echte Mails verschickt
Woher weiß man, dass der Wachhund bellt? tasks/selftest.yml stoppt Node Exporter kurz, wartet, bis Monit den Ausfall meldet, und startet ihn wieder – was eine echte Alarm-Mail und eine echte Entwarnungs-Mail verschickt. Das ist ein Eingriff, also läuft es nur auf Anforderung, aus den Verify-Rollen:
ansible-playbook blog.yml --limit <host> -e monit_selftest=trueNachtrag 19.10.2026: Seit dieser Teil geschrieben wurde, ist das Monitoring gewachsen. common_selinux_monitoring gibt Prometheus, den Exportern und Grafana eigene SELinux-Domains (zuerst in einer Permissive-Lernphase), Monit läuft in einer systemd-Sandbox, und Grafana loggt auf warn statt info. Zwei neue Rollen, zuerst auf dem Blog: common_loki (Loki + Grafana Alloy: Journal, Pod-Logs, Audit- und fail2ban-Log, 14 Tage, mit Mails bei blockiertem SELinux-Zugriff, fail2ban-Fehlern, OOM-Kills des Kernels und fehlenden Logs) und common_trivy (ein nächtlicher Scan aller laufenden Images; Mail nur bei neuen behebbaren CRITICAL/HIGH-Befunden). Beide bekommen einen eigenen Deep Dive.
🛠️ Im Betrieb: wer was mailt
Alle Mails der ganzen Tour an einer Stelle – das ist die Tabelle, die man neben dem Posteingang liegen haben sollte:
| Von | Bedeutung | Teil | |
|---|---|---|---|
| Verify-Rolle | Ergebnis des Deployments | nach jedem Playbook-Lauf: alle Prüfungen grün, oder die gescheiterten | 1, 3, 4 |
| dnf-automatic | Neustart nach Sicherheitsupdate / Neustart weiterhin nötig | nächtliche Updates | 2 |
| Grafana | Platte, Speicher oder CPU über der Schwelle | eine Ressource wird knapp – planen, nicht in Panik geraten | 6 |
| Monit | Dienst, Pod, Firewall, Jail, Zertifikat, Backup | gerade ist etwas kaputt – nachsehen | 6 |
| SSH-Alarm | neuer interaktiver Login | wenn du es nicht warst: Vorfall | 5 |
| auditd | k3s_manifests, token, tls, identity | etwas, das sich nie ändert, hat sich geändert: Vorfall, bis es erklärt ist | 5 |
| AIDE | Änderungen an der Dateiintegrität | die Liste lesen; nach geplanten Änderungen erwartet | 5 |
| rkhunter | Warnungen | meist eine Paketänderung; sonst untersuchen | 5 |
| ClamAV | infizierte Dateien gefunden | die Datei ansehen und wer sie hochgeladen hat | 5 |
Nachsehen
# Monit's view of everything it watches
monit summary
# Prometheus: are all targets up?
curl -s http://127.0.0.1:9090/api/v1/targets | python3 -m json.tool | grep -E '"job"|"health"'
# Prometheus: which of its own alert rules are firing?
curl -s http://127.0.0.1:9090/api/v1/alerts | python3 -m json.tool
# Grafana: https://<your host>/grafana/ – dashboards "Node Exporter Full", MySQL, PHP-FPM, Container-RessourcenDie Kurven lesen
- Platte: auf die Steigung schauen, nicht auf den Stand. Eine flache Linie bei 70 % ist monatelang in Ordnung; eine Linie, die zwei Prozent pro Woche steigt, erreicht den 80-%-Alarm an einem vorhersagbaren Tag.
- Speicher: Linux nutzt freien Speicher als Dateicache, „belegt“ ist also immer hoch. Was zählt, ist „verfügbar“. Schrumpft das stetig, wächst etwas.
- PHP-FPM: Sind alle Worker beschäftigt und Anfragen stauen sich, ist die Seite langsam, egal wie schnell der Server ist. Mehr Worker brauchen mehr Speicher – siehe die Größenplanung in Teil 2.
- MariaDB: Langsame Abfragen zeigen sich im Slow-Query-Log, nicht nur in Grafana. Erst messen, dann optimieren ist ein Beitrag genau darüber.
🏁 Das Ende der Tour
Das ist jede Rolle des Repositorys – 36 Stück, vier Playbooks, das Inventory, das sie zusammenhält, und die Tests drumherum. Wer alle sechs Teile gelesen hat, weiß, was auf diesen Servern läuft und warum, wie jedes Teil dorthin kam, was der Code bei jedem Schritt tut und was zu tun ist, wenn eine Mail kommt. Das war das Ziel: nicht, dass du dieses Setup kopierst, sondern dass es verständlich genug wird, um es zu betreiben – oder ein eigenes zu bauen und die Fehler zu vermeiden, die in seinen Kommentaren aufgeschrieben sind.
Die Rollen werden sich weiter ändern. Die Links in dieser Serie zeigen auf das Release v3.3.0, damit sie weiter den Code zeigen, den der Text beschreibt. Manche Themen verdienen mehr als einen Abschnitt – nftables, fail2ban, SELinux und K3s bekommen jeweils einen eigenen Beitrag, über das Werkzeug selbst statt über dieses Repository. Sie werden hier verlinkt.

