يتحوّل تشغيل عمّال حل CAPTCHA من مهمة بسيطة إلى تحدٍّ حقيقي لحظة أن يتجاوز أسطولك خادمًا واحدًا. فضبط كل خادم يدويًا — تثبيت الاعتماديات، ونسخ سكربت العامل، وتهيئة الخدمة — يفتح الباب أمام الأخطاء البشرية وتباين الإعدادات بين المضيفين. الحل هو تحويل هذا الضبط بالكامل إلى شيفرة قابلة للتكرار، وهنا يأتي دور Ansible.
يمنحك Ansible تعريفًا واحدًا لعامل CaptchaAI تطبّقه على الأسطول بأمر واحد، ثم تمرّر تغييرات الإعدادات وتنفّذ التحديثات المتدرّجة دون إيقاف الخدمة. يغطّي هذا الدليل المخزون، والدور، وملفات playbook للنشر والتحديث وفحص الصحة، مع أوامر التشغيل الجاهزة.
لماذا تعتمد على Ansible في نشر عمّال CaptchaAI؟
من المفيد التمييز بين أداتين: Terraform يُنشئ البنية التحتية — الخوادم والشبكات — بينما Ansible يضبط ما بعد الإنشاء من تثبيت البرمجيات ونشر الشيفرة وإدارة الخدمات. تعملان معًا: Terraform يجهّز الأسطول، ثم يتسلّم Ansible ضبطه فيصبح كل خادم عاملٍ نسخةً مطابقةً للبقية.
- اتساق الأسطول: تعريف واحد يضمن أن جميع العمّال يشغّلون النسخة نفسها بالإعدادات نفسها.
- تحديثات بلا توقّف: التحديث المتدرّج يستبدل العمّال واحدًا تلو الآخر مع فحص صحة بينها.
- مصدر واحد للحقيقة: تعيش الإعدادات في مستودع Git، فتصبح قابلة للمراجعة والتراجع.
نقطة مهمة قبل ضبط التزامن: يحاسب CaptchaAI على أساس عدد الخيوط (threads) المتزامنة لا على أساس كل عملية حل، مع حلول غير محدودة لكل خيط. لذلك يجب أن يبقى مجموع قيمة captchaai_concurrency عبر الأسطول ضمن حدود الخيوط في خطتك؛ فتجاوزها يولّد طلبات تنتظر خيطًا حرًّا بلا زيادة في الإنتاجية.
بنية مشروع Ansible لأسطول العمّال
رتّب المشروع في بنية أدوار (roles) قياسية تفصل المخزون عن منطق النشر وتجعل الدور قابلًا لإعادة الاستخدام عبر بيئات متعددة:
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
المخزون في inventory/ يعرّف الخوادم لكل بيئة، والدور في roles/captcha-worker/ يجمع مهام العامل وقوالبه ومعالجاته وقيمه الافتراضية، بينما تنسّق ملفات playbooks/ بينها.
المخزون: فصل الإنتاج عن بيئة التجهيز
عرّف بيئتين منفصلتين على الأقل: ملف الإنتاج يضمّ عدة عمّال بتزامن مرتفع وتسجيل مقتضب، بينما تبقى بيئة التجهيز (staging) بعاملٍ واحد وتزامن منخفض وتسجيل تفصيلي يسهّل التشخيص:
# 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"
لاحظ اختلاف المتغيّرات: قيمة captchaai_concurrency أعلى في الإنتاج (20 لكل مضيف) وأقل في التجهيز (5)، ويعتمد التجهيز إصدارًا مرشّحًا (1.4.0-rc1) لاختباره قبل أن تصل التغييرات إلى العمّال الحيّين.
دور captcha-worker: عرّف العامل مرة واحدة
الدور هو قلب المشروع: يجمع القيم الافتراضية والمهام والقوالب والمعالجات في وحدة واحدة تُطبَّق على أي مضيف ضمن مجموعة captcha_workers.
القيم الافتراضية للدور
تحدّد القيم الافتراضية سلوك العامل حين لا يعرّفه المخزون صراحةً:
# 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
أي متغيّر يعرّفه المخزون يتجاوز هذه القيمة الافتراضية، ويعمل العامل بمستخدم نظام غير تفاعلي ضمن مسار معزول تحت /opt لتقليل مساحة الخطر.
مهام التثبيت والنشر
تنفّذ المهام خطوات الإعداد بالترتيب: إنشاء المستخدم والمجلد، ثم تثبيت اعتماديات النظام وبيئة Python المعزولة، ثم نشر سكربت العامل والإعداد ووحدة systemd:
# 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
المهام في Ansible خاملة التأثير (idempotent): تشغيلها مجددًا لا يعيد تنفيذ ما اكتمل. ويُطلق notify إعادة تشغيل الخدمة تلقائيًا فقط عند تغيّر السكربت أو الإعداد.
القوالب: الإعداد ووحدة systemd
تولّد القوالب (templates) ملفات الإعداد من المتغيّرات، فيصبح الملف على كل خادم انعكاسًا لمخزونه: الأول يبني config.yaml، والثاني يعرّف وحدة systemd:
# 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
يُمرَّر مفتاح الـ API عبر متغيّر بيئة (CAPTCHAAI_API_KEY) لا عبر ملف على القرص، فيبقى خارج الشيفرة المصدرية. وتضيف الوحدة تقييدات أمان مثل NoNewPrivileges وProtectSystem=strict، مع Restart=always للتعافي بعد أي انقطاع.
المعالجات (handlers)
المعالجات مهام لا تعمل إلا عند استدعائها بـ notify؛ نعرّف هنا معالجين: أحدهما يعيد تحميل إعدادات systemd، والآخر يعيد تشغيل الخدمة عند تغيّر الشيفرة أو الإعداد:
# 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
playbooks: النشر والتحديث المتدرّج والمراقبة
تنسّق ملفات playbook بين الدور والمخزون لتنفيذ عمليات محددة: نشر أولي، وتحديث متدرّج، وفحص صحة.
playbook النشر الأولي
يطلب هذا الـ playbook مفتاح الـ 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 }}"
يمنع private: true ظهور المفتاح على الشاشة أو في السجلّ، وتتأكد مهام post_tasks من أن الخدمة نشطة فعليًا قبل اعتبار النشر ناجحًا.
التحديث المتدرّج بلا توقّف
التحديث المتدرّج يجعل الأسطول قابلًا للصيانة أثناء الإنتاج: باستخدام serial: 1 يعالج Ansible خادمًا واحدًا في كل مرة، فيبقى بقيته يخدم الطلبات:
# 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 }}"
يفرّغ كل خادم مهامه الجارية (drain) قبل إيقافه، ثم يُحدَّث ويُعاد تشغيله، ولا يُنتقل إلى التالي إلا بعد أن يعيد فحص الصحة الرمز 200؛ ويوقف max_fail_percentage: 0 التحديث فور فشل أي خادم فلا تنتشر ترقية معطوبة.
فحص صحة الأسطول
يجمع هذا الـ playbook حالة الخدمة على كل مضيف مع فحص اتصال واحد بواجهة CaptchaAI:
# 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 }}
يستدعي الفحص نقطة النهاية res.php بالإجراء getbalance مرة واحدة عبر localhost، فيؤكّد صلاحية المفتاح وكفاية الرصيد إلى جانب حالة الخدمة المحلية، ويمنحك صورة كاملة عن جاهزية الأسطول بأمر واحد.
تشغيل الـ playbooks
بعد جاهزية الملفات، شغّل العمليات من سطر الأوامر، بدءًا بالتجهيز قبل الإنتاج:
# 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
شغّل النشر على التجهيز أولًا لالتقاط أخطاء الإعداد قبل لمس الإنتاج، واستخدم --limit worker-1 لتشخيص مضيف بعينه دون إزعاج البقية.
استكشاف الأخطاء الشائعة
تندرج معظم مشكلات النشر ضمن فئات قليلة متكررة يلخّصها الجدول التالي مع السبب والإجراء:
| المشكلة | السبب المحتمل | الإجراء |
|---|---|---|
| المضيف «unreachable» | مفتاح SSH غير مُعدّ أو ansible_host خاطئ |
تحقّق من العنوان وأضف المفتاح عبر ssh-copy-id user@host |
| فشل تثبيت الحزم | فهرس apt قديم |
أضف update_cache: true إلى مهمة apt |
| الخدمة لا تبدأ | متغيّر CAPTCHAAI_API_KEY فارغ |
مرّر المفتاح عبر vars_prompt أو Ansible Vault |
| تعطّل التحديث المتدرّج | فشل فحص الصحة مع max_fail_percentage: 0 |
راجع journalctl -u captcha-worker وارفع retries |
| الإعداد لا يُطبَّق | لم يُستدعَ المعالج المناسب | شغّل بـ --force-handlers أو أضف changed_when: true |
سيناريو عملي: من ثلاثة عمّال إلى ثلاثين
تخيّل فريق أتمتة يشغّل ثلاثة عمّال ثم يحتاج إلى مضاعفة الطاقة قبل موسم ذروة. بدل تجهيز كل خادم يدويًا، يضيف المضيفين إلى ملف المخزون ويشغّل playbook النشر، فيصل الجميع إلى الحالة نفسها خلال دقائق؛ وعند صدور إصدار جديد، يرقّي التحديث المتدرّج الأسطول كله دون نافذة توقّف. هذا هو الفرق العملي بين إدارة الأسطول بالشيفرة وإدارته يدويًا.
الأسئلة الشائعة
كيف أُخزّن مفتاح CaptchaAI API بأمان بدل كتابته في الملفات؟
استخدم Ansible Vault لتشفير المفتاح: ansible-vault encrypt_string 'your-api-key' --name 'captchaai_api_key'، ثم أشِر إلى المتغيّر المشفّر في المخزون أو في group_vars. بهذا يبقى المفتاح مشفّرًا داخل المستودع دون أن يظهر نصًّا صريحًا في أي ملف.
كيف أحدّد قيمة captchaai_concurrency المناسبة لأسطولي؟
اربط قيمة التزامن بعدد الخيوط المتاح في خطتك؛ فبما أن المحاسبة على أساس الخيوط المتزامنة مع حلول غير محدودة لكل خيط، يجب ألا يتجاوز مجموع captchaai_concurrency عبر الأسطول حدّ الخيوط. ابدأ بقيمة محافِظة، وراقب زمن الانتظار في السجلّات، ثم ارفعها تدريجيًا.
كيف أُعيد تشغيل عامل واحد فقط دون التأثير على الأسطول؟
استخدم خيار --limit مع اسم المضيف، مثل ansible-playbook -i inventory/production.yml playbooks/deploy.yml --limit worker-1. يحصر هذا التنفيذ في مضيف واحد، وهو مثالي لتطبيق إصلاح عاجل أو لتشخيص مشكلة معزولة دون لمس بقية العمّال.
ماذا يحدث إذا فشل فحص الصحة أثناء التحديث المتدرّج؟
بفضل max_fail_percentage: 0 يتوقف التحديث فور فشل خادم واحد في اجتياز فحص الصحة، فلا تصل الترقية المعطوبة إلى بقية الأسطول. عالج الخادم المتعثّر عبر journalctl -u captcha-worker، ثم أعد تشغيل الـ playbook؛ ستتخطّى Ansible الخوادم المحدَّثة وتُكمل من حيث توقفت.
الخطوات التالية
- ابدأ سريعًا: حلّ أول كابتشا في خمس دقائق
- حلّ reCAPTCHA v2 عبر الـ API خطوة بخطوة
- التعامل مع Cloudflare Turnstile عبر الـ API
- حلّ GeeTest v3 باستخدام الـ API