El despliegue azul-verde consiste en mantener dos entornos idénticos de workers de resolución —uno activo y otro en espera— y conmutar todo el tráfico de uno a otro en segundos. Para una flota que resuelve CAPTCHA de forma continua, esa es la diferencia entre subir la versión de tus workers sin que un solo scraper note el cambio y arriesgarte a un corte a mitad de una cola de tareas. Si la nueva versión falla, vuelves al entorno anterior de inmediato, sin reconstruir nada.
Cuándo conviene aplicarlo a tu resolución de CAPTCHA
Piensa en una agencia de datos en Madrid o Ciudad de México que ejecuta scrapers de monitoreo de precios las 24 horas y factura su infraestructura en USD. Cada minuto de inactividad detiene la recolección y deja tareas a medias. Cuando toca subir una nueva versión del worker no puedes permitirte parar la cola. El patrón azul-verde te deja probar esa versión con tráfico real en el entorno en espera y promoverla solo cuando supera las pruebas canary.
Son justo los cambios que más miedo dan de desplegar en caliente:
- Un ajuste en la lógica de sondeo o en los intervalos de
res.php. - Una actualización de dependencias del worker.
- Un cambio en el manejo de errores o en los reintentos.
- Un nuevo reparto de threads entre colas.
A diferencia de un despliegue directo, aquí el rollback no implica volver a desplegar: el entorno anterior sigue vivo y basta con reapuntar el router. Eso encaja bien con un servicio como CaptchaAI, donde ambos entornos comparten la misma API key y el mismo grupo de threads, así que duplicar el entorno no significa duplicar tu plan.
Si tus scrapers recolectan datos de terceros, respeta siempre los términos de servicio del sitio y la normativa de protección de datos aplicable (GDPR y LOPDGDD en la UE, LFPDPPP en México). El patrón azul-verde es una técnica de despliegue, no un permiso para saltarte esas reglas.
Arquitectura azul-verde
┌─────────────────────┐
[Scraper Clients] → │ Traffic Router │
└──────┬──────┬───────┘
│ │
Active│ │Standby
▼ ▼
┌───────┐ ┌───────┐
│ BLUE │ │ GREEN │
│Workers│ │Workers│
└───┬───┘ └───┬───┘
│ │
└────┬─────┘
▼
[CaptchaAI API]
El router es la única pieza que decide a qué entorno va cada tarea. Sus responsabilidades son mínimas y deliberadas:
- Mantiene el puntero al entorno activo y al de espera.
- Envía cada solicitud nueva al activo sin bloquear.
- Cambia el puntero de forma atómica cuando ordenas el corte o el rollback.
Qué validar antes, durante y después del corte
Antes de tocar el router, confirma que azul y verde son realmente idénticos salvo por la versión que estás probando. Esta lista mínima evita los fallos más habituales de un corte de tráfico:
| Fase | Qué validar | Señal para hacer rollback |
|---|---|---|
| Antes del corte | Misma configuración, proxies, secretos y límites de threads en azul y verde | Diferencias entre variables de entorno o rutas |
| Canary 5-10 % | Latencia, CAPCHA_NOT_READY, tasa de error y consumo por worker |
Tasa de error por encima de la línea base + 2 % |
| Corte total | El entorno verde sostiene toda la carga al menos un ciclo completo de tráfico | Picos de timeout, saldo inconsistente o errores por sesión |
| Post-corte | Azul queda listo para el rollback con las colas drenadas y las métricas en vivo | No poder volver atrás en menos de 5 minutos |
Cómo implementarlo en código
Los dos ejemplos siguientes implementan el mismo ciclo, uno en cada lenguaje:
- Enrutar cada tarea al entorno activo.
- Enviar el CAPTCHA a
in.phpy sondear el resultado enres.php. - Correr las pruebas canary sobre el entorno en espera.
- Conmutar el tráfico y vigilar la tasa de error tras el corte.
El primer bloque define el enrutador y las piscinas de workers en Python; el segundo automatiza el corte con pruebas canary y monitoreo posterior en JavaScript.
Python: router azul-verde con pruebas canary
import os
import time
import threading
import requests
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
class CaptchaWorkerPool:
"""Represents one environment (blue or green)."""
def __init__(self, name, config):
self.name = name
self.config = config
self.session = requests.Session()
self.tasks_solved = 0
self.errors = 0
self.healthy = True
def solve(self, task):
resp = self.session.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": task.get("method", "userrecaptcha"),
"googlekey": task["sitekey"],
"pageurl": task["pageurl"],
"json": 1
})
data = resp.json()
if data.get("status") != 1:
self.errors += 1
return {"error": data.get("request")}
captcha_id = data["request"]
for _ in range(60):
time.sleep(5)
result = self.session.get(
"https://ocr.captchaai.com/res.php",
params={
"key": API_KEY,
"action": "get",
"id": captcha_id,
"json": 1
}
).json()
if result.get("status") == 1:
self.tasks_solved += 1
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
self.errors += 1
return {"error": result.get("request")}
self.errors += 1
return {"error": "TIMEOUT"}
@property
def error_rate(self):
total = self.tasks_solved + self.errors
return self.errors / total if total > 0 else 0.0
@property
def stats(self):
return {
"name": self.name,
"solved": self.tasks_solved,
"errors": self.errors,
"error_rate": round(self.error_rate, 4),
"healthy": self.healthy
}
class BlueGreenRouter:
def __init__(self, blue_config, green_config):
self.blue = CaptchaWorkerPool("blue", blue_config)
self.green = CaptchaWorkerPool("green", green_config)
self.active = self.blue
self.standby = self.green
self.lock = threading.Lock()
def solve(self, task):
"""Route task to the active environment."""
with self.lock:
pool = self.active
return pool.solve(task)
def switch(self):
"""Swap active and standby environments."""
with self.lock:
self.active, self.standby = self.standby, self.active
print(f"Switched: {self.active.name} is now ACTIVE")
return self.active.name
def rollback(self):
"""Switch back to the previous environment."""
return self.switch()
def canary_test(self, test_tasks, threshold=0.9):
"""Run test tasks on standby before switching."""
successes = 0
for task in test_tasks:
result = self.standby.solve(task)
if "solution" in result:
successes += 1
success_rate = successes / len(test_tasks) if test_tasks else 0
passed = success_rate >= threshold
print(
f"Canary test: {successes}/{len(test_tasks)} "
f"({success_rate:.0%}) — {'PASS' if passed else 'FAIL'}"
)
return passed
@property
def status(self):
return {
"active": self.active.stats,
"standby": self.standby.stats
}
# Usage
router = BlueGreenRouter(
blue_config={"version": "1.2.0", "workers": 4},
green_config={"version": "1.3.0", "workers": 4}
)
# Canary test before switching
test_tasks = [
{"sitekey": "6Le-wvkS...", "pageurl": "https://example.com/test"}
]
if router.canary_test(test_tasks, threshold=0.8):
router.switch()
print(f"Now active: {router.status['active']['name']}")
else:
print("Canary failed — staying on current environment")
JavaScript: conmutador con monitoreo posterior al corte
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
class BlueGreenDeployment {
constructor() {
this.environments = {
blue: { name: "blue", version: null, solved: 0, errors: 0 },
green: { name: "green", version: null, solved: 0, errors: 0 },
};
this.activeEnv = "blue";
}
get active() {
return this.environments[this.activeEnv];
}
get standby() {
return this.environments[this.activeEnv === "blue" ? "green" : "blue"];
}
async deploy(version, config = {}) {
const target = this.standby;
target.version = version;
target.solved = 0;
target.errors = 0;
console.log(`Deployed v${version} to ${target.name} (standby)`);
// Run canary checks
const canaryPassed = await this.canaryCheck(config.canaryTasks || []);
if (!canaryPassed && config.canaryTasks?.length > 0) {
console.log("Canary check failed — aborting deployment");
return { success: false, reason: "canary_failed" };
}
// Switch traffic
this.activeEnv = target.name;
console.log(`Switched traffic to ${target.name} (v${version})`);
// Monitor for rollback
if (config.monitorDuration) {
const stable = await this.monitorAfterSwitch(config.monitorDuration);
if (!stable) {
this.rollback();
return { success: false, reason: "post_deploy_errors" };
}
}
return { success: true, active: this.activeEnv };
}
async canaryCheck(tasks) {
if (tasks.length === 0) return true;
let successes = 0;
for (const task of tasks) {
try {
await this.solveCaptcha(task);
successes++;
} catch (err) {
console.log(`Canary task failed: ${err.message}`);
}
}
const rate = successes / tasks.length;
console.log(`Canary: ${successes}/${tasks.length} (${(rate * 100).toFixed(0)}%)`);
return rate >= 0.8;
}
async monitorAfterSwitch(durationMs) {
const start = Date.now();
const checkInterval = 10000;
while (Date.now() - start < durationMs) {
await new Promise((r) => setTimeout(r, checkInterval));
const errorRate = this.active.errors /
Math.max(1, this.active.solved + this.active.errors);
if (errorRate > 0.2) {
console.log(`Error rate ${(errorRate * 100).toFixed(1)}% — triggering rollback`);
return false;
}
}
return true;
}
rollback() {
const previous = this.activeEnv === "blue" ? "green" : "blue";
console.log(`Rolling back: ${this.activeEnv} → ${previous}`);
this.activeEnv = previous === "blue" ? "blue" : "green";
}
async solveCaptcha(task) {
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: task.sitekey,
pageurl: task.pageurl,
json: 1,
},
});
if (submitResp.data.status !== 1) {
this.active.errors++;
throw new Error(submitResp.data.request);
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (pollResp.data.status === 1) {
this.active.solved++;
return pollResp.data.request;
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
this.active.errors++;
throw new Error(pollResp.data.request);
}
}
this.active.errors++;
throw new Error("TIMEOUT");
}
}
// Deploy new version with canary and monitoring
const deployer = new BlueGreenDeployment();
deployer
.deploy("1.3.0", {
canaryTasks: [
{ sitekey: "6Le-wvkS...", pageurl: "https://example.com/test" },
],
monitorDuration: 60000, // Monitor for 1 minute after switch
})
.then((result) => console.log("Deploy result:", result));
Flujo de trabajo del despliegue
Resumido en cinco pasos, un corte azul-verde para tus workers de resolución se ve así:
| Paso | Acción | Disparador de rollback |
|---|---|---|
| 1 | Despliega el nuevo código en el entorno en espera | Fallo de compilación |
| 2 | Ejecuta pruebas canary en el entorno en espera | Tasa de éxito < 80 % |
| 3 | Conmuta el tráfico a la nueva versión | — |
| 4 | Vigila la tasa de error durante 5 minutos | Tasa de error > 20 % |
| 5 | Desmantela el entorno anterior | — |
Solución de problemas
| Problema | Causa | Solución |
|---|---|---|
| El canary pasa pero producción falla | Tareas de prueba demasiado simples | Usa tareas reales tomadas de la cola de producción |
| Rollbacks demasiado frecuentes | Umbrales de monitoreo agresivos | Sube el umbral de error y alarga el período de estabilización |
| El tráfico no se divide limpio durante el cambio | Solicitudes en vuelo sobre el entorno anterior | Espera a que se drenen las tareas en vuelo antes de desmantelar |
| Ambos entornos quedan degradados | Fallo en una dependencia compartida (red, API) | Aplica un disyuntor; no hagas rollback por problemas de infraestructura |
Preguntas frecuentes
¿Cómo evito perder las tareas en vuelo durante el cambio?
Deja que el entorno anterior termine las solicitudes que ya están en curso antes de desmantelarlo. Conmuta solo las tareas nuevas al entorno verde y espera a que la cola del azul se drene; así ningún CAPTCHA a medio resolver se pierde en el corte.
¿Necesito duplicar mi plan de CaptchaAI para tener dos entornos?
No. CaptchaAI factura por thread concurrente, no por resolución, y ambos entornos pueden compartir la misma API key. Solo necesitas threads suficientes para la carga combinada durante los segundos en que azul y verde coinciden; el plan BASIC ($15/mes, 5 threads) cubre pruebas pequeñas y escalas subiendo de tier cuando crece el volumen.
¿Cuándo conviene azul-verde y cuándo un despliegue canary?
Azul-verde conmuta el 100 % del tráfico de una vez y es más sencillo de operar. Un despliegue canary sube el tráfico de forma gradual (1 % → 10 % → 50 % → 100 %) y es más seguro en sistemas muy grandes. Puedes combinarlos: usa una fase canary como validación previa al corte azul-verde.
¿Qué señal debe disparar un rollback automático?
La más fiable es la tasa de error del entorno activo justo después del corte: si supera un umbral durante una ventana de observación (por ejemplo, más del 20 % durante 5 minutos), reapunta el router al entorno anterior. Combínala con la latencia y la aparición de CAPCHA_NOT_READY para no reaccionar ante un pico puntual.
Próximos pasos
Consigue tu API key en captchaai.com y prueba un corte azul-verde en un entorno de espera antes de llevarlo a producción: despliega la nueva versión, valídala con pruebas canary y promuévela sin cortar la cola de resolución.
Guías relacionadas: