Kısa yanıt: bir CAPTCHA çözme worker'ını üretime güvenle almak için ona üç HTTP uç noktası ekleyin — canlılık (/health/live), hazırlık (/health/ready) ve bağımlılık (/health/dependencies). Bu üçlü, Kubernetes'in veya yük dengeleyicinin donmuş, bakiyesi tükenmiş ya da bir döngüde takılmış bir worker'ı otomatik fark edip trafikten çıkarmasını sağlar. Aksi hâlde orkestratörünüz, on dakikadır tek görev çözemeyen "ölü" bir worker'a iş göndermeye devam eder — süreç hâlâ ayakta göründüğü için hiçbir alarm çalmaz.
Fark burada gizlidir: sürecin çalışıyor olması, iş yapabildiği anlamına gelmez. Bir worker'ın PID'si canlıdır ama API anahtarının bakiyesi sıfıra inmiştir; başka bir worker açılış aşamasındadır ama henüz tek görev çözmemiştir. Bu iki durumu ayırt edecek olan, aşağıdaki üç kontrol türüdür.
Canlılık, hazırlık ve bağımlılık: üç kontrol türü
| Kontrol | Yanıtladığı soru | Başarısızlıkta ne yapılır |
|---|---|---|
| Canlılık (liveness) | Süreç çalışıyor mu? | Kapsayıcıyı yeniden başlatın |
| Hazırlık (readiness) | İş kabul edebilir mi? | Trafiği yönlendirmeyi durdurun |
| Bağımlılık (dependency) | Yukarı akış servisleri sağlıklı mı? | Kontrollü biçimde işlevi azaltın |
Canlılık (liveness): süreç ayakta mı?
Canlılık kontrolü hiçbir dış çağrı yapmaz; yalnızca HTTP sunucusunun yanıt verdiğini kanıtlar. Başarısız olursa Kubernetes kapsayıcıyı yeniden başlatır. Bu yüzden içine bakiye ya da API sorgusu koymayın — yavaş bir dış çağrı, aslında sağlıklı olan bir worker'ı gereksiz yere yeniden başlatılmaya iter.
Hazırlık (readiness): iş alabilir mi?
Hazırlık, worker'ın o an görev kabul edip edemeyeceğini söyler. Bakiye düşükse, art arda hatalar birikmişse ya da worker henüz ısınıyorsa 503 döner ve Service'in arkasından çekilir — ama süreç yeniden başlatılmaz. Geçici bir durumu kalıcı bir arıza gibi ele almamanın yolu budur.
Bağımlılık (dependency): yukarı akış sağlıklı mı?
Bağımlılık kontrolü CaptchaAI API'sine erişilebilirliği ölçer. Amaç worker'ı öldürmek değil; sorunun worker'ın kendisinde mi yoksa yukarı akışta mı olduğunu panoda ayırt edebilmektir. Yukarı akış bozulduğunda tüm filoyu birden yeniden başlatmak sorunu çözmez, yalnızca büyütür.
Örnek bir senaryo: İstanbul'daki bir e-ticaret ekibi, kampanya döneminde ödeme akışlarını test eden bir worker filosu çalıştırıyor. CaptchaAI thread tabanlı faturalandırdığı için (BASIC planı $15/ay, 5 thread) plan bakiyesi düştüğünde çözümler durabilir. Böyle bir anda hazırlık kontrolünün 503 dönmesi, Kubernetes'in o worker'a yeni görev yollamasını keser; canlılık kontrolü ise donan süreci yeniden başlatır. İki sinyali karıştırmayın: bakiye düşüklüğü worker'ı hazır değil yapar ama yeniden başlatmayı gerektirmez.
Python: Flask ile durum kontrolü uç noktaları
Aşağıdaki Flask uygulaması, worker'ın çözüm sayacını thread güvenli biçimde tutar ve üç uç noktayı yayınlar. Bakiye sorgusu 60 saniyelik bir önbellekle sınırlıdır; böylece her istek CaptchaAI API'sine gitmez.
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()
Node.js: Express ile durum kontrolü uç noktaları
Aynı mantığın Node.js karşılığı. Canlılık uç noktası hiçbir dış çağrı yapmaz; hazırlık ve bağımlılık kontrolleri bakiyeyi yine önbellekten okur.
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"));
Kubernetes probe yapılandırması
livenessProbe donmuş kapsayıcıyı yeniden başlatır, readinessProbe ise worker geçici olarak iş alamadığında onu Service'in arkasından çeker. İki probe'un failureThreshold ve periodSeconds değerlerini ayrı ayrı ayarlayın — canlılığı hazırlıktan daha toleranslı tutmak, gereksiz yeniden başlatmaları önler.
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
Yapılandırmada dikkat edilecek birkaç pratik nokta:
initialDelaySecondsdeğerini worker'ın gerçek açılış süresine göre seçin; çok kısa tutmak, daha ilk çözümüne ulaşmadan açılıştaki worker'ları yeniden başlatmaya iter.livenessProbe'unfailureThresholddeğerinireadinessProbe'unkinden yüksek tutun — geçici bir yavaşlama yeniden başlatmayı değil, yalnızca trafikten çıkarmayı tetiklemeli.- Hazırlık probe'unu daha sık çalıştırın; böylece bakiyesi tükenen bir worker filodan hızla çekilir ve boşa görev harcanmaz.
Uç noktalara göre HTTP yanıt kodları
| Uç nokta | 200 | 503 |
|---|---|---|
/health/live |
Süreç yanıt veriyor | Süreç donmuş — yeniden başlat |
/health/ready |
İş kabul edebilir | Görev göndermeyi durdur |
/health/dependencies |
Tüm bağımlılıklar tamam | Yukarı akış bozulmuş |
Operatör için eşik ayarları
- Hazırlığı yeni işleri durdurmak, canlılığı yeniden başlatmayı tetiklemek, uyarıları ise düşen verimi yakalamak için kullanın — üçünü aynı sinyale bağlamayın.
- Durum kontrollerini yalnızca sürecin çalışma süresine değil; kuyruk derinliğine, son hata oranına ve bağımlılıkların erişilebilirliğine dayandırın.
- Eşik değerlerini nöbetçi ekibin görebileceği bir yerde tutun ki durum değişiklikleri gürültü değil, eyleme dönüştürülebilir bir sinyal olsun.
Sorun giderme
| Sorun | Sebep | Düzeltme |
|---|---|---|
| Worker sürekli yeniden başlatılıyor | Canlılık eşiği fazla düşük | failureThreshold veya periodSeconds değerini artırın |
| Worker açılışta "not-ready" işaretleniyor | Henüz çözüm yokken "çok uzun süre" sayılıyor | seconds_since_last_solve'yi yalnızca ilk çözümden sonra kontrol edin |
| Bakiye kontrolü uç noktayı yavaşlatıyor | Her istekte API çağrısı yapılıyor | Bakiyeyi bir TTL ile önbelleğe alın (60 saniye önerilir) |
| Durum uç noktasının kendisi çöküyor | Kontrolde yakalanmayan istisna | Her kontrolü try/except ile sarın; 500 yerine "degraded" dönün |
| Bağımlılık kontrolünden hatalı negatifler | Bakiye sorgusu sırasında geçici ağ kesintisi | Önbellekteki değeri stale-while-revalidate yaklaşımıyla kullanın |
Sık sorulan sorular
Canlılık ile hazırlık probe'u arasındaki fark nedir?
Canlılık yalnızca "süreç yanıt veriyor mu?" sorusuna bakar ve başarısız olursa kapsayıcıyı yeniden başlatır. Hazırlık ise "bu worker şu an iş alabilir mi?" sorusuna bakar; bakiye düşükse veya art arda hatalar varsa 503 döner ama süreci yeniden başlatmaz — sadece trafik dışına alır.
Durum kontrolü CaptchaAI thread'lerimi tüketir mi?
Hayır. getbalance sorgusu bir çözüm görevi değildir, dolayısıyla aktif thread saymaz ve plan bakiyenizi harcamaz. Yine de sorguyu 60 saniyelik bir önbellekle sınırlayın; her hazırlık isteğinde API'ye gitmek gereksiz gecikme yaratır.
Worker açılışta neden "not-ready" görünüyor?
Çünkü henüz tek bir çözüm yapmadığı için seconds_since_last_solve yüksek çıkar. total_solved > 0 koşulunu ilk çözümden sonra devreye sokun; böylece worker ısınma süresinde boş yere trafikten çıkarılmaz.
Bir worker filosunu tek yerden nasıl izlerim?
Durum uç noktalarının yanına Prometheus biçiminde bir /metrics uç noktası ekleyin ve Grafana panelinde birleştirin. Böylece tüm filonun başarı oranını, bakiyesini ve son çözüm zamanını tek ekranda görürsünüz.
İlgili Makaleler
Sonraki Adımlar
CAPTCHA worker'larınızı üretime hazır hale getirin — CaptchaAI API anahtarınızı alın ve durum kontrolü uç noktalarını ekleyin.
İlgili kılavuzlar: