DevOps والتوسع

نشر عمّال CaptchaAI باستخدام Ansible

يتحوّل تشغيل عمّال حل 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 الخوادم المحدَّثة وتُكمل من حيث توقفت.

الخطوات التالية

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.