Khi fleet worker CaptchaAI lớn hơn vài máy, SSH tay vào từng host để đổi concurrency hay khởi động lại service sớm thành nút thắt vận hành. Playbook Ansible dưới đây giải quyết đúng việc đó: một role cài worker, template đồng bộ cấu hình, và một playbook rolling update không cần dừng dịch vụ. Terraform dựng hạ tầng; Ansible cấu hình những gì chạy bên trong.
Khi nào bạn cần playbook này
Cân nhắc chuyển sang Ansible khi:
- Bạn quản lý từ 3 worker trở lên và SSH tay vào từng máy để đổi
captchaai_concurrencychiếm nhiều thời gian hơn viết playbook. - Staging và production lệch cấu hình vì ai đó sửa tay một host rồi quên đồng bộ.
- Bạn cần nâng cấp
captcha_worker.pymà không được dừng cả fleet cùng lúc. - Đội bạn đã dùng Ansible cho hạ tầng khác và muốn quản lý worker theo cùng quy trình.
Fleet một host thì chạy trực tiếp qua systemd đơn giản hơn.
Cấu trúc dự án
Role, playbook và inventory tách riêng thư mục để dễ tái sử dụng:
inventory/— khai báo host theo môi trường (production, staging) cùng biến riêng cho từng nhóm.roles/captcha-worker/— toàn bộ logic cài đặt: tasks, template cấu hình, handler restart, biến mặc định.playbooks/— ba kịch bản chạy: triển khai lần đầu, rolling update, health check.
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
Ví dụ thực tế: mở rộng fleet cho một đội QA outsourcing
Tình huống điển hình ở các công ty outsourcing tại TP.HCM hoặc Hà Nội: đội automation bắt đầu với một worker cho một client, rồi nhận thêm hai client mới cần chạy song song trên staging trước khi lên production. Thay vì SSH tay vào từng host, họ thêm entry host vào inventory/production.yml, giữ nguyên role captcha-worker, rồi chạy lại deploy.yml — cấu hình tự đồng bộ xuống cả fleet. captchaai_concurrency khai báo trong inventory nên khớp đúng số thread đã mua: ba worker song song với tổng 15 thread cần gói STANDARD ($30/tháng, 15 thread), không nhồi vào gói BASIC ($15/tháng, 5 thread) rồi gặp lỗi giới hạn thread giữa batch của client.
Inventory: khai báo máy chủ theo môi trường
File inventory nhóm host theo môi trường. Production chạy concurrency cao hơn, log level warning:
# 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"
Staging dùng concurrency thấp hơn, log level debug để soi lỗi trước khi lên production:
# 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"
Role: captcha-worker
Biến mặc định
Các giá trị mặc định — override được qua inventory ở trên:
# 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: các bước cài đặt worker
Role chạy tuần tự: tạo user, dựng virtualenv, cài dependency, rồi deploy code và cấu hình 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
Templates: mẫu cấu hình và service
Template render các biến trên thành config.yaml thật trên từng host:
# 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 }}
Unit systemd chạy worker qua virtualenv, tự khởi động lại khi crash (Restart=always):
# 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
Handlers: kích hoạt khi có thay đổi
Handler chỉ chạy khi task tương ứng báo changed:
# 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
Bảo mật khi lưu API key và cấu hình
captchaai_api_key đi qua ba lớp: vars_prompt lúc chạy deploy.yml, file config.yaml quyền 0600 chỉ user captcha đọc được, và biến môi trường CAPTCHAAI_API_KEY trong unit systemd. Với production, mã hóa key bằng Ansible Vault và tham chiếu biến đã mã hóa trong group_vars thay vì nhập tay mỗi lần.
Vài điểm nên kiểm tra định kỳ:
ansible.cfgkhông commit mật khẩu SSH dạng plaintext.ReadWritePathstrong unit systemd chỉ trỏ vàoworker_dir.- Xoay API key qua dashboard CaptchaAI, cập nhật Vault rồi chạy lại
rolling-update.ymlđể áp dụng không downtime.
Playbook: deploy, rolling update và health check
Deploy: triển khai lần đầu
Playbook hỏi API key qua vars_prompt, ping host, áp role captcha-worker, rồi kiểm tra service:
# 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: cập nhật không downtime
serial: 1 giới hạn Ansible chỉ đụng một host mỗi lượt: drain, dừng, deploy bản mới, khởi động lại rồi chờ health check 200:
# 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: xác minh sau khi deploy
Playbook này gọi API không cần quyền root (become: false ở mức play) — kiểm tra service systemd rồi gọi endpoint getbalance để xác nhận API key còn hoạt động:
# 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 }}
Các lệnh chạy playbook
Luôn chạy staging trước, dùng --limit khi chỉ cần vài host:
# 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
Khắc phục sự cố thường gặp
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Host báo "unreachable" | Chưa cấu hình SSH key | Thêm SSH key: ssh-copy-id user@host |
| Service không khởi động được | Thiếu biến env chứa API key | Kiểm tra vars_prompt hoặc chuyển sang Ansible Vault |
| Rolling update bị kẹt giữa chừng | Health check không pass | Xem journalctl -u captcha-worker; tăng số retries trong playbook |
| Cấu hình mới không được áp dụng | Handler không được trigger | Chạy với --force-handlers hoặc thêm changed_when: true vào task |
Mẹo phòng ngừa:
- Chạy
ansible-playbook --checktrước khi apply thật trên production. - Giữ
staging-worker-1chạyworker_versionmới nhất trước production ít nhất một ngày. - Log level
debugtrên staging giúp đọc lỗi API rõ hơnwarningtrên production.
Câu hỏi thường gặp
Lưu API key CaptchaAI trong Ansible bằng cách nào cho an toàn?
Dùng Ansible Vault: ansible-vault encrypt_string 'your-api-key' --name 'captchaai_api_key'. Tham chiếu biến đã mã hóa trong inventory hoặc group_vars.
Playbook rolling update có làm gián đoạn worker đang chạy không?
Không, với serial: 1 trong rolling-update.yml: mỗi lượt chỉ một host bị drain, cập nhật, khởi động lại; các host còn lại vẫn giải CAPTCHA bình thường.
Nên đặt captchaai_concurrency bao nhiêu cho mỗi worker?
Khớp số thread bạn đã mua, không đặt tùy ý. Gói BASIC ($15/tháng, 5 thread) vừa cho một worker captchaai_concurrency: 5; ba worker song song với 15 thread cần gói STANDARD ($30/tháng, 15 thread).
Có triển khai captcha-worker trong Docker container thay vì systemd được không?
Có. Thay task ansible.builtin.systemd bằng module community.docker.docker_container — Ansible quản lý vòng đời container, phần còn lại giữ nguyên.
Làm sao biết fleet đang chạy ổn định mà không cần SSH vào từng host?
Chạy health-check.yml — playbook tổng hợp trạng thái systemd và số dư API của cả fleet vào một báo cáo. Đặt vào cron trên host điều phối để cảnh báo sớm thay vì phát hiện lỗi khi client báo CAPTCHA không giải được.
Bước tiếp theo
Đưa playbook này vào fleet worker: lấy API key CaptchaAI, chạy deploy.yml lần đầu, rolling-update.yml cho lần sau.
Hướng dẫn liên quan: