GCP Cloud Functions ile HTTP Fonksiyon Oluşturma

Serverless dünyasına adım atmak, altyapı yönetiminden kurtulup sadece kod yazmaya odaklanmak isteyenler için gerçek bir özgürleşme hissi veriyor. Google Cloud Platform’un sunduğu Cloud Functions servisi de tam olarak bu ihtiyacı karşılıyor. HTTP tetikleyicili fonksiyonlar oluşturmak, basit API endpoint’leri yazmaktan karmaşık webhook sistemleri kurgulamaya kadar geniş bir kullanım alanı sunuyor. Bu yazıda, GCP Cloud Functions üzerinde HTTP fonksiyon oluşturmayı, yapılandırmayı ve production ortamına taşımayı adım adım inceleyeceğiz.

Cloud Functions Nedir ve Neden Kullanırız?

Cloud Functions, Google’ın event-driven serverless compute platformudur. Sen sadece fonksiyonu yazarsın, GCP gerisini halleder. Sunucu provisioning yok, işletim sistemi yaması yok, ölçeklendirme endişesi yok. Fonksiyonun çalışmadığı anlarda para ödemezsin, bu da özellikle düşük veya değişken trafikli uygulamalar için büyük bir avantaj.

HTTP Cloud Functions özellikle şu senaryolarda parlıyor:

  • Webhook alıcıları: GitHub, Stripe, Slack gibi servislerin webhook’larını karşılamak
  • REST API endpoint’leri: Mobil uygulamalar veya frontend’ler için hafif API katmanı
  • Form işleyicileri: Statik site form verilerini işlemek
  • Veri dönüştürücüler: Bir formattan diğerine anlık dönüşüm servisleri
  • Zamanlama tetikleyicileri: Cloud Scheduler ile birlikte periyodik görevler

Ortam Hazırlığı

GCP SDK ve gcloud Kurulumu

Önce yerel geliştirme ortamını hazırlayalım. gcloud CLI olmadan Cloud Functions ile ciddi iş yapılamaz.

# Linux/Debian tabanlı sistemler için gcloud kurulumu
curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-456.0.0-linux-x86_64.tar.gz
tar -xf google-cloud-cli-456.0.0-linux-x86_64.tar.gz
./google-cloud-sdk/install.sh

# Kurulum sonrası shell'i yenile
source ~/.bashrc

# gcloud'u başlat ve authenticate et
gcloud init
gcloud auth application-default login
# Proje ayarla ve gerekli API'ları etkinleştir
gcloud config set project PROJE_ADINIZ

# Cloud Functions ve Cloud Build API'larını aç
gcloud services enable cloudfunctions.googleapis.com
gcloud services enable cloudbuild.googleapis.com
gcloud services enable artifactregistry.googleapis.com

# Mevcut yapılandırmayı kontrol et
gcloud config list

Bu aşamada dikkat edilmesi gereken önemli bir nokta var: Cloud Functions 2. nesil (gen2), Cloud Run altyapısı üzerinde çalışıyor. Bazı özellikler sadece gen2’de mevcut, bu yüzden yeni projeler için gen2’yi tercih etmeni öneririm.

İlk HTTP Fonksiyonu: Hello World’den Gerçeğe

Temel Proje Yapısı

# Proje dizinini oluştur
mkdir -p gcf-http-demo && cd gcf-http-demo

# Python sanal ortamı oluştur (Python kullanıyorsak)
python3 -m venv venv
source venv/bin/activate

# Gerekli bağımlılıkları kur
pip install functions-framework flask

main.py dosyamızı oluşturalım:

cat > main.py << 'EOF'
import functions_framework
import json
import os
from datetime import datetime

@functions_framework.http
def http_handler(request):
    """
    Ana HTTP handler fonksiyonu.
    GET ve POST isteklerini işler.
    """
    # CORS header'ları - özellikle frontend entegrasyonlarda şart
    if request.method == 'OPTIONS':
        headers = {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET, POST',
            'Access-Control-Allow-Headers': 'Content-Type, Authorization',
            'Access-Control-Max-Age': '3600'
        }
        return ('', 204, headers)

    headers = {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/json'
    }

    if request.method == 'GET':
        params = request.args
        name = params.get('name', 'Dünya')
        
        response_data = {
            'message': f'Merhaba, {name}!',
            'timestamp': datetime.utcnow().isoformat(),
            'environment': os.environ.get('ENVIRONMENT', 'development')
        }
        return (json.dumps(response_data, ensure_ascii=False), 200, headers)

    elif request.method == 'POST':
        try:
            request_json = request.get_json(silent=True)
            if not request_json:
                return (json.dumps({'error': 'Geçersiz JSON body'}), 400, headers)
            
            # İş mantığı burada
            result = process_data(request_json)
            return (json.dumps(result, ensure_ascii=False), 200, headers)
            
        except Exception as e:
            error_response = {
                'error': 'İşlem sırasında hata oluştu',
                'detail': str(e)
            }
            return (json.dumps(error_response), 500, headers)
    
    return (json.dumps({'error': 'Method not allowed'}), 405, headers)


def process_data(data):
    """Gelen veriyi işle"""
    required_fields = ['name', 'email']
    for field in required_fields:
        if field not in data:
            raise ValueError(f'Zorunlu alan eksik: {field}')
    
    return {
        'status': 'success',
        'processed': True,
        'received': data,
        'processed_at': datetime.utcnow().isoformat()
    }
EOF
cat > requirements.txt << 'EOF'
functions-framework==3.5.0
flask==3.0.0
google-cloud-secret-manager==2.18.0
google-cloud-firestore==2.13.1
EOF

Lokal Test

Fonksiyonu production’a göndermeden önce mutlaka lokal test yapmalısın:

# Functions framework ile lokal sunucu başlat
functions-framework --target=http_handler --port=8080 --debug

# Başka bir terminal'de test et
# GET isteği
curl "http://localhost:8080?name=Sysadmin"

# POST isteği
curl -X POST http://localhost:8080 
  -H "Content-Type: application/json" 
  -d '{"name": "Ahmet", "email": "[email protected]"}'

# CORS preflight test
curl -X OPTIONS http://localhost:8080 
  -H "Origin: https://siteniz.com" 
  -H "Access-Control-Request-Method: POST" 
  -v

Cloud’a Deploy Etmek

gcloud ile Deploy

# Gen2 fonksiyon olarak deploy et
gcloud functions deploy http-demo-fonksiyon 
  --gen2 
  --runtime=python311 
  --region=europe-west1 
  --source=. 
  --entry-point=http_handler 
  --trigger-http 
  --allow-unauthenticated 
  --memory=256Mi 
  --timeout=60s 
  --min-instances=0 
  --max-instances=10 
  --set-env-vars=ENVIRONMENT=production 
  --service-account=fonksiyon-sa@PROJE_ADINIZ.iam.gserviceaccount.com

Bu komuttaki parametrelerin ne işe yaradığını açıklayalım:

  • –gen2: İkinci nesil Cloud Functions kullan (Cloud Run tabanlı)
  • –runtime=python311: Python 3.11 runtime’ı kullan
  • –region=europe-west1: Avrupa bölgesinde çalıştır (GDPR uyumu için önemli)
  • –entry-point: Hangi fonksiyonun HTTP isteğini karşılayacağını belirtir
  • –allow-unauthenticated: Public erişime açık (dikkatli kullan!)
  • –memory=256Mi: Fonksiyona ayrılan RAM miktarı
  • –timeout=60s: Maksimum çalışma süresi, 3600s’e kadar çıkabilir
  • –min-instances=0: Cold start kabul ediyoruz, maliyet optimizasyonu için
  • –max-instances=10: Maksimum instance sayısı, maliyet kontrolü için kritik
  • –service-account: Fonksiyonun hangi kimlikle çalışacağı
# Deploy sonrası fonksiyon URL'ini al
gcloud functions describe http-demo-fonksiyon 
  --gen2 
  --region=europe-west1 
  --format="value(serviceConfig.uri)"

# Fonksiyonu test et
FUNCTION_URL=$(gcloud functions describe http-demo-fonksiyon 
  --gen2 
  --region=europe-west1 
  --format="value(serviceConfig.uri)")

curl "${FUNCTION_URL}?name=GCP"

Güvenlik: Authentication ve IAM

Authenticated Fonksiyon Oluşturma

Production ortamında --allow-unauthenticated kullanmak çoğu zaman kötü bir fikirdir. Kimlik doğrulamalı fonksiyon oluşturalım:

# Kimlik doğrumu zorunlu fonksiyon
gcloud functions deploy guvenli-fonksiyon 
  --gen2 
  --runtime=python311 
  --region=europe-west1 
  --source=. 
  --entry-point=http_handler 
  --trigger-http 
  --no-allow-unauthenticated 
  --memory=256Mi 
  --timeout=30s

# Belirli bir servis hesabına invoker yetkisi ver
gcloud functions add-invoker-policy-binding guvenli-fonksiyon 
  --region=europe-west1 
  --member="serviceAccount:backend-sa@PROJE_ADINIZ.iam.gserviceaccount.com"

# Token ile fonksiyonu çağır
TOKEN=$(gcloud auth print-identity-token)
curl -H "Authorization: Bearer ${TOKEN}" "${FUNCTION_URL}"

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

Gelin daha gerçekçi bir örnek yapalım. E-ticaret uygulamanız var ve Stripe ödeme webhook’larını işlemeniz gerekiyor.

cat > stripe_webhook.py << 'EOF'
import functions_framework
import json
import os
import hmac
import hashlib
import time
from google.cloud import firestore
from google.cloud import secretmanager

def get_stripe_secret():
    """Secret Manager'dan Stripe webhook secret'ı al"""
    client = secretmanager.SecretManagerServiceClient()
    name = f"projects/{os.environ['PROJECT_ID']}/secrets/stripe-webhook-secret/versions/latest"
    response = client.access_secret_version(request={"name": name})
    return response.payload.data.decode("UTF-8")

def verify_stripe_signature(payload, sig_header, secret):
    """Stripe imza doğrulama - güvenlik açısından kritik!"""
    try:
        # Timestamp ve imzayı ayır
        elements = sig_header.split(',')
        timestamp = None
        signatures = []
        
        for element in elements:
            key, value = element.split('=', 1)
            if key == 't':
                timestamp = int(value)
            elif key == 'v1':
                signatures.append(value)
        
        if not timestamp or not signatures:
            return False
        
        # Replay attack kontrolü - 5 dakikadan eski istekleri reddet
        if abs(time.time() - timestamp) > 300:
            return False
        
        # HMAC hesapla
        signed_payload = f"{timestamp}.{payload}"
        expected_sig = hmac.new(
            secret.encode('utf-8'),
            signed_payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        
        return any(hmac.compare_digest(expected_sig, sig) for sig in signatures)
    
    except Exception:
        return False

@functions_framework.http
def stripe_webhook_handler(request):
    headers = {'Content-Type': 'application/json'}
    
    if request.method != 'POST':
        return (json.dumps({'error': 'Sadece POST kabul edilir'}), 405, headers)
    
    # İmza doğrulama
    sig_header = request.headers.get('Stripe-Signature')
    if not sig_header:
        return (json.dumps({'error': 'İmza eksik'}), 400, headers)
    
    payload = request.get_data(as_text=True)
    webhook_secret = get_stripe_secret()
    
    if not verify_stripe_signature(payload, sig_header, webhook_secret):
        return (json.dumps({'error': 'Geçersiz imza'}), 401, headers)
    
    # Event'i işle
    try:
        event = json.loads(payload)
        event_type = event.get('type')
        
        # Firestore'a kaydet
        db = firestore.Client()
        
        if event_type == 'payment_intent.succeeded':
            payment_intent = event['data']['object']
            handle_successful_payment(db, payment_intent)
            
        elif event_type == 'payment_intent.payment_failed':
            payment_intent = event['data']['object']
            handle_failed_payment(db, payment_intent)
        
        # Event'i Firestore'a logla
        db.collection('stripe_events').add({
            'event_id': event.get('id'),
            'type': event_type,
            'processed_at': firestore.SERVER_TIMESTAMP,
            'status': 'processed'
        })
        
        return (json.dumps({'received': True}), 200, headers)
    
    except json.JSONDecodeError:
        return (json.dumps({'error': 'Geçersiz JSON'}), 400, headers)
    except Exception as e:
        print(f"Webhook işleme hatası: {e}")
        return (json.dumps({'error': 'İşleme hatası'}), 500, headers)

def handle_successful_payment(db, payment_intent):
    """Başarılı ödeme işlemi"""
    order_id = payment_intent.get('metadata', {}).get('order_id')
    if order_id:
        db.collection('orders').document(order_id).update({
            'status': 'paid',
            'payment_intent_id': payment_intent['id'],
            'paid_at': firestore.SERVER_TIMESTAMP
        })

def handle_failed_payment(db, payment_intent):
    """Başarısız ödeme işlemi"""
    order_id = payment_intent.get('metadata', {}).get('order_id')
    if order_id:
        db.collection('orders').document(order_id).update({
            'status': 'payment_failed',
            'failure_reason': payment_intent.get('last_payment_error', {}).get('message'),
            'failed_at': firestore.SERVER_TIMESTAMP
        })
EOF

CI/CD ile Otomatik Deploy

Manuel deploy yapmak uzun vadede sürdürülebilir değil. GitHub Actions ile otomatik pipeline kuralım:

# .github/workflows/deploy-function.yml
cat > deploy-function.yml << 'EOF'
name: Deploy Cloud Function

on:
  push:
    branches:
      - main
    paths:
      - 'functions/**'
      - '.github/workflows/deploy-function.yml'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Python kur
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Bağımlılıkları kur
        run: |
          pip install -r functions/requirements.txt
          pip install pytest pytest-mock
      
      - name: Testleri çalıştır
        run: pytest functions/tests/ -v

  deploy:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: GCP Auth
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}
      
      - name: gcloud kur
        uses: google-github-actions/setup-gcloud@v2
      
      - name: Fonksiyonu deploy et
        run: |
          gcloud functions deploy http-demo-fonksiyon 
            --gen2 
            --runtime=python311 
            --region=europe-west1 
            --source=./functions 
            --entry-point=http_handler 
            --trigger-http 
            --no-allow-unauthenticated 
            --memory=256Mi 
            --timeout=60s 
            --min-instances=0 
            --max-instances=20 
            --set-env-vars=ENVIRONMENT=production,PROJECT_ID=${{ vars.GCP_PROJECT_ID }}
EOF

Monitoring ve Logging

Fonksiyonlarınızın sağlığını izlemek production’da hayati önem taşıyor:

# Son 1 saatin loglarını görüntüle
gcloud functions logs read http-demo-fonksiyon 
  --gen2 
  --region=europe-west1 
  --limit=50 
  --format="table(time_utc,severity,log_name,text_payload)"

# Gerçek zamanlı log izleme
gcloud alpha functions logs tail http-demo-fonksiyon 
  --gen2 
  --region=europe-west1

# Fonksiyon metriklerini sorgula (Cloud Monitoring)
gcloud monitoring metrics list 
  --filter="metric.type:cloudfunctions"

# Hata oranını kontrol et
gcloud logging read 
  'resource.type="cloud_function" 
   AND resource.labels.function_name="http-demo-fonksiyon" 
   AND severity>=ERROR' 
  --limit=20 
  --format=json

Yapılandırılmış Logging

Fonksiyon içinde structured logging kullanmak, Cloud Logging’de filtrelemeyi çok kolaylaştırır:

cat > logging_utils.py << 'EOF'
import json
import sys
import os

def log(severity, message, **kwargs):
    """
    Cloud Logging için yapılandırılmış log çıktısı.
    severity: DEBUG, INFO, WARNING, ERROR, CRITICAL
    """
    entry = {
        "severity": severity,
        "message": message,
        "component": os.environ.get('K_SERVICE', 'unknown'),
        **kwargs
    }
    print(json.dumps(entry, ensure_ascii=False), file=sys.stdout)

def log_info(message, **kwargs):
    log("INFO", message, **kwargs)

def log_error(message, **kwargs):
    log("ERROR", message, **kwargs)

def log_warning(message, **kwargs):
    log("WARNING", message, **kwargs)
EOF

Yaygın Sorunlar ve Çözümleri

Sysadmin olarak sık karşılaşılan sorunları bilmek çok değerli:

Cold Start Sorunu: Uzun süre istek almayan fonksiyonlar ilk istekte geç yanıt verebilir. Kritik fonksiyonlar için --min-instances=1 ayarı yaparak bir instance’ı her zaman sıcak tut. Ama bu maliyet demek, bunu göz önünde bulundur.

Timeout Ayarları: Varsayılan timeout 60 saniyedir. Uzun süren işlemler için maksimum 3600 saniyeye kadar çıkabilirsin. Ama uzun süren işlemler için Cloud Tasks veya Cloud Run daha uygun olabilir.

Memory ve CPU: Gen2’de memory ile CPU orantılı olarak artıyor. 256Mi için 0.167 vCPU, 2Gi için 1 vCPU alırsın. CPU yoğun işlemler için memory’yi artırmak mantıklı olabilir.

Environment Variables vs Secret Manager: Hassas bilgileri asla environment variable olarak koyma. API key, şifre, token gibi veriler için mutlaka Secret Manager kullan.

Bölge Seçimi: Kullanıcılarına en yakın bölgeyi seç. GDPR kapsamındaki veriler için EU bölgelerini (europe-west1, europe-west3) tercih et.

Maliyet Optimizasyonu

Cloud Functions serverless olduğu için maliyet kontrolü kritik:

# Mevcut fonksiyonların listesi ve detayları
gcloud functions list --gen2 --regions=europe-west1

# Bütçe alarmı oluştur (aylık 50$ limitinde uyar)
gcloud billing budgets create 
  --billing-account=BILLING_HESAP_ID 
  --display-name="Cloud Functions Butce" 
  --budget-amount=50USD 
  --threshold-rule=percent=80 
  --threshold-rule=percent=100

Maliyet optimizasyonu için dikkat etmen gerekenler:

  • İsteksiz bekleme: --min-instances=0 ile sıfır kullanımda sıfır maliyet
  • Uygun memory ayarı: Gereğinden fazla memory ayırmak direkt maliyet artışıdır
  • Timeout değeri: Makul bir timeout belirle, sonsuz döngülerin önüne geç
  • Verimli kod: Gereksiz bağımlılıkları temizle, cold start süresini kıs
  • Tekrar eden bağlantılar: Veritabanı bağlantılarını global scope’ta tut, her istekte yeniden oluşturma

Sonuç

GCP Cloud Functions ile HTTP fonksiyon oluşturmak, modern uygulama geliştirmenin temel taşlarından biri haline geldi. Bu yazıda ele aldığımız konuları özetlemek gerekirse; ortam hazırlığından başlayıp, güvenli fonksiyon tasarımı, gerçek dünya senaryoları (Stripe webhook örneği gibi), CI/CD entegrasyonu, monitoring ve maliyet optimizasyonuna kadar uçtan uca bir tablo çizdik.

En kritik noktaların altını çizelim: Kimlik doğrulamasını asla atlama, Secret Manager’ı aktif kullan, structured logging ile operasyonel görünürlüğünü artır ve maliyet alarmlarını baştan kur. Gen2 fonksiyonları artık varsayılan tercih olmalı, Cloud Run’ın gücünü arkana alıyorsun.

Serverless demek “sunucusuz değil, sunucudan habersiz” demek. Altyapı hala orada, sadece sen yönetmiyorsun. Bu yüzden GCP’nin senin adına ne yaptığını anlamak, sorunları daha hızlı çözmen için kritik. Fonksiyonlarını küçük, odaklı ve test edilebilir tut. Her fonksiyon tek bir işi iyi yapmalı, geri kalanını başka servislere veya fonksiyonlara bırak. Bu prensiple inşa edilen sistemler hem bakımı kolay hem de hataya dayanıklı olur.

Bir yanıt yazın

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