GCP Cloud Run ile Konteyner Tabanlı Serverless Mimariye Giriş

Konteyner teknolojisini seviyorsun ama sunucu yönetmekten bıktın mı? O zaman Google Cloud Platform’un Cloud Run servisi tam sana göre. Hem Docker konteynerlerinin esnekliğini kullanıyorsun, hem de altyapı kafası olmadan. Serverless dünyasına geçiş yapmak isteyenler için de mükemmel bir köprü çünkü mevcut konteyner bilgini sıfırdan öğrenmek zorunda kalmadan taşıyabiliyorsun.

Bu yazıda Cloud Run’ı gerçek dünya senaryolarıyla ele alacağız. Basit bir “Hello World” demo’sundan değil, production’a yakın kurulumlardan bahsedeceğiz.

Cloud Run Nedir ve Neden Önemli

Cloud Run, Google Cloud’un tam yönetimli serverless konteyner platformu. Klasik serverless fonksiyonlardan (Cloud Functions, Lambda) farkı şu: sen bir fonksiyon değil, tam bir konteyner çalıştırıyorsun. Bu da şu anlama geliyor; herhangi bir dil, herhangi bir kütüphane, herhangi bir bağımlılık. Sınır yok.

Temel çalışma mantığı şöyle:

  • İstek geldiğinde konteyner başlar (cold start)
  • İstek işlenir ve cevap döner
  • İstekler azaldığında konteynerler sıfıra iner
  • Sıfır istek = sıfır maliyet

Bu modelin en güzel yanı, trafik artışlarında otomatik ölçeklenmesi. Dakikada 10 istek mi var, 10 instance çalışır. Dakikada 10.000 istek mi geldi, Cloud Run halleder. Sen sadece maksimum instance sayısını belirlersin, gerisini platform yönetir.

Klasik Kubernetes ile karşılaştırınca şunu görürsün: GKE’de deployment yaz, service yaz, ingress konfigüre et, HPA ayarla, node pool yönet. Cloud Run’da tek komut yeterli.

Ortam Hazırlığı ve İlk Kurulum

Başlamadan önce birkaç şeyi halletmem gerekiyor. Google Cloud SDK’yı kurduğunu ve projenin hazır olduğunu varsayıyorum.

# gcloud CLI kurulumu sonrası kimlik doğrulama
gcloud auth login
gcloud auth configure-docker

# Proje ayarla
gcloud config set project PROJE_ID

# Gerekli API'leri aktif et
gcloud services enable run.googleapis.com
gcloud services enable containerregistry.googleapis.com
gcloud services enable artifactregistry.googleapis.com

# Bölge ayarla (EU için)
gcloud config set run/region europe-west1

Artifact Registry’yi Container Registry yerine kullanmanı öneriyorum. Google artık CR’yi deprecated yapmaya doğru gidiyor ve AR çok daha esnek. Hemen bir repository oluşturalım:

# Artifact Registry repository oluştur
gcloud artifacts repositories create uygulama-repo 
    --repository-format=docker 
    --location=europe-west1 
    --description="Uygulama konteyner deposu"

# Authentication konfigürasyonu
gcloud auth configure-docker europe-west1-docker.pkg.dev

İlk Uygulamayı Deploy Etmek

Gerçek bir senaryo kullanalım. Diyelim ki bir Python FastAPI uygulaması var ve bunu Cloud Run’a taşıyacağız.

Önce Dockerfile:

FROM python:3.11-slim

WORKDIR /app

# Bağımlılıkları önce kopyala (layer cache optimizasyonu)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Uygulama kodunu kopyala
COPY . .

# Cloud Run PORT environment variable kullanır
ENV PORT=8080
EXPOSE 8080

# Non-root user ile çalıştır (güvenlik)
RUN adduser --disabled-password --gecos '' appuser
USER appuser

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

Build, push ve deploy işlemleri:

# Image'ı build et
docker build -t europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/api:v1.0 .

# Artifact Registry'e push et
docker push europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/api:v1.0

# Cloud Run'a deploy et
gcloud run deploy api-servisi 
    --image europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/api:v1.0 
    --platform managed 
    --region europe-west1 
    --allow-unauthenticated 
    --min-instances 0 
    --max-instances 10 
    --memory 512Mi 
    --cpu 1 
    --timeout 60 
    --concurrency 80

Buradaki parametreleri açıklayayım:

  • –allow-unauthenticated: Public erişime açık servis, auth gerekmez
  • –min-instances 0: Sıfıra inebilir, maliyet tasarrufu
  • –max-instances 10: En fazla 10 konteyner ayağa kalkar
  • –memory 512Mi: Her instance için bellek limiti
  • –cpu 1: Her instance için 1 vCPU
  • –timeout 60: İstek zaman aşımı 60 saniye
  • –concurrency 80: Her instance eş zamanlı 80 istek işler

Cloud Build ile CI/CD Pipeline

Elle build alıp push etmek production için yeterli değil. Cloud Build ile otomatik pipeline kuralım. Bunun için cloudbuild.yaml dosyası oluşturuyoruz:

steps:
  # Docker image'ı build et
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'europe-west1-docker.pkg.dev/$PROJECT_ID/uygulama-repo/api:$COMMIT_SHA'
      - '-t'
      - 'europe-west1-docker.pkg.dev/$PROJECT_ID/uygulama-repo/api:latest'
      - '.'

  # Artifact Registry'e push et
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'push'
      - '--all-tags'
      - 'europe-west1-docker.pkg.dev/$PROJECT_ID/uygulama-repo/api'

  # Cloud Run'a deploy et
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args:
      - 'run'
      - 'deploy'
      - 'api-servisi'
      - '--image'
      - 'europe-west1-docker.pkg.dev/$PROJECT_ID/uygulama-repo/api:$COMMIT_SHA'
      - '--platform'
      - 'managed'
      - '--region'
      - 'europe-west1'
      - '--allow-unauthenticated'

options:
  logging: CLOUD_LOGGING_ONLY

images:
  - 'europe-west1-docker.pkg.dev/$PROJECT_ID/uygulama-repo/api'

Bu pipeline’ı GitHub reposuna bağlamak için:

# Cloud Build trigger oluştur (GitHub bağlantısı için önce GitHub reposunu bağlamalısın)
gcloud builds triggers create github 
    --repo-name=uygulama-repo 
    --repo-owner=github-kullanici 
    --branch-pattern="^main$" 
    --build-config=cloudbuild.yaml 
    --name="main-branch-deploy"

Artık main branch’e her push yapıldığında otomatik olarak deploy gerçekleşir.

Ortam Değişkenleri ve Secret Yönetimi

Uygulama konfigürasyonu için environment variable kullanmak şart. Ama veritabanı şifreleri, API anahtarları gibi hassas bilgileri düz metin olarak vermek olmaz. Bunun için Secret Manager kullanıyoruz.

# Secret oluştur
echo -n "super-gizli-db-sifresi" | gcloud secrets create db-password 
    --data-file=-

# Secret'a yeni versiyon ekle
echo -n "yeni-sifre" | gcloud secrets versions add db-password 
    --data-file=-

# Cloud Run servisine Secret bağla
gcloud run deploy api-servisi 
    --image europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/api:latest 
    --update-secrets=DB_PASSWORD=db-password:latest 
    --update-env-vars=DB_HOST=10.0.0.5,APP_ENV=production 
    --region europe-west1

–update-secrets parametresiyle Secret Manager’daki değer, container’a environment variable olarak enjekte edilir. DB_PASSWORD=db-password:latest formatı şu anlama gelir: db-password isimli secret’ın latest versiyonunu DB_PASSWORD env var olarak kullan.

Cloud Run’ın Service Account’ına Secret Manager okuma yetkisi de vermek gerekiyor:

# Service Account'ı bul
gcloud run services describe api-servisi 
    --region europe-west1 
    --format="value(spec.template.spec.serviceAccountName)"

# Secret Manager erişim yetkisi ver
gcloud secrets add-iam-policy-binding db-password 
    --member="serviceAccount:SA_EMAIL" 
    --role="roles/secretmanager.secretAccessor"

VPC Bağlantısı ve Özel Ağ Erişimi

Çoğu production senaryosunda Cloud Run’ın internete açık olmayan kaynaklara (Cloud SQL, Redis, internal servisler) erişmesi gerekiyor. Bunun için VPC Connector kullanıyoruz.

# Serverless VPC Access Connector oluştur
gcloud compute networks vpc-access connectors create cr-connector 
    --region europe-west1 
    --subnet anasubnet 
    --subnet-project PROJE_ID 
    --min-instances 2 
    --max-instances 10 
    --machine-type e2-micro

# Cloud Run servisini VPC Connector ile güncelle
gcloud run services update api-servisi 
    --vpc-connector cr-connector 
    --vpc-egress all-traffic 
    --region europe-west1

–vpc-egress all-traffic: Tüm outbound trafiği VPC üzerinden yönlendir. Eğer sadece özel IP’lere erişim lazımsa private-ranges-only kullanabilirsin, bu da daha ucuza gelir.

Cloud SQL bağlantısı için ayrıca Cloud SQL Auth Proxy desteği var:

# Cloud SQL instance adını bul
gcloud sql instances describe sql-instance --format="value(connectionName)"

# Cloud Run'a Cloud SQL bağlantısı ekle
gcloud run services update api-servisi 
    --add-cloudsql-instances PROJE_ID:europe-west1:sql-instance 
    --update-env-vars INSTANCE_CONNECTION_NAME=PROJE_ID:europe-west1:sql-instance 
    --region europe-west1

Uygulamanın içinde Unix socket üzerinden bağlantı kuruluyor: /cloudsql/PROJE_ID:europe-west1:sql-instance. Bu yöntemi doğrudan IP bağlantısına tercih et çünkü SSL ve auth otomatik hallediliyor.

Traffic Splitting ve Canary Deploy

Cloud Run’ın en güzel özelliklerinden biri traffic splitting. Yeni bir versiyon çıkarken %10 trafiği yeni versiyona yönlendir, sorun yoksa %100’e çıkar. Bunu Kubernetes’te yapmak için bir sürü konfigürasyon gerekiyor, Cloud Run’da birkaç komut yeterli.

# Yeni versiyonu deploy et ama trafik verme
gcloud run deploy api-servisi 
    --image europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/api:v2.0 
    --no-traffic 
    --tag v2 
    --region europe-west1

# %10 trafiği yeni versiyona yönlendir
gcloud run services update-traffic api-servisi 
    --to-tags v2=10 
    --region europe-west1

# Logları izle, sorun yoksa %50'ye çıkar
gcloud run services update-traffic api-servisi 
    --to-tags v2=50 
    --region europe-west1

# Tamamen geçiş yap
gcloud run services update-traffic api-servisi 
    --to-latest 
    --region europe-west1

Eğer bir sorun çıkarsa hızlıca rollback yapabilirsin:

# Bir önceki revision'a dön
gcloud run services update-traffic api-servisi 
    --to-revisions api-servisi-00001-abc=100 
    --region europe-west1

Revision listesini görmek için:

gcloud run revisions list --service api-servisi --region europe-west1

Monitoring ve Logging

Cloud Run, Google Cloud Monitoring ve Logging ile native olarak entegre. Ama birkaç önemli noktayı bilmek gerekiyor.

Uygulamanın loglarını structured JSON formatında yazması çok önemli:

import json
import logging
import sys

class CloudRunHandler(logging.StreamHandler):
    def emit(self, record):
        log_entry = {
            "severity": record.levelname,
            "message": self.format(record),
            "component": "api-servisi"
        }
        print(json.dumps(log_entry), file=sys.stdout, flush=True)

logger = logging.getLogger()
logger.addHandler(CloudRunHandler())

Log filtrelemek ve izlemek için gcloud komutları:

# Gerçek zamanlı log izle
gcloud run services logs tail api-servisi --region europe-west1

# Son 1 saatin error loglarını göster
gcloud logging read 
    'resource.type="cloud_run_revision" AND resource.labels.service_name="api-servisi" AND severity>=ERROR' 
    --freshness=1h 
    --format="table(timestamp,severity,textPayload)"

# Özel metric oluştur (yüksek latency alertı için)
gcloud monitoring alert-policies create 
    --display-name="Cloud Run Yüksek Latency" 
    --condition-display-name="P99 Latency > 2s" 
    --condition-filter='resource.type="cloud_run_revision" metric.type="run.googleapis.com/request_latencies"' 
    --condition-threshold-value=2000 
    --condition-threshold-comparison=COMPARISON_GT 
    --notification-channels=KANAL_ID

Maliyet Optimizasyonu

Cloud Run’ın faturası iki ana bileşenden oluşur: CPU/bellek kullanımı ve istek sayısı. Maliyet optimizasyonu için şunlara dikkat etmek gerekiyor:

CPU allocation ayarı kritik. Varsayılan olarak CPU sadece istek işlenirken tahsis edilir. Ama arka plan iş yapıyorsan (periyodik task, mesaj kuyruğu işleme) CPU’yu her zaman tahsis edilmiş tutman gerekiyor.

# CPU'yu her zaman tahsis et (arka plan işler için)
gcloud run services update api-servisi 
    --cpu-throttling 
    --region europe-west1

# Sadece istek sırasında CPU tahsis et (varsayılan, ucuz)
gcloud run services update api-servisi 
    --no-cpu-throttling 
    --region europe-west1

Minimum instance sayısını dikkatli ayarla. --min-instances 0 en ucuz seçenek ama cold start var. Kritik servisler için 1-2 minimum instance tutmak mantıklı, cold start sorununu çözer ve maliyet artışı nispeten az olur.

Concurrency değerini optimize et. Stateless, I/O bound uygulamalarda 80-100 concurrency normal. CPU bound işlemlerde bu değeri düşürmek gerekebilir, aksi halde instance’lar boğulur.

# Mevcut servislerin maliyet özetini görmek için
gcloud run services list 
    --platform managed 
    --format="table(metadata.name,spec.template.spec.containers.resources.limits.memory,spec.template.spec.containers.resources.limits.cpu)"

Gerçek Dünya Senaryosu: Webhook İşleyici

Birçok firmada webhook alan ve işleyen servisler var. Stripe, GitHub, Slack gibi servislerden webhook geliyor, bunlar işleniyor. Cloud Run bu senaryo için mükemmel çünkü trafik anlık geliyor, saatlerce boş kalıyor.

Senaryo: GitHub webhookları alıp Slack’e bildirim gönderen bir servis.

# Slack token'ını secret olarak kaydet
echo -n "xoxb-slack-token-buraya" | gcloud secrets create slack-token 
    --data-file=-

# GitHub webhook secret'ı da kaydet
echo -n "github-webhook-secret" | gcloud secrets create github-webhook-secret 
    --data-file=-

# Servisi deploy et
gcloud run deploy webhook-handler 
    --image europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/webhook:latest 
    --platform managed 
    --region europe-west1 
    --allow-unauthenticated 
    --min-instances 0 
    --max-instances 5 
    --memory 256Mi 
    --cpu 1 
    --timeout 30 
    --concurrency 100 
    --update-secrets=SLACK_TOKEN=slack-token:latest,WEBHOOK_SECRET=github-webhook-secret:latest

# Servis URL'ini al ve GitHub webhook ayarlarına ekle
gcloud run services describe webhook-handler 
    --region europe-west1 
    --format="value(status.url)"

Bu servis hiç istek olmadığında sıfır instance çalışır, aylık maliyeti neredeyse sıfır. GitHub’dan webhook geldiğinde saniyeler içinde ayağa kalkar ve işlemi tamamlar. Klasik bir VM veya konteyner platformunda bu için sürekli çalışan bir instance lazım olurdu.

Servis Hesapları ve IAM En İyi Uygulamaları

Her Cloud Run servisinin kendi Service Account’ı olmalı, default Compute Engine SA kullanma. Bu, en az yetki prensibi için şart.

# Her servis için özel SA oluştur
gcloud iam service-accounts create webhook-handler-sa 
    --display-name="Webhook Handler Service Account"

# Sadece gerekli rolleri ver
gcloud projects add-iam-policy-binding PROJE_ID 
    --member="serviceAccount:webhook-handler-sa@PROJE_ID.iam.gserviceaccount.com" 
    --role="roles/secretmanager.secretAccessor"

# Pub/Sub yayıncı rolü gerekiyorsa ekle
gcloud projects add-iam-policy-binding PROJE_ID 
    --member="serviceAccount:webhook-handler-sa@PROJE_ID.iam.gserviceaccount.com" 
    --role="roles/pubsub.publisher"

# Servisi bu SA ile deploy et
gcloud run deploy webhook-handler 
    --image europe-west1-docker.pkg.dev/PROJE_ID/uygulama-repo/webhook:latest 
    --service-account webhook-handler-sa@PROJE_ID.iam.gserviceaccount.com 
    --region europe-west1

Servisler arası iletişimde de IAM kullan. Bir Cloud Run servisi diğerini çağıracaksa, anonymous erişim verme:

# Çağıran servise hedef servise erişim ver
gcloud run services add-iam-policy-binding hedef-servis 
    --region europe-west1 
    --member="serviceAccount:cagiran-servis-sa@PROJE_ID.iam.gserviceaccount.com" 
    --role="roles/run.invoker"

Çağıran tarafta ise istek header’ına Google-signed ID token eklemek gerekiyor. Bu token, hedef servisin kimliği doğrulamasını sağlar.

Sorun Giderme İpuçları

Production’da karşılaşacağın yaygın sorunlar ve çözümleri:

Cold start süreleri uzun geliyor diyorsan, önce image boyutuna bak. Multi-stage build kullan, gereksiz katmanları sil. Python için slim base image tercih et. Minimum instance sayısını 1’e çek.

Memory limit aşımı durumunda container restart oluyor ve istek başarısız dönüyor. Loglardan OOMKilled görürsün. Memory limitini artır veya uygulamada bellek sızdıran yeri bul.

Timeout hataları için, Cloud Run maximum 3600 saniye timeout destekliyor ama uzun süren işler için Cloud Tasks veya Cloud Run Jobs kullanmak daha mantıklı.

# Servis detaylarını ve hata mesajlarını gör
gcloud run services describe api-servisi 
    --region europe-west1

# Revision durumlarını kontrol et
gcloud run revisions describe api-servisi-00005-xyz 
    --region europe-west1 
    --format="yaml(status)"

# Instance sayısını anlık izle
gcloud monitoring metrics list 
    --filter="metric.type:run.googleapis.com"

Sonuç

Cloud Run, konteyner dünyasından gelen sistem yöneticileri ve geliştiriciler için serverless’a geçişin en az ağrılı yolu. Mevcut Docker bilgini doğrudan kullanabiliyorsun, Kubernetes kompleksliğine girmen gerekmiyor, ama aynı zamanda ciddi bir esnekliğin var.

Hangi durumlarda Cloud Run’ı tercih etmelisin diye sorarsan: Trafik patternı öngörülemeyen veya düzensiz olan servisler için kesinlikle. API backend’ler, webhook handler’lar, batch işlemler, zamanlı görevler (Cloud Scheduler ile tetiklenen) için çok uygun. Aynı zamanda mikroservis mimarisine geçiş yapıyorsan, monoliti parçalara bölerken her parçayı Cloud Run’a atmak mantıklı bir strateji.

Nerede dikkatli olman gerekiyor? WebSocket gerektiren uygulamalar için sınırlı destek var. Stateful işlemler, uzun süren hesaplamalar (video transcode gibi) için Cloud Run Jobs’a veya GCE’ye bakmalısın. Çok düşük latency gerektiren, cold start’ın hiç kabul edilemediği durumlar için minimum instance sayısını sıfır yapmamalısın ama bu da maliyeti artırır.

Benim önerim: Yeni bir proje başlatıyorsan doğrudan Cloud Run’la başla. Sonradan ölçeklenme sorunu çıkarsa o zaman GKE’ye geçiş kararı verirsin. Ama çoğu durumda Cloud Run’ın sağladığı otomatik ölçekleme yeterli geliyor ve operasyonel yükü ciddi ölçüde azaltıyor. Altyapı yerine uygulamana odaklanmak istiyorsan, Cloud Run bunu gerçekten mümkün kılıyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir