DevOps & Scaling

Ansible-плейбуки для развёртывания воркеров CaptchaAI

Нужно раскатать воркеров CaptchaAI на десяток серверов без ручного SSH и без простоя при обновлении версии? Terraform поднимает сами серверы, а дальше в дело вступает Ansible: один плейбук устанавливает Python-зависимости, деплоит captcha_worker.py, настраивает systemd-сервис и — при следующем релизе — обновляет парк по одному хосту за раз, не роняя очередь задач. Ручной SSH на каждый сервер не масштабируется дальше пары хостов: команда неизбежно разъезжается по версиям Python-зависимостей и конфигов, а откат при сбое превращается в ручной разбор логов на каждой машине по отдельности. Ansible закрывает именно это — один прогон, один источник истины для конфигурации, воспроизводимый результат на staging и production. Ниже — готовая структура роли captcha-worker, три плейбука (деплой, rolling update, health check) и инвентарь для staging и production.

Структура Ansible-проекта для CaptchaAI

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

Инвентарь хостов для воркеров CaptchaAI

Группа captcha_workers задаёт список серверов и переменные, специфичные для окружения: concurrency, интервал опроса и версию воркера. Так production и staging живут в разных файлах, но используют одну и ту же роль — staging заведомо держит меньший captchaai_concurrency и debug-уровень логирования, а production выставляет warning, чтобы не захламлять журнал. Для команд, чьи серверы стоят в европейских регионах или в Казахстане (частый выбор при развёртывании инфраструктуры для рынков СНГ), стоит держать captchaai_timeout и captchaai_retries с запасом на нестабильных мобильных или спутниковых каналах, а не занижать их ради скорости отдельного запроса — таймаут в 300 секунд и три повтора из defaults/main.yml ниже — разумная стартовая точка, которую стоит калибровать по реальной сети, а не оставлять как есть без проверки.

# 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"

Роль captcha-worker: настройка и запуск воркера CaptchaAI

Переменные по умолчанию

# 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

Задачи роли

# 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

Шаблоны Jinja2

Конфиг и unit-файл собираются из переменных инвентаря — при откате на staging достаточно поменять значения в inventory/staging.yml, шаблоны трогать не нужно. Обратите внимание на блок security hardening в unit-файле: NoNewPrivileges, ProtectSystem=strict и явный список ReadWritePaths ограничивают воркеру доступ к файловой системе даже при компрометации Python-процесса — воркер физически не сможет записать что-либо за пределы worker_dir, даже если исполняемый код окажется скомпрометирован через уязвимую зависимость. Это дешёвая защита на уровне systemd, которую стоит держать включённой на проде, а не только на staging, где риск ниже, но привычка должна быть одна и та же.

# 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

Обработчики

# 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

Плейбуки Ansible: деплой, rolling update, health check

Развёртывание

Первый прогон разворачивает роль на всех хостах группы captcha_workers: создаёт пользователя, ставит зависимости, кладёт код и включает systemd-сервис. API-ключ запрашивается интерактивно через vars_prompt, чтобы не хранить его в открытом виде в инвентаре.

# 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)

# 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 }}"

Проверка работоспособности

Этот плейбук не требует прав root (become: false) и проверяет две вещи разом: статус systemd-юнита на каждом хосте и живой ответ CaptchaAI API через action=getbalance — то есть не только "процесс запущен", но и "ключ рабочий, баланс потоков виден".

# 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 }}

Команды запуска плейбуков

# 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

Часто задаваемые вопросы

  • Как безопасно хранить API-ключ CaptchaAI в Ansible? Не кладите ключ в inventory открытым текстом. Зашифруйте его через Ansible Vault: ansible-vault encrypt_string 'ваш-api-ключ' --name 'captchaai_api_key' — и подставляйте зашифрованную переменную в group_vars или host_vars. Второй вариант — интерактивный vars_prompt, как в playbooks/deploy.yml выше: ключ вообще не попадает на диск CI-раннера.
  • Как выкатить обновление воркера без простоя очереди задач? Используйте playbooks/rolling-update.yml с serial: 1: плейбук останавливает по одному хосту, ставит новую версию captcha_worker.py, поднимает сервис и ждёт 200 OK от /health, прежде чем перейти к следующему серверу. Пока один хост обновляется, остальные продолжают принимать задачи из очереди.
  • Сколько воркеров нужно под план ADVANCE (50 потоков)? Тариф ADVANCE — $90/мес и 50 потоков с неограниченным числом решений на поток (актуальные тарифы — на captchaai.com/pricing). CaptchaAI считает потоки, а не решения: пока запрос не завершился, поток занят, а как только освободился — берёт следующую задачу из очереди. При captchaai_concurrency: 20 на воркер трёх серверов из inventory/production.yml суммарно дают 60 одновременных запросов — это уже больше лимита ADVANCE, так что либо снижайте concurrency на воркер, либо переходите на PREMIUM (100 потоков за $170/мес); саму роль и плейбуки менять не нужно — только значение переменной в inventory.
  • Чем Ansible отличается от Terraform при развёртывании CaptchaAI? Terraform создаёт инфраструктуру — серверы, сети, диски. Ansible настраивает то, что уже создано: ставит зависимости, деплоит код воркера, управляет systemd-сервисом. На практике оба инструмента работают в связке: Terraform поднимает парк, Ansible донастраивает и обновляет его.

Устранение неполадок

  • Хост "Unreachable" — на целевом сервере не настроен SSH-ключ. Добавьте его: ssh-copy-id user@host.
  • Сервис не стартует — не задана переменная окружения с API-ключом. Проверьте vars_prompt в плейбуке или храните ключ в Ansible Vault.
  • Rolling update зависает на одном хосте — не проходит проверка health у только что обновлённого воркера. Смотрите journalctl -u captcha-worker; при нестабильной сети увеличьте retries/delay в задаче uri.
  • Конфиг не применяется после прогона — не сработал handler (задача отчиталась ok, а не changed, и restart не запустился). Перезапустите с флагом --force-handlers или добавьте changed_when: true в задачу.

Следующие шаги

Автоматизируйте деплой парка воркеров — получите API-ключ CaptchaAI на captchaai.com, разверните его плейбуками из этой статьи, а затем подключите нужный тип CAPTCHA по руководствам: быстрый старт CaptchaAI, решение reCAPTCHA v2 через API, решение Cloudflare Turnstile через API и решение GeeTest v3 через API.

Комментарии для этой статьи отключены.