Sobald mehr als eine Handvoll Server CAPTCHAs löst, wird die Konfiguration per SSH von Hand zur Fehlerquelle: Jeder Worker driftet in eine leicht andere Version, Konfigurationsdateien laufen auseinander, und jedes Update bedeutet Ausfallzeit. Ansible beseitigt genau diese Drift. Sie beschreiben den gewünschten Zustand Ihrer Worker-Flotte einmal deklarativ – ein Inventar, eine Rolle und drei kompakte Playbooks – und rollen ihn reproduzierbar auf 3 oder 300 Server aus, inklusive Rolling Updates ohne Downtime.
Kurz zur Rollenteilung: Terraform erzeugt die Server, Ansible bringt sie in den betriebsbereiten Zustand. In der Praxis greifen beide ineinander; dieser Leitfaden konzentriert sich auf den Ansible-Teil und zeigt das vollständige Setup – von der Projektstruktur über die Rolle captcha-worker bis zu Health-Checks gegen die CaptchaAI-API.
Projektstruktur des Ansible-Repositorys
Ein sauberes Layout trennt Inventar, wiederverwendbare Rolle und die ausführbaren Playbooks. Die Rolle kapselt alles, was auf jedem Worker gleich ist; das Inventar hält die umgebungsspezifischen Unterschiede zwischen Staging und Produktion.
ansible/
├── inventory/
│ ├── production.yml
│ └── staging.yml
├── roles/
│ └── captcha-worker/
│ ├── tasks/
│ │ └── main.yml
│ ├── templates/
│ │ ├── captcha-worker.service.j2
│ │ └── config.yaml.j2
│ ├── handlers/
│ │ └── main.yml
│ └── defaults/
│ └── main.yml
├── playbooks/
│ ├── deploy.yml
│ ├── rolling-update.yml
│ └── health-check.yml
└── ansible.cfg
Inventar: Staging und Produktion trennen
Das Inventar listet Ihre Hosts und setzt pro Umgebung die Gruppenvariablen. In der Produktion laufen drei Worker mit höherer Nebenläufigkeit und knapperem Poll-Intervall; Staging fährt mit einer Instanz, ausführlichem Logging und einem Release-Kandidaten. Ob Hetzner, netcup oder AWS die zugrunde liegenden VPS bereitstellt, ist dabei zweitrangig – Ansible spricht jeden Host über SSH an.
# inventory/production.yml
all:
children:
captcha_workers:
hosts:
worker-1:
ansible_host: 10.0.1.10
worker-2:
ansible_host: 10.0.1.11
worker-3:
ansible_host: 10.0.1.12
vars:
captchaai_concurrency: 20
captchaai_poll_interval: 3
captchaai_log_level: warning
worker_version: "1.3.0"
# inventory/staging.yml
all:
children:
captcha_workers:
hosts:
staging-worker-1:
ansible_host: 10.0.2.10
vars:
captchaai_concurrency: 5
captchaai_poll_interval: 5
captchaai_log_level: debug
worker_version: "1.4.0-rc1"
Ein wichtiger Punkt für die Kapazitätsplanung: captchaai_concurrency beschreibt, wie viele CAPTCHAs ein Worker gleichzeitig in Bearbeitung hält. Drei Produktions-Worker mit je 20 ergeben 60 gleichzeitige Lösungen – und CaptchaAI rechnet Thread-basiert ab, nicht pro Lösung. Für 60 parallele Threads reicht der Tarif ADVANCE (90 $/Monat, 50 Threads) nicht mehr; hier passt PREMIUM (170 $/Monat, 100 Threads). Staging mit 5 gleichzeitigen Lösungen ist bereits von BASIC (15 $/Monat, 5 Threads) abgedeckt. Skalieren Sie die Nebenläufigkeit im Inventar also stets im Einklang mit Ihrer Thread-Zuteilung.
Die Rolle „captcha-worker“
Standardvariablen der Rolle
Die Defaults definieren einen konservativen Ausgangszustand, den das Inventar pro Umgebung überschreibt. So bleibt die Rolle allgemeingültig, während die konkreten Werte dort liegen, wo sie hingehören.
# roles/captcha-worker/defaults/main.yml
captchaai_concurrency: 10
captchaai_poll_interval: 5
captchaai_log_level: info
captchaai_timeout: 300
captchaai_retries: 3
worker_version: "latest"
worker_user: captcha
worker_dir: /opt/captcha-worker
worker_venv: /opt/captcha-worker/venv
Tasks: Worker installieren und starten
Die Task-Datei arbeitet einen klaren Ablauf ab: dedizierten Systembenutzer und Arbeitsverzeichnis anlegen, Python samt virtueller Umgebung und Abhängigkeiten installieren, dann Anwendung, Konfiguration und systemd-Unit ausrollen und den Dienst starten. Die notify-Direktiven stoßen bei Änderungen die passenden Handler an, sodass nur bei tatsächlichem Bedarf neu gestartet wird.
# roles/captcha-worker/tasks/main.yml
---
- name: Create worker user
ansible.builtin.user:
name: "{{ worker_user }}"
system: true
shell: /usr/sbin/nologin
home: "{{ worker_dir }}"
- name: Create worker directory
ansible.builtin.file:
path: "{{ worker_dir }}"
state: directory
owner: "{{ worker_user }}"
mode: "0755"
- name: Install system dependencies
ansible.builtin.apt:
name:
- python3
- python3-venv
- python3-pip
state: present
update_cache: true
- name: Create Python virtual environment
ansible.builtin.command:
cmd: python3 -m venv {{ worker_venv }}
creates: "{{ worker_venv }}/bin/activate"
- name: Install Python dependencies
ansible.builtin.pip:
name:
- requests>=2.31.0
- pyyaml>=6.0
virtualenv: "{{ worker_venv }}"
- name: Deploy worker application
ansible.builtin.copy:
src: captcha_worker.py
dest: "{{ worker_dir }}/captcha_worker.py"
owner: "{{ worker_user }}"
mode: "0644"
notify: restart captcha-worker
- name: Deploy configuration
ansible.builtin.template:
src: config.yaml.j2
dest: "{{ worker_dir }}/config.yaml"
owner: "{{ worker_user }}"
mode: "0600"
notify: restart captcha-worker
- name: Deploy systemd service
ansible.builtin.template:
src: captcha-worker.service.j2
dest: /etc/systemd/system/captcha-worker.service
mode: "0644"
notify:
- reload systemd
- restart captcha-worker
- name: Enable and start service
ansible.builtin.systemd:
name: captcha-worker
enabled: true
state: started
Templates für Konfiguration und systemd-Service
Zwei Jinja2-Templates rendern die worker-spezifische Konfiguration und die systemd-Unit. Die Konfigurationsdatei liegt mit Modus 0600 beim Worker-Benutzer, weil sie Laufzeitparameter enthält; der API-Schlüssel kommt separat über eine Environment-Variable in den Dienst und landet nie auf der Platte im Klartext.
# roles/captcha-worker/templates/config.yaml.j2
# CaptchaAI Worker Configuration
# Managed by Ansible — do not edit manually
concurrency: {{ captchaai_concurrency }}
poll_interval: {{ captchaai_poll_interval }}
timeout: {{ captchaai_timeout }}
retries: {{ captchaai_retries }}
log_level: {{ captchaai_log_level }}
# roles/captcha-worker/templates/captcha-worker.service.j2
[Unit]
Description=CaptchaAI CAPTCHA Solving Worker
After=network.target
Wants=network-online.target
[Service]
Type=simple
User={{ worker_user }}
WorkingDirectory={{ worker_dir }}
ExecStart={{ worker_venv }}/bin/python {{ worker_dir }}/captcha_worker.py
Environment=CAPTCHAAI_API_KEY={{ captchaai_api_key }}
Restart=always
RestartSec=10
TimeoutStopSec=30
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths={{ worker_dir }}
[Install]
WantedBy=multi-user.target
Die drei Härtungsdirektiven NoNewPrivileges, ProtectSystem=strict und ReadWritePaths schränken den Dienst auf das Nötigste ein – ein sinnvoller Standard, wenn der Worker rund um die Uhr auf einem geteilten Host läuft.
Handler für Reload und Neustart
Handler laufen genau einmal am Ende eines Durchlaufs, egal wie oft sie benachrichtigt wurden. Das vermeidet unnötige Neustarts, wenn mehrere Tasks dieselbe Unit anfassen.
# roles/captcha-worker/handlers/main.yml
---
- name: reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: restart captcha-worker
ansible.builtin.systemd:
name: captcha-worker
state: restarted
Die drei Playbooks
Deploy-Playbook: Erstausrollung
Das Deploy-Playbook fragt den API-Schlüssel interaktiv ab (vars_prompt), prüft per Ping die Erreichbarkeit, wendet die Rolle an und meldet am Ende den systemd-Status jedes Hosts. Für automatisierte Läufe – etwa aus einer GitLab-CI-Pipeline – ersetzen Sie vars_prompt durch eine mit Ansible Vault verschlüsselte Variable.
# playbooks/deploy.yml
---
- name: Deploy CaptchaAI Workers
hosts: captcha_workers
become: true
vars_prompt:
- name: captchaai_api_key
prompt: "Enter CaptchaAI API key"
private: true
pre_tasks:
- name: Verify connectivity
ansible.builtin.ping:
roles:
- captcha-worker
post_tasks:
- name: Wait for worker to start
ansible.builtin.wait_for:
port: 8080
timeout: 30
ignore_errors: true
- name: Check worker status
ansible.builtin.systemd:
name: captcha-worker
register: worker_status
- name: Report status
ansible.builtin.debug:
msg: "Worker {{ inventory_hostname }}: {{ worker_status.status.ActiveState }}"
Rolling-Update ohne Ausfallzeit
Der Kern für unterbrechungsfreie Updates ist serial: 1: Ansible aktualisiert genau einen Host nach dem anderen. Jeder Worker wird zuerst geleert (drain.py lässt laufende Lösungen zu Ende bringen), gestoppt, neu bespielt und erst dann wieder in Betrieb genommen. Der abschließende Health-Check mit until/retries gibt den Host erst frei, wenn /health mit Status 200 antwortet. max_fail_percentage: 0 stoppt den Rollout beim ersten Fehler, bevor die halbe Flotte betroffen ist.
# playbooks/rolling-update.yml
---
- name: Rolling Update CaptchaAI Workers
hosts: captcha_workers
become: true
serial: 1 # Update one host at a time
max_fail_percentage: 0
tasks:
- name: Drain current tasks
ansible.builtin.command:
cmd: "{{ worker_venv }}/bin/python {{ worker_dir }}/drain.py"
timeout: 120
ignore_errors: true
- name: Stop worker
ansible.builtin.systemd:
name: captcha-worker
state: stopped
- name: Deploy new version
ansible.builtin.copy:
src: "captcha_worker.py"
dest: "{{ worker_dir }}/captcha_worker.py"
owner: "{{ worker_user }}"
mode: "0644"
- name: Update dependencies
ansible.builtin.pip:
requirements: "{{ worker_dir }}/requirements.txt"
virtualenv: "{{ worker_venv }}"
- name: Start worker
ansible.builtin.systemd:
name: captcha-worker
state: started
- name: Verify worker health
ansible.builtin.uri:
url: "http://localhost:8080/health"
return_content: true
register: health
until: health.status == 200
retries: 6
delay: 10
- name: Report update result
ansible.builtin.debug:
msg: "{{ inventory_hostname }} updated — {{ health.content }}"
Health-Check-Playbook
Dieses Playbook prüft zwei Ebenen: den lokalen systemd-Status jedes Workers und – von der Kontrollmaschine aus, einmalig – die Erreichbarkeit der CaptchaAI-API über getbalance. So sehen Sie in einem Durchlauf, ob die Dienste laufen und ob das Guthaben noch reicht.
# playbooks/health-check.yml
---
- name: Check CaptchaAI Worker Health
hosts: captcha_workers
become: false
gather_facts: false
tasks:
- name: Check systemd service
ansible.builtin.systemd:
name: captcha-worker
register: service_status
become: true
- name: Check API connectivity
ansible.builtin.uri:
url: "https://ocr.captchaai.com/res.php?key={{ captchaai_api_key }}&action=getbalance&json=1"
return_content: true
register: api_check
delegate_to: localhost
run_once: true
- name: Summary
ansible.builtin.debug:
msg: |
Host: {{ inventory_hostname }}
Service: {{ service_status.status.ActiveState }}
API Balance: {{ (api_check.content | from_json).request }}
Playbooks ausführen
Jedes Playbook wird gegen ein Inventar ausgeführt. Testen Sie neue Versionen zuerst gegen Staging, bevor Sie das Rolling-Update in Produktion anstoßen; mit --limit grenzen Sie einen Lauf auf einzelne Hosts ein.
# Deploy to staging
ansible-playbook -i inventory/staging.yml playbooks/deploy.yml
# Rolling update in production
ansible-playbook -i inventory/production.yml playbooks/rolling-update.yml
# Health check
ansible-playbook -i inventory/production.yml playbooks/health-check.yml
# Limit to specific hosts
ansible-playbook -i inventory/production.yml playbooks/deploy.yml --limit worker-1
Fehlerbehebung
| Problem | Ursache | Lösung |
|---|---|---|
| Host „unreachable“ | SSH-Schlüssel nicht hinterlegt | Schlüssel verteilen: ssh-copy-id user@host, dann Erreichbarkeit per ping-Task prüfen |
| Dienst startet nicht | API-Schlüssel als Environment-Variable fehlt | vars_prompt prüfen oder den Schlüssel mit Ansible Vault einbinden |
| Rolling-Update bleibt hängen | Health-Check auf /health schlägt fehl |
Logs mit journalctl -u captcha-worker sichten; retries/delay erhöhen |
| Konfiguration wird nicht übernommen | Handler wurde nicht ausgelöst | Mit --force-handlers laufen lassen oder changed_when: true ergänzen |
Häufige Fragen
Wie aktualisiere ich alle Worker ohne Ausfallzeit?
Mit dem Rolling-Update-Playbook und serial: 1. Ansible nimmt jeweils einen Host aus dem Betrieb, leert ihn per drain.py, spielt die neue Version ein und gibt ihn erst nach einem erfolgreichen Health-Check wieder frei. max_fail_percentage: 0 bricht den Rollout beim ersten Fehler ab.
Wie hinterlege ich den API-Schlüssel sicher?
Über Ansible Vault: ansible-vault encrypt_string 'ihr-api-key' --name 'captchaai_api_key'. Referenzieren Sie die verschlüsselte Variable in den Group-Vars des Inventars. So liegt der Schlüssel weder im Klartext im Repository noch im Shell-Verlauf, und CI-Läufe erhalten ihn über eine Vault-Passwortdatei.
Kann ich Staging und Produktion mit denselben Playbooks betreiben?
Ja. Die Playbooks und die Rolle bleiben identisch; nur das Inventar wechselt. Über -i inventory/staging.yml bzw. -i inventory/production.yml steuern Sie Hostliste, Nebenläufigkeit und Log-Level – ohne eine Zeile der Rolle anzupassen.
Welcher CaptchaAI-Plan passt zu meiner Worker-Flotte?
Das hängt an der Summe Ihrer captchaai_concurrency-Werte, denn CaptchaAI rechnet Thread-basiert mit unbegrenzten Lösungen pro Thread ab. Drei Worker mit je 20 gleichzeitigen Lösungen brauchen 60 Threads – hier greift PREMIUM (170 $/Monat, 100 Threads). Kleinere Flotten kommen mit STANDARD (30 $/Monat, 15 Threads) oder ADVANCE (90 $/Monat, 50 Threads) aus. Die aktuellen Tarife finden Sie unter captchaai.com/pricing.
Läuft das Setup auch auf Hetzner, netcup oder in Docker?
Ja. Die Rolle setzt nur einen erreichbaren Linux-Host mit apt und systemd voraus – ob dieser bei Hetzner, netcup oder AWS läuft, ist gleichgültig. Für Container ersetzen Sie die systemd-Tasks durch das Modul community.docker.docker_container; Ansible verwaltet dann den Container-Lebenszyklus statt eines Dienstes.
Verwandte Leitfäden
- CaptchaAI in wenigen Minuten einrichten
- API-Antwortformate und Fehlercodes verstehen
- reCAPTCHA v2 per API lösen