Reference

CaptchaAI в производстве: руководство по управлению конфигурацией

API-ключ CaptchaAI прямо в коде сходит с рук на прототипе, но в проде почти всегда стреляет: ключ утекает в git-историю, смена лимита требует передеплоя, а дежурному инженеру в три часа ночи некогда собирать новый релиз ради одной цифры в конфиге.

Ниже — приоритет источников конфигурации, полный список переменных, загрузчики на Python/Node.js, конфиг-файлы под каждое окружение и где хранить секреты.

Приоритет источников: что перекрывает что

Priority (highest → lowest):

1. Environment variables     ← deployment-specific overrides
2. Config file (YAML/JSON)   ← version-controlled defaults
3. Application defaults      ← fallback values in code

Переменная окружения перекрывает файл, файл — дефолт в коде. Есть CLI-флаг — ставьте его выше переменных окружения по тому же принципу. В Kubernetes тот же принцип действует для ConfigMap и Secret, смонтированных как переменные окружения контейнера, — они всё равно выше файла конфигурации, зафиксированного в образе.

Переменные окружения CaptchaAI

Ниже — полный список переменных, которые понимает клиент CaptchaAI: имя, значение по умолчанию и что произойдёт, если её не задать.

Параметр Переменная окружения По умолчанию Описание
API-ключ CAPTCHAAI_API_KEY Обязателен. Ваш API-ключ CaptchaAI
Отправить URL CAPTCHAAI_SUBMIT_URL https://ocr.captchaai.com/in.php Конечная точка отправки задачи
URL опроса CAPTCHAAI_POLL_URL https://ocr.captchaai.com/res.php Конечная точка опроса результатов
Интервал опроса CAPTCHAAI_POLL_INTERVAL 5 Секунды между попытками опроса
Макс. попыток опроса CAPTCHAAI_MAX_POLLS 60 Число попыток опроса до тайм-аута
Параллелизм CAPTCHAAI_CONCURRENCY 10 Лимит параллельных задач CAPTCHA
Тайм-аут CAPTCHAAI_TIMEOUT 300 Общий тайм-аут в секундах
Прокси CAPTCHAAI_PROXY URL прокси для решения CAPTCHA
URL обратного вызова CAPTCHAAI_CALLBACK_URL URL webhook для асинхронных результатов
Повторные попытки CAPTCHAAI_RETRIES 3 Повторы при временных сбоях
Уровень журнала CAPTCHAAI_LOG_LEVEL info Подробность журналирования

Как загружать конфигурацию: Python и Node.js

Python

import os
import yaml
from dataclasses import dataclass, field
from pathlib import Path


@dataclass
class CaptchaAIConfig:
    api_key: str = ""
    submit_url: str = "https://ocr.captchaai.com/in.php"
    poll_url: str = "https://ocr.captchaai.com/res.php"
    poll_interval: int = 5
    max_polls: int = 60
    concurrency: int = 10
    timeout: int = 300
    proxy: str = ""
    callback_url: str = ""
    retries: int = 3
    log_level: str = "info"

    @classmethod
    def load(cls, config_path=None):
        """Load config: env vars override file, which overrides defaults."""
        config = cls()

        # Layer 2: Config file
        if config_path and Path(config_path).exists():
            with open(config_path) as f:
                file_config = yaml.safe_load(f) or {}
            for key, value in file_config.items():
                if hasattr(config, key):
                    setattr(config, key, value)

        # Layer 1: Environment variables (highest priority)
        env_map = {
            "CAPTCHAAI_API_KEY": "api_key",
            "CAPTCHAAI_SUBMIT_URL": "submit_url",
            "CAPTCHAAI_POLL_URL": "poll_url",
            "CAPTCHAAI_POLL_INTERVAL": "poll_interval",
            "CAPTCHAAI_MAX_POLLS": "max_polls",
            "CAPTCHAAI_CONCURRENCY": "concurrency",
            "CAPTCHAAI_TIMEOUT": "timeout",
            "CAPTCHAAI_PROXY": "proxy",
            "CAPTCHAAI_CALLBACK_URL": "callback_url",
            "CAPTCHAAI_RETRIES": "retries",
            "CAPTCHAAI_LOG_LEVEL": "log_level",
        }

        for env_key, attr_name in env_map.items():
            value = os.environ.get(env_key)
            if value is not None:
                # Cast to correct type
                current = getattr(config, attr_name)
                if isinstance(current, int):
                    value = int(value)
                setattr(config, attr_name, value)

        config.validate()
        return config

    def validate(self):
        if not self.api_key:
            raise ValueError("CAPTCHAAI_API_KEY is required")
        if self.poll_interval < 1:
            raise ValueError("poll_interval must be >= 1")
        if self.concurrency < 1:
            raise ValueError("concurrency must be >= 1")


# Usage
config = CaptchaAIConfig.load("config/captchaai.yaml")
print(f"Concurrency: {config.concurrency}, Timeout: {config.timeout}s")

Логика в обоих языках одинаковая — сначала дефолты, потом файл, потом переменные окружения; отличаются только имена полей: snake_case в Python и camelCase в JavaScript.

JavaScript

const fs = require("fs");
const yaml = require("js-yaml");
const path = require("path");

class CaptchaAIConfig {
  static defaults = {
    apiKey: "",
    submitUrl: "https://ocr.captchaai.com/in.php",
    pollUrl: "https://ocr.captchaai.com/res.php",
    pollInterval: 5,
    maxPolls: 60,
    concurrency: 10,
    timeout: 300,
    proxy: "",
    callbackUrl: "",
    retries: 3,
    logLevel: "info",
  };

  static envMap = {
    CAPTCHAAI_API_KEY: "apiKey",
    CAPTCHAAI_SUBMIT_URL: "submitUrl",
    CAPTCHAAI_POLL_URL: "pollUrl",
    CAPTCHAAI_POLL_INTERVAL: { key: "pollInterval", type: "int" },
    CAPTCHAAI_MAX_POLLS: { key: "maxPolls", type: "int" },
    CAPTCHAAI_CONCURRENCY: { key: "concurrency", type: "int" },
    CAPTCHAAI_TIMEOUT: { key: "timeout", type: "int" },
    CAPTCHAAI_PROXY: "proxy",
    CAPTCHAAI_CALLBACK_URL: "callbackUrl",
    CAPTCHAAI_RETRIES: { key: "retries", type: "int" },
    CAPTCHAAI_LOG_LEVEL: "logLevel",
  };

  static load(configPath = null) {
    let config = { ...CaptchaAIConfig.defaults };

    // Layer 2: Config file
    if (configPath && fs.existsSync(configPath)) {
      const ext = path.extname(configPath);
      const raw = fs.readFileSync(configPath, "utf8");
      const fileConfig = ext === ".json" ? JSON.parse(raw) : yaml.load(raw);
      config = { ...config, ...fileConfig };
    }

    // Layer 1: Environment variables
    for (const [envKey, mapping] of Object.entries(CaptchaAIConfig.envMap)) {
      const value = process.env[envKey];
      if (value !== undefined) {
        const attrKey = typeof mapping === "string" ? mapping : mapping.key;
        const type = typeof mapping === "string" ? "string" : mapping.type;
        config[attrKey] = type === "int" ? parseInt(value, 10) : value;
      }
    }

    CaptchaAIConfig.validate(config);
    return config;
  }

  static validate(config) {
    if (!config.apiKey) throw new Error("CAPTCHAAI_API_KEY is required");
    if (config.pollInterval < 1) throw new Error("pollInterval must be >= 1");
    if (config.concurrency < 1) throw new Error("concurrency must be >= 1");
  }
}

// Usage
const config = CaptchaAIConfig.load("config/captchaai.yaml");
console.log(`Concurrency: ${config.concurrency}, Timeout: ${config.timeout}s`);

Конфиг-файлы под каждое окружение

Базовый файл задаёт дефолты, per-environment переопределяет только то, что реально отличается.

# config/captchaai.yaml — base
api_key: ""  # Always set via env var
concurrency: 5
poll_interval: 5
retries: 3
log_level: info
# config/captchaai.production.yaml
concurrency: 20
poll_interval: 3
timeout: 180
log_level: warning
# config/captchaai.staging.yaml
concurrency: 3
poll_interval: 5
timeout: 300
log_level: debug

Где хранить секреты API-ключа

Никогда не храните API-ключ в конфиг-файлах или в git — частая причина утечек.

Способ Когда использовать Пример
Переменные окружения Контейнеры, CI/CD export CAPTCHAAI_API_KEY=abc123
AWS Secrets Manager Инфраструктура AWS Авторотация при старте
HashiCorp Vault Мультиоблако, on-prem Динамические секреты с TTL
Docker secrets Docker Swarm / Compose /run/secrets/
Kubernetes Secrets Kubernetes-кластеры Монтируются как файл или переменная окружения пода
.env (только для разработки) Локальная разработка dotenv; добавьте в .gitignore

Маскируйте CAPTCHAAI_API_KEY в логах на уровне логирующей библиотеки — для команд под юрисдикцией РФ это важно и из-за 152-ФЗ, если рядом оказываются персональные данные пользователей.

Пример: секреты воркера в европейском регионе

Команда, которая держит воркеры CaptchaAI в дата-центре во Франкфурте или Амстердаме — обычный выбор для RU-читателей из-за задержки и доступности, — как правило хранит CAPTCHAAI_API_KEY в secret-хранилище оркестратора (Kubernetes Secret или Docker secret), а не в .env на самой машине: так ключ переживает пересоздание контейнера и не попадает в образ. Ротация сводится к обновлению значения в хранилище и перезапуску пода или сервиса — без пересборки образа и без передеплоя остального кода.

Пример docker-compose.yml

services:
  captcha-worker:
    image: captcha-worker:latest
    environment:

      - CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
      - CAPTCHAAI_CONCURRENCY=15
      - CAPTCHAAI_LOG_LEVEL=warning
    env_file:

      - .env.production

Фича-флаги: переключение без передеплоя

Флаги живут в тех же переменных окружения, что и остальная конфигурация, — отдельный сервис фича-флагов для одного воркера не нужен. Это удобно, чтобы сначала включить рискованную настройку (например, прокси или новый лимит параллелизма) на части инстансов, а потом раскатать на все, не трогая образ.

Меняйте поведение сервиса без пересборки образа:

class FeatureFlags:
    def __init__(self):
        self.flags = {
            "use_callback": os.environ.get("FF_USE_CALLBACK", "false") == "true",
            "enable_proxy": os.environ.get("FF_ENABLE_PROXY", "true") == "true",
            "max_concurrent": int(os.environ.get("FF_MAX_CONCURRENT", "10")),
        }

    def is_enabled(self, flag):
        return self.flags.get(flag, False)

    def get(self, flag, default=None):
        return self.flags.get(flag, default)

Типичные проблемы конфигурации

Большинство проблем всплывает не сразу, а через несколько недель после переноса конфигурации в переменные окружения — когда в команде появляется новый сервис или новое окружение.

Проблема Причина Решение
API-ключ не подхватывается Опечатка в имени переменной echo $CAPTCHAAI_API_KEY; сверьте написание
Файл конфигурации игнорируется Неверный путь или нет YAML-библиотеки Проверьте путь; поставьте pyyaml / js-yaml
В проде применяются dev-настройки Не сработало переопределение под окружение Сверьте приоритет источников и NODE_ENV / APP_ENV
Секреты видны в логах Дамп конфигурации выводит ключ целиком Маскируйте чувствительные поля перед логированием
Переменные из .env не подхватываются в Docker env_file не подключён к сервису в docker-compose.yml Проверьте docker-compose config и путь к файлу

Если ни один пункт не подошёл, выведите итоговый объект конфигурации в лог целиком (с маскированием ключа) и сравните с ожидаемым — почти всегда причина в порядке загрузки источников, а не в самой переменной.

Частые вопросы

Вопросы ниже собраны из тикетов поддержки CaptchaAI и обсуждений в чатах, где команды переносят конфигурацию в переменные окружения.

Как поменять concurrency в проде без рестарта?

Читайте CAPTCHAAI_CONCURRENCY на каждую партию задач, а не только при старте — тогда правка переменной обходится без остановки процесса.

Это работает и с CLI-флагом, и с любым сервисом конфигурации (Consul, etcd) поверх переменных окружения — процесс просто должен перечитывать значение, а не кэшировать его на старте навсегда.

Что делать, если API-ключ попал в лог?

Считайте его скомпрометированным, ротируйте через панель CaptchaAI и добавьте маскирование в форматтере логов.

Совет: заведите алерт на изменение значения CAPTCHAAI_API_KEY в хранилище секретов — незапланированная ротация обычно означает, что кто-то уже отреагировал на утечку без вас.

Нужен ли Vault для одного воркера?

Нет, хватит переменных окружения и .env. Vault и AWS Secrets Manager нужны при росте числа ключей и сервисов.

Порог здесь не про количество воркеров, а про то, сколько людей и систем трогают секреты: как только к ключу нужен доступ у трёх и более сервисов или регулярная ротация без ручных действий, Vault окупается.

Как часто менять API-ключ CaptchaAI?

Каждые 90 дней для комплаенса; при утечке — сразу.

Как передать API-ключ в CI/CD, не храня его в репозитории?

Принцип один и тот же в любой системе — секрет живёт в настройках CI, а не в коде:

  • GitHub Actions: Settings → Secrets and variables → Actions
  • GitLab CI/CD: Settings → CI/CD → Variables (с флагом Masked)
  • CircleCI: Project Settings → Environment Variables

Прокиньте секрет в job как переменную окружения — тот же принцип, что и в проде.

Следующие шаги

Комментарии для этой статьи отключены.