Phần lớn sự cố CaptchaAI trên production không đến từ code mà từ cấu hình: khóa API hardcode trong source, timeout copy từ máy dev, secret lộ trong log. Cách xử lý: tách cấu hình ba lớp ưu tiên rõ ràng, đưa bí mật khỏi source control, để bật/tắt tính năng không cần deploy — gồm độ ưu tiên env var, loader Python/JavaScript, file theo môi trường, quản lý bí mật và feature flag.
Ví dụ thực tế: đội automation TP.HCM và 3 tầng môi trường
Một đội automation ở TP.HCM chạy pipeline theo dõi giá Shopee, Tiki cho nhiều khách hàng, dùng chung một worker CaptchaAI trên ba tầng dev/staging/production. Mặc định nằm trong config/captchaai.yaml, mỗi tầng ghi đè bằng file riêng, khóa API chỉ vào qua secrets manager của CI/CD — không commit vào git. Cần tăng tải đợt cao điểm, họ đổi concurrency trên production rồi reload, không build lại image.
Khi lưu dữ liệu thu thập được, nên ghi log có kiểm soát theo tinh thần Nghị định 13/2023/NĐ-CP về bảo vệ dữ liệu cá nhân — không phải tư vấn pháp lý, chỉ là lý do giữ secret tách biệt khỏi log nghiệp vụ.
Thứ tự ưu tiên: env var thắng file, file thắng mặc định
Priority (highest → lowest):
1. Environment variables ← deployment-specific overrides
2. Config file (YAML/JSON) ← version-controlled defaults
3. Application defaults ← fallback values in code
Biến môi trường ghi đè file, file ghi đè mặc định trong code. Có CLI flag thì đặt trên cùng — càng gần người vận hành lúc chạy lệnh, ưu tiên càng cao.
Toàn bộ tham số cấu hình
| Tham số | Biến Env | Mặc định | Mô tả |
|---|---|---|---|
| API key | CAPTCHAAI_API_KEY |
— | Bắt buộc |
| Submit URL | CAPTCHAAI_SUBMIT_URL |
https://ocr.captchaai.com/in.php |
Endpoint gửi task |
| Poll URL | CAPTCHAAI_POLL_URL |
https://ocr.captchaai.com/res.php |
Endpoint polling |
| Khoảng polling | CAPTCHAAI_POLL_INTERVAL |
5 |
Giây giữa các lần polling |
| Số lần polling tối đa | CAPTCHAAI_MAX_POLLS |
60 |
Trước khi timeout |
| Concurrency | CAPTCHAAI_CONCURRENCY |
10 |
Task song song tối đa |
| Timeout | CAPTCHAAI_TIMEOUT |
300 |
Giây |
| Proxy | CAPTCHAAI_PROXY |
— | URL proxy khi giải |
| Callback URL | CAPTCHAAI_CALLBACK_URL |
— | Webhook bất đồng bộ |
| Số lần thử lại | CAPTCHAAI_RETRIES |
3 |
Khi lỗi tạm thời |
| Log level | CAPTCHAAI_LOG_LEVEL |
info |
Mức chi tiết log |
Loader đọc cấu hình theo đúng thứ tự ưu tiên
Loader dưới đây tự áp dụng nguyên tắc trên: mặc định → file YAML nếu có → biến môi trường ghi đè cuối cùng.
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")
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`);
File cấu hình riêng cho từng môi trường
Giữ một file base an toàn, mỗi môi trường chỉ ghi đè phần khác biệt — như đội automation ở ví dụ trên đang làm:
# 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
Production tăng concurrency, siết timeout; staging giữ log_level: debug để soi lỗi trước khi lên production.
Quản lý bí mật: khóa API không nằm trong file cấu hình
Không lưu khóa API trong file cấu hình hay commit vào source control, kể cả file dùng nội bộ.
| Phương pháp | Phù hợp nhất | Ví dụ |
|---|---|---|
| Biến môi trường | Container, CI/CD | export CAPTCHAAI_API_KEY=abc123 |
| AWS Secrets Manager | Hạ tầng AWS | Tự xoay vòng |
| HashiCorp Vault | Đa cloud, on-prem | Secret động, TTL |
| Docker secrets | Docker Swarm / Compose | Mount /run/secrets/ |
File .env (chỉ dev) |
Local | dotenv, thêm .gitignore |
Ví dụ Docker Compose
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
${CAPTCHAAI_API_KEY} lấy từ host hoặc .env.production — khóa API không nằm trong image hay file compose đã commit.
Feature flag: bật/tắt tính năng không cần deploy lại
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)
Đổi biến môi trường và reload là chuyển được polling sang callback (use_callback) hoặc bật/tắt proxy, không cần build lại image.
Khắc phục sự cố cấu hình thường gặp
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| API key không nạp được | Thiếu env var hoặc sai tên biến | echo $CAPTCHAAI_API_KEY, đối chiếu bảng tham số |
| File cấu hình bị bỏ qua | Sai đường dẫn hoặc thiếu thư viện YAML | Cài pyyaml / js-yaml, kiểm tra đường dẫn |
| Production chạy nhầm cấu hình dev | Ghi đè riêng môi trường chưa áp dụng | Kiểm tra thứ tự ưu tiên, biến NODE_ENV / APP_ENV |
| Secret lộ trong log | Log dump cả cấu hình, gồm API key | Che trường nhạy cảm trước khi ghi log |
Câu hỏi thường gặp
CAPTCHAAI_TIMEOUT nên đặt bao nhiêu cho production?
Theo SLA giải của loại CAPTCHA dùng nhiều nhất, cộng biên độ retry. Giữ timeout production thấp hơn staging (180s so với 300s ở trên) để phát hiện task treo nhanh hơn.
Có thể đổi CAPTCHAAI_CONCURRENCY mà không khởi động lại service không?
Có, nếu loader đọc lại biến môi trường theo từng lô task thay vì chỉ lúc khởi động — cập nhật env var rồi gửi tín hiệu reload, như đội automation ở ví dụ trên vẫn làm.
Có nên dùng secrets manager cùng lúc với file YAML không?
Có: YAML giữ giá trị không nhạy cảm (concurrency, timeout, log_level), còn CAPTCHAAI_API_KEY luôn đến từ secrets manager ở lớp ưu tiên cao nhất, không trộn vào YAML dù file không bị commit.
Bước tiếp theo
Lấy API key CaptchaAI và dựng cấu hình từ các mẫu ở trên.
Liên quan: