Pour mettre à jour un pool de workers de résolution de CAPTCHA sans jamais interrompre le trafic, faites tourner deux environnements identiques et basculez de l'un à l'autre en une seule opération. C'est tout le principe du déploiement bleu-vert : l'environnement « bleu » sert la production pendant que le « vert » reçoit la nouvelle version. Vous validez le vert avec quelques résolutions réelles, puis vous redirigez le trafic. Si la nouvelle version dérape, le retour arrière est immédiat — vous repointez le routeur vers le bleu, sans redéploiement ni fenêtre de maintenance.
Faut-il vraiment passer au bleu-vert ?
Le bleu-vert double le nombre d'environnements à opérer. Ce coût ne se justifie que dans certains cas ; ailleurs, une stratégie plus légère suffit.
| Votre situation | Bleu-vert utile ? | Alternative plus légère |
|---|---|---|
| Des workers résolvent des CAPTCHA en continu pour plusieurs équipes | Oui — le rollback doit être quasi instantané | – |
| Vous modifiez la logique de polling, de session ou de proxy | Oui — le risque de régression opérationnelle est élevé | – |
| Vous déployez rarement et tolérez une courte fenêtre de maintenance | Rarement nécessaire | Rolling update ou redémarrage piloté |
| Vous gérez peu de workers avec un trafic faible | Non | La complexité ajoutée apporte peu de valeur |
Côté facturation, une précision utile : CaptchaAI facture au thread concurrent, pas à l'environnement. Faire tourner un bleu et un vert ne double pas votre coût — l'environnement en veille ne consomme aucun thread tant qu'il ne résout rien. Un plan BASIC ($15/mois, 5 threads) suffit largement pour répéter une bascule avant de monter en charge.
Comment fonctionne l'architecture bleu-vert
Un routeur en façade dirige le trafic vers l'environnement actif. Les deux pools de workers pointent vers la même API CaptchaAI ; seule la version du code des workers change entre le bleu et le vert.
┌─────────────────────┐
[Scraper Clients] → │ Traffic Router │
└──────┬──────┬───────┘
│ │
Active│ │Standby
▼ ▼
┌───────┐ ┌───────┐
│ BLUE │ │ GREEN │
│Workers│ │Workers│
└───┬───┘ └───┬───┘
│ │
└────┬─────┘
▼
[CaptchaAI API]
En pratique, ce routeur est un reverse proxy NGINX ou un load balancer managé — par exemple chez OVHcloud ou Scaleway — placé devant les deux pools. Basculer revient à changer la cible amont ; rien ne touche à vos clés ni à l'API.
Mise en place
Python : le routeur bleu-vert
Ce routeur encapsule chaque environnement dans un CaptchaWorkerPool, expose les statistiques (taux d'erreur, résolutions) et bascule l'actif et la veille sous un verrou. La méthode canary_test rejoue quelques tâches sur la veille avant d'autoriser le changement.
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 : la bascule automatisée
Côté Node.js, la classe BlueGreenDeployment enchaîne le déploiement sur la veille, le canary, le basculement du trafic, puis une fenêtre de surveillance qui déclenche le rollback si le taux d'erreur dépasse le seuil.
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));
Le workflow de bascule, étape par étape
Chaque étape porte son propre déclencheur de rollback : si l'une échoue, vous revenez à l'état précédent sans dérouler la suite.
| Étape | Action | Déclencheur de rollback |
|---|---|---|
| 1 | Déployer le nouveau code sur l'environnement en veille | Échec du build |
| 2 | Lancer les tests canary sur la veille | Taux de réussite < 80 % |
| 3 | Basculer le trafic vers la nouvelle version | – |
| 4 | Surveiller le taux d'erreur (5 min) | Taux d'erreur > 20 % |
| 5 | Décommissionner l'ancien environnement | – |
Dépannage
| Problème | Cause | Correctif |
|---|---|---|
| Le canary passe mais la production échoue | Tâches de test trop simples | Rejouez des tâches réelles issues de la file de production |
| Rollbacks trop fréquents | Seuils de surveillance trop agressifs | Relevez le seuil d'erreur et allongez la période d'observation |
| Le trafic n'est pas basculé proprement | Requêtes encore en cours sur l'ancien environnement | Laissez les tâches en vol se vider avant de décommissionner |
| Les deux environnements tombent en panne | Défaillance d'une dépendance partagée (réseau, API) | Ajoutez un disjoncteur ; ne faites pas de rollback pour un incident d'infrastructure |
FAQ
Le déploiement bleu-vert double-t-il ma facture CaptchaAI ?
Non. CaptchaAI facture au thread concurrent, pas par environnement : l'environnement en veille ne consomme aucun thread tant qu'il ne résout rien. Vous ne payez que la capacité réellement utilisée pendant la bascule, quel que soit le nombre d'environnements en place.
Comment vider les tâches en cours avant de basculer ?
Cessez d'envoyer de nouvelles tâches à l'ancien environnement, puis attendez que les résolutions déjà lancées se terminent — le polling peut durer jusqu'à quelques minutes. Ne décommissionnez qu'une fois la file en vol vidée, sinon vous perdez des résolutions déjà lancées.
Peut-on faire du bleu-vert sur un seul serveur ?
Oui. Lancez le bleu et le vert comme deux processus ou deux conteneurs sur le même hôte — par exemple une instance chez OVHcloud ou Scaleway — et placez un reverse proxy NGINX devant pour rediriger le trafic d'un port à l'autre.
Bleu-vert ou canary : que choisir pour un pool de workers CAPTCHA ?
Le bleu-vert bascule 100 % du trafic d'un coup et se restaure en une seule opération ; le canary monte le trafic par paliers (1 %, 10 %, 50 %, 100 %). Pour un pool de workers, le bleu-vert est plus simple à opérer ; le canary limite le rayon d'impact sur les très gros volumes.
Prochaines étapes
Déployez en toute confiance : récupérez votre clé API CaptchaAI et mettez en place des mises à jour sans coupure pour vos workers de résolution.
Guides associés :
- L'architecture multi-régions
- Le basculement haute disponibilité
- La résolution conteneurisée avec Docker