30 Playbook-Optimierung und Performance

30.1 Einführung

In großen IT-Umgebungen können Playbooks mit Hunderten von Hosts oder zeitaufwändigen Tasks schnell zu Engpässen werden. Dieses Kapitel zeigt Ihnen, wie Sie durch gezielte Optimierungsstrategien die Ausführungszeit drastisch reduzieren und gleichzeitig die Zuverlässigkeit Ihrer Automatisierung erhöhen können.

Lernziele:

30.2 1. Parallelisierung von Tasks (serial, throttle, forks)

30.2.1 Globale Parallelisierung mit forks

Die grundlegende Parallelität in Ansible wird über die forks-Einstellung in der ansible.cfg gesteuert:

# ansible.cfg
[defaults]
forks = 20
host_key_checking = False
gathering = smart
fact_caching = memory

Standard: 5 parallele Verbindungen
Empfehlung: 10-50 je nach Netzwerk und Ziel-Hosts

30.2.2 Host-basierte Parallelisierung mit serial

Der serial-Parameter steuert, wie viele Hosts gleichzeitig bearbeitet werden - ideal für Rolling Updates:

---
- name: Rolling Update mit serial
  hosts: webservers
  serial: "30%"  # Prozentual oder absolute Zahl (z.B. 5)
  tasks:
    - name: Stoppe Service
      service:
        name: apache2
        state: stopped
    
    - name: Update Anwendung
      copy:
        src: app-v2.0.tar.gz
        dest: /opt/app/
    
    - name: Starte Service
      service:
        name: apache2
        state: started
    
    - name: Warte auf Verfügbarkeit
      uri:
        url: "http://{{ ansible_host }}:80/health"
        status_code: 200
      retries: 5
      delay: 10

30.2.3 Task-basierte Begrenzung mit throttle

Der throttle-Parameter begrenzt die Anzahl gleichzeitig ausgeführter Instanzen eines bestimmten Tasks:

---
- name: Beispiel für ressourcenintensive Tasks
  hosts: all
  tasks:
    - name: Datenbank-Backup (ressourcenintensiv)
      shell: |
        mysqldump --all-databases > /backup/db_{{ ansible_date_time.epoch }}.sql
        gzip /backup/db_{{ ansible_date_time.epoch }}.sql
      throttle: 2  # Nur 2 Hosts gleichzeitig
    
    - name: Normale Tasks ohne Begrenzung
      apt:
        name: htop
        state: present

30.3 2. Asynchrone Ausführung mit async und poll

30.3.1 Grundlagen der asynchronen Ausführung

---
- name: Demonstration asynchroner Tasks
  hosts: all
  tasks:
    - name: Starte lange Aufgabe asynchron
      command: /usr/bin/long_running_script.sh
      async: 3600      # Max. 60 Minuten Laufzeit
      poll: 30         # Prüfe alle 30 Sekunden
      register: long_task
    
    - name: Führe andere Tasks parallel aus
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - vim
        - curl
        - wget
    
    - name: Warte auf Abschluss der langen Aufgabe
      async_status:
        jid: "{{ long_task.ansible_job_id }}"
      register: job_result
      until: job_result.finished
      retries: 120
      delay: 30

30.3.2 Fire-and-Forget Tasks mit poll: 0

---
- name: Starte Hintergrund-Tasks
  hosts: all
  tasks:
    - name: Starte Monitoring-Agent im Hintergrund
      shell: |
        nohup /opt/monitoring/agent --daemon > /dev/null 2>&1 &
      async: 1
      poll: 0
      register: bg_task
    
    # Wichtig: Job-ID für spätere Referenz speichern
    - name: Speichere Job-IDs für spätere Kontrolle
      set_fact:
        background_jobs: "{{ background_jobs | default([]) + [bg_task.ansible_job_id] }}"

30.3.3 Erweiterte async-Behandlung

---
- name: Komplexe asynchrone Verarbeitung
  hosts: database_servers
  tasks:
    - name: Starte mehrere Backups parallel
      shell: |
        mysqldump {{ item }} > /backup/{{ item }}_{{ ansible_date_time.epoch }}.sql
      async: 1800
      poll: 0
      register: backup_jobs
      loop:
        - app_db
        - user_db
        - log_db
    
    - name: Prüfe Status aller Backup-Jobs
      async_status:
        jid: "{{ item.ansible_job_id }}"
      register: backup_status
      until: backup_status.finished
      retries: 60
      delay: 30
      loop: "{{ backup_jobs.results }}"
      when: item.ansible_job_id is defined
    
    - name: Validiere erfolgreiche Backups
      stat:
        path: "/backup/{{ item }}_{{ ansible_date_time.epoch }}.sql"
      register: backup_files
      loop:
        - app_db
        - user_db
        - log_db
      failed_when: not backup_files.stat.exists

30.4 3. Kombination von Optimierungsstrategien mit Fehlerbehandlung

---
- name: Optimierte Deployment-Pipeline
  hosts: production
  serial: 2
  vars:
    deployment_timeout: 1200
  
  tasks:
    - block:
        - name: Erstelle Deployment-Backup asynchron
          shell: |
            tar -czf /backup/pre-deploy-{{ ansible_date_time.epoch }}.tar.gz /opt/app
          async: "{{ deployment_timeout }}"
          poll: 5
          throttle: 1  # Nur ein Backup gleichzeitig
        
        - name: Stoppe Anwendung graceful
          service:
            name: myapp
            state: stopped
          timeout: 60
        
        - name: Deploy neue Version
          unarchive:
            src: "{{ app_package }}"
            dest: /opt/app
            remote_src: no
            owner: app
            group: app
        
        - name: Starte Anwendung
          service:
            name: myapp
            state: started
        
        - name: Validiere Deployment
          uri:
            url: "http://{{ ansible_host }}:8080/health"
            status_code: 200
          retries: 10
          delay: 15
      
      rescue:
        - name: Rollback bei Fehler
          shell: |
            systemctl stop myapp
            rm -rf /opt/app/*
            tar -xzf /backup/pre-deploy-*.tar.gz -C /
            systemctl start myapp
          register: rollback_result
        
        - name: Benachrichtige bei Rollback
          debug:
            msg: "WARNUNG: Rollback ausgeführt auf {{ inventory_hostname }}"

30.5 4. Effizientes Variablenmanagement

30.5.1 Lazy Evaluation und Caching

---
- name: Optimiertes Variablenmanagement
  hosts: all
  vars:
    # Lazy evaluation - wird nur berechnet wenn benötigt
    app_config_path: "{{ '/etc/myapp' if ansible_os_family == 'Debian' else '/opt/myapp/etc' }}"
    
    # Teure Berechnung nur einmal durchführen
    package_list: "{{ lookup('file', '/etc/required_packages.txt').splitlines() | select('match', '^[^#].*') | list }}"
  
  tasks:
    - name: Cache häufig verwendete Werte
      set_fact:
        system_info: {
          'os': "{{ ansible_distribution }}",
          'version': "{{ ansible_distribution_version }}",
          'arch': "{{ ansible_architecture }}",
          'memory_mb': "{{ ansible_memtotal_mb }}"
        }
        cacheable: yes  # Wird zwischen Playbook-Runs gespeichert
    
    - name: Verwende gecachte Werte
      debug:
        msg: "System: {{ system_info.os }} {{ system_info.version }} ({{ system_info.arch }})"
    
    - name: Installiere Pakete mit vordefinierter Liste
      package:
        name: "{{ package_list }}"
        state: present

30.5.2 Vermeidung redundanter Facts

---
- name: Effiziente Fact-Sammlung
  hosts: all
  gather_facts: no  # Deaktiviert automatische Sammlung
  
  tasks:
    - name: Sammle nur benötigte Facts
      setup:
        gather_subset:
          - "!all"
          - "!any"
          - network
          - hardware
      when: custom_facts_needed | default(false)
    
    - name: Verwende spezifische Facts nur bei Bedarf
      set_fact:
        network_interface: "{{ ansible_default_ipv4.interface | default('eth0') }}"
      when: ansible_default_ipv4 is defined

30.6 5. Performance-Monitoring und Debugging

30.6.1 Profiling mit erweiterten Optionen

# Detaillierte Profiling-Informationen
ansible-playbook playbook.yml \
  --profile_tasks \
  --diff \
  --check \
  -v

# Mit zusätzlichen Callback-Plugins
ANSIBLE_STDOUT_CALLBACK=profile_tasks \
ANSIBLE_CALLBACK_WHITELIST=profile_tasks,timer \
ansible-playbook playbook.yml

30.6.2 Konfiguration für Performance-Monitoring

# ansible.cfg - Optimierte Konfiguration
[defaults]
forks = 25
host_key_checking = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 3600
pipelining = True
callback_whitelist = profile_tasks, timer

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null
control_path_dir = ~/.ansible/cp
control_path = %(directory)s/%%h-%%p-%%r
pipelining = True

30.6.3 Erweiterte Profiling-Strategien

---
- name: Performance-Testplaybook
  hosts: all
  vars:
    start_time: "{{ ansible_date_time.epoch }}"
  
  tasks:
    - name: Markiere Startzeit für kritischen Abschnitt
      set_fact:
        section_start: "{{ ansible_date_time.epoch }}"
    
    - name: Kritische Tasks hier...
      # Ihre zeitkritischen Tasks
      
    - name: Berechne Ausführungszeit
      set_fact:
        section_duration: "{{ ansible_date_time.epoch | int - section_start | int }}"
    
    - name: Logge Performance-Metriken
      debug:
        msg: |
          Performance-Report für {{ inventory_hostname }}:
          - Gesamtzeit: {{ ansible_date_time.epoch | int - start_time | int }}s
          - Kritischer Abschnitt: {{ section_duration }}s
          - Memory: {{ ansible_memtotal_mb }}MB
      when: section_duration | int > 30  # Nur bei langsamen Hosts