La mise à niveau de votre pipeline de résolution de CAPTCHA ne devrait pas entraîner de temps d'arrêt. Les déploiements bleu-vert vous permettent d'exécuter deux environnements identiques : basculez le trafic vers la nouvelle version, vérifiez qu'elle fonctionne et annulez instantanément en cas de panne.
Quand l'approche bleu-vert est-elle justifiée ?
| Situation | Bleu-vert pertinent | Option plus simple |
|---|---|---|
| Les workers résolvent des tâches en continu pour plusieurs équipes | Oui, parce que le rollback doit être quasi immédiat | – |
| Vous changez la logique de polling, de session ou de proxy | Oui, le risque de régression opérationnelle est plus élevé | – |
| Vous déployez rarement et tolérez une courte maintenance | – | Rolling update ou redémarrage piloté |
| Vous gérez peu de workers et un trafic faible | – | La complexité supplémentaire apporte peu de valeur |
Architecture bleu-vert
┌─────────────────────┐
[Scraper Clients] → │ Traffic Router │
└──────┬──────┬───────┘
│ │
Active│ │Standby
▼ ▼
┌───────┐ ┌───────┐
│ BLUE │ │ GREEN │
│Workers│ │Workers│
└───┬───┘ └───┬───┘
│ │
└────┬─────┘
▼
[CaptchaAI API]
Mise en œuvre
Python – Routeur bleu-vert
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 – Sélecteur bleu-vert automatisé
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));
Flux de travail de déploiement
| Étape | Action | Déclencheur de restauration |
|---|---|---|
| 1 | Déployer le nouveau code en veille | Échec de la construction |
| 2 | Exécuter des tests Canary en veille | Taux de réussite < 80% |
| 3 | Basculer le trafic vers la nouvelle version | - |
| 4 | Taux d'erreur du moniteur (5 min) | Taux d'erreur > 20 % |
| 5 | Mettre hors service l'ancien environnement | - |
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| Canary réussit mais la production échoue | Tâches de test trop simples | Utilisez des tâches réalistes de la file d'attente de production |
| Annulations fréquentes | Seuils de surveillance agressifs | Augmenter le seuil d'erreur ; période de trempage plus longue |
| La répartition du trafic n'est pas propre pendant le changement | Demandes en cours sur l'ancien environnement | Attendez que les tâches en vol s'épuisent avant de mettre hors service |
| Les deux environnements deviennent malsains | Échec des dépendances partagées (réseau, API) | Disjoncteur ; ne pas revenir en arrière pour des problèmes d'infrastructure |
FAQ
Combien de temps les tests Canary doivent-ils durer ?
Exécutez au moins 10 résolutions réelles de CAPTCHA sur l'environnement de veille. Pour les systèmes critiques, faites passer un pourcentage du trafic de production (5 à 10 %) via le mode veille pendant 10 minutes avant le basculement complet.
Puis-je faire du bleu-vert avec un seul serveur ?
Oui : exécutez le bleu et le vert en tant que processus ou conteneurs distincts sur le même hôte. Utilisez un proxy inverse (NGINX) pour basculer le trafic entre les ports.
Quelle est la différence entre le déploiement bleu-vert et canari ?
Le bleu-vert commute 100 % du trafic en même temps. Canary augmente progressivement le trafic vers la nouvelle version (1 % – 10 % – 50 % – 100 %). Le bleu-vert est plus simple ; Canary est plus sûr pour les systèmes à grande échelle.
Prochaines étapes
Déployez en toute confiance :récupérez votre clé API CaptchaAIet mettre en place des déploiements sans temps d'arrêt.
Guides associés :
- Architecture multi-régions
- Basculement haute disponibilité
- Résolution conteneurisée Docker