Pour traiter des CAPTCHA à grande échelle sans surveiller chaque processus, faites tourner un pool de worker pods qui consomment une file Redis et laissez Kubernetes ajouter des pods quand la file s'allonge. Ce guide déploie ce schéma de bout en bout, avec une mise à l'échelle pilotée par la profondeur de la file via l'API CaptchaAI. Il tient à l'identique sur OVHcloud Managed Kubernetes, Scaleway Kapsule ou un EKS en eu-west-3 (Paris).
Un seul worker suffit tant que le volume reste modeste, mais dès quelques centaines de résolutions par minute il devient le goulot d'étranglement : la file gonfle, les délais s'allongent, et un plantage interrompt tout le traitement. Répartir la charge sur plusieurs pods pilotés par Kubernetes règle les trois problèmes d'un coup — capacité, latence et résilience.
Architecture de la file d'attente
Producer → Redis Queue → Worker Pods (auto-scaled) → CaptchaAI API
↓
Results Store (Redis)
Le producteur pousse les tâches dans Redis, les workers les dépilent et réécrivent le résultat dans un magasin partagé. La file découple producteur et workers : vous ajoutez, redémarrez ou supprimez des pods sans jamais toucher au code qui soumet les tâches. Chaque résultat étant indexé par identifiant, un worker qui redémarre en cours de traitement ne fait perdre qu'une seule tâche, pas tout le lot.
Déployer les worker pods
Démarrez avec trois réplicas et des ressources modestes : un worker passe l'essentiel de son temps à attendre l'API et consomme peu de CPU.
# k8s/worker-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: captcha-worker
labels:
app: captcha-worker
spec:
replicas: 3
selector:
matchLabels:
app: captcha-worker
template:
metadata:
labels:
app: captcha-worker
spec:
containers:
- name: worker
image: your-registry/captcha-worker:latest
env:
- name: CAPTCHAAI_KEY
valueFrom:
secretKeyRef:
name: captchaai-secret
key: api-key
- name: REDIS_URL
value: "redis://redis-service:6379"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
Créer le secret CaptchaAI
Stockez la clé API dans un Secret, jamais dans l'image ni dans un manifeste versionné :
kubectl create secret generic captchaai-secret \
--from-literal=api-key=YOUR_API_KEY
Déployer Redis comme file d'attente
Un seul pod Redis exposé par un Service interne suffit pour démarrer :
# k8s/redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "128Mi"
cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- port: 6379
Le code du worker
Chaque worker boucle sur un blpop bloquant, résout la tâche via CaptchaAI, écrit le résultat dans captcha:results et met à jour la longueur de la file.
# worker.py
import os
import json
import time
import redis
import requests
class CaptchaWorker:
"""Kubernetes worker that processes CAPTCHA tasks from Redis."""
def __init__(self):
self.api_key = os.environ["CAPTCHAAI_KEY"]
self.redis = redis.from_url(
os.environ.get("REDIS_URL", "redis://localhost:6379"),
)
self.base = "https://ocr.captchaai.com"
def run(self):
"""Main worker loop."""
hostname = os.environ.get("HOSTNAME", "unknown")
print(f"Worker {hostname} started")
while True:
result = self.redis.blpop("captcha:queue", timeout=30)
if result is None:
continue
_, raw = result
task = json.loads(raw)
task_id = task.get("id", "unknown")
print(f"[{hostname}] Processing {task_id}")
start = time.time()
try:
token = self._solve(task["method"], task["params"])
duration = time.time() - start
self.redis.hset("captcha:results", task_id, json.dumps({
"status": "success",
"token": token,
"duration": f"{duration:.1f}s",
"worker": hostname,
}))
print(f"[{hostname}] {task_id} solved in {duration:.1f}s")
except Exception as e:
self.redis.hset("captcha:results", task_id, json.dumps({
"status": "error",
"error": str(e),
"worker": hostname,
}))
print(f"[{hostname}] {task_id} failed: {e}")
# Update queue length metric
queue_len = self.redis.llen("captcha:queue")
self.redis.set("captcha:queue_length", queue_len)
def _solve(self, method, params, timeout=120):
resp = requests.post(f"{self.base}/in.php", data={
"key": self.api_key,
"method": method,
"json": 1,
**params,
}, timeout=30)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(result.get("request"))
captcha_id = result["request"]
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
resp = requests.get(f"{self.base}/res.php", params={
"key": self.api_key,
"action": "get",
"id": captcha_id,
"json": 1,
}, timeout=15)
data = resp.json()
if data["request"] != "CAPCHA_NOT_READY":
if data.get("status") == 1:
return data["request"]
raise RuntimeError(data["request"])
raise TimeoutError("Solve timeout")
if __name__ == "__main__":
CaptchaWorker().run()
Le même worker traite reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile ou GeeTest v3 : seul le champ method change. La boucle interne interroge res.php toutes les 5 secondes jusqu'à un plafond de 120 secondes ; ajustez ces valeurs selon le type de CAPTCHA, certains défis se résolvant plus vite que d'autres. Ne loggez ni le token ni de données personnelles — un réflexe RGPD utile en France comme en Belgique.
Mise à l'échelle automatique avec le HPA
Faites évoluer les workers selon la profondeur de la file plutôt que le CPU. Au-delà de dix tâches en attente par pod, le HorizontalPodAutoscaler ajoute des réplicas jusqu'à maxReplicas.
Si configurer un adaptateur de métriques externes vous rebute, KEDA lit directement la longueur d'une liste Redis comme déclencheur et gère même la mise à l'échelle jusqu'à zéro. Dans les deux cas, allongez la fenêtre de stabilisation (scaleDown) pour éviter que les pods ne montent et descendent à chaque pic bref.
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: captcha-worker-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: captcha-worker
minReplicas: 2
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: redis_queue_length
selector:
matchLabels:
queue: captcha
target:
type: AverageValue
averageValue: "10"
Le producteur de tâches
Le producteur pousse les tâches et récupère les résultats par identifiant, sans connaître les workers. La fonction get_results interroge le hash de résultats jusqu'à un délai limite ; en production, préférez un abonnement Redis Pub/Sub ou un webhook à ce polling serré.
import json
import uuid
import redis
def submit_tasks(redis_url, tasks):
"""Submit CAPTCHA tasks to the queue."""
r = redis.from_url(redis_url)
task_ids = []
for task in tasks:
task_id = str(uuid.uuid4())[:8]
task["id"] = task_id
r.rpush("captcha:queue", json.dumps(task))
task_ids.append(task_id)
return task_ids
def get_results(redis_url, task_ids, timeout=180):
"""Wait for and collect results."""
r = redis.from_url(redis_url)
results = {}
deadline = time.time() + timeout
while len(results) < len(task_ids) and time.time() < deadline:
for tid in task_ids:
if tid in results:
continue
raw = r.hget("captcha:results", tid)
if raw:
results[tid] = json.loads(raw)
time.sleep(1)
return results
Observabilité et maîtrise des coûts
Le worker publie captcha:queue_length à chaque itération : exposez cette valeur à Prometheus pour suivre la profondeur de la file, le taux de réussite et le temps de résolution médian par pod. Ces trois signaux suffisent à décider quand ajuster maxReplicas ou le seuil du HPA.
Côté facturation, gardez en tête que CaptchaAI facture au thread concurrent, pas au CAPTCHA résolu, avec des résolutions illimitées par thread. Au-delà d'un certain nombre de pods, c'est donc votre allocation de threads — et non Kubernetes — qui plafonne le débit réel : inutile de dépasser 20 workers si votre plan n'ouvre que 50 threads.
Dépannage
| Problème | Cause probable | Correctif |
|---|---|---|
| Les workers ne démarrent pas | Secret non créé | Lancez la commande kubectl create secret |
| Pods en CrashLoopBackOff | Variables d'environnement ou Redis manquants | Inspectez les logs avec kubectl logs |
| Le HPA ne monte pas en charge | Métriques externes non configurées | Installez un adaptateur de métriques (KEDA) |
| File qui grossit sans traitement | Workers inactifs ou plantés | Vérifiez l'état des pods et redémarrez |
FAQ
Combien de threads CaptchaAI faut-il pour alimenter mes workers ?
Alignez le nombre de résolutions simultanées sur votre allocation de threads, pas sur le nombre de pods. BASIC ($15/mois, 5 threads) convient aux tests ; ADVANCE ($90/mois, 50 threads) ou PREMIUM ($170/mois, 100 threads) soutiennent un débit continu, avec résolutions illimitées par thread.
Comment régler le seuil du HPA selon ma file ?
Partez d'averageValue: "10". Si la file reste au-dessus de la cible, abaissez la valeur ; si les pods oscillent, remontez-la et allongez la fenêtre de stabilisation.
Où déployer les workers pour limiter la latence en Europe ?
Rapprochez le cluster de votre orchestrateur — eu-west-3 (Paris) sur AWS, ou OVHcloud/Scaleway en France. La résolution reste toutefois dominée par le temps de traitement de l'API.
Que se passe-t-il si une résolution dépasse le timeout ?
Le worker lève une exception, écrit un statut error dans captcha:results et passe à la tâche suivante. Prévoyez côté producteur un retry avec backoff exponentiel.
CaptchaAI prend-il en charge tous les types poussés dans la file ?
Les types courants sont pris en charge : reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile et GeeTest v3. hCaptcha et FunCaptcha ne sont pas encore pris en charge — ne les routez pas vers la file en attendant.
Guides connexes
- Conteneuriser les workers avec Docker
- Distribuer le traitement via une file Redis
Passez à des milliers de résolutions – adoptez CaptchaAI pour Kubernetes.