GCP Cloud SQL Yedekleme ve Geri Yükleme Rehberi

Veritabanı yedeklemesi, birçok sysadmin’in “bir şey olmadan önce” düşündüğü ama “bir şey olduktan sonra” gerçekten ciddiye aldığı bir konu. GCP Cloud SQL kullanıyorsanız, Google size oldukça güçlü bir yedekleme altyapısı sunuyor. Ancak bu altyapıyı doğru yapılandırmak, test etmek ve geri yükleme senaryolarını önceden prova etmek tamamen sizin sorumluluğunuzda. Bu yazıda Cloud SQL’in yedekleme mekanizmalarını baştan sona ele alacağız, gerçek dünya senaryolarıyla pratik örnekler vereceğiz.

Cloud SQL Yedekleme Türleri

Cloud SQL iki temel yedekleme türü sunar ve bunları birbirinin alternatifi değil, tamamlayıcısı olarak düşünmek gerekir.

Otomatik Yedeklemeler (Automated Backups)

Google’ın yönettiği, her gün belirli bir zaman diliminde çekilen anlık yedeklerdir. Instance çalışırken arka planda alınır, yani production’ı etkilemez. Varsayılan olarak son 7 gün saklanır, bunu 365 güne kadar uzatabilirsiniz.

On-Demand Yedeklemeler

Siz tetiklediğinizde çekilen yedeklerdir. Migration öncesi, büyük bir schema değişikliği yapmadan önce veya “şu an tam çalışıyor, şunu kayıt altına alalım” dediğiniz durumlarda kullanışlıdır. Bu yedekler otomatik olarak silinmez, siz silene kadar kalır.

Point-in-Time Recovery (PITR)

Bu özellik, binary log’ları (MySQL için) veya write-ahead log’ları (PostgreSQL için) kullanarak belirli bir saniyeye kadar geri dönmenizi sağlar. Örneğin sabah 09:14:32’de yanlış bir DELETE çalıştırdıysanız, 09:14:31 durumuna dönebilirsiniz. Bu özellik için binary logging’in aktif olması gerekir.

gcloud CLI ile Temel Yedekleme İşlemleri

Önce ortamınızı hazırlayalım. Tüm örneklerde my-sql-instance instance adı ve my-project proje adı kullanacağım.

# Aktif projeyi ayarla
gcloud config set project my-project

# Instance listesini gör
gcloud sql instances list

# Instance detaylarına bak
gcloud sql instances describe my-sql-instance

Manuel (on-demand) yedek almak oldukça basit:

# Anlık yedek al
gcloud sql backups create 
  --instance=my-sql-instance 
  --description="Pre-migration backup - $(date +%Y%m%d_%H%M%S)"

# Mevcut yedekleri listele
gcloud sql backups list --instance=my-sql-instance

# Belirli bir yedeğin detaylarına bak
gcloud sql backups describe BACKUP_ID --instance=my-sql-instance

Yedekleri listelerken çıktıda BACKUP_ID değerini not alın, geri yükleme sırasında bu ID’ye ihtiyacınız olacak.

Otomatik Yedekleme Yapılandırması

Yeni bir instance oluştururken yedekleme ayarlarını da belirlemek en sağlıklı yaklaşım:

# Yedekleme ve PITR aktif olarak yeni instance oluştur
gcloud sql instances create my-sql-instance 
  --database-version=POSTGRES_14 
  --tier=db-n1-standard-2 
  --region=europe-west1 
  --backup-start-time=02:00 
  --enable-bin-log 
  --retained-backups-count=30 
  --retained-transaction-log-days=7

Parametrelerin ne işe yaradığına bakalım:

  • –backup-start-time: Yedeklemenin başlayacağı saat (UTC). Trafiğinizin en düşük olduğu saati seçin
  • –enable-bin-log: MySQL için binary log, PostgreSQL için WAL aktif eder. PITR için zorunlu
  • –retained-backups-count: Kaç adet yedek saklanacak. 30 günlük yedek iş yerinde genellikle makul bir başlangıç
  • –retained-transaction-log-days: Transaction log’larının kaç gün saklanacağı. Bu değer PITR pencerenizi belirler

Mevcut bir instance’ın ayarlarını güncellemek için:

# Mevcut instance'ın yedekleme ayarlarını güncelle
gcloud sql instances patch my-sql-instance 
  --backup-start-time=03:00 
  --retained-backups-count=14 
  --retained-transaction-log-days=7

Yedekten Geri Yükleme

İşte kritik kısım. Yedekleme almak bir beceri, geri yükleme yapmak ise başka bir beceri. Ve ikincisini hiç pratik yapmadan gerçek bir kriz anında ilk kez denemek istemezsiniz.

Aynı Instance’a Geri Yükleme

# Önce backup ID'sini bul
gcloud sql backups list --instance=my-sql-instance

# Belirli bir yedeği aynı instance'a geri yükle
gcloud sql backups restore BACKUP_ID 
  --restore-instance=my-sql-instance 
  --backup-instance=my-sql-instance

Bu işlem instance’ı kısa süreliğine kullanılamaz hale getirir. Production’da kullanmadan önce maintenance window’u hesaba katın.

Farklı Instance’a Geri Yükleme

Genellikle tercih edilen yöntem budur. Yedeği önce test instance’ına yükler, doğrularsınız, sonra gerekirse production’ı değiştirirsiniz:

# Önce hedef instance'ı oluştur (kaynak ile aynı tier ve versiyonda olmalı)
gcloud sql instances create my-sql-instance-restore 
  --database-version=POSTGRES_14 
  --tier=db-n1-standard-2 
  --region=europe-west1

# Yedeği yeni instance'a geri yükle
gcloud sql backups restore BACKUP_ID 
  --restore-instance=my-sql-instance-restore 
  --backup-instance=my-sql-instance

Point-in-Time Recovery

Bir geliştirici sabah 10:23’te yanlışlıkla DELETE FROM orders WHERE 1=1 çalıştırdı. PITR olmadan bu felakettir. PITR ile:

# 10:22:59 anına geri dön (timestamp UTC olmalı)
gcloud sql instances clone my-sql-instance my-sql-instance-pitr 
  --point-in-time="2024-01-15T07:22:59Z"

Dikkat edin: clone komutu yeni bir instance oluşturur, mevcut instance’ı değiştirmez. Bu aslında bir güvenlik özelliği. Klonu doğruladıktan sonra uygulamanızın connection string’ini güncelleyebilirsiniz.

Terraform ile Infrastructure as Code

Manuel işlemler geliştirme ortamı için kabul edilebilir olabilir, ama production’da her şeyin kod olarak tanımlı olması gerekir. İşte Cloud SQL instance’ını backup ayarlarıyla birlikte Terraform ile tanımlamak:

resource "google_sql_database_instance" "main" {
  name             = "my-sql-instance"
  database_version = "POSTGRES_14"
  region           = "europe-west1"

  settings {
    tier = "db-n1-standard-2"

    backup_configuration {
      enabled                        = true
      start_time                     = "02:00"
      point_in_time_recovery_enabled = true
      transaction_log_retention_days = 7
      backup_retention_settings {
        retained_backups = 30
        retention_unit   = "COUNT"
      }
    }

    maintenance_window {
      day          = 7
      hour         = 3
      update_track = "stable"
    }
  }

  deletion_protection = true
}

deletion_protection = true ayarını asla ihmal etmeyin. Terraform destroy çalıştırıldığında veya yanlışlıkla kaynak silinmeye çalışıldığında sizi korur.

Yedekleme Doğrulama Script’i

Yedek almak yeterli değil. O yedeğin gerçekten çalışıp çalışmadığını doğrulamanız gerekiyor. Aşağıdaki script, her gün son yedeği kontrol eder ve başarısızsa alert gönderir:

#!/bin/bash
# backup-verify.sh - Cloud SQL backup doğrulama scripti

PROJECT_ID="my-project"
INSTANCE_NAME="my-sql-instance"
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX"
MAX_BACKUP_AGE_HOURS=25  # 24 saatten fazlaysa alert

send_slack_alert() {
    local message="$1"
    curl -s -X POST "$SLACK_WEBHOOK_URL" 
        -H 'Content-type: application/json' 
        --data "{"text":"$message"}"
}

# Son yedeği kontrol et
LATEST_BACKUP=$(gcloud sql backups list 
    --instance="$INSTANCE_NAME" 
    --project="$PROJECT_ID" 
    --format="json" 
    --limit=1 
    --sort-by="~startTime" 2>/dev/null | 
    python3 -c "import json,sys; data=json.load(sys.stdin); print(data[0]['startTime']) if data else print('NONE')")

if [ "$LATEST_BACKUP" = "NONE" ]; then
    send_slack_alert ":red_circle: KRITIK: $INSTANCE_NAME icin hic yedek bulunamadi!"
    exit 1
fi

# Yedek zamanını kontrol et
BACKUP_TIMESTAMP=$(date -d "$LATEST_BACKUP" +%s 2>/dev/null || 
    date -j -f "%Y-%m-%dT%H:%M:%S" "${LATEST_BACKUP%.*}" +%s)
CURRENT_TIMESTAMP=$(date +%s)
AGE_HOURS=$(( (CURRENT_TIMESTAMP - BACKUP_TIMESTAMP) / 3600 ))

if [ "$AGE_HOURS" -gt "$MAX_BACKUP_AGE_HOURS" ]; then
    send_slack_alert ":warning: UYARI: $INSTANCE_NAME son yedegi $AGE_HOURS saat once alinmis! Beklenen maksimum: $MAX_BACKUP_AGE_HOURS saat"
    exit 1
else
    echo "OK: Son yedek $AGE_HOURS saat once alinmis. ($LATEST_BACKUP)"
fi

Bu script’i Cloud Scheduler veya basit bir cron job ile her gün çalıştırabilirsiniz.

Disaster Recovery Tatbikatı

Gerçek bir ekip için yılda en az iki kez DR tatbikatı yapılmasını öneririm. İşte tipik bir tatbikat senaryosu ve adımları:

Senaryo: Production veritabanı corrupt oldu, 4 saat önceki duruma dönmemiz gerekiyor.

#!/bin/bash
# dr-drill.sh - DR tatbikat scripti

set -e

PROJECT_ID="my-project"
SOURCE_INSTANCE="my-sql-instance"
DR_INSTANCE="my-sql-instance-dr-test"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "[$(date)] DR tatbikati basliyor..."

# 1. Adim: Mevcut yedekleri listele
echo "[$(date)] Mevcut yedekler listeleniyor..."
BACKUP_ID=$(gcloud sql backups list 
    --instance="$SOURCE_INSTANCE" 
    --project="$PROJECT_ID" 
    --format="value(id)" 
    --limit=1 
    --sort-by="~startTime")

echo "[$(date)] Kullanilacak backup ID: $BACKUP_ID"

# 2. Adim: DR instance olustur
echo "[$(date)] DR instance olusturuluyor: $DR_INSTANCE"
gcloud sql instances create "$DR_INSTANCE" 
    --database-version=POSTGRES_14 
    --tier=db-n1-standard-2 
    --region=europe-west1 
    --project="$PROJECT_ID"

# 3. Adim: Geri yukleme
echo "[$(date)] Yedek geri yukleniyor..."
gcloud sql backups restore "$BACKUP_ID" 
    --restore-instance="$DR_INSTANCE" 
    --backup-instance="$SOURCE_INSTANCE" 
    --project="$PROJECT_ID"

# 4. Adim: Dogrulama (uygulamaniza gore ozellestirin)
echo "[$(date)] Veritabani dogrulamasi yapiliyor..."
DB_PASSWORD=$(gcloud secrets versions access latest 
    --secret="db-password" 
    --project="$PROJECT_ID")

TABLE_COUNT=$(gcloud sql connect "$DR_INSTANCE" 
    --user=postgres 
    --project="$PROJECT_ID" 
    --quiet << EOF
SELECT count(*) FROM information_schema.tables WHERE table_schema='public';
EOF
)

echo "[$(date)] Tablo sayisi: $TABLE_COUNT"
echo "[$(date)] DR tatbikati tamamlandi. RTO olcumu bitti."

# 5. Adim: Temizlik
echo "[$(date)] DR instance temizleniyor..."
gcloud sql instances delete "$DR_INSTANCE" 
    --project="$PROJECT_ID" 
    --quiet

echo "[$(date)] Temizlik tamamlandi."

Bu script’i çalıştırırken süreyi ölçün. RTO (Recovery Time Objective) hedefiniz 2 saatse ve bu script 3 saatte tamamlanıyorsa, daha büyük bir tier kullanmayı veya read replica yapılandırmasını gözden geçirmeyi düşünün.

Cloud Storage’a Export ile Ek Güvence

Cloud SQL yedekleri GCP içinde kalır. Bazı compliance gereksinimleri veya cross-region disaster recovery senaryoları için veritabanını Cloud Storage’a export etmek isteyebilirsiniz:

# MySQL dump'ı Cloud Storage'a export et
gcloud sql export sql my-sql-instance 
    gs://my-backup-bucket/exports/my-sql-$(date +%Y%m%d).sql 
    --database=myapp_db 
    --project=my-project

# PostgreSQL için CSV export (belirli tablolar)
gcloud sql export csv my-sql-instance 
    gs://my-backup-bucket/exports/orders-$(date +%Y%m%d).csv 
    --database=myapp_db 
    --query="SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '30 days'"

Export bucket’ının lifecycle policy’si olduğundan emin olun, yoksa storage maliyetleri hızla birikir:

# Bucket'a 90 gunluk lifecycle ekle
cat > lifecycle.json << 'EOF'
{
  "lifecycle": {
    "rule": [
      {
        "action": {"type": "Delete"},
        "condition": {"age": 90}
      }
    ]
  }
}
EOF

gsutil lifecycle set lifecycle.json gs://my-backup-bucket

Monitoring ve Alerting

Cloud SQL yedekleme durumunu Cloud Monitoring üzerinden takip edebilirsiniz. Önemli metrikleri bir araya getiren basit bir uptime check ve log-based alert kurulumu:

# Backup failure log-based alert olustur
gcloud logging metrics create sql-backup-failure 
    --description="Cloud SQL backup failure" 
    --log-filter='resource.type="cloudsql_database"
protoPayload.methodName="cloudsql.backupRuns.create"
protoPayload.status.code!=0'

# Bu metric uzerine alerting policy olustur
gcloud alpha monitoring policies create 
    --policy-from-file=backup-alert-policy.json

backup-alert-policy.json dosyası için temel yapı:

{
  "displayName": "Cloud SQL Backup Failure Alert",
  "conditions": [
    {
      "displayName": "Backup failure detected",
      "conditionThreshold": {
        "filter": "metric.type="logging.googleapis.com/user/sql-backup-failure"",
        "comparison": "COMPARISON_GT",
        "thresholdValue": 0,
        "duration": "0s"
      }
    }
  ],
  "alertStrategy": {
    "notificationRateLimit": {
      "period": "3600s"
    }
  },
  "combiner": "OR",
  "enabled": true
}

Sık Yapılan Hatalar

Yedek penceresi ve maintenance window çakışması: Backup start time ile maintenance window’u aynı saate denk getirmeyin. Maintenance sırasında instance restart olabilir ve yedek yarıda kalabilir.

PITR için yeterli log retention: Transaction log retention’ı çok kısa tutarsanız PITR pencereniz daralır. 7 gün genellikle minimum, kritik sistemler için 14 gün tercih edin.

Restore sonrası connection string güncelleme: Farklı bir instance’a restore yaptıktan sonra uygulama konfigürasyonunu güncellemeyi unutmak klasik bir hatadır. Bunu runbook’unuza ekleyin.

IAM izinlerini test ortamında doğrulamamak: DR anında servis hesabının restore yapma izni olmadığını fark etmek kötü bir sürpriz olur. Tatbikat sırasında aynı izin seti ile test edin.

Tek bölge yedekleri: Eğer bir bölge tamamen düşerse, o bölgedeki yedekler de erişilemez olabilir. Kritik veriler için cross-region replica veya Cloud Storage export kombinasyonu kullanın.

Maliyet Optimizasyonu

Yedekleme maliyetleri sessiz sedasız büyüyebilir. Birkaç pratik önlem:

  • Otomatik yedekler için retention count’u iş gereksinimlerinizle orantılı tutun. Her şeyi 365 gün saklamak çoğu zaman gereksiz
  • On-demand aldığınız yedekleri düzenli olarak gözden geçirin ve artık gerekmeyenleri silin
  • Cloud Storage export’ları için Nearline veya Coldline storage class kullanın, erişim nadirse maliyeti önemli ölçüde düşer
  • PITR log retention’ı ihtiyaçtan fazla tutmayın, her ek gün storage maliyeti demek
# Eski on-demand yedekleri temizle (30 gunden eski)
gcloud sql backups list 
    --instance=my-sql-instance 
    --format="value(id,startTime)" | 
while read backup_id backup_time; do
    backup_age=$(( ($(date +%s) - $(date -d "$backup_time" +%s)) / 86400 ))
    if [ "$backup_age" -gt 30 ]; then
        echo "Siliniyor: $backup_id ($backup_age gun eski)"
        gcloud sql backups delete "$backup_id" 
            --instance=my-sql-instance 
            --quiet
    fi
done

Sonuç

Cloud SQL’in yedekleme altyapısı oldukça olgun ve kullanımı nispeten kolay. Ancak “varsayılan ayarlar yeterli” düşüncesiyle geçiştirmek, gerçek bir kriz anında pahalıya mal olabilir. Bu yazıda ele aldığımız konuları özetlersek: otomatik yedekleme zamanlamasını trafiğinize göre optimize edin, PITR’ı mutlaka aktif tutun, yedek doğrulama scriptlerini CI/CD veya cron’a entegre edin ve en önemlisi yılda en az iki kez DR tatbikatı yapın.

Runbook’lar yazın, tatbikat sürelerini ölçün ve RTO/RPO hedeflerinizin gerçekten karşılanıp karşılanmadığını somut verilerle doğrulayın. Yedekleme stratejisi “bir kez kur unut” değil, sürekli test ve iyileştirme gerektiren bir süreçtir. Production’da ilk kez geri yükleme yapmak zorunda kaldığınızda, en az birkaç kez prova yapmış olmak isteyeceksiniz.

Bir yanıt yazın

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