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ı
gcloudCLI 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,
%1sampling ç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.
