Maximierung der Ansible-Leistung mit ControlPersist und Pipelining

Steigern Sie die Leistung Ihrer Ansible-Playbooks erheblich, indem Sie die SSH-Verbindungswiederverwendung mit ControlPersist aktivieren und die Modulausführung durch Pipelining optimieren. Dieser Leitfaden bietet wesentliche Einblicke und praktische Konfigurationen zur Reduzierung der Ausführungszeiten, insbesondere in großen Umgebungen. Erfahren Sie, wie Sie Ihre `ansible.cfg` für schnellere, effizientere IT-Automation optimieren.

Maximierung der Ansible-Leistung mit ControlPersist und Pipelining

Langsame Ansible-Ausführungen wirken oft rätselhaft, bis man beobachtet, was auf der Leitung passiert. Ein Playbook mit zwanzig kleinen Aufgaben gegen hundert Hosts kann überraschend viel Zeit damit verbringen, SSH-Sitzungen zu öffnen, temporäre Moduldateien zu kopieren, Python auszuführen, Ausgaben zu sammeln und Verbindungen zu schließen. Die Arbeit auf dem entfernten Host mag Millisekunden dauern, aber der Verbindungs-Overhead wiederholt sich immer wieder.

Zwei Einstellungen helfen oft: SSH-Verbindungswiederverwendung durch ControlPersist und Ansible-Pipelining. Sie sind keine magischen Schalter und werden keine langsamen Paketquellen, überlasteten Datenbanken oder Aufgaben, die schwere Arbeit verrichten, reparieren. Sie reduzieren jedoch vermeidbaren Kommunikations-Overhead, was genau der Bereich ist, in dem viele Playbooks mit kleinen Aufgaben Zeit verschwenden.

Zuerst den aktuellen Schmerz messen

Bevor Sie die Konfiguration ändern, führen Sie das Playbook einmal mit aktiviertem Timing aus:

ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml

Wenn dieser Callback nicht installiert ist, verwenden Sie die einfachere integrierte Timing-Ausgabe Ihres CI-Systems oder umschließen Sie den Befehl mit time. Ziel ist kein perfekter Benchmark. Sie möchten eine Basislinie und ein Gefühl dafür, ob die langsamen Aufgaben tatsächliche Arbeit oder winzige Aufgaben sind, die über viele Hosts wiederholt werden.

Ein nützlicher Rauchtest ist ein Ad-hoc-Ping über ein repräsentatives Inventar:

time ansible all -m ping

Führen Sie es zweimal aus. Wenn der zweite Durchlauf nach der Konfiguration der Verbindungswiederverwendung viel schneller ist, haben Sie bestätigt, dass die SSH-Einrichtungskosten Teil des Problems waren.

Was ControlPersist ändert

ControlPersist ist eine OpenSSH-Funktion. Es hält eine Master-SSH-Verbindung für einen bestimmten Zeitraum offen, sodass spätere SSH-Befehle an denselben Host, Benutzer und Port sie wiederverwenden können. Ansible verwendet häufig SSH für jede Aufgabe, sodass Verbindungsmultiplexing wiederholte Handshakes entfernt.

Eine praktische projektbezogene ansible.cfg sieht so aus:

[defaults]
forks = 20

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r

Erstellen Sie das Socket-Verzeichnis mit privaten Berechtigungen:

mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp

ControlMaster=auto teilt SSH mit, eine vorhandene Master-Verbindung zu verwenden, wenn eine verfügbar ist, und eine zu erstellen, wenn keine vorhanden ist. ControlPersist=600s hält den Master zehn Minuten nach Beendigung der letzten Sitzung am Leben. Dieser Wert ist nicht heilig. Für kurze CI-Jobs können ein paar Minuten ausreichen. Für einen Operator, der während eines Wartungsfensters wiederholt Playbooks ausführt, können zehn oder fünfzehn Minuten angenehm sein.

Der ControlPath ist wichtiger, als die Leute erwarten. Unix-Socket-Pfade haben auf vielen Systemen Längenbeschränkungen. Ein langer Inventar-Hostname innerhalb eines tiefen Arbeitsbereichspfads kann das Multiplexing mit einem verwirrenden Fehler unterbrechen. Wenn Sie den Pfad kurz halten, z. B. unter ~/.ansible/cp, vermeiden Sie diese Art von Fehlern.

Neuere Ansible-Versionen aktivieren SSH-Multiplexing in vielen normalen Konfigurationen bereits standardmäßig, aber explizite Einstellungen in einer Projekt-ansible.cfg machen das Verhalten leichter überprüfbar. Überprüfen Sie die aktive Konfiguration mit:

ansible-config dump --only-changed

Was Pipelining ändert

Ohne Pipelining kopiert Ansible ein Modul oft in ein temporäres Verzeichnis auf dem entfernten Host, führt es aus und räumt dann auf. Mit aktiviertem Pipelining kann Ansible Modulcode über die SSH-Verbindung übergeben, anstatt so viele temporäre Dateien zu schreiben. Das spart Roundtrips und entfernte Dateisystemarbeit.

Aktivieren Sie es in derselben Datei:

[ssh_connection]
pipelining = True

Oder testen Sie es für einen Durchlauf:

ANSIBLE_PIPELINING=True ansible-playbook site.yml

Die Einstellung macht sich am meisten bemerkbar, wenn das Playbook viele kleine Module hat: file, lineinfile, template, user, service und kurze Befehlsaufgaben. Sie ist weniger wichtig, wenn eine Aufgabe die meiste Zeit damit verbringt, Pakete zu installieren, Software zu bauen, große Artefakte zu übertragen oder auf einen externen Dienst zu warten.

Die Sudo-requiretty-Falle

Das klassische Pipelining-Problem ist requiretty in sudoers. Einige ältere Enterprise-Linux-Konfigurationen erforderten ein TTY für sudo. Pipelining funktioniert mit dieser Anforderung nicht gut, da Ansible versucht, Arbeit nicht-interaktiv über SSH zu streamen.

Überprüfen Sie sudoers sorgfältig. Bearbeiten Sie /etc/sudoers nicht mit einem normalen Editor; verwenden Sie visudo:

sudo visudo

Wenn Sie eine globale Zeile wie diese sehen:

Defaults requiretty

Müssen Sie sie möglicherweise entfernen oder für den Ansible-Automatisierungsbenutzer überschreiben:

Defaults:ansible !requiretty

Nehmen Sie diese Änderung nur vor, wenn sie der Sicherheitsrichtlinie Ihrer Organisation entspricht. In vielen modernen Distributionen ist requiretty standardmäßig nicht aktiviert.

Eine sicherere kombinierte Konfiguration

Für viele Teams ist dies ein vernünftiger Ausgangspunkt:

[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True

forks steuert die Parallelität. Es hängt mit der Leistung zusammen, ist aber nicht dieselbe Optimierung. Die Erhöhung von forks von 5 auf 20 kann helfen, wenn Ihr Kontrollknoten und Ihr Netzwerk damit umgehen können. Eine zu starke Erhöhung kann Bastion-Hosts, Paketquellen oder die verwalteten Knoten selbst überlasten.

Fact-Caching hilft bei einer anderen Art von Langsamkeit. Wenn Ihre Playbooks bei jeder Ausführung Fakten sammeln und die Fakten nicht jede Minute aktuell sein müssen, kann Caching wiederholte Einrichtungsarbeit entfernen. Verwenden Sie es gezielt; veraltete Fakten können verwirrend sein, wenn Sie sich auf aktuelle Speicher-, Festplatten- oder Schnittstellendaten verlassen.

Wie man testet, ohne sich selbst zu täuschen

Testen Sie auf einer Teilmenge, die wie die Produktion aussieht. Fünf untätige Test-VMs im selben Subnetz werden Ihnen nicht viel über hundert gemischte Hosts hinter einer Bastion verraten.

Führen Sie dasselbe Playbook vor und nach der Änderung aus. Löschen Sie vorhandene Kontroll-Sockets zwischen Testläufen, wenn Sie einen kalten Vergleich benötigen:

rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web

Führen Sie dann einen warmen Vergleich durch:

time ansible-playbook site.yml --limit web

Achten Sie auf weniger Sekunden, die für kleine wiederholte Aufgaben aufgewendet werden. Beobachten Sie auch Fehlermodi. Wenn Aufgaben, die become verwenden, fehlschlagen, untersuchen Sie die Sudo-Konfiguration, bevor Sie Pipelining selbst die Schuld geben.

Wann diese Einstellungen nicht viel helfen werden

ControlPersist und Pipelining machen langsame Remote-Arbeit nicht schnell. Wenn apt update auf einen Spiegel wartet, wird Ihnen die Verbindungswiederverwendung nicht helfen. Wenn ein Neustart eines Dienstes dreißig Sekunden wartet, weil die App Verbindungen abbaut, ist Pipelining irrelevant. Wenn Ihr Playbook ein 2 GB großes Artefakt auf jeden Host kopiert, konzentrieren Sie sich auf Artefaktverteilung, Caching oder lokale Paketquellen.

Sie ersetzen auch kein idempotentes Playbook-Design. Ein Playbook, das Shell-Befehle bedingungslos ausführt, wird immer noch Zeit verschwenden. Verwenden Sie Module, die den aktuellen Zustand erkennen können, fügen Sie bei Bedarf creates oder removes zu Befehlsaufgaben hinzu und vermeiden Sie das Sammeln von Fakten, wenn das Play sie nicht verwendet.

Der praktische Ansatz ist einfach: Aktivieren Sie ControlPersist mit einem kurzen, privaten Kontrollpfad; testen Sie Pipelining mit Ihrer Sudo-Richtlinie; passen Sie forks schrittweise an; und messen Sie jedes Mal mit demselben Inventar und derselben Arbeitslast. In vielen realen Ansible-Umgebungen verwandeln diese Änderungen eine träge Ausführung in eine erträgliche, da sie wiederholten Overhead entfernen, anstatt ihn zu verstecken.

Bastion-Hosts und Jump-Hosts

Viele Inventare verbinden sich nicht direkt mit verwalteten Knoten. Sie gehen durch eine Bastion. ControlPersist hilft trotzdem, aber Sie müssen über beide Verbindungen nachdenken: Kontrollknoten zur Bastion und Bastion zum Ziel.

Eine häufige Inventarvariable sieht so aus:

[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12

[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'

Wenn jede Aufgabe wiederholt eine Jump-Verbindung aufbaut, wird die Bastion Teil des Overheads. Halten Sie den Kontrollpfad kurz und privat und beobachten Sie die SSH-Verbindungslimits der Bastion. Ein Playbook, das gegen zehn Hosts gut läuft, kann eine kleine Bastion überlasten, wenn forks auf fünfzig erhöht wird.

Für ältere SSH-Clients sehen Sie möglicherweise ProxyCommand anstelle von ProxyJump:

ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'

Testen Sie eine einfache SSH-Verbindung, bevor Sie Ansible die Schuld geben:

ssh -o ProxyJump=bastion.example.com [email protected] hostname

Wenn das langsam ist, wird Ansible auch langsam sein.

Pipelining und Become in echten Playbooks

Der alte Ratschlag, dass Pipelining nicht mit Privilegieneskalation funktioniert, ist zu allgemein. Pipelining kann mit become funktionieren; der häufigste Blocker ist sudo, das ein TTY erfordert, oder eine Richtlinie, die die nicht-interaktive Ausführung stört. Die einzig zuverlässige Antwort ist, mit denselben become-Einstellungen zu testen, die Ihre Playbooks verwenden.

Ein kleiner Test-Play reicht aus:

- hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Bestätigen, dass privilegierter Befehl funktioniert
      ansible.builtin.command: id
      changed_when: false

Führen Sie es mit aktiviertem Pipelining aus. Wenn es fehlschlägt, überprüfen Sie sudoers, Authentifizierungsaufforderungen und ob der Automatisierungsbenutzer die erforderlichen Befehle ohne Passwortaufforderung ausführen kann. Passwortaufforderungen in großen Automatisierungsläufen sind fragil; sie machen auch Zeitmessungen verrauscht.

Forks mit Respekt vor Abhängigkeiten einstellen

Die Erhöhung von forks ist verlockend, weil sie eine sofortige sichtbare Änderung bewirkt. Sie kann auch einen neuen Engpass schaffen. Wenn ein Play Paketcaches auf jedem Host gleichzeitig aktualisiert, kann eine hohe Fork-Anzahl den Paketspiegel bestrafen. Wenn jeder Host gleichzeitig neu startet und sich wieder mit derselben Datenbank verbindet, sieht die Datenbank den Schlag.

Ein gemessener Ansatz ist besser:

[defaults]
forks = 10

Führen Sie das Play aus. Versuchen Sie 20. Dann 30. Beobachten Sie die CPU des Kontrollknotens, die Anzahl der SSH-Prozesse, die Bastion-Last, die Netzwerkauslastung, die Paketquelle und den Dienst, der geändert wird. Die schnellste Einstellung ist nicht immer die mit der höchsten Parallelität. Für rollierende Anwendungsbereitstellungen möchten Sie möglicherweise auch serial im Playbook, um die Verfügbarkeit zu schützen:

- hosts: web
  serial: 10

forks steuert, wie viel Ansible gleichzeitig tun kann. serial steuert, wie viele Hosts das Play gleichzeitig verarbeiten soll. Sie lösen unterschiedliche Probleme.

Veraltete Kontroll-Sockets bereinigen

Kontroll-Sockets bereinigen sich normalerweise selbst, aber Laptops schlafen ein, CI-Jobs werden abgebrochen und Netzwerkpfade ändern sich. Wenn SSH beginnt, Multiplexing-Fehler zu melden, entfernen Sie veraltete Sockets:

rm -f ~/.ansible/cp/*

Das ist sicher, wenn kein Ansible-Lauf für diese Sockets aktiv ist. Teilen Sie in gemeinsam genutzten Automatisierungs-Runnern keine Kontroll-Sockets in einem gemeinsam beschreibbaren Verzeichnis. Jeder Automatisierungsbenutzer sollte seinen eigenen privaten Pfad haben.

Inventardesign kann Leistungsgewinne zunichtemachen

Verbindungsoptimierung hilft, aber das Inventardesign kann trotzdem alles verlangsamen. Dynamische Inventarskripte, die Cloud-APIs langsam aufrufen, Gruppen-Vars, die teure Lookups ausführen, und Playbooks, die Fakten für Hosts sammeln, die sie nie berühren, können Verzögerung hinzufügen, bevor SSH überhaupt startet.

Wenn ein Lauf vor der ersten Aufgabe pausiert, profilieren Sie das Laden des Inventars. Cachen Sie dynamische Inventare, wo das Plugin dies unterstützt. Vermeiden Sie teure Variablenlookups zur Parse-Zeit. Halten Sie Host-Muster eng:

ansible-playbook site.yml --limit web:&prod

Dieser Befehl zielt auf Hosts in sowohl web als auch prod. Ein breites Play gegen all auszuführen und dann die meisten Aufgaben mit when-Bedingungen zu überspringen, verschwendet Zeit und macht die Ausgabe schwerer lesbar.

Weniger, klarere Aufgaben bevorzugen, wenn der Zustand zusammenhängt

Ansible-Lesbarkeit ist wichtig, aber übermäßig viele kleine Aufgaben können den Verbindungs-Overhead sichtbarer machen. Wenn Sie zehn zusammenhängende Zeilen in einer Konfigurationsdatei mit zehn separaten lineinfile-Aufgaben setzen, zahlen Sie den Aufgaben-Overhead zehnmal und erschweren das Rollback-Denken. Eine Vorlage kann klarer und schneller sein:

- name: Anwendungskonfiguration rendern
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
    mode: '0644'
  notify: app neu starten

Dies ist kein Argument für riesige Shell-Skripte in Ansible. Es ist eine Erinnerung daran, den gewünschten Zustand auf der richtigen Ebene zu modellieren. Eine Vorlage für eine Konfigurationsdatei ist oft besser als viele Mikro-Bearbeitungen.

Vermeiden Sie vorzeitige globale Änderungen

Setzen Sie Leistungseinstellungen nach Möglichkeit in die Projekt-ansible.cfg. Das Ändern von /etc/ansible/ansible.cfg auf einem gemeinsam genutzten Kontrollknoten kann andere Teams überraschen. Eine Datei auf Projektebene lässt das Verhalten mit dem Repository reisen und hält CI, Laptops und Automatisierungs-Runner näher an derselben Konfiguration.

Bestätigen Sie, welche Konfigurationsdatei Ansible verwendet:

ansible --version

Die Ausgabe enthält den aktiven Konfigurationspfad. Dies fängt einen häufigen Fehler auf: Bearbeiten einer ansible.cfg, während Ansible eine andere liest.

Wissen, wann man aufhören sollte, Ansible zu optimieren

Wenn der Verbindungs-Overhead kein Hauptbestandteil der Laufzeit mehr ist, hören Sie auf, SSH zu optimieren, und schauen Sie sich die Arbeit an. Paket-Cache-Updates, Container-Pulls, Datenbankmigrationen, Service-Health-Checks und Cloud-API-Aufrufe dominieren oft ausgereifte Playbooks. An diesem Punkt ist die bessere Optimierung möglicherweise ein lokaler Paketspiegel, Artefakt-Caching, kleinere Bereitstellungschargen oder das Verschieben der einmaligen Bereitstellung aus jedem Bereitstellungslauf.