Votre outil de résolution de CAPTCHA semble vivant (le processus est en cours d'exécution) mais il n'a pas réussi à résoudre une tâche en 10 minutes. La clé API est épuisée ou le travailleur est coincé dans une boucle. Sans vérifications de l’état, votre orchestrateur continue d’acheminer le travail vers un travailleur décédé. Les points de terminaison d’intégrité permettent aux équilibreurs de charge et à Kubernetes de détecter le problème et de le réacheminer.
Quel type de probe répond à quel problème ?
| Situation | Probe la plus utile | Décision attendue |
|---|---|---|
| Le processus ne répond plus du tout | Liveness | Redémarrer le conteneur ou le worker |
| Le worker tourne encore mais ne doit plus recevoir de trafic | Readiness | Le retirer du routage sans le tuer immédiatement |
| Une dépendance en amont est dégradée | Dependency check | Dégrader le service ou alerter avant d'échouer en cascade |
| Vous déployez sur Kubernetes ou derrière un LB | Les trois combinées | Chaque probe couvre un angle de panne différent |
Trois types de contrôles de santé
| Vérifier | Question | Réponse à l'échec |
|---|---|---|
| Vivacité | Le processus est-il en cours ? | Redémarrez le conteneur |
| Préparation | Peut-il accepter du travail ? | Arrêter le routage du trafic |
| Dépendance | Les services en amont sont-ils corrects ? | Dégrader gracieusement |
Python : points de terminaison d'intégrité du flacon
import requests
import time
import threading
from flask import Flask, jsonify
from dataclasses import dataclass, field
API_KEY = "YOUR_API_KEY"
RESULT_URL = "https://ocr.captchaai.com/res.php"
app = Flask(__name__)
@dataclass
class WorkerHealth:
"""Tracks worker health metrics."""
started_at: float = field(default_factory=time.monotonic)
last_solve_at: float = 0.0
total_solved: int = 0
total_failed: int = 0
consecutive_failures: int = 0
balance: float | None = None
balance_checked_at: float = 0.0
_lock: threading.Lock = field(default_factory=threading.Lock)
def record_success(self):
with self._lock:
self.total_solved += 1
self.last_solve_at = time.monotonic()
self.consecutive_failures = 0
def record_failure(self):
with self._lock:
self.total_failed += 1
self.consecutive_failures += 1
@property
def success_rate(self) -> float:
total = self.total_solved + self.total_failed
return self.total_solved / total if total > 0 else 1.0
@property
def seconds_since_last_solve(self) -> float:
if self.last_solve_at == 0:
return time.monotonic() - self.started_at
return time.monotonic() - self.last_solve_at
health = WorkerHealth()
# Thresholds
MAX_CONSECUTIVE_FAILURES = 10
MAX_SECONDS_WITHOUT_SOLVE = 600 # 10 minutes
MIN_BALANCE = 1.0
def check_balance() -> float | None:
"""Check CaptchaAI balance."""
now = time.monotonic()
# Cache balance for 60 seconds
if health.balance is not None and now - health.balance_checked_at < 60:
return health.balance
try:
resp = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "getbalance", "json": 1,
}, timeout=10).json()
health.balance = float(resp.get("request", 0))
health.balance_checked_at = now
return health.balance
except Exception:
return health.balance # Return cached value on error
@app.route("/health/live")
def liveness():
"""Liveness probe — is the process responsive?"""
return jsonify({"status": "ok", "uptime_s": int(time.monotonic() - health.started_at)}), 200
@app.route("/health/ready")
def readiness():
"""Readiness probe — can the worker accept tasks?"""
issues = []
# Check consecutive failures
if health.consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
issues.append(f"consecutive_failures={health.consecutive_failures}")
# Check time since last solve
if health.total_solved > 0 and health.seconds_since_last_solve > MAX_SECONDS_WITHOUT_SOLVE:
issues.append(f"no_solve_for={int(health.seconds_since_last_solve)}s")
# Check balance
balance = check_balance()
if balance is not None and balance < MIN_BALANCE:
issues.append(f"low_balance=${balance:.2f}")
if issues:
return jsonify({
"status": "not_ready",
"issues": issues,
"stats": {
"solved": health.total_solved,
"failed": health.total_failed,
"success_rate": round(health.success_rate, 3),
},
}), 503
return jsonify({
"status": "ready",
"stats": {
"solved": health.total_solved,
"failed": health.total_failed,
"success_rate": round(health.success_rate, 3),
"balance": balance,
},
}), 200
@app.route("/health/dependencies")
def dependencies():
"""Check upstream dependencies."""
checks = {}
# CaptchaAI API reachability
try:
resp = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "getbalance", "json": 1,
}, timeout=10)
checks["captchaai_api"] = {
"status": "ok" if resp.status_code == 200 else "degraded",
"response_ms": int(resp.elapsed.total_seconds() * 1000),
}
except Exception as e:
checks["captchaai_api"] = {"status": "down", "error": str(e)}
all_ok = all(c["status"] == "ok" for c in checks.values())
return jsonify({
"status": "ok" if all_ok else "degraded",
"checks": checks,
}), 200 if all_ok else 503
# --- Worker loop (runs in background) ---
def worker_loop():
"""Simulated CAPTCHA solving worker."""
while True:
try:
# ... solve CAPTCHA logic ...
health.record_success()
except Exception:
health.record_failure()
time.sleep(1)
threading.Thread(target=worker_loop, daemon=True).start()
JavaScript : Express Health Endpoints
const express = require("express");
const API_KEY = "YOUR_API_KEY";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const app = express();
const health = {
startedAt: Date.now(),
lastSolveAt: 0,
totalSolved: 0,
totalFailed: 0,
consecutiveFailures: 0,
balance: null,
balanceCheckedAt: 0,
recordSuccess() {
this.totalSolved++;
this.lastSolveAt = Date.now();
this.consecutiveFailures = 0;
},
recordFailure() {
this.totalFailed++;
this.consecutiveFailures++;
},
get successRate() {
const total = this.totalSolved + this.totalFailed;
return total > 0 ? this.totalSolved / total : 1;
},
};
async function checkBalance() {
if (health.balance !== null && Date.now() - health.balanceCheckedAt < 60000) {
return health.balance;
}
try {
const url = `${RESULT_URL}?key=${API_KEY}&action=getbalance&json=1`;
const resp = await (await fetch(url)).json();
health.balance = parseFloat(resp.request);
health.balanceCheckedAt = Date.now();
return health.balance;
} catch {
return health.balance;
}
}
app.get("/health/live", (req, res) => {
res.json({ status: "ok", uptimeMs: Date.now() - health.startedAt });
});
app.get("/health/ready", async (req, res) => {
const issues = [];
if (health.consecutiveFailures >= 10) {
issues.push(`consecutive_failures=${health.consecutiveFailures}`);
}
if (health.totalSolved > 0) {
const silentMs = Date.now() - health.lastSolveAt;
if (silentMs > 600_000) {
issues.push(`no_solve_for=${Math.round(silentMs / 1000)}s`);
}
}
const balance = await checkBalance();
if (balance !== null && balance < 1.0) {
issues.push(`low_balance=$${balance.toFixed(2)}`);
}
const stats = {
solved: health.totalSolved,
failed: health.totalFailed,
successRate: Math.round(health.successRate * 1000) / 1000,
balance,
};
if (issues.length > 0) {
return res.status(503).json({ status: "not_ready", issues, stats });
}
res.json({ status: "ready", stats });
});
app.get("/health/dependencies", async (req, res) => {
const checks = {};
try {
const start = Date.now();
const url = `${RESULT_URL}?key=${API_KEY}&action=getbalance&json=1`;
const resp = await fetch(url);
checks.captchaaiApi = {
status: resp.ok ? "ok" : "degraded",
responseMs: Date.now() - start,
};
} catch (e) {
checks.captchaaiApi = { status: "down", error: e.message };
}
const allOk = Object.values(checks).every((c) => c.status === "ok");
res.status(allOk ? 200 : 503).json({
status: allOk ? "ok" : "degraded",
checks,
});
});
app.listen(8080, () => console.log("Health server on :8080"));
Configuration Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: captcha-worker
spec:
replicas: 3
template:
spec:
containers:
- name: worker
image: captcha-worker:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
Codes de réponse du bilan de santé
| Point de terminaison | 200 | 503 |
|---|---|---|
/health/live |
Réactivité aux processus | Processus gelé – redémarrage |
/health/ready |
Peut accepter du travail | Arrêter d'envoyer des tâches |
/health/dependencies |
Toutes les dépendances sont OK | Amont dégradé |
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| Travailleur constamment redémarré | Seuil de vivacité trop bas | Augmentez failureThreshold ou periodSeconds |
| Travailleur marqué comme non prêt lors du démarrage | Aucune résolution n'est encore considérée comme "trop longue" | Vérifiez uniquement seconds_since_last_solve après la première résolution |
| La vérification de la balance ralentit le point final de santé | Appel API à chaque requête | Cachez le solde avec un TTL (60 s recommandé) |
| Le point de terminaison de santé lui-même plante | Exception non gérée lors du contrôle | Enveloppez chaque chèque dans try/except ; retour dégradé au lieu de 500 |
| Faux négatifs du contrôle de dépendance | Problème de réseau lors de la vérification du solde | Utiliser les valeurs mises en cache avec une approche obsolète pendant la revalidation |
FAQ
À quelle fréquence Kubernetes doit-il sonder les points de terminaison de santé ?
Vivacité : toutes les 10 à 30 secondes avec un seuil de défaillance de 3. Préparation : toutes les 5 à 10 secondes avec un seuil de défaillance de 2. Des sondes plus fréquentes détectent les problèmes plus rapidement mais ajoutent une surcharge.
Le point de terminaison de santé doit-il accéder à l’API CaptchaAI ?
Uniquement pour les vérifications de préparation et de dépendance, et mettez toujours en cache le résultat. La sonde d'activité ne doit jamais effectuer d'appels externes : elle doit répondre instantanément pour prouver que le processus est actif.
Comment puis-je surveiller la santé de plusieurs travailleurs ?
Exposez les métriques de santé au format Prometheus (/metrics) aux côtés des points de terminaison de santé. Regroupez tous les travailleurs avec les tableaux de bord Grafana pour connaître l’état de santé de l’ensemble de la flotte.
Articles connexes
Prochaines étapes
Préparez vos collaborateurs CAPTCHA pour la production -récupérez votre clé API CaptchaAIet ajoutez des points de terminaison de vérification de l’état.
Guides associés :
- Modèle de disjoncteur pour les appels d'API CAPTCHA
- Modèle de cloison pour la résolution de CAPTCHA
- Surveillance des taux de résolution de CAPTCHA avec Prometheus et Grafana