Test Ortamı Yönetimi: Staging ve Test Sunucusu Kurulumu

Prodüksiyona doğrudan deploy etmek… Kim yapmadı ki? El kaldırmak istemiyorum çünkü ben de yaptım. Ve bir Cuma akşamı, müşteri verilerini etkileyen bir bug yüzünden üç saat uğraşmak zorunda kaldım. O geceden sonra test ortamı yönetimi benim için bir tercih değil, bir zorunluluk haline geldi.

Bu yazıda size gerçekten işe yarayan bir staging/test ortamı nasıl kurulur, nasıl yönetilir, nelere dikkat etmek gerekir bunları anlatacağım. Teorik değil, sahada öğrenilmiş bilgiler bunlar.

Test Ortamı Felsefesi: Neden Bu Kadar Önemli?

Çoğu ekip test ortamını “prodüksiyonun kötü kopyası” olarak görür. Sunucu daha küçük, veri eskimiş, konfigürasyon yarım… Bu yaklaşım tamamen yanlış. İyi bir test ortamı, prodüksiyonu mümkün olduğunca taklit etmeli, ama aynı zamanda hızlı iterasyona izin vermeli.

Temel prensip şu: Prodüksiyonda sürprizle karşılaşmak istemiyorsanız, staging’i prodüksiyon gibi davranın.

Tipik bir ortam hiyerarşisi şöyle kurgulanmalı:

  • Development (DEV): Geliştiricinin kendi makinesi veya paylaşımlı geliştirme sunucusu. Hızlı, kirli, deneysel.
  • Test/QA: Otomatik testlerin koştuğu, QA ekibinin manuel test yaptığı ortam. Stabil ama sık güncellenen.
  • Staging: Prodüksiyonun birebir kopyası. Deploy öncesi son durak.
  • Production: Müşteri görür, para döner, uyku kaçar.

Altyapı Planlaması

Önce kaynak planlamasını doğru yapmak gerekiyor. Staging sunucusu prodüksiyonun tam kopyası olmak zorunda değil, ama kritik bileşenler aynı olmalı.

Bir e-ticaret projesi için tipik bir staging kurulumunu ele alalım:

# Temel sistem bilgilerini kontrol et
hostnamectl set-hostname staging-app-01
timedatectl set-timezone Europe/Istanbul

# Sistem güncellemelerini yap
apt update && apt upgrade -y

# Temel araçları kur
apt install -y 
    curl 
    wget 
    git 
    htop 
    net-tools 
    vim 
    jq 
    unzip 
    software-properties-common

Sunucu kurulumunun hemen ardından network izolasyonunu sağlamak şart. Staging ortamından dış dünyaya erişimi kısıtlamak, özellikle üçüncü parti servislere (ödeme sistemleri, SMS gatewayler) yanlışlıkla istek atılmasını engeller.

# UFW ile temel firewall kuralları
ufw default deny incoming
ufw default allow outgoing
ufw allow from 10.0.0.0/8 to any port 22
ufw allow from 192.168.0.0/16 to any port 22
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

# Staging'den prodüksiyon veritabanına erişimi engelle
# (Prodüksiyon DB IP'si örnek olarak 203.0.113.10)
ufw deny out to 203.0.113.10 port 5432

Docker ile İzole Test Ortamı

Docker kullanmıyorsanız, 2024’te bunu sorgulamanızı öneririm. Container’lar test ortamı yönetimini dramatik biçimde kolaylaştırıyor.

İşte gerçek bir projeye ait, sadeleştirilmiş bir docker-compose.yml:

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.staging
    environment:
      - NODE_ENV=staging
      - DATABASE_URL=postgresql://staging_user:${DB_PASSWORD}@db:5432/staging_db
      - REDIS_URL=redis://redis:6379
      - PAYMENT_GATEWAY_URL=https://sandbox.odeme-sistemi.com
      - LOG_LEVEL=debug
    depends_on:
      - db
      - redis
    ports:
      - "3000:3000"
    volumes:
      - app_logs:/var/log/app
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=staging_db
      - POSTGRES_USER=staging_user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/init-staging-db.sql:/docker-entrypoint-initdb.d/init.sql
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/staging.conf:/etc/nginx/conf.d/default.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    restart: unless-stopped

volumes:
  postgres_data:
  app_logs:

Burada dikkat edilmesi gereken kritik nokta: PAYMENT_GATEWAY_URL sandbox’a işaret ediyor. Bu tür konfigürasyonları her zaman ortam değişkeni üzerinden yönetin, asla koda gömmeyın.

Veri Yönetimi: En Kritik Kısım

Test ortamının en can sıkıcı problemi veri yönetimidir. Prodüksiyon verisini staging’e kopyalamak hem KVKK/GDPR açısından sorunlu, hem de pratik değil. Ama tamamen sahte veriyle de gerçekçi testler yapmak zor.

Çözüm: veri anonimizasyonu ve maskeleme.

#!/bin/bash
# prod-to-staging-data.sh
# Prodüksiyon verisini staging için hazırlayan script

set -euo pipefail

PROD_DB_HOST="prod-db.internal"
STAGING_DB_HOST="localhost"
DUMP_FILE="/tmp/staging_dump_$(date +%Y%m%d).sql"

echo "[$(date)] Prodüksiyon dump alınıyor..."
pg_dump 
    -h "$PROD_DB_HOST" 
    -U readonly_user 
    -d production_db 
    --no-owner 
    --no-acl 
    -f "$DUMP_FILE"

echo "[$(date)] Staging veritabanı sıfırlanıyor..."
psql -h "$STAGING_DB_HOST" -U postgres -c "DROP DATABASE IF EXISTS staging_db;"
psql -h "$STAGING_DB_HOST" -U postgres -c "CREATE DATABASE staging_db OWNER staging_user;"

echo "[$(date)] Dump staging'e yükleniyor..."
psql -h "$STAGING_DB_HOST" -U staging_user -d staging_db -f "$DUMP_FILE"

echo "[$(date)] Hassas veriler maskeleniyor..."
psql -h "$STAGING_DB_HOST" -U staging_user -d staging_db << 'SQL'
-- Email adreslerini maskele
UPDATE users
SET email = 'user_' || id || '@staging.test'
WHERE email NOT LIKE '%@staging.test';

-- Telefon numaralarını maskele
UPDATE users
SET phone = '+90555' || LPAD(CAST(id AS TEXT), 7, '0')
WHERE phone IS NOT NULL;

-- Kredi kartı bilgilerini tamamen sil
UPDATE payment_methods
SET card_number = '****-****-****-' || RIGHT(card_number, 4),
    cvv = '***';

-- TC kimlik numaralarını sahte veriyle değiştir
UPDATE users
SET national_id = '1' || LPAD(CAST((random() * 1000000000)::BIGINT AS TEXT), 9, '0')
WHERE national_id IS NOT NULL;

SQL

echo "[$(date)] Veri hazırlama tamamlandı."
rm -f "$DUMP_FILE"

Bu scripti her hafta başı cron ile çalıştırın. Staging verisi haftalık taze olsun, geliştiriciler de gerçekçi hacimde veriyle test edebilsin.

CI/CD Pipeline ile Staging Entegrasyonu

Staging ortamını CI/CD’ye bağlamak, otomatik deploy sürecini kurmak işlerin gerçekten yürüdüğü yer burasıdır. GitHub Actions ile basit ama etkili bir örnek:

# .github/workflows/staging-deploy.yml
name: Staging Deploy

on:
  push:
    branches:
      - develop
      - 'release/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Unit Testleri Çalıştır
        run: |
          npm ci
          npm run test:unit
          
      - name: Integration Testleri Çalıştır
        run: npm run test:integration

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    environment: staging
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Docker Image Build
        run: |
          docker build 
            -t myapp:staging-${{ github.sha }} 
            -f Dockerfile.staging 
            .
      
      - name: Staging Sunucusuna Deploy
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: deploy
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            cd /opt/myapp
            git pull origin ${{ github.ref_name }}
            docker-compose pull
            docker-compose up -d --build
            docker-compose run --rm app npm run db:migrate
            
      - name: Smoke Test
        run: |
          sleep 10
          curl -f https://staging.myapp.com/health || exit 1
          
      - name: Slack Bildirimi
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: "Staging deploy: ${{ job.status }} | Branch: ${{ github.ref_name }}"
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Staging Ortamı Sağlık Kontrolü

Deploy sonrası otomatik sağlık kontrolü yapmak hayat kurtarır. Elle kontrol etmeye güvenmek, özellikle gece deploylarında felaket reçetesidir.

#!/bin/bash
# staging-health-check.sh
# Deploy sonrası temel sağlık kontrolü

STAGING_URL="https://staging.myapp.com"
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL}"
FAILED=0

check_endpoint() {
    local endpoint="$1"
    local expected_status="$2"
    local description="$3"
    
    actual_status=$(curl -s -o /dev/null -w "%{http_code}" 
        --max-time 10 
        "${STAGING_URL}${endpoint}")
    
    if [ "$actual_status" = "$expected_status" ]; then
        echo "OK: $description ($actual_status)"
    else
        echo "FAIL: $description - Beklenen: $expected_status, Gelen: $actual_status"
        FAILED=$((FAILED + 1))
    fi
}

# Temel endpoint kontrolleri
check_endpoint "/health" "200" "Health check endpoint"
check_endpoint "/api/v1/status" "200" "API status"
check_endpoint "/login" "200" "Login sayfası"
check_endpoint "/nonexistent-page-xyz" "404" "404 handler"

# Veritabanı bağlantı kontrolü
DB_STATUS=$(curl -s "${STAGING_URL}/health" | jq -r '.database')
if [ "$DB_STATUS" = "connected" ]; then
    echo "OK: Veritabanı bağlantısı"
else
    echo "FAIL: Veritabanı bağlantısı - $DB_STATUS"
    FAILED=$((FAILED + 1))
fi

# Redis bağlantı kontrolü
REDIS_STATUS=$(curl -s "${STAGING_URL}/health" | jq -r '.cache')
if [ "$REDIS_STATUS" = "connected" ]; then
    echo "OK: Redis bağlantısı"
else
    echo "FAIL: Redis bağlantısı - $REDIS_STATUS"
    FAILED=$((FAILED + 1))
fi

# Sonuç raporu
if [ $FAILED -gt 0 ]; then
    echo ""
    echo "UYARI: $FAILED kontrol başarısız!"
    
    # Slack bildirimi gönder
    curl -s -X POST "$SLACK_WEBHOOK" 
        -H 'Content-type: application/json' 
        -d "{"text": "Staging sağlık kontrolü BAŞARISIZ: $FAILED hata tespit edildi. Detaylar için sunucuyu kontrol edin."}"
    
    exit 1
else
    echo ""
    echo "Tüm kontroller başarılı."
fi

Ortam Değişkenleri ve Gizli Bilgi Yönetimi

Staging ortamında gizli bilgileri nasıl yönettiğiniz, güvenlik açısından kritik önem taşır. .env dosyalarını git reposuna commit etmek hala çok yaygın bir hata.

HashiCorp Vault kullanmıyorsanız, en azından şu yaklaşımı benimseyin:

# /etc/environment.d/staging-app.conf
# Bu dosya deploy kullanıcısına ait, 600 izinleri var

# Vault veya AWS Secrets Manager'dan çek
export APP_SECRETS=$(vault kv get -format=json secret/staging/myapp)
export DB_PASSWORD=$(echo $APP_SECRETS | jq -r '.data.data.db_password')
export JWT_SECRET=$(echo $APP_SECRETS | jq -r '.data.data.jwt_secret')
export REDIS_PASSWORD=$(echo $APP_SECRETS | jq -r '.data.data.redis_password')

Vault yoksa en azından şifreli bir .env dosyası:

# Ansible Vault ile env dosyasını şifrele
ansible-vault encrypt staging.env

# Deploy sırasında çöz
ansible-vault decrypt staging.env --output /tmp/staging.env
# Kullan
# Sonra sil
rm -f /tmp/staging.env

Log Yönetimi ve Gözlemlenebilirlik

Staging ortamında log yönetimini ihmal etmek, sorun debuglarken çok vakit kaybettiriyor. Basit ama işlevsel bir log kurulumu:

# Loki + Promtail kurulumu (Grafana stack)
cat > /etc/promtail/config.yml << 'EOF'
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: staging-app
    static_configs:
      - targets:
          - localhost
        labels:
          job: staging-app
          environment: staging
          __path__: /var/log/app/*.log
    
    pipeline_stages:
      - json:
          expressions:
            level: level
            timestamp: timestamp
            message: message
            request_id: requestId
      
      - labels:
          level:
          request_id:
      
      - timestamp:
          source: timestamp
          format: RFC3339
EOF

systemctl restart promtail

Staging Ortamı Bakım Rutinleri

Kurulum kadar önemli olan, bakımın düzenli yapılması. Aşağıdaki cron tablosunu deploy kullanıcısına ekleyin:

# crontab -e

# Her Pazartesi 02:00: Prodüksiyon verisi senkronizasyonu
0 2 * * 1 /opt/scripts/prod-to-staging-data.sh >> /var/log/staging-sync.log 2>&1

# Her gece 01:00: Docker image'ları temizle
0 1 * * * docker image prune -f >> /var/log/docker-cleanup.log 2>&1

# Her gece 00:30: Eski log dosyalarını temizle
30 0 * * * find /var/log/app -name "*.log" -mtime +7 -delete

# Her 5 dakikada: Sağlık kontrolü
*/5 * * * * /opt/scripts/staging-health-check.sh >> /var/log/health-check.log 2>&1

# Her Pazar 03:00: Sistem güncellemeleri (güvenlik yamaları)
0 3 * * 0 apt-get update && apt-get upgrade -y --only-upgrade 2>&1 | mail -s "Staging Weekly Update" [email protected]

Sık Yapılan Hatalar

Yıllar içinde gördüğüm, benim de düştüğüm tuzaklar:

  • Staging’de prodüksiyon credential kullanmak: Bir geliştirici yanlışlıkla gerçek SMS gönderir, gerçek ödeme oluşturur. Sandbox hesapları mutlaka ayrı olsun.
  • Kaynak yetersizliği: Staging sunucusu çok küçük tutulunca bellek sorunları prodüksiyonda ortaya çıkar. En azından kritik servislere prodüksiyonun yüzde otuz kapasitesini ayırın.
  • Stale data sorunu: Aylar önce alınmış dump ile test etmek. Schema değişiklikleri, yeni alanlar eksik kalıyor. Veri senkronizasyonunu otomatize edin.
  • Tek staging ortamı: Birden fazla ekip aynı staging’i kullanıyorsa çakışmalar kaçınılmaz. Mümkünse her feature branch için ephemeral ortam kurun.
  • SSL sertifikası yoksa test etmek: HTTPS ile ilgili cookie, mixed content gibi sorunları HTTP’de fark edemezsiniz. Let’s Encrypt ücretsiz, bahane yok.

Ephemeral (Geçici) Test Ortamları

Modern yaklaşım, her pull request için otomatik bir test ortamı oluşturup PR kapandığında silmek. Kubernetes kullanıyorsanız bu çok kolay:

#!/bin/bash
# create-pr-environment.sh
# PR numarasını parametre alır

PR_NUMBER=$1
NAMESPACE="pr-${PR_NUMBER}"

kubectl create namespace "$NAMESPACE"

helm install 
    "myapp-pr-${PR_NUMBER}" 
    ./helm/myapp 
    --namespace "$NAMESPACE" 
    --set image.tag="pr-${PR_NUMBER}" 
    --set ingress.host="pr-${PR_NUMBER}.staging.myapp.com" 
    --set resources.requests.memory="256Mi" 
    --set resources.requests.cpu="100m" 
    --set replicaCount=1

echo "PR ortamı hazır: https://pr-${PR_NUMBER}.staging.myapp.com"

# 48 saat sonra otomatik sil
echo "kubectl delete namespace $NAMESPACE" | at now + 48 hours

Sonuç

Test ortamı yönetimi, birçok ekibin küçümsediği ama prodüksiyon olaylarında en çok ağladığı konulardan biri. İyi kurulmuş bir staging ortamı size şunu verir: sabah 2’de alarm almak yerine, iş saatlerinde sessizce sorunu çözme lüksü.

Özetle:

  • Otomasyona yatırım yapın. Manuel veri kopyalama, manuel deploy, manuel test; bunların hepsi hata üretir.
  • Prodüksiyonu taklit edin ama tam kopya olmak zorunda değilsiniz. Kritik servisler, konfigürasyon, bağımlılıklar aynı olsun.
  • Veri güvenliğini ciddiye alın. KVKK ihlali staging ihmalinden de gelebilir.
  • Gözlemlenebilirliği ihmal etmeyin. Staging’de ne olduğunu göremezseniz, prodüksiyonda da göremezsiniz.
  • Ephemeral ortamlara geçmeye çalışın. Tek bir staging sunucusu üzerinde birden fazla ekip çalışıyorsa, bu eninde sonunda sorun çıkarır.

Başlamak için büyük bir altyapıya gerek yok. Tek bir sunucu, Docker Compose ve birkaç basit script ile çok daha kontrollü bir süreç elde edebilirsiniz. Önemli olan başlamak.

Bir yanıt yazın

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