GitLab CI/CD ile Review Apps Kurulumu ve Yapılandırması

Bir özellik dalı üzerinde çalışıyorsunuz, değişikliklerinizi push’ladınız ve şimdi “acaba production’a benzer bir ortamda bu nasıl görünüyor?” diye merak ediyorsunuz. İşte tam bu noktada Review Apps devreye giriyor. GitLab’ın en güçlü özelliklerinden biri olan Review Apps, her merge request için otomatik olarak geçici bir ortam oluşturur ve bu ortamı test etmenize, incelemenize olanak tanır. QA ekibinin “hangi branch’te test edeceğim?” sorusuna son verir, product manager’ların kodu görmeden önce ürünü görmesini sağlar.

Review Apps Nedir ve Neden Kullanmalısınız

Review Apps, temelde her branch veya merge request için dinamik olarak oluşturulan geçici deployment ortamlarıdır. Normal bir CI/CD pipeline’ında staging ve production ortamlarınız sabit olur. Review Apps ise buna ek olarak, her geliştirme dalı için feature-login-page.review.sirketim.com gibi benzersiz bir URL ile erişilebilen bir ortam yaratır.

Bu sistemin getirdiği faydalar oldukça somuttur. Öncelikle paralel geliştirme kolaylaşır; beş farklı feature branch üzerinde aynı anda çalışan bir ekip olduğunu düşünün, her biri kendi izole ortamında test edilebilir. Geri bildirim döngüsü kısalır çünkü tasarımcılar, ürün yöneticileri ve QA ekibi kodu okumak zorunda kalmadan değişiklikleri canlı olarak görebilir. Ayrıca merge request’e doğrudan bir “View App” butonu eklenir, bu da iş akışını ciddi ölçüde hızlandırır.

Ön Koşullar ve Altyapı Gereksinimleri

Review Apps kurulumu için birkaç temel bileşene ihtiyaç duyacaksınız.

  • GitLab Runner: Shell, Docker veya Kubernetes executor kullanan en az bir runner
  • Deployment hedefi: Kubernetes cluster, bir VPS havuzu veya container platformu (Heroku, DigitalOcean App Platform gibi)
  • DNS yapılandırması: Wildcard DNS kaydı (*.review.sirketim.com -> load balancer IP)
  • SSL sertifikası: Wildcard sertifika veya Let’s Encrypt ile otomatik sertifika üretimi
  • Container registry: GitLab’ın kendi registry’si veya Docker Hub

Bu yazıda Kubernetes üzerine kurulu bir ortamı ana senaryo olarak ele alacağız, ancak daha sade bir Docker Compose yaklaşımını da göstereceğiz.

Temel .gitlab-ci.yml Yapısı

Review Apps’in kalbinde .gitlab-ci.yml dosyası yatar. Önce genel yapıya bakalım:

# .gitlab-ci.yml temel iskeleti

stages:
  - build
  - test
  - review
  - staging
  - production

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  APP_NAME: myapp
  REVIEW_DOMAIN: review.sirketim.com

.default_rules:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

Burada dikkat edilmesi gereken nokta rules bloğudur. Review App job’larının yalnızca merge request eventlarında tetiklenmesini istiyoruz, her push’ta değil. Bu ayrım hem kaynak tüketimini optimize eder hem de ortam karmaşasını önler.

Docker Build Stage

Önce uygulamanın image’ını oluşturalım:

build:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  variables:
    IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG
    - echo "IMAGE_TAG=$IMAGE_TAG" >> build.env
  artifacts:
    reports:
      dotenv: build.env
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

build.env dosyasına IMAGE_TAG değerini yazıp bunu artifact olarak saklamamızın nedeni, sonraki stage’lerin bu değişkeni kullanabilmesidir. dotenv report tipi bu değişkeni pipeline genelinde erişilebilir kılar.

Review App Deploy Job’u

İşte kritik bölüm. Review App’i deploy eden job şöyle görünür:

deploy_review:
  stage: review
  image: bitnami/kubectl:latest
  variables:
    REVIEW_APP_HOST: "$CI_ENVIRONMENT_SLUG.$REVIEW_DOMAIN"
    KUBE_NAMESPACE: "review-$CI_MERGE_REQUEST_IID"
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_ENVIRONMENT_SLUG.$REVIEW_DOMAIN
    on_stop: stop_review
    auto_stop_in: 3 days
  script:
    # Namespace oluştur
    - kubectl create namespace $KUBE_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
    
    # ConfigMap ve Secret'ları uygula
    - |
      kubectl create configmap app-config 
        --from-literal=APP_ENV=review 
        --from-literal=APP_URL=https://$REVIEW_APP_HOST 
        -n $KUBE_NAMESPACE 
        --dry-run=client -o yaml | kubectl apply -f -
    
    # Deployment manifest'ini dinamik olarak oluştur ve uygula
    - envsubst < k8s/review-deployment.yaml | kubectl apply -n $KUBE_NAMESPACE -f -
    - envsubst < k8s/review-service.yaml | kubectl apply -n $KUBE_NAMESPACE -f -
    - envsubst < k8s/review-ingress.yaml | kubectl apply -n $KUBE_NAMESPACE -f -
    
    # Deploy'un tamamlanmasını bekle
    - kubectl rollout status deployment/$APP_NAME -n $KUBE_NAMESPACE --timeout=120s
    
    - echo "Review App hazir: https://$REVIEW_APP_HOST"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  needs:
    - job: build
      artifacts: true

auto_stop_in: 3 days ayarı çok önemlidir. Merge request kapatılsa bile ortam otomatik olarak 3 gün sonra silinir. Bu sayede Kubernetes cluster’ınız aylık yüzlerce ölü environment ile dolup taşmaz.

Kubernetes Manifest Dosyaları

envsubst komutunun değiştireceği template dosyalarını oluşturalım:

# k8s/review-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${APP_NAME}
  labels:
    app: ${APP_NAME}
    env: review
    mr-iid: "${CI_MERGE_REQUEST_IID}"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ${APP_NAME}
  template:
    metadata:
      labels:
        app: ${APP_NAME}
    spec:
      containers:
        - name: ${APP_NAME}
          image: ${IMAGE_TAG}
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: app-config
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "200m"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
---
# k8s/review-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ${APP_NAME}-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
  tls:
    - hosts:
        - ${REVIEW_APP_HOST}
      secretName: ${APP_NAME}-tls
  rules:
    - host: ${REVIEW_APP_HOST}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: ${APP_NAME}-service
                port:
                  number: 80

Review ortamları için resource limitlerini kasıtlı olarak düşük tutuyoruz. Bu ortamlar production kalitesinde servis vermek için değil, test ve inceleme için var.

Review App Durdurma Job’u

Ortamı temizleyen stop_review job’unu da eklememiz gerekiyor:

stop_review:
  stage: review
  image: bitnami/kubectl:latest
  variables:
    KUBE_NAMESPACE: "review-$CI_MERGE_REQUEST_IID"
    GIT_STRATEGY: none
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  script:
    - echo "Review App siliniyor: $KUBE_NAMESPACE"
    - kubectl delete namespace $KUBE_NAMESPACE --ignore-not-found=true
    - echo "Temizlik tamamlandi"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: manual
  needs: []

Bu job when: manual olarak işaretlenmiştir, yani GitLab arayüzünden manuel olarak tetiklenebilir. Ayrıca merge request kapandığında on_stop: stop_review sayesinde otomatik olarak da çalışır.

Docker Compose ile Basit Alternatif

Kubernetes’e erişiminiz yoksa veya daha küçük bir ekipte çalışıyorsanız, Docker Compose ve SSH tabanlı bir yaklaşım kullanabilirsiniz. Tek bir sunucuda birden fazla review ortamını port yönetimiyle ayırabilirsiniz:

deploy_review_simple:
  stage: review
  image: alpine:latest
  variables:
    SSH_HOST: "review-server.sirketim.com"
    APP_DIR: "/opt/reviews/$CI_COMMIT_REF_SLUG"
    APP_PORT: ""
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: http://$SSH_HOST:$DYNAMIC_PORT
    on_stop: stop_review_simple
    auto_stop_in: 2 days
  before_script:
    - apk add --no-cache openssh-client bash
    - eval $(ssh-agent -s)
    - echo "$REVIEW_SERVER_SSH_KEY" | tr -d 'r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts
  script:
    # MR numarasına gore port hesapla (8100-8999 arasi)
    - DYNAMIC_PORT=$((8100 + $CI_MERGE_REQUEST_IID % 900))
    - echo "DYNAMIC_PORT=$DYNAMIC_PORT" >> deploy.env
    
    # Sunucuya docker-compose dosyasini gonder
    - ssh deploy@$SSH_HOST "mkdir -p $APP_DIR"
    - |
      cat > /tmp/docker-compose.review.yml << EOF
      version: '3.8'
      services:
        app:
          image: ${IMAGE_TAG}
          ports:
            - "${DYNAMIC_PORT}:8080"
          environment:
            - APP_ENV=review
            - APP_URL=http://${SSH_HOST}:${DYNAMIC_PORT}
          restart: unless-stopped
      EOF
    - scp /tmp/docker-compose.review.yml deploy@$SSH_HOST:$APP_DIR/docker-compose.yml
    - ssh deploy@$SSH_HOST "cd $APP_DIR && docker-compose pull && docker-compose up -d"
  artifacts:
    reports:
      dotenv: deploy.env
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  needs:
    - job: build
      artifacts: true

Bu yaklaşımda port çakışmalarına dikkat etmeniz gerekiyor. MR IID’yi baz alan basit bir formül işinizi görür ama uzun vadede merkezi bir port yönetim mekanizması düşünmek mantıklı olacaktır.

Veritabanı ve Bağımlılık Yönetimi

Gerçek dünya senaryolarında uygulamanız muhtemelen bir veritabanına ihtiyaç duyuyor. Review ortamları için birkaç strateji mevcuttur.

Seçenek 1 – Her review için ayrı veritabanı: En izole çözüm ama en maliyetli olanı. Kubernetes üzerinde her namespace’e bir PostgreSQL pod’u deploy edebilirsiniz.

Seçenek 2 – Paylaşılan veritabanı, ayrı şema: Tek bir PostgreSQL sunucusunda her MR için ayrı bir schema oluşturmak hem izolasyon hem de kaynak verimliliği açısından iyi bir orta yoldur.

Seçenek 3 – Staging veritabanının kopyası: Staging DB’nin son yedeğini restore ederek gerçekçi verilerle test yapabilirsiniz.

# Veritabani migration ve seed scripti
.setup_review_db: &setup_review_db
  - DB_NAME="review_${CI_MERGE_REQUEST_IID}"
  - |
    PGPASSWORD=$DB_ROOT_PASSWORD psql -h $DB_HOST -U postgres << EOSQL
      SELECT 'CREATE DATABASE ${DB_NAME}'
      WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_NAME}')gexec
      GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO appuser;
    EOSQL
  - |
    PGPASSWORD=$DB_ROOT_PASSWORD psql -h $DB_HOST -U postgres -d $DB_NAME 
      -c "CREATE SCHEMA IF NOT EXISTS app_schema;"
  # Migration'lari calistir
  - DATABASE_URL="postgresql://appuser:$DB_PASS@$DB_HOST/$DB_NAME" 
    ./scripts/migrate.sh

deploy_review:
  stage: review
  script:
    - *setup_review_db
    # ... geri kalan deploy adimlari

Dinamik Ortam Değişkenleri ve Gizli Bilgiler

Review ortamları için credentials yönetimi kritik bir konudur. Production secret’larını review ortamlarına açmamak için GitLab’ın environment-scoped variables özelliğini kullanın:

GitLab UI’da Settings > CI/CD > Variables bölümünde değişken tanımlarken Environment scope alanına review/* yazabilirsiniz. Bu sayede o değişken yalnızca review ortamlarında görünür olur.

# .gitlab-ci.yml icinde environment-specific davranis
deploy_review:
  stage: review
  script:
    # Bu degiskenler otomatik olarak dogru deger alir
    # STRIPE_KEY -> review ortami icin test anahtari
    # DATABASE_URL -> review DB baglantisi
    - echo "Ortam: $CI_ENVIRONMENT_NAME"
    - echo "Uygulama URL: $CI_ENVIRONMENT_URL"
    
    # Review'a ozel feature flag'leri aktif et
    - |
      kubectl create secret generic app-secrets 
        --from-literal=STRIPE_KEY=$STRIPE_KEY 
        --from-literal=DATABASE_URL=$DATABASE_URL 
        --from-literal=DEBUG_MODE=true 
        -n $KUBE_NAMESPACE 
        --dry-run=client -o yaml | kubectl apply -f -

Merge Request Arayüzü Entegrasyonu

Review Apps’in en güzel yanlarından biri GitLab merge request arayüzüyle doğal entegrasyonudur. environment.url doğru şekilde tanımlandığında MR sayfasında bir “View App” butonu belirir. Buna ek olarak visual review araçlarını da entegre edebilirsiniz:

# Visual review toolbar entegrasyonu (GitLab EE)
deploy_review:
  stage: review
  script:
    - |
      # Review toolbar script'ini HTML'e ekle
      REVIEW_SCRIPT="<script 
        data-project-id='$CI_PROJECT_ID'
        data-merge-request-id='$CI_MERGE_REQUEST_IID'
        data-mr-url='$CI_SERVER_URL'
        data-project-path='$CI_PROJECT_PATH'
        id='review-app-toolbar-script'
        src='https://gitlab.com/assets/webpack/visual_review_toolbar.js'>
      </script>"
      
      # Bu scripti index.html'e inject et
      sed -i "s|</body>|${REVIEW_SCRIPT}</body>|" dist/index.html

Bu özellik sayesinde review uygulamasını kullanan kişi, doğrudan uygulama üzerinden yorum bırakabilir ve bu yorum otomatik olarak ilgili MR’a eklenir.

Monitoring ve Temizlik Stratejileri

Zamanla review ortamları birikir. auto_stop_in mekanizmasına ek olarak, aktif olmayan ortamları temizleyen düzenli bir job oluşturmak iyi bir pratiktir:

# Haftalik temizlik pipeline'i (.gitlab-ci.yml icinde ayri bir scheduled pipeline ile)
cleanup_stale_reviews:
  stage: .pre
  image: bitnami/kubectl:latest
  script:
    - |
      # 5 gunden eski review namespace'lerini temizle
      for ns in $(kubectl get namespaces -l env=review -o jsonpath='{.items[*].metadata.name}'); do
        CREATED=$(kubectl get namespace $ns -o jsonpath='{.metadata.creationTimestamp}')
        CREATED_EPOCH=$(date -d "$CREATED" +%s)
        NOW_EPOCH=$(date +%s)
        AGE_DAYS=$(( (NOW_EPOCH - CREATED_EPOCH) / 86400 ))
        
        if [ $AGE_DAYS -gt 5 ]; then
          echo "Siliniyor: $ns (${AGE_DAYS} gun eski)"
          kubectl delete namespace $ns
        fi
      done
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

Bu job’ı GitLab’da CI/CD > Schedules bölümünden haftalık olarak zamanlayabilirsiniz.

Yaygın Sorunlar ve Çözümleri

Wildcard DNS çalışmıyor: nslookup anything.review.sirketim.com komutuyla wildcard kaydınızı test edin. Bazı DNS sağlayıcıları wildcard’ı farklı şekilde ele alır. .review.sirketim.com yerine .review gibi kısa formlar bazen sorun çıkarabilir.

SSL sertifikası gecikmeleri: cert-manager ile Let’s Encrypt kullanıyorsanız sertifika üretimi birkaç dakika sürebilir. Review App URL’ini MR’a eklemeden önce sertifikanın hazır olmasını bekleyen bir kubectl wait adımı eklemek mantıklıdır.

Image pull hatası: imagePullSecrets tanımlamayı unutmayın. Her yeni namespace’e registry credentials’ı kopyalamanız gerekir:

# Her review namespace'ine registry secret'i kopyala
- |
  kubectl get secret registry-credentials -n default 
    -o yaml | 
    sed "s/namespace: default/namespace: $KUBE_NAMESPACE/" | 
    kubectl apply -f -

Kaynak tükenmesi: Cluster üzerinde resource quota tanımlamak kritiktir. Her review namespace’i için LimitRange tanımlayarak kontrolsüz kaynak tüketimini önleyebilirsiniz.

Tam Pipeline Örneği

Tüm parçaları bir araya getiren minimal ama çalışan bir örnek:

# Tam .gitlab-ci.yml ornegi
stages:
  - build
  - test
  - review
  - cleanup

variables:
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  REVIEW_DOMAIN: review.sirketim.com
  KUBE_NAMESPACE: review-$CI_MERGE_REQUEST_IID

build_image:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $IMAGE_TAG .
    - docker push $IMAGE_TAG
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

unit_tests:
  stage: test
  image: $IMAGE_TAG
  script:
    - ./run-tests.sh
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  needs: [build_image]

deploy_review:
  stage: review
  image: bitnami/kubectl:latest
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.$REVIEW_DOMAIN
    on_stop: stop_review
    auto_stop_in: 3 days
  script:
    - kubectl create namespace $KUBE_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
    - envsubst < k8s/review-deployment.yaml | kubectl apply -n $KUBE_NAMESPACE -f -
    - envsubst < k8s/review-ingress.yaml | kubectl apply -n $KUBE_NAMESPACE -f -
    - kubectl rollout status deployment/myapp -n $KUBE_NAMESPACE --timeout=180s
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  needs: [unit_tests]

stop_review:
  stage: review
  image: bitnami/kubectl:latest
  variables:
    GIT_STRATEGY: none
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  script:
    - kubectl delete namespace $KUBE_NAMESPACE --ignore-not-found=true
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: manual
  needs: []

Sonuç

Review Apps kurulumu ilk bakışta karmaşık görünebilir, ancak bir kez doğru şekilde yapılandırıldığında ekibinizin çalışma şeklini kökten değiştirir. Geliştiriciler artık “şunu staging’e at bir bakayım” diye birbirini beklemez. Ürün yöneticisi doğrudan kendi URL’ine gidip değişiklikleri görür. QA mühendisi beş farklı feature’ı aynı anda test edebilir.

Başlangıç için Docker Compose yaklaşımı yeterlidir, takım büyüdükçe Kubernetes tabanlı sisteme geçiş yapabilirsiniz. Önemli olan temiz bir wildcard DNS yapısı kurmak, resource limitlerini doğru tanımlamak ve eski ortamları düzenli temizleyen bir mekanizma oluşturmaktır. auto_stop_in ve scheduled cleanup pipeline’larını ihmal etmeyin, aksi takdirde birkaç hafta sonra “neden cluster’ımız doldu?” sorusunu sormak zorunda kalırsınız.

Review Apps, code review kültürünü “sadece kodu okuyanlar” çemberinden çıkarıp tüm paydaşlara açar. Bu da daha hızlı iterasyon, daha az üretim sürprizi ve daha sağlıklı bir geliştirme süreci demektir.

Bir yanıt yazın

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