DevOps & Scaling

用于 CaptchaAI Worker 部署的 Ansible Playbook

验证码识别 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_keyvars_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.cfgforks,默认 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.ymlrolling-update.yml--limit worker-1,只对一台执行,确认没问题后去掉这个参数跑全量。

升级 worker_version 需要重启所有节点吗?

不需要,用 rolling-update.yml 逐台升级即可。

多机房部署要维护几份 inventory?

建议每个机房或云区域单独一份 inventory 文件,例如 inventory/production-cn.ymlinventory/production-us.yml,在各自的 vars 里设置符合当地网络条件的并发数和轮询间隔。role 和 playbook 代码不用改,执行时用 -i 指定对应文件即可。

下一步怎么做

先在 staging 跑通 deploy.yml,再切到 production。立即注册 CaptchaAI,拿到 API Key 后接入部署流程。

相关指南:

该文章已禁用评论。