18.2 forks — Παράλληλη εκτέλεση 🔀

Τι είναι τα forks

Το forks καθορίζει πόσα hosts εκτελούν ένα task ταυτόχρονα. Είναι η πιο άμεση ρύθμιση για performance.

📝 Αναλογία: Σκέψου τα forks σαν σεφ σε κουζίνα:

forks = 1:  1 σεφ → μαγειρεύει 1 πιάτο τη φορά
forks = 5:  5 σεφ → μαγειρεύουν 5 πιάτα ταυτόχρονα
forks = 20: 20 σεφ → 20 πιάτα ταυτόχρονα (αλλά χρειάζεσαι κουζίνα!)

Default τιμή και πρόβλημα

# Δες την τρέχουσα τιμή
ansible --version | grep "config file"
# Στο ansible.cfg:

[defaults]
forks = 5    # ← default: μόνο 5 hosts ταυτόχρονα!
Πρόβλημα: 50 webservers, forks=5 (default)

Task: Install nginx (2s ανά host)

Batch 1:  hosts 1-5   → 2s
Batch 2:  hosts 6-10  → 2s
Batch 3:  hosts 11-15 → 2s
...
Batch 10: hosts 46-50 → 2s

Σύνολο: 10 batches × 2s = 20s  ← μόνο για 50 hosts! ❌

Ρύθμιση forks

Μέθοδος 1: ansible.cfg (μόνιμη)

# ansible.cfg
[defaults]
forks = 20    # ← συνίσταται για τυπικό infrastructure

Μέθοδος 2: Command line (εφάπαξ)

# -f ή --forks
ansible-playbook site.yml -f 20
ansible-playbook site.yml --forks 20

# Ad-hoc command
ansible all -m ping -f 50

Μέθοδος 3: Environment variable

export ANSIBLE_FORKS=20
ansible-playbook site.yml

Πόσα forks να βάλεις;

Οδηγός επιλογής forks:
│
├── Controller specs:
│   ├── 2 CPU, 2GB RAM  → forks = 10-15
│   ├── 4 CPU, 4GB RAM  → forks = 20-30
│   └── 8 CPU, 8GB RAM  → forks = 50+
│
├── Network:
│   ├── LAN (fast)      → forks ↑↑
│   └── WAN (slow)      → forks ↓
│
├── Managed hosts:
│   ├── Λίγα (< 20)     → forks = αριθμός hosts
│   ├── Μεσαία (20-100) → forks = 20-30
│   └── Πολλά (100+)    → forks = 50-100
│
└── Τύπος tasks:
    ├── Network I/O heavy → forks ↑↑ (περιμένουν)
    └── CPU heavy         → forks = CPU cores

💡 Γρήγορος κανόνας: forks = αριθμός_hosts αν ο controller έχει αρκετή RAM. Κάθε fork καταναλώνει ~5-10MB RAM.


Benchmark — Μέτρηση επίδρασης

# Μέτρηση χρόνου εκτέλεσης με διαφορετικά forks
time ansible-playbook site.yml -f 1
time ansible-playbook site.yml -f 5
time ansible-playbook site.yml -f 20
time ansible-playbook site.yml -f 50

# Παράδειγμα αποτελεσμάτων (50 hosts):
# forks=1  → real 1m43s
# forks=5  → real 0m22s
# forks=20 → real 0m08s
# forks=50 → real 0m04s ← σχεδόν γραμμικό!

forks με pipelining

Ο συνδυασμός forks + pipelining δίνει τη μέγιστη βελτίωση:

# ansible.cfg — Optimized configuration
[defaults]
forks = 20

[ssh_connection]
pipelining   = true
ssh_args     = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30
control_path = ~/.ansible/cp/%%h-%%p-%%r
Επίδραση pipelining:

Χωρίς pipelining (1 task):
  1. SSH connect
  2. Upload Python module (~20KB)
  3. Execute module
  4. SSH disconnect
  → 4 operations per task

Με pipelining (1 task):
  1. SSH connect (επαναχρησιμοποίηση)
  2. Execute module (inline)
  → 1-2 operations per task → ~3x γρηγορότερο!

⚠️ Προϋπόθεση pipelining: Το requiretty πρέπει να είναι απενεργοποιημένο στο /etc/sudoers των managed hosts. Στα σύγχρονα συστήματα (RHEL 8+, Ubuntu 20.04+) είναι ήδη απενεργοποιημένο.


forks και Throttling ανά host group

Μπορείς να ορίσεις διαφορετικά forks ανά play:

---
# Διαφορετικά forks ανά group
- name: Γρήγορα tasks σε webservers
  hosts: webservers
  gather_facts: false
  # Χρησιμοποιεί global forks (20)

  tasks:
    - name: Reload nginx config
      ansible.builtin.service:
        name:  nginx
        state: reloaded

---
- name: Αργά tasks σε database servers
  hosts: databases
  gather_facts: false
  throttle: 1    # ← ΜΟΝΟ 1 database server τη φορά!
               # (ανεξάρτητα από τα global forks)

  tasks:
    - name: Database maintenance
      ansible.builtin.command:
        cmd: /opt/db/maintenance.sh
      changed_when: true

📝 Το throttle keyword ελέγχει τη μέγιστη παράλληλη εκτέλεση ανά task ή ανά play — είναι πιο λεπτομερές από τα global forks.


throttle ανά task

tasks:

  - name: Κανονικό task (τρέχει με global forks)
    ansible.builtin.apt:
      name:  nginx
      state: present

  - name: Resource-intensive task (περιορισμένο)
    ansible.builtin.command:
      cmd: /opt/backup/full-backup.sh
    throttle: 2    # ← max 2 hosts ταυτόχρονα
                   #   ακόμα κι αν forks=50!
    async:  3600
    poll:   0
    changed_when: true

  - name: API call με rate limit
    ansible.builtin.uri:
      url:    "https://api.example.com/register"
      method: POST
    throttle: 1    # ← σειριακά (ένα-ένα)
                   #   για rate-limited APIs

Πρακτικό παράδειγμα — forks benchmark playbook

---
# forks-benchmark.yml
# Χρησιμοποίησε με: ansible-playbook forks-benchmark.yml -f N

- name: Forks Benchmark
  hosts: all
  gather_facts: false

  tasks:

    - name: Timestamp έναρξης
      ansible.builtin.command:
        cmd: date +%s%N
      register:     start_time
      changed_when: false
      run_once:     true    # ← εκτελείται ΜΟΝΟ σε 1 host

    - name: Simulate work (sleep 1s)
      ansible.builtin.command:
        cmd: sleep 1
      changed_when: false

    - name: Timestamp λήξης
      ansible.builtin.command:
        cmd: date +%s%N
      register:     end_time
      changed_when: false
      run_once:     true

    - name: Αποτέλεσμα
      ansible.builtin.debug:
        msg: >
          Forks: {{ ansible_play_hosts | length }} hosts,
          Χρόνος: {{ ((end_time.stdout | int) -
                      (start_time.stdout | int)) / 1e9
                      | round(2) }}s
      run_once: true
# Σύγκριση
ansible-playbook playbooks/forks-benchmark.yml -f 1  -v
ansible-playbook playbooks/forks-benchmark.yml -f 5  -v
ansible-playbook playbooks/forks-benchmark.yml -f 20 -v

Σύνοψη 18.2

forks — Παράλληλη εκτέλεση
│
├── Ορισμός: N hosts εκτελούν task ταυτόχρονα
│
├── Ρύθμιση:
│   ├── ansible.cfg → forks = N  (μόνιμο)
│   ├── -f N        → εφάπαξ
│   └── ANSIBLE_FORKS=N → env variable
│
├── Επιλογή τιμής:
│   ├── Μικρό infra  → forks = αριθμός hosts
│   ├── Μεσαίο       → forks = 20-30
│   └── Μεγάλο       → forks = 50-100
│
├── Βελτιστοποίηση:
│   ├── pipelining = true → ~3x γρηγορότερο
│   └── ControlPersist → SSH reuse
│
└── Throttling:
    ├── throttle: N  (ανά task/play)
    └── Χρήση: rate limits, resource-intensive tasks