DevOps & Scaling

CAPTCHA Çözme Altyapısı için Mavi-Yeşil Dağıtım

Yeni bir solver sürümünü üretime almak, tek bir çözülemeyen CAPTCHA'ya bile mal olmamalı. Mavi-yeşil dağıtım tam olarak bunu güvence altına alır: iki özdeş çözüm ortamını yan yana çalıştırır, trafiği yalnızca yeni sürümü doğruladıktan sonra ona yönlendirir ve bir sorun çıkarsa saniyeler içinde eski ortama döner. Aşağıda mimariyi, çalışan Python ile Node.js yönlendiricilerini ve güvenli geçiş için izleme eşiklerini bulacaksınız.

Mavi-yeşil dağıtım CAPTCHA çözmede neden gerekir?

CAPTCHA çözme işlem hattı kesintisiz çalışır; veri kazıma işleri, otomasyon akışları ve zamanlanmış görevler ona bağımlıdır. Klasik bir "yerinde güncelleme"de yeni kod doğrudan üretim worker'larına iner; kütüphane sürümü, sorgulama mantığı ya da bir yapılandırma hatası ters giderse hata oranı anında yükselir. Mavi-yeşil bu riski yalıtır: yeni sürümü ayrı bir ortamda gerçek görevlerle sınar, yalnızca yeşil ışık alınca geçersiniz.

Türkiye'deki bir e-ticaret ekibini düşünün: kampanya dönemine girmeden ödeme adımındaki reCAPTCHA worker havuzunu yükseltmek istiyor. En yoğun haftada bir dağıtım hatası doğrudan başarısız QA testlerine dönüşür. Mavi-yeşil ile yeni sürümü bekleme ortamında doğrular, kampanya trafiğini durdurmadan geçirir, gerekirse anında geri alırsınız.

Bir ayrıntıyı atlamayın: iki ortam da aynı CaptchaAI hesabının thread havuzunu kullanır. Thread tabanlı, öngörülebilir USD fiyatlandırması, geçişteki zirve eş zamanlılığı TL dalgalanmasından bağımsız planlamanızı sağlar.

Mavi-yeşil mimari nasıl kurulur?

Temel yapı tek bir yönlendirici ve iki özdeş worker havuzundan oluşur. Yönlendirici trafiği yalnızca aktif ortama gönderir; bekleme ortamı yeni sürümü barındırıp geçişe hazır bekler. İki havuz da tek bir CaptchaAI API'siyle konuşur:

                    ┌─────────────────────┐
[Scraper Clients] → │   Traffic Router    │
                    └──────┬──────┬───────┘
                           │      │
                     Active│      │Standby
                           ▼      ▼
                    ┌───────┐  ┌───────┐
                    │ BLUE  │  │ GREEN │
                    │Workers│  │Workers│
                    └───┬───┘  └───┬───┘
                        │          │
                        └────┬─────┘
                             ▼
                    [CaptchaAI API]

Uygulama: yönlendirici ve otomatik geçiş

Her iki örnek aynı deseni farklı yığınlarda gösterir: worker havuzu görevi in.php'ye gönderir, sonucu res.php ile sorgular, çözüm ve hata sayısını takip eder. Yönlendirici bu sayılarla kanarya testi yapıp geçiş kararı verir.

Python yönlendirici: aktif ortama görev dağıtımı

CaptchaWorkerPool tek bir ortamı temsil eder; BlueGreenRouter aktif/bekleme değişimini, kanaryayı ve geri almayı yönetir. Eşik tutmazsa geçiş yapılmaz:

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 (Node.js): otomatik geçiş ve kanarya kontrolü

Aynı akışın olay tabanlı karşılığı: BlueGreenDeployment yeni sürümü bekleme ortamına dağıtır, kanaryayı çalıştırır, trafiği geçirir, hata oranını izler ve eşik aşılırsa rollback() devreye girer:

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));

Adım adım dağıtım iş akışı

Güvenli bir mavi-yeşil geçiş beş adıma iner; her adımın geri alma tetikleyicisi vardır, eşik aşıldığında ilerlemeyin:

Adım Eylem Geri alma tetikleyicisi
1 Bekleme ortamına yeni kodu dağıtın Derleme hatası
2 Bekleme ortamında kanarya testleri çalıştırın Başarı oranı < %80
3 Trafiği yeni sürüme geçirin
4 Hata oranını izleyin (5 dk) Hata oranı > %20
5 Eski ortamı hizmet dışı bırakın

Tablodaki eşikler tavsiye niteliğinde başlangıç değerleridir; kendi üretim verinize göre ayarlayın. Her geçişi ve geri almayı zaman damgasıyla loglayın — olay sonrası hangi sürümün ne zaman aktif olduğunu bilmek, kök neden analizini kısaltır. Islatma süresini (adım 4) trafiğinizin en az bir tam yük döngüsünü kapsayacak kadar uzun tutun; kısa bir pencere, yavaş biriken hataları gizler ve sizi erken bir "yeşil" kararına iter.

Trafik kesintisi runbook'u

  • Canlı trafiği yeni ortama vermeden önce mavi ve yeşil havuzları aynı sağlık kontrollerinden geçirin; ikisi de yeşil ışık vermeden geçiş başlamasın.
  • Trafiği tek seferde değil aşamalar hâlinde kaydırın; her aşamadan önce sabit bekleme süresine ek olarak hedef sitenin reddetme oranını kontrol edin.
  • Çözüm süresi, reddetme oranı veya kuyruk derinliği eşiği aşarsa beklemeden eski ortama geri dönün.

Sorun giderme

Sorun Sebep Düzeltme
Kanarya geçiyor ama üretim başarısız oluyor Test görevleri fazla basit Üretim kuyruğundaki gerçekçi görevleri kullanın
Sık sık geri alma yaşanıyor Fazla agresif izleme eşikleri Hata eşiğini yükseltin; ıslatma süresini uzatın
Geçiş sırasında trafik ayrımı temiz değil Eski ortamda uçuştaki istekler Hizmet dışı bırakmadan önce uçuştaki görevlerin bitmesini bekleyin
İki ortam da sağlıksızlaşıyor Paylaşılan bağımlılık hatası (ağ, API) Devre kesici kullanın; altyapı sorunlarında geri alma yapmayın

Sık sorulan sorular

Mavi-yeşil geçiş sırasında uçuştaki görevler ne oluyor?

Eski ortamda çözülmekte olan görevler orada tamamlanır. Yönlendirici yalnızca yeni gelen trafiği yeni sürüme yollar; eski ortamı, uçuştaki görevler bitene kadar hizmet dışı bırakmayın.

Mavi ve yeşil ortamlar için ne kadar thread gerekir?

İki ortam da aynı CaptchaAI thread havuzunu kullanır ve kanarya sırasında kısa süre eş zamanlı çalışır. Zirve eş zamanlılığınızı karşılayan bir plan seçin; örneğin STANDARD ($30/ay, 15 thread) orta ölçekli iş akışlarına rahat pay bırakır.

Geri alma gerçekten anında mı?

Evet. Eski ortam ayakta beklediği için geri alma yalnızca yönlendiricideki aktif/bekleme değişimidir; yeniden dağıtım gerekmez, saniyeler içinde tamamlanır. Tek maliyet, geçiş anında eski ortamda uçuşta olan görevlerdir; onlar da mevcut ortamda tamamlandığı için kaybolmaz.

Kanarya testi için kaç görev yeterli?

Bekleme ortamında en az 10 gerçek CAPTCHA çözümü çalıştırın. Kritik sistemlerde tam geçişten önce üretim trafiğinin %5–10'unu 10 dakika bekleme ortamından geçirin.

Tek sunucuda mavi-yeşil kurulabilir mi?

Evet. Mavi ve yeşili aynı ana bilgisayarda ayrı işlem veya konteyner olarak çalıştırın; portlar arasındaki trafiği ters proxy (NGINX) ile değiştirin.

Sonraki adımlar

Güvenle yayına alın: CaptchaAI API anahtarınızı alın ve sıfır kesintili dağıtımlarınızı kurun.

İlgili rehberler:

Bu makale için yorumlar devre dışı bırakılmıştır.