DevOps & Scaling

Terraform + CaptchaAI: CAPTCHA Worker Altyapısını Kod Olarak Yönetin

CAPTCHA çözüm katmanını AWS panelinden elle kurduğunuzda asıl sorun ilk kurulum değil, üçüncü aydır: staging'de üç worker, production'da beş worker koşuyordur ama kimse eşzamanlılık ayarının plan thread sayısıyla hâlâ uyumlu olup olmadığını bilmez. Terraform bu belirsizliği ortadan kaldırır — worker sayısı, CPU/bellek, ölçeklendirme eşikleri ve eşzamanlılık tek bir depoda gözden geçirilebilir dosyalar hâlinde durur, her ortam aynı koddan doğar.

Aşağıda AWS ECS Fargate üzerinde çalışan bir CaptchaAI worker kümesini sıfırdan kuruyoruz: uzak state, Secrets Manager'da saklanan API anahtarı, görev tanımı, otomatik ölçeklendirme ve ortam bazlı .tfvars dosyaları. Kurulum kod olduğu için tekrarlanabilir, yıkım tek komutluk, maliyet hesabı da öngörülebilir kalır.

Başlamadan önce hazır olması gerekenler

  • Terraform 1.5 veya üstü ve aws configure ile tanımlanmış kimlik bilgileri.
  • CaptchaAI API anahtarı ve iş yükünüze uyan bir plan — hangi planın hangi eşzamanlılığa denk geldiğini bir sonraki bölümde hesaplıyoruz.
  • ECR'ye gönderilmiş bir worker container imajı.
  • State için bir S3 bucket, kilitleme için bir DynamoDB tablosu.

API'yi hiç kullanmadıysanız önce tek bir çözümü lokalde çalıştırın; altyapıyı ancak akışın uçtan uca çalıştığını gördükten sonra koda dökmek çok daha az zaman kaybettirir.

Kapasiteyi plana göre hesaplayın

Bu kurulumdaki en kritik bağ, Terraform değişkenleriyle CaptchaAI planınız arasındadır:

Toplam eşzamanlı görev = worker_count × captchaai_concurrency

Bu sayı planınızın thread sayısını aşmamalıdır. Production örneğimizde 5 × 20 = 100 eşzamanlı görev çıkar; bu da PREMIUM ($170/ay, 100 thread) planına denk gelir. Dev ortamında 1 × 3 = 3 görev, BASIC ($15/ay, 5 thread) ile rahatça karşılanır. Thread sayısını aştığınızda fazla istekler hata almaz, yalnızca kuyrukta bekler — yani belirti düşen çözüm oranı değil, uzayan çözüm süresidir.

Türkiye'deki ekipler için burada pratik bir avantaj var: CaptchaAI thread başına faturalandırır, her thread'de sınırsız çözüm sunar ve fiyatlar USD'dir. Bir e-ticaret QA ekibi kampanya döneminde ölçek yukarı çıkarken çözüm başına maliyeti yeniden hesaplamak zorunda kalmaz; aylık gider seçtiğiniz plan neyse odur. .tfvars dosyasındaki eşzamanlılık değerini artırmadan önce planın thread havuzunu büyütmek, sürprizsiz bütçe için en basit kuraldır.

Depo düzeni

Kod üç katmana ayrılır; bu ayrım sayesinde tek bir depodan dev, staging ve production yönetilir:

  • modules/captcha-worker — ECS/EC2 kaynaklarını tutan, yeniden kullanılabilir modül.
  • environments/*.tfvars — her ortamın worker sayısını, CPU/bellek ve eşzamanlılık değerlerini belirleyen dosyalar.
  • Uzak state (backend "s3") — S3'te duran, DynamoDB ile kilitlenen ortak durum dosyası.
terraform/
├── main.tf              # Provider config
├── variables.tf         # Input variables
├── outputs.tf           # Output values
├── modules/
│   └── captcha-worker/
│       ├── main.tf      # ECS/EC2 resources
│       ├── variables.tf # Module inputs
│       └── outputs.tf   # Module outputs
├── environments/
│   ├── dev.tfvars
│   ├── staging.tfvars
│   └── production.tfvars

Adım 1: Provider ve uzak state

State'i S3'te tutar, DynamoDB tablosuyla kilitlersiniz. Kilit olmadan iki mühendis aynı anda apply çalıştırdığında state bozulur; encrypt = true ise dosyayı sunucu tarafında şifreler.

# main.tf
terraform {
  required_version = ">= 1.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "captcha-workers/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
}

Adım 2: Ortamlar arasında değişecek girdiler

worker_count, worker_cpu ve captchaai_concurrency her ortamda farklı değer alır. Varsayılanlar burada dururken gerçek değerler .tfvars dosyalarından gelir — böylece production ayarını değiştirmek için modül koduna hiç dokunmazsınız.

# variables.tf
variable "aws_region" {
  description = "AWS region for deployment"
  type        = string
  default     = "us-east-1"
}

variable "environment" {
  description = "Environment name (dev, staging, production)"
  type        = string
}

variable "worker_count" {
  description = "Number of CAPTCHA solving workers"
  type        = number
  default     = 3
}

variable "worker_cpu" {
  description = "CPU units for each worker (1024 = 1 vCPU)"
  type        = number
  default     = 512
}

variable "worker_memory" {
  description = "Memory in MB for each worker"
  type        = number
  default     = 1024
}

variable "max_workers" {
  description = "Maximum workers for auto-scaling"
  type        = number
  default     = 10
}

variable "captchaai_concurrency" {
  description = "Concurrent CAPTCHA tasks per worker"
  type        = number
  default     = 10
}

Adım 3: API anahtarını state'in dışında tutun

Terraform state'i, içine giren her değeri düz metin olarak saklar. Bu yüzden API anahtarı üç kuralla yönetilir:

  • Anahtarı hiçbir .tf dosyasına veya değişken varsayılanına yazmayın.
  • Terraform yalnızca Secrets Manager kaydını oluşturur; değeri panelden ya da CLI ile ayrıca girersiniz.
  • ECS görev tanımı anahtarı sadece valueFrom ile referans alır; anahtar çalışma anında container'a enjekte edilir.
# secrets.tf — Store API key in AWS Secrets Manager
resource "aws_secretsmanager_secret" "captchaai_api_key" {
  name        = "${var.environment}/captchaai-api-key"
  description = "CaptchaAI API key for CAPTCHA solving workers"
}

# Reference secret in ECS task (never in plain text)
data "aws_secretsmanager_secret_version" "captchaai_api_key" {
  secret_id = aws_secretsmanager_secret.captchaai_api_key.id
}

Adım 4: Fargate worker kümesi

Worker'lar containerInsights açık bir kümede çalışır. Her container anahtarı Secrets Manager'dan alır, görevleri kuyruğunuzdan çeker ve sonucu res.php uç noktasından sorgular. Görev tanımı en az yetki ilkesi için iki ayrı IAM rolü kullanır:

  • execution_role — imajı ECR'den çeker, logları CloudWatch'a yazar.
  • task_role — container'ın çalışma anında ihtiyaç duyduğu izinleri taşır (örneğin secret okuma).

Worker'lar özel alt ağlarda (private_subnets) koşar; dışarıdan doğrudan erişime kapalıdır ve CaptchaAI API'sine yalnızca giden isteklerle ulaşırlar.

# ecs.tf — Fargate-based CAPTCHA workers
resource "aws_ecs_cluster" "captcha" {
  name = "captcha-workers-${var.environment}"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

resource "aws_ecs_task_definition" "captcha_worker" {
  family                   = "captcha-worker-${var.environment}"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = var.worker_cpu
  memory                   = var.worker_memory
  execution_role_arn       = aws_iam_role.ecs_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn

  container_definitions = jsonencode([
    {
      name  = "captcha-worker"
      image = "${aws_ecr_repository.captcha_worker.repository_url}:latest"

      environment = [
        { name = "CAPTCHAAI_CONCURRENCY", value = tostring(var.captchaai_concurrency) },
        { name = "CAPTCHAAI_POLL_INTERVAL", value = "5" },
        { name = "ENVIRONMENT", value = var.environment },
      ]

      secrets = [
        {
          name      = "CAPTCHAAI_API_KEY"
          valueFrom = aws_secretsmanager_secret.captchaai_api_key.arn
        }
      ]

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = aws_cloudwatch_log_group.captcha.name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "worker"
        }
      }
    }
  ])
}

resource "aws_ecs_service" "captcha_worker" {
  name            = "captcha-workers"
  cluster         = aws_ecs_cluster.captcha.id
  task_definition = aws_ecs_task_definition.captcha_worker.arn
  desired_count   = var.worker_count
  launch_type     = "FARGATE"

  network_configuration {
    subnets         = var.private_subnets
    security_groups = [aws_security_group.captcha_worker.id]
  }
}

Adım 5: Kuyruk derinliğine göre otomatik ölçeklendirme

Kuyruk büyüdüğünde worker sayısı artar, boşta kaldığında azalır. Yukarı çıkarken kısa (120 sn), aşağı inerken uzun (300 sn) bir cooldown kullanmak, dalgalı yükte kümenin sürekli büyüyüp küçülmesini engeller.

# autoscaling.tf
resource "aws_appautoscaling_target" "captcha" {
  max_capacity       = var.max_workers
  min_capacity       = var.worker_count
  resource_id        = "service/${aws_ecs_cluster.captcha.name}/${aws_ecs_service.captcha_worker.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
}

# Scale up when queue is deep
resource "aws_appautoscaling_policy" "scale_up" {
  name               = "captcha-scale-up"
  policy_type        = "StepScaling"
  resource_id        = aws_appautoscaling_target.captcha.resource_id
  scalable_dimension = aws_appautoscaling_target.captcha.scalable_dimension
  service_namespace  = aws_appautoscaling_target.captcha.service_namespace

  step_scaling_policy_configuration {
    adjustment_type         = "ChangeInCapacity"
    cooldown                = 120

    step_adjustment {
      scaling_adjustment          = 2
      metric_interval_lower_bound = 0
    }
  }
}

# Scale down when idle
resource "aws_appautoscaling_policy" "scale_down" {
  name               = "captcha-scale-down"
  policy_type        = "StepScaling"
  resource_id        = aws_appautoscaling_target.captcha.resource_id
  scalable_dimension = aws_appautoscaling_target.captcha.scalable_dimension
  service_namespace  = aws_appautoscaling_target.captcha.service_namespace

  step_scaling_policy_configuration {
    adjustment_type         = "ChangeInCapacity"
    cooldown                = 300

    step_adjustment {
      scaling_adjustment          = -1
      metric_interval_upper_bound = 0
    }
  }
}

Adım 6: Ortam dosyalarını yazın

Buradaki sayılar, yukarıda hesapladığınız plan kapasitesinin doğrudan yansımasıdır. Dev ortamı tek worker ve düşük eşzamanlılıkla çalışır:

# environments/dev.tfvars
environment           = "dev"
worker_count          = 1
max_workers           = 3
worker_cpu            = 256
worker_memory         = 512
captchaai_concurrency = 3

Production ise beş worker ve worker başına 20 eşzamanlı görevle koşar:

# environments/production.tfvars
environment           = "production"
worker_count          = 5
max_workers           = 20
worker_cpu            = 1024
worker_memory         = 2048
captchaai_concurrency = 20

Adım 7: Worker uygulama kodu

Container şu kodu çalıştırır: anahtarı ortam değişkeninden okur, görevi in.php uç noktasına gönderir ve token hazır olana kadar res.php'yi sorgular. SIGTERM yakalayan graceful shutdown, ölçek aşağı inerken devam eden çözümlerin yarıda kesilmemesini sağlar — bu ayrıntı atlandığında her ölçek küçülmesi birkaç görevi çöpe atar.

"""captcha_worker.py — The container runs this."""
import os
import time
import signal
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
CONCURRENCY = int(os.environ.get("CAPTCHAAI_CONCURRENCY", "10"))
POLL_INTERVAL = int(os.environ.get("CAPTCHAAI_POLL_INTERVAL", "5"))

running = True

def shutdown_handler(signum, frame):
    global running
    print("Graceful shutdown initiated")
    running = False

signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)

session = requests.Session()

def solve_captcha(sitekey, pageurl):
    resp = session.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    })
    data = resp.json()
    if data.get("status") != 1:
        return {"error": data.get("request")}

    captcha_id = data["request"]
    for _ in range(60):
        time.sleep(POLL_INTERVAL)
        result = 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:
            return {"solution": result["request"]}
        if result.get("request") != "CAPCHA_NOT_READY":
            return {"error": result.get("request")}

    return {"error": "TIMEOUT"}

# Main loop — pull tasks from SQS or Redis
print(f"Worker started: concurrency={CONCURRENCY}")
while running:
    # Pull tasks from your queue here
    time.sleep(1)

print("Worker shutdown complete")

Örnekteki method değeri userrecaptcha; aynı worker iskeletini yalnızca bu değeri değiştirerek reCAPTCHA v3, Cloudflare Turnstile veya GeeTest v3 akışlarında da kullanabilirsiniz. Görev gönderimi ve sorgulama mantığı tüm türlerde aynı kalır.

Adım 8: Dağıtım

# Initialize
terraform init

# Plan for production
terraform plan -var-file=environments/production.tfvars

# Apply
terraform apply -var-file=environments/production.tfvars

# Destroy (dev cleanup)
terraform destroy -var-file=environments/dev.tfvars

Sıra sabittir: init sağlayıcıları ve state backend'ini hazırlar, plan uygulanacak değişiklikleri gösterir, apply altyapıyı ilgili .tfvars dosyasıyla kurar, destroy ise dev ortamını iş bittiğinde temizler. Ekip içinde plan çıktısını pull request'e eklemek, altyapı değişikliğini tıpkı kod gibi gözden geçirilebilir hâle getirir.

Üretimde izlenecek üç sinyal

  • CloudWatch logları — her worker'ın çözüm ve hata çıktısı, awslogs-stream-prefix ile ayrıştırılır.
  • Container Insights — CPU/bellek kullanımı ve görev sayısı; ölçeklendirme eşiklerini burada doğrularsınız.
  • CaptchaAI paneli — aktif thread kullanımı ve bakiye; plan sınırınıza yaklaşıp yaklaşmadığınızı gösterir.

Sorun giderme

Sorun Olası neden Çözüm
Dağıtımda gizli anahtar bulunamıyor Secrets Manager değeri henüz girilmemiş terraform apply öncesi gizli anahtar değerini oluşturun
Worker'lar ilk açılışta çöküyor Eksik ortam değişkeni veya yanlış imaj CloudWatch loglarını inceleyin; ECR imaj etiketini doğrulayın
Otomatik ölçeklendirme tetiklenmiyor Eksik CloudWatch alarmı veya yanlış metrik Ölçeklendirme politikasındaki alarm ARN'sini kontrol edin
State kilidi hatası Önceki apply yarıda kesilmiş Kilidi kaldırın: terraform force-unlock <lock-id>
Ölçek büyüdükçe çözüm süresi uzuyor Eşzamanlılık plan thread sayısını aşmış captchaai_concurrency değerini düşürün ya da planı yükseltin

Sık sorulan sorular

Terraform state dosyasını ekipçe nasıl güvende tutarım?

State'i S3 backend'inde tutun ve bir DynamoDB kilit tablosu tanımlayın; kilit, iki kişinin aynı anda apply çalıştırıp state'i bozmasını engeller. encrypt = true ile dosya şifrelenir; bucket versiyonlamayı açarsanız hatalı bir apply sonrası geçmişe dönebilirsiniz.

Ölçek aşağı inerken devam eden çözümler kaybolur mu?

Worker kodu SIGTERM sinyalini yakalıyorsa hayır. ECS önce sinyali gönderir, worker yeni görev almayı bırakır ve elindeki çözümü tamamlar. Bu işleyicisi bulunmayan bir container'da her ölçek küçülmesi yarım kalmış görevler üretir.

worker_cpu ve worker_memory değerlerini nasıl seçmeliyim?

CAPTCHA worker'ı ağırlıklı olarak I/O bekler, CPU'yu zorlamaz; dev için 256 CPU / 512 MB fazlasıyla yeter. Production'da 1024 CPU / 2048 MB tercih etmemizin nedeni hesaplama yükü değil, yüksek eşzamanlılıkta açık bağlantı ve bellek tüketiminin artmasıdır.

Aynı yapıyı GCP veya Azure üzerinde kurabilir miyim?

Evet. AWS provider'ını ve kaynaklarını GCP (Cloud Run, GKE) ya da Azure (Container Instances, AKS) karşılıklarıyla değiştirmeniz yeterli; modül düzeni, değişken isimleri ve eşzamanlılık hesabı aynı kalır.

Bu altyapı hangi CAPTCHA türlerinde çalışır?

method değerini değiştirerek reCAPTCHA v2/v3, Cloudflare Turnstile, Cloudflare doğrulama akışı, GeeTest v3, BLS ve görüntü/OCR türlerini çözebilirsiniz. CaptchaFox (beta), Friendly Captcha (beta) ve Lemin (beta) de desteklenir. hCaptcha ve FunCaptcha desteklenmez; GeeTest v4 için ise çok yakında notu geçerlidir.

Sonraki adımlar

Altyapınızı koda dökün: CaptchaAI API anahtarınızı alın ve ilk worker kümenizi Terraform ile dağıtın.

İlgili kılavuzlar:

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