验证码识别 worker 从 3 台加到 30 台后,手动 SSH 逐台改配置很快会失控——版本不一致、配置漂移是常态。Ansible 用声明式 playbook 解决这个问题:一次编写角色,随时在整支服务器群上重复执行、回滚、扩容。Terraform 负责把服务器建出来,Ansible 接手之后的持续配置。
项目结构一览
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/:编排部署、滚动更新、健康检查的操作顺序。ansible.cfg:统一 SSH 用户、并发 fork 数。
新增一台 worker 只需在 inventory 里加一行主机记录,不用改 role 或 playbook。
Inventory 清单:区分 Production 与 Staging
Inventory 定义主机与变量,生产和 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"
国内网络下装 pip 依赖慢,可给 ansible.builtin.pip 任务加 extra_args: "-i https://pypi.tuna.tsinghua.edu.cn/simple" 走清华源。
captcha-worker 角色:从变量到系统服务
role 拆成变量、任务、模板、handler 四块,职责边界清晰:
defaults:可被覆盖的默认值。tasks:具体执行步骤。templates:渲染下发到目标机器的文件。handlers:收敛重复的重启、重载动作。
这样拆分后,同一份 role 在生产、staging 甚至新机房都能直接复用。
默认变量(defaults)
# 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)
这份 task 清单做四件事:
- 建用户、建目录。
- 装依赖和虚拟环境。
- 部署脚本与配置(改动才触发
notify重启)。 - 注册并启动 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)
两个模板各管一块:
config.yaml.j2:渲染运行参数。captcha-worker.service.j2:渲染 systemd unit,captchaai_api_key由vars_prompt运行时输入,不进仓库。
# 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
事件处理器(handlers)
两个 handler 各司其职:
reload systemd:unit 文件变了才需要。restart captcha-worker:脚本或配置变了才需要,多次notify会被去重为一次。
# 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
Playbook 编排:部署、滚动更新与健康检查
三份 playbook 对应三种场景:deploy.yml 从零部署,rolling-update.yml 不停机升级,health-check.yml 日常巡检。三者共用同一个 role 和 inventory,避免逻辑重复。
部署 Playbook
两处关键设计:
vars_prompt:交互式输入 API Key,避免明文入库。pre_tasks:先做连通性检查,SSH 不通不跑 role。
# 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 }}"
滚动更新 Playbook
两个参数决定了更新节奏:
serial: 1:一次只更新一台,/health返回 200 才继续下一台。max_fail_percentage: 0:一台失败就整体停止。
# 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 }}"
健康检查 Playbook
巡检只做两件事:
- 确认 systemd 服务状态。
- 确认 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 }}
配置漂移怎么防:几条实操经验
规模一大,"某台机器手动改过、谁也不记得" 是漂移最常见的起点:
- 变量改动只进
defaults/main.yml或 inventory 的vars,不手动改配置。 - 改完立刻跑一遍对应 playbook,让改动实际下发。
- 生产环境先用
--check --diff预览。 - worker 涨到两位数后调大
ansible.cfg的forks,默认 5 并发太慢。
常用命令
# 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
常见故障排查
| 问题 | 原因 | 处理方式 |
|---|---|---|
| “无法访问”主机 | 未配置 SSH 密钥 | 添加 SSH 密钥:ssh-copy-id user@host |
| 服务无法启动 | 缺少 API 密钥环境变量 | 检查 vars_prompt 或使用 Ansible Vault |
| 滚动更新卡住了 | 健康检查失败 | 检查journalctl -u captcha-worker;增加重试次数 |
| 配置未应用 | 处理程序未触发 | 使用 --force-handlers 运行或添加 changed_when: true |
报 UNREACHABLE 但手动 SSH 能连 |
端口或解释器路径与默认值不一致 | 在 inventory 里设置 ansible_port / ansible_python_interpreter |
常见问题
API 密钥应该怎么安全存储,而不是写死在 playbook 里?
用 Ansible Vault 加密:ansible-vault encrypt_string 'your-api-key' --name 'captchaai_api_key',密文粘贴进 inventory 或 group_vars,仓库里不会出现明文 Key。
想先验证一台,再推全量,怎么做?
给 deploy.yml 或 rolling-update.yml 加 --limit worker-1,只对一台执行,确认没问题后去掉这个参数跑全量。
升级 worker_version 需要重启所有节点吗?
不需要,用 rolling-update.yml 逐台升级即可。
多机房部署要维护几份 inventory?
建议每个机房或云区域单独一份 inventory 文件,例如 inventory/production-cn.yml、inventory/production-us.yml,在各自的 vars 里设置符合当地网络条件的并发数和轮询间隔。role 和 playbook 代码不用改,执行时用 -i 指定对应文件即可。
下一步怎么做
先在 staging 跑通 deploy.yml,再切到 production。立即注册 CaptchaAI,拿到 API Key 后接入部署流程。
相关指南:
- Terraform 基础设施即代码
- Docker 容器化解决方案
- 生产环境配置管理