GCP Cloud Functions ile Serverless Fonksiyon Oluşturma
Sunucu yönetmek güzel bir şeydir; ta ki o sunucunun bakımını, güncellemelerini, yamalarını ve ölçeklendirmesini düşünmeye başlayana kadar. İşte tam bu noktada serverless mimarisi hayat kurtarıcı oluyor. Google Cloud Functions, altyapıyla uğraşmadan kod yazıp çalıştırmanı sağlayan, GCP’nin en güçlü serverless çözümlerinden biri. Bu yazıda Cloud Functions’ı sıfırdan kuracağız, gerçek dünya senaryolarıyla pekiştireceğiz ve production ortamında dikkat etmen gereken her şeyi ele alacağız.
Cloud Functions Nedir ve Ne Zaman Kullanmalısın?
Cloud Functions, olay güdümlü (event-driven) bir serverless platformdur. Bir HTTP isteği geldiğinde, bir Pub/Sub mesajı yayınlandığında, Cloud Storage’a dosya yüklendiğinde ya da Firebase olayları tetiklendiğinde senin fonksiyonun çalışır, işini yapar ve durur. Arkada çalışan bir VM yok, yönetmen gereken bir container yok, ölçeklendirme için uğraşman gereken bir config yok.
Ne zaman kullanmalısın?
- Webhook alıcıları yazmak istediğinde (Stripe, GitHub, Slack gibi servislerin callback’leri)
- Periyodik görevler için cron job alternatifleri oluştururken
- Mikroservis mimarisinde küçük, bağımsız iş parçaları tanımlarken
- ETL pipeline’larında veri dönüştürme adımları için
- API Gateway arkasında basit backend mantığı çalıştırmak istediğinde
Ne zaman kullanmamalısın?
- Uzun süren işlemler için (Cloud Functions maksimum 60 dakika timeout’a sahip, ama bu bile ideal değil uzun işler için)
- Kalıcı durum tutman gereken senaryolarda
- Yüksek frekanslı, düşük latency gerektiren workload’larda (cold start problemi var)
Ortam Hazırlığı ve gcloud CLI Kurulumu
Başlamadan önce gcloud CLI’nin kurulu ve konfigüre edilmiş olması gerekiyor.
# gcloud CLI kurulumu (Debian/Ubuntu)
curl https://sdk.cloud.google.com | bash
exec -l $SHELL
# Kimlik doğrulama
gcloud auth login
# Proje seçimi
gcloud config set project YOUR_PROJECT_ID
# Gerekli API'lerin aktifleştirilmesi
gcloud services enable cloudfunctions.googleapis.com
gcloud services enable cloudbuild.googleapis.com
gcloud services enable artifactregistry.googleapis.com
# Mevcut konfigürasyonu doğrula
gcloud config list
Cloud Functions 2. nesli (Gen 2) artık Google’ın önerdiği versiyon. Gen 2, Cloud Run altyapısı üzerine inşa edildiği için çok daha güçlü. Bu yazıda ağırlıklı olarak Gen 2 üzerinde çalışacağız.
İlk Cloud Function: Basit HTTP Trigger
En temel senaryodan başlayalım. Bir HTTP isteği alıp JSON dönen basit bir fonksiyon.
# Proje dizinini oluştur
mkdir -p ~/gcp-functions/hello-http
cd ~/gcp-functions/hello-http
main.py dosyasını oluştur:
cat > main.py << 'EOF'
import functions_framework
import json
from datetime import datetime
@functions_framework.http
def hello_http(request):
"""HTTP Cloud Function."""
# CORS başlıkları (gerekirse)
if request.method == 'OPTIONS':
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
headers = {'Access-Control-Allow-Origin': '*'}
# Request body parse et
request_json = request.get_json(silent=True)
request_args = request.args
if request_json and 'name' in request_json:
name = request_json['name']
elif request_args and 'name' in request_args:
name = request_args['name']
else:
name = 'Misafir'
response_data = {
'message': f'Merhaba, {name}!',
'timestamp': datetime.utcnow().isoformat(),
'status': 'success'
}
return (json.dumps(response_data, ensure_ascii=False), 200, headers)
EOF
requirements.txt dosyasını oluştur:
cat > requirements.txt << 'EOF'
functions-framework==3.*
EOF
Şimdi fonksiyonu deploy edelim:
gcloud functions deploy hello-http
--gen2
--runtime=python311
--region=europe-west1
--source=.
--entry-point=hello_http
--trigger-http
--allow-unauthenticated
--memory=256MB
--timeout=30s
--min-instances=0
--max-instances=10
Deploy sonrası fonksiyonu test edelim:
# Fonksiyonun URL'ini al
FUNCTION_URL=$(gcloud functions describe hello-http
--gen2
--region=europe-west1
--format='value(serviceConfig.uri)')
echo "Function URL: $FUNCTION_URL"
# GET isteği ile test
curl -s "$FUNCTION_URL?name=Ahmet" | python3 -m json.tool
# POST isteği ile test
curl -s -X POST
-H "Content-Type: application/json"
-d '{"name": "Mehmet"}'
"$FUNCTION_URL" | python3 -m json.tool
Pub/Sub Trigger ile Asenkron Fonksiyon
Gerçek dünyada çoğu zaman event-driven mimariye ihtiyaç duyarsın. Örneğin bir sipariş geldiğinde e-posta göndermek, bir dosya yüklendiğinde işlemek gibi senaryolar. Pub/Sub trigger bunun için biçilmiş kaftan.
# Önce Pub/Sub topic oluştur
gcloud pubsub topics create siparis-olaylari
# Fonksiyon dizini
mkdir -p ~/gcp-functions/siparis-isleme
cd ~/gcp-functions/siparis-isleme
main.py:
cat > main.py << 'EOF'
import functions_framework
import base64
import json
import logging
# Logging konfigürasyonu
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@functions_framework.cloud_event
def siparis_isle(cloud_event):
"""Pub/Sub mesajı geldiğinde sipariş işleme fonksiyonu."""
# Pub/Sub mesajını decode et
pubsub_message = base64.b64decode(
cloud_event.data["message"]["data"]
).decode('utf-8')
try:
siparis = json.loads(pubsub_message)
logger.info(f"Sipariş alındı: {siparis.get('siparis_id', 'bilinmiyor')}")
# Sipariş doğrulama
gerekli_alanlar = ['siparis_id', 'musteri_id', 'urunler', 'toplam']
for alan in gerekli_alanlar:
if alan not in siparis:
logger.error(f"Eksik alan: {alan}")
return
# İş mantığı buraya gelir
# Örneğin: veritabanına kaydet, e-posta gönder, vs.
logger.info(
f"Sipariş işlendi - ID: {siparis['siparis_id']}, "
f"Musteri: {siparis['musteri_id']}, "
f"Toplam: {siparis['toplam']} TL"
)
# Başarılı işlem bildirimi (örn: başka bir topic'e yayınla)
logger.info("Sipariş başarıyla işlendi.")
except json.JSONDecodeError as e:
logger.error(f"JSON parse hatası: {e}")
except Exception as e:
logger.error(f"Beklenmeyen hata: {e}")
raise # Retry mekanizması için exception'ı yeniden fırlat
EOF
cat > requirements.txt << 'EOF'
functions-framework==3.*
EOF
Deploy:
gcloud functions deploy siparis-isleme
--gen2
--runtime=python311
--region=europe-west1
--source=.
--entry-point=siparis_isle
--trigger-topic=siparis-olaylari
--memory=512MB
--timeout=120s
--retry
Test için Pub/Sub’a mesaj gönder:
# Test mesajı gönder
gcloud pubsub topics publish siparis-olaylari
--message='{"siparis_id": "ORD-001", "musteri_id": "USR-123", "urunler": ["kitap", "kalem"], "toplam": 150.50}'
# Log'ları kontrol et
gcloud functions logs read siparis-isleme
--gen2
--region=europe-west1
--limit=20
Cloud Storage Trigger: Görsel İşleme Senaryosu
Kullanıcılar profil fotoğrafı yüklediğinde otomatik olarak thumbnail oluşturan bir sistem düşün. Bu tam anlamıyla Cloud Functions’ın parladığı bir senaryo.
# Storage bucket oluştur
gsutil mb -l europe-west1 gs://PROJE_ADI-fotograflar
gsutil mb -l europe-west1 gs://PROJE_ADI-thumbnail
mkdir -p ~/gcp-functions/gorsel-isle
cd ~/gcp-functions/gorsel-isle
main.py:
cat > main.py << 'EOF'
import functions_framework
from google.cloud import storage
from PIL import Image
import io
import logging
logger = logging.getLogger(__name__)
storage_client = storage.Client()
THUMBNAIL_BOYUT = (200, 200)
THUMBNAIL_BUCKET = "PROJE_ADI-thumbnail"
@functions_framework.cloud_event
def gorsel_isle(cloud_event):
"""Yeni yüklenen görseller için thumbnail oluştur."""
data = cloud_event.data
bucket_adi = data["bucket"]
dosya_adi = data["name"]
# Sadece görsel dosyaları işle
izin_verilen_uzantilar = ['.jpg', '.jpeg', '.png', '.webp']
if not any(dosya_adi.lower().endswith(ext) for ext in izin_verilen_uzantilar):
logger.info(f"Görsel değil, atlanıyor: {dosya_adi}")
return
# Thumbnail klasörüne düşen dosyaları tekrar işleme
if dosya_adi.startswith('thumb_'):
logger.info("Thumbnail dosyası, döngü önlendi.")
return
logger.info(f"İşleniyor: gs://{bucket_adi}/{dosya_adi}")
try:
# Orijinal dosyayı indir
kaynak_bucket = storage_client.bucket(bucket_adi)
blob = kaynak_bucket.blob(dosya_adi)
gorsel_veri = blob.download_as_bytes()
# Thumbnail oluştur
gorsel = Image.open(io.BytesIO(gorsel_veri))
gorsel.thumbnail(THUMBNAIL_BOYUT, Image.Resampling.LANCZOS)
# Thumbnail'i byte'a çevir
cikti = io.BytesIO()
format_adi = gorsel.format if gorsel.format else 'JPEG'
gorsel.save(cikti, format=format_adi, optimize=True, quality=85)
cikti.seek(0)
# Thumbnail'i hedef bucket'a yükle
hedef_bucket = storage_client.bucket(THUMBNAIL_BUCKET)
thumb_adi = f"thumb_{dosya_adi}"
hedef_blob = hedef_bucket.blob(thumb_adi)
hedef_blob.upload_from_file(cikti, content_type=blob.content_type)
logger.info(f"Thumbnail oluşturuldu: gs://{THUMBNAIL_BUCKET}/{thumb_adi}")
except Exception as e:
logger.error(f"Hata: {dosya_adi} - {e}")
raise
EOF
cat > requirements.txt << 'EOF'
functions-framework==3.*
google-cloud-storage==2.*
Pillow==10.*
EOF
Deploy:
gcloud functions deploy gorsel-isle
--gen2
--runtime=python311
--region=europe-west1
--source=.
--entry-point=gorsel_isle
--trigger-event-filters="type=google.cloud.storage.object.v1.finalized"
--trigger-event-filters="bucket=PROJE_ADI-fotograflar"
--memory=1GB
--timeout=120s
Environment Variables ve Secret Manager Kullanımı
Production ortamında API anahtarlarını, veritabanı şifrelerini doğrudan kod içine yazmak büyük bir güvenlik açığı. Secret Manager kullanmak şart.
# Secret Manager API'yi aktifleştir
gcloud services enable secretmanager.googleapis.com
# Secret oluştur
echo -n "super-gizli-api-anahtari-123" |
gcloud secrets create EXTERNAL_API_KEY
--replication-policy="automatic"
--data-file=-
# Service account'a secret erişim izni ver
SA_EMAIL=$(gcloud functions describe hello-http
--gen2
--region=europe-west1
--format='value(serviceConfig.serviceAccountEmail)')
gcloud secrets add-iam-policy-binding EXTERNAL_API_KEY
--member="serviceAccount:$SA_EMAIL"
--role="roles/secretmanager.secretAccessor"
Secret Manager kullanan fonksiyon:
cat > main.py << 'EOF'
import functions_framework
from google.cloud import secretmanager
import os
import json
def secret_al(secret_adi, project_id=None):
"""Secret Manager'dan secret değeri al."""
if not project_id:
project_id = os.environ.get('GOOGLE_CLOUD_PROJECT')
client = secretmanager.SecretManagerServiceClient()
secret_yol = f"projects/{project_id}/secrets/{secret_adi}/versions/latest"
response = client.access_secret_version(request={"name": secret_yol})
return response.payload.data.decode("UTF-8")
# Cold start'ta bir kez yükle (performans optimizasyonu)
_api_key = None
def get_api_key():
global _api_key
if _api_key is None:
_api_key = secret_al("EXTERNAL_API_KEY")
return _api_key
@functions_framework.http
def guvenli_api(request):
"""Secret Manager'dan alınan key ile dış API çağrısı."""
try:
api_key = get_api_key()
# Environment variable'dan da değer okuma örneği
ortam = os.environ.get('ENVIRONMENT', 'development')
return json.dumps({
'status': 'success',
'environment': ortam,
'api_key_length': len(api_key) # Key'i asla dışarı verme!
}), 200
except Exception as e:
return json.dumps({'error': str(e)}), 500
EOF
cat > requirements.txt << 'EOF'
functions-framework==3.*
google-cloud-secret-manager==2.*
EOF
Deploy ederken environment variable eklemek:
gcloud functions deploy guvenli-api
--gen2
--runtime=python311
--region=europe-west1
--source=.
--entry-point=guvenli_api
--trigger-http
--allow-unauthenticated
--set-env-vars="ENVIRONMENT=production"
--memory=256MB
Lokal Geliştirme ve Test
Production’a her değişikliği deploy etmek hem zaman kaybı hem de gereksiz maliyet. Lokal test ortamı kurmak hayat kurtarır.
# functions-framework kur
pip install functions-framework
# Lokal sunucu başlat
cd ~/gcp-functions/hello-http
functions-framework --target=hello_http --port=8080 --debug
# Başka terminal'de test et
curl -X POST
-H "Content-Type: application/json"
-d '{"name": "Test Kullanicisi"}'
http://localhost:8080
Unit test yazımı:
cat > test_main.py << 'EOF'
import unittest
from unittest.mock import MagicMock
import json
import sys
sys.path.insert(0, '.')
from main import hello_http
class TestHelloHttp(unittest.TestCase):
def _mock_request(self, json_data=None, args=None, method='POST'):
mock_req = MagicMock()
mock_req.method = method
mock_req.get_json = MagicMock(return_value=json_data)
mock_req.args = args or {}
return mock_req
def test_json_body_ile_istek(self):
request = self._mock_request(json_data={'name': 'Ahmet'})
response, status_code, _ = hello_http(request)
data = json.loads(response)
self.assertEqual(status_code, 200)
self.assertIn('Ahmet', data['message'])
def test_isimsiz_istek(self):
request = self._mock_request(json_data={})
response, status_code, _ = hello_http(request)
data = json.loads(response)
self.assertEqual(status_code, 200)
self.assertIn('Misafir', data['message'])
def test_options_cors(self):
request = self._mock_request(method='OPTIONS')
result = hello_http(request)
self.assertEqual(result[1], 204)
if __name__ == '__main__':
unittest.main()
EOF
# Testleri çalıştır
python -m pytest test_main.py -v
Monitoring, Logging ve Alerting
Production’da görmezden gelinen ama kritik olan kısım bu. Fonksiyonun çalışıyor olması yeterli değil, sağlıklı çalışıyor olması lazım.
# Son 1 saatin loglarını getir
gcloud functions logs read hello-http
--gen2
--region=europe-west1
--limit=50
--format='table(time_utc, severity, log)'
# Hata loglarını filtrele
gcloud logging read
'resource.type="cloud_run_revision" AND severity>=ERROR'
--freshness=1h
--format='value(textPayload)'
--limit=20
# Fonksiyon metriklerini görüntüle
gcloud monitoring metrics list
--filter="metric.type:cloudfunctions"
Alert policy oluşturmak için:
# Hata oranı için alert (gcloud ile basit versiyon)
gcloud alpha monitoring policies create
--notification-channels="CHANNEL_ID"
--display-name="Cloud Function Hata Alarmı"
--condition-display-name="Hata oranı yüksek"
--condition-filter='resource.type="cloud_function" AND metric.type="cloudfunctions.googleapis.com/function/execution_count" AND metric.labels.status="error"'
--condition-threshold-value=10
--condition-threshold-comparison=COMPARISON_GT
--condition-duration=60s
Maliyet Optimizasyonu ve Best Practice’ler
Cloud Functions kulağa ücretsizmiş gibi gelir ama dikkat etmezsen fatura sürpriz yapabilir.
Min/Max instance ayarları:
- –min-instances=0: Sıfır maliyet ama cold start yaşarsın
- –min-instances=1: Cold start yok ama sürekli bir instance çalışıyor
- –max-instances=100: Kontrolsüz ölçeklemeyi engeller, maliyet patlamasını önler
Cold start süresini azaltmak için:
- Küresel değişkenleri fonksiyon dışında tanımla (connection pooling, client başlatma)
- Gereksiz kütüphane import’larından kaçın
requirements.txt‘i minimumda tut
Memory ve CPU ilişkisi: Gen 2’de CPU, memory ile doğru orantılı artar. 256MB bellek az CPU anlamına gelir. CPU-intensive işler için 1GB veya 2GB tercih et.
# Mevcut fonksiyonların maliyetini tahmin etmek için
gcloud functions list
--gen2
--regions=europe-west1
--format='table(name, serviceConfig.availableMemory, serviceConfig.minInstanceCount, serviceConfig.maxInstanceCount)'
# Kullanılmayan fonksiyonları temizle
gcloud functions delete eski-fonksiyon
--gen2
--region=europe-west1
--quiet
Güvenlik best practice’leri:
- –no-allow-unauthenticated: Herkese açık olmayan fonksiyonlar için mutlaka kullan
- IAM tabanlı erişim: Servisten servise çağrılarda service account kullan
- VPC Connector: Private kaynaklara erişim için fonksiyonu VPC’ye bağla
- Secret Manager: Tüm gizli bilgileri environment variable yerine Secret Manager’da sakla
- Minimum yetki prensibini uygula, service account’lara sadece gerekli rolleri ver
# Authenticated endpoint test (token ile)
TOKEN=$(gcloud auth print-identity-token)
curl -H "Authorization: Bearer $TOKEN" https://FUNCTION_URL
CI/CD Pipeline ile Otomatik Deploy
Manuel deploy yapmak hata riski yaratır. GitHub Actions ile otomatik pipeline kurmak standart pratik olmalı.
cat > .github/workflows/deploy.yml << 'EOF'
name: Deploy Cloud Function
on:
push:
branches: [main]
paths:
- 'functions/hello-http/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Google Auth
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS }}
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@v2
- name: Run Tests
run: |
cd functions/hello-http
pip install -r requirements.txt
python -m pytest test_main.py -v
- name: Deploy Function
run: |
gcloud functions deploy hello-http
--gen2
--runtime=python311
--region=europe-west1
--source=functions/hello-http
--entry-point=hello_http
--trigger-http
--memory=256MB
--timeout=30s
--set-env-vars="ENVIRONMENT=production"
--quiet
EOF
Sonuç
Cloud Functions, doğru kullanıldığında inanılmaz güçlü bir araç. Sunucu yönetiminden tamamen kurtuluyorsun, sadece iş mantığına odaklanıyorsun. Bu yazıda HTTP trigger’dan Pub/Sub’a, Storage event’lerinden güvenlik pratiklerine kadar geniş bir yelpazede konuları ele aldık.
Gözden kaçırmaman gereken ana noktaları toparlayalım:
- Gen 2’yi tercih et: Gen 1 hala çalışıyor ama Gen 2 çok daha iyi performans ve Cloud Run altyapısının tüm avantajlarını sunuyor
- Lokal test şart: Her değişikliği production’a pushlama,
functions-frameworkile lokal test et - Secret Manager kullan: Environment variable’lara gizli bilgi koyma, bir gün log’lara sızar
- Min/Max instance’ı düşün: Sıfır maliyet mi yoksa sıfır cold start mı, kullanım durumuna göre karar ver
- Log’ları takip et: Cloud Logging ve Monitoring olmadan kör uçuyorsun
- CI/CD kur: Manuel deploy hata doğurur, pipeline yatırımı karşılığını hızlı verir
İlk fonksiyonu deploy ettiğinde “bu kadar mı kolaydı” diyeceksin. Ondan sonrası zaten çığ gibi büyüyor. Bir sonraki adım olarak Cloud Workflows ile birden fazla fonksiyonu orkestre etmeyi veya Cloud Scheduler ile zamanlanmış görevler oluşturmayı inceleyebilirsin.
