Tutoriels

Points de terminaison du bilan de santé pour les travailleurs résolvant des CAPTCHA

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 :

Les commentaires sont désactivés pour cet article.