GCP Cloud Storage ile CDN Entegrasyonu: Adım Adım Kurulum Rehberi

Bir web uygulaması deploy ettiğinizde ve trafiğin patladığını gördüğünüzde, ilk refleks genellikle sunucu kapasitesini artırmak oluyor. Oysa çoğu zaman asıl sorun statik varlıkların (resimler, CSS, JS dosyaları, videolar) kullanıcıya coğrafi olarak uzak bir noktadan servis edilmesidir. GCP Cloud Storage ile Cloud CDN’i entegre etmek bu sorunu kökten çözer ve aynı zamanda origin sunucunuzdaki yükü dramatik biçimde düşürür.

Neden Cloud Storage + CDN Kombinasyonu?

Geleneksel yaklaşımda statik dosyalar uygulama sunucusundan servis edilir. Bu hem gereksiz CPU/memory kullanımı yaratır hem de farklı kıtalardaki kullanıcılar için latency sorununa yol açar. GCP’nin bu ikilisi şu avantajları sunar:

  • Global edge noktaları: Google’ın 100’den fazla PoP (Point of Presence) lokasyonu sayesinde kullanıcılar en yakın cache noktasından içerik alır
  • Maliyet optimizasyonu: Cache hit oranı yükseldikçe Cloud Storage egress maliyeti düşer
  • SSL/TLS yönetimi: Managed sertifikalar ile ekstra iş yükü olmadan HTTPS
  • HTTP/2 ve HTTP/3 desteği: Otomatik protokol optimizasyonu
  • Cloud Armor entegrasyonu: DDoS koruması CDN katmanında devreye girer

Bir e-ticaret projesinde bu kombinasyonu uyguladıktan sonra ortalama sayfa yükleme süresinin 4.2 saniyeden 0.9 saniyeye düştüğünü gördüm. Tek değişiklik buydu, backend’e tek satır dokunmadık.

Ön Koşullar ve Hazırlık

Başlamadan önce şunların hazır olması gerekiyor:

  • GCP projesi aktif ve billing etkinleştirilmiş olmalı
  • gcloud CLI kurulu ve authenticate edilmiş olmalı
  • Compute Engine API ve Cloud CDN API etkinleştirilmiş olmalı

API’leri etkinleştirmek için:

gcloud services enable compute.googleapis.com
gcloud services enable storage.googleapis.com
gcloud services enable certificatemanager.googleapis.com

Proje ve region değişkenlerini tanımlayalım, bunları ileride tekrar tekrar kullanacağız:

export PROJECT_ID="my-production-project"
export BUCKET_NAME="my-static-assets-prod"
export BACKEND_BUCKET_NAME="my-cdn-backend"
export URL_MAP_NAME="my-cdn-url-map"
export LB_NAME="my-cdn-lb"
export IP_NAME="my-cdn-ip"
export CERT_NAME="my-ssl-cert"
export DOMAIN="cdn.myapp.com"

gcloud config set project $PROJECT_ID

Cloud Storage Bucket Oluşturma ve Yapılandırma

CDN’e bağlanacak bucket’ı oluştururken dikkat edilmesi gereken birkaç nokta var. Bucket’ın multi-region olması genellikle daha iyi availability sağlar ama maliyeti artırır. Tek bir region’da bile CDN edge cache sayesinde global performans elde edebilirsiniz.

# Multi-region bucket oluşturma (EU veya US veya ASIA)
gsutil mb -p $PROJECT_ID 
  -c STANDARD 
  -l EU 
  --pap enforced 
  gs://$BUCKET_NAME

# Uniform bucket-level access aktif et (legacy ACL kullanma)
gsutil uniformbucketlevelaccess set on gs://$BUCKET_NAME

Şimdi bucket’ı public erişime açıyoruz. CDN’in dosyaları okuyabilmesi için bu şart:

# Tüm kullanıcılara okuma yetkisi ver
gsutil iam ch allUsers:objectViewer gs://$BUCKET_NAME

Güvenlik notu: allUsers:objectViewer yetkisi bucket’taki tüm dosyaları herkese açar. Hassas dosyaların bu bucket’ta tutulmaması gerekiyor. Erişim kısıtlaması gerekiyorsa Cloud Armor veya signed URL kullanmalısınız.

Birkaç test dosyası yükleyelim ve cache control header’larını doğru ayarlayalım:

# Örnek statik dosyalar oluştur
mkdir -p /tmp/static-test/{css,js,images}
echo "body { margin: 0; }" > /tmp/static-test/css/main.css
echo "console.log('app loaded');" > /tmp/static-test/js/app.js

# Cache-Control header ile yükleme
# Statik varlıklar için 1 yıl (immutable dosyalar için)
gsutil -h "Cache-Control:public, max-age=31536000, immutable" 
  cp /tmp/static-test/css/main.css gs://$BUCKET_NAME/css/

gsutil -h "Cache-Control:public, max-age=31536000, immutable" 
  cp /tmp/static-test/js/app.js gs://$BUCKET_NAME/js/

# HTML dosyaları için daha kısa TTL
gsutil -h "Cache-Control:public, max-age=300, must-revalidate" 
  cp /tmp/static-test/index.html gs://$BUCKET_NAME/ 2>/dev/null || true

Load Balancer ve Backend Bucket Yapılandırması

Cloud CDN, Google Cloud Load Balancing üzerinden çalışır. Yani bir HTTP(S) Load Balancer oluşturmanız ve bunu bucket’a bağlamanız gerekiyor. Bu adım biraz uzun ama bir kez kurulunca dokunmuyorsunuz.

# 1. Static IP rezerve et
gcloud compute addresses create $IP_NAME 
  --network-tier=PREMIUM 
  --ip-version=IPV4 
  --global

# IP adresini öğren
RESERVED_IP=$(gcloud compute addresses describe $IP_NAME 
  --global 
  --format="get(address)")
echo "Rezerve edilen IP: $RESERVED_IP"

# 2. Backend bucket oluştur ve CDN'i aktif et
gcloud compute backend-buckets create $BACKEND_BUCKET_NAME 
  --gcs-bucket-name=$BUCKET_NAME 
  --enable-cdn 
  --cache-mode=CACHE_ALL_STATIC 
  --default-ttl=3600 
  --max-ttl=86400 
  --client-ttl=3600 
  --custom-response-header="X-Cache-Status: {cdn_cache_status}" 
  --custom-response-header="Strict-Transport-Security: max-age=31536000"

Cache mode seçenekleri önemli, ne işe yaradıklarını açıklayayım:

  • CACHE_ALL_STATIC: HTML dışındaki statik varlıkları (CSS, JS, resim, font) otomatik cache’ler
  • FORCE_CACHE_ALL: Cache-Control header’ını görmezden gelerek her şeyi cache’ler (dikkatli kullanın)
  • USE_ORIGIN_HEADERS: Sadece origin’in söylediği TTL’e göre cache’ler
# 3. URL map oluştur
gcloud compute url-maps create $URL_MAP_NAME 
  --default-backend-bucket=$BACKEND_BUCKET_NAME

# 4. SSL sertifikası oluştur (managed certificate)
gcloud compute ssl-certificates create $CERT_NAME 
  --domains=$DOMAIN 
  --global

# 5. HTTPS proxy oluştur
gcloud compute target-https-proxies create ${LB_NAME}-https-proxy 
  --url-map=$URL_MAP_NAME 
  --ssl-certificates=$CERT_NAME 
  --global

# 6. HTTP proxy oluştur (HTTP -> HTTPS redirect için)
gcloud compute target-http-proxies create ${LB_NAME}-http-proxy 
  --url-map=$URL_MAP_NAME 
  --global

# 7. Forwarding rule'ları oluştur
gcloud compute forwarding-rules create ${LB_NAME}-https-rule 
  --address=$IP_NAME 
  --global 
  --target-https-proxy=${LB_NAME}-https-proxy 
  --ports=443

gcloud compute forwarding-rules create ${LB_NAME}-http-rule 
  --address=$IP_NAME 
  --global 
  --target-http-proxy=${LB_NAME}-http-proxy 
  --ports=80

DNS kaydınızı rezerve ettiğiniz IP’ye yönlendirin. Managed certificate’in provision edilmesi DNS propagasyonu tamamlandıktan sonra 10-15 dakika alabilir.

HTTP’den HTTPS’e Yönlendirme

HTTP isteğini doğrudan backend’e göndermek yerine HTTPS’e redirect etmek daha güvenli bir pratik. Bunun için ayrı bir URL map oluşturalım:

# HTTP redirect URL map
gcloud compute url-maps import ${URL_MAP_NAME}-http-redirect 
  --global 
  --source /dev/stdin << 'EOF'
name: my-cdn-url-map-http-redirect
defaultUrlRedirect:
  redirectResponseCode: MOVED_PERMANENTLY_DEFAULT
  httpsRedirect: true
EOF

# HTTP proxy'yi bu yeni map'e bağla
gcloud compute target-http-proxies update ${LB_NAME}-http-proxy 
  --url-map=${URL_MAP_NAME}-http-redirect 
  --global

Cache Invalidation (Önbellek Temizleme)

Yeni bir deployment yaptığınızda eski dosyaların cache’den temizlenmesi gerekebilir. Bu işlemi dikkatli yapmalısınız çünkü global invalidation tüm edge node’larını etkiler ve maliyeti olabilir.

# Belirli bir dosyayı invalidate et
gcloud compute url-maps invalidate-cdn-cache $URL_MAP_NAME 
  --path="/css/main.css" 
  --global

# Wildcard ile bir dizini temizle
gcloud compute url-maps invalidate-cdn-cache $URL_MAP_NAME 
  --path="/js/*" 
  --global

# Tüm cache'i temizle (dikkatli kullanın, origin load artacak)
gcloud compute url-maps invalidate-cdn-cache $URL_MAP_NAME 
  --path="/*" 
  --global

Pratik öneri: Cache invalidation yerine dosya adlarına hash eklemek çok daha iyi bir yaklaşım. main.css yerine main.a3f2c1.css gibi. Bu sayede cache’i temizlemenize gerek kalmaz, eski dosyalar expire olana kadar servis edilir, yeni dosyalar hemen devreye girer.

Monitoring ve Logging Kurulumu

CDN’i deploy ettikten sonra ne oluyor görmek için monitoring şart. Cloud CDN logları Cloud Logging’e yazılır.

# CDN cache hit/miss metriklerini sorgula
gcloud logging read 
  'resource.type="http_load_balancer" AND
   jsonPayload.cacheHit=true' 
  --limit=50 
  --format="table(timestamp, jsonPayload.requestUrl, jsonPayload.cacheHit)"

# Cache miss oranını hesapla (son 1 saat)
gcloud logging read 
  'resource.type="http_load_balancer"
   timestamp>="2024-01-01T00:00:00Z"' 
  --limit=1000 
  --format=json | 
  python3 -c "
import json, sys
data = json.load(sys.stdin)
total = len(data)
hits = sum(1 for r in data if r.get('jsonPayload', {}).get('cacheHit'))
print(f'Total: {total}, Hits: {hits}, Miss: {total-hits}')
print(f'Hit Rate: {hits/total*100:.1f}%' if total > 0 else 'No data')
"

Alerting için bir Cloud Monitoring policy oluşturalım, cache hit oranı düşerse bildirim gelsin:

# Alert policy JSON dosyası oluştur
cat > /tmp/cdn-alert-policy.json << 'EOF'
{
  "displayName": "CDN Cache Hit Rate Low",
  "conditions": [
    {
      "displayName": "Cache hit rate below 70%",
      "conditionThreshold": {
        "filter": "resource.type="https_lb_rule" AND metric.type="loadbalancing.googleapis.com/https/request_count"",
        "comparison": "COMPARISON_LT",
        "thresholdValue": 0.7,
        "duration": "300s",
        "aggregations": [
          {
            "alignmentPeriod": "60s",
            "perSeriesAligner": "ALIGN_RATE"
          }
        ]
      }
    }
  ],
  "alertStrategy": {
    "notificationRateLimit": {
      "period": "3600s"
    }
  }
}
EOF

gcloud alpha monitoring policies create 
  --policy-from-file=/tmp/cdn-alert-policy.json

Gerçek Dünya Senaryosu: Deployment Scriptinin Otomatikleştirilmesi

Bir frontend ekibiyle çalışırken her build sonrası statik varlıkların CDN’e otomatik push edilmesi gerekiyordu. Şu script’i CI/CD pipeline’ına entegre ettik:

#!/bin/bash
# deploy-to-cdn.sh

set -euo pipefail

BUCKET_NAME="${CDN_BUCKET:-my-static-assets-prod}"
BUILD_DIR="${BUILD_OUTPUT:-./dist}"
URL_MAP_NAME="${CDN_URL_MAP:-my-cdn-url-map}"
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")

echo "==> Deploying build $COMMIT_HASH to CDN bucket: $BUCKET_NAME"

# Versiyonlanmış varlıklar (hash'li dosyalar) - uzun TTL
echo "==> Uploading versioned assets (1 year cache)..."
gsutil -m -h "Cache-Control:public, max-age=31536000, immutable" 
  rsync -r -c -x ".*.html$" 
  "$BUILD_DIR/" 
  "gs://$BUCKET_NAME/"

# HTML dosyaları - kısa TTL
echo "==> Uploading HTML files (5 min cache)..."
gsutil -m -h "Cache-Control:public, max-age=300, must-revalidate" 
  rsync -r -c -x ".*.(css|js|png|jpg|svg|woff2)$" 
  "$BUILD_DIR/" 
  "gs://$BUCKET_NAME/"

# HTML dosyalarını invalidate et (sadece HTML, diğerleri zaten hash'li)
echo "==> Invalidating HTML cache..."
gcloud compute url-maps invalidate-cdn-cache "$URL_MAP_NAME" 
  --path="/*.html" 
  --global

echo "==> Deployment complete! Commit: $COMMIT_HASH"
echo "==> CDN URL: https://$DOMAIN"

Bu script birkaç önemli şey yapıyor: rsync komutu değişmeyen dosyaları tekrar yüklemiyor (-c flag’i checksum karşılaştırması yapar), HTML dışındaki dosyalar hash’li olduğu için 1 yıl cache’lenebilir, sadece HTML’ler invalidate ediliyor.

Signed URL ile Özel İçerik Koruması

Bazı dosyaları herkese açmak istemeyebilirsiniz. Premium içerik, kullanıcıya özel raporlar veya geçici indirme linkleri için Signed URL kullanılır:

# Service account oluştur
gcloud iam service-accounts create cdn-signed-url-sa 
  --display-name="CDN Signed URL Service Account"

# Key oluştur
gcloud iam service-accounts keys create /tmp/cdn-sa-key.json 
  --iam-account=cdn-signed-url-sa@$PROJECT_ID.iam.gserviceaccount.com

# Bucket'a okuma yetkisi ver (allUsers yerine service account)
gsutil iam ch 
  serviceAccount:cdn-signed-url-sa@$PROJECT_ID.iam.gserviceaccount.com:objectViewer 
  gs://$BUCKET_NAME

# 1 saatlik signed URL oluştur
gsutil signurl 
  -d 1h 
  -m GET 
  /tmp/cdn-sa-key.json 
  gs://$BUCKET_NAME/premium/report.pdf

Python backend’inizden programatik olarak signed URL üretmek istiyorsanız google-cloud-storage kütüphanesi bunu kolaylaştırıyor. Expiry süresini uygulamanızın ihtiyacına göre ayarlayın, genellikle 15-60 dakika makul bir süre.

Yaygın Sorunlar ve Çözümleri

CORS Hatası: Browser’dan farklı domain’den bucket’a istek atılıyorsa CORS ayarlanmalı:

cat > /tmp/cors-config.json << 'EOF'
[
  {
    "origin": ["https://myapp.com", "https://www.myapp.com"],
    "method": ["GET", "HEAD"],
    "responseHeader": ["Content-Type", "Cache-Control", "ETag"],
    "maxAgeSeconds": 3600
  }
]
EOF

gsutil cors set /tmp/cors-config.json gs://$BUCKET_NAME
gsutil cors get gs://$BUCKET_NAME

Cache bypass sorunu: Eğer CDN her zaman origin’e gidiyorsa, Pragma: no-cache veya Cache-Control: no-store header’larının origin’den gelip gelmediğini kontrol edin:

# Bir URL'in cache davranışını kontrol et
curl -sI https://$DOMAIN/css/main.css | grep -E "(cache|age|x-cache|via)"

Response’da Age: 3600 gibi bir değer görüyorsanız CDN’den cache’lenmiş içerik geliyor. Age: 0 ile birlikte X-Cache-Status: MISS geliyorsa ilk istek veya cache bozuk demektir.

Büyük dosya yükleme optimizasyonu: 100MB’ın üzerindeki dosyalar için parallel composite upload kullanın:

# .boto config dosyasına ekle
echo "[GSUtil]
parallel_composite_upload_threshold = 150M
parallel_composite_upload_component_size = 50M" >> ~/.boto

# Büyük video dosyası yükleme
gsutil -o "GSUtil:parallel_composite_upload_threshold=150M" 
  cp large-video.mp4 gs://$BUCKET_NAME/videos/

Maliyet Optimizasyonu İpuçları

CDN kullanımı genellikle maliyeti düşürür ama yanlış yapılandırılırsa artırabilir. Şu noktalara dikkat edin:

  • Cache hit oranını maksimize edin: Hedef %85 ve üzeri olmalı. Düşükse TTL değerlerini artırın
  • Nearline/Coldline storage kullanmayın: CDN’e bağlı bucket’larda Standard storage kullanın, sık erişim retrieval ücreti ekler
  • Gereksiz region’lardan kaçının: Bucket ve Load Balancer aynı veya yakın region’da olsun
  • Log sampling yapın: Tüm CDN loglarını yazmak Logging maliyetini artırır, %1 sampling çoğu zaman yeterli
# Backend bucket'ta compression aktif et
gcloud compute backend-buckets update $BACKEND_BUCKET_NAME 
  --compression-mode=AUTOMATIC

# Logging'i sadece MISS'ler için aktif et (maliyet azaltma)
gcloud compute backend-buckets update $BACKEND_BUCKET_NAME 
  --logging-sample-rate=0.1

Sonuç

GCP Cloud Storage ile Cloud CDN entegrasyonu, teknik olarak karmaşık görünse de adım adım yaklaşıldığında gayet yönetilebilir. Kurulumun kritik noktaları şunlar:

  • Backend bucket’ı doğru cache mode ile oluşturmak (CACHE_ALL_STATIC çoğu senaryo için ideal)
  • Cache-Control header’larını dosya tipine göre doğru ayarlamak
  • Cache invalidation yerine hash’li dosya isimlerini tercih etmek
  • HTTPS redirect’i ayrı bir URL map ile yönetmek
  • Deployment sürecini CI/CD pipeline’ına entegre etmek

Bu mimari kurulduktan sonra statik varlıklarınız için sunucu kapasitesi planlaması yapmanız gerekmez. Google’ın global ağı sizin için bu işi üstlenir. Origin sunucunuz yalnızca dinamik içerik üretmeye odaklanır, bu da hem performans hem de maliyet açısından ciddi kazanımlar sağlar. İlk kez kuruyorsanız mutlaka bir test ortamında başlayın, production’a geçmeden önce cache davranışını curl -I ile doğrulayın ve monitoring’i aktif edin.

Bir yanıt yazın

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