GitLab Environments ile Staging ve Production Yönetimi

Bir projeyi geliştirirken en sık karşılaşılan sorunlardan biri şudur: “Bu değişikliği direkt production’a mı atsak?” sorusu. Bu soruyu sormaya başladığınız an, aslında bir staging ortamına ihtiyaç duyduğunuzu fark etmişsiniz demektir. GitLab Environments, tam da bu noktada devreye girerek birden fazla ortamı tek bir yerden yönetmenizi, deployment geçmişini takip etmenizi ve hatta rollback yapmanızı sağlar. Bu yazıda, gerçek dünya senaryoları üzerinden GitLab Environments’ı nasıl kuracağınızı ve staging/production akışını nasıl otomatize edeceğinizi adım adım anlatacağım.

GitLab Environments Nedir?

GitLab Environments, CI/CD pipeline’larınızın hangi ortama (staging, production, development vb.) deploy ettiğini izlemenizi sağlayan bir özelliktir. Sadece bir deployment aracından fazlasıdır; ortamların durumunu, hangi commit’in hangi ortamda çalıştığını ve deployment geçmişini merkezi bir yerden görmenizi sağlar.

Neden önemli?

  • Görünürlük: Hangi sürümün nerede çalıştığını anlık olarak takip edersiniz.
  • Rollback: Bir şeyler ters giderse tek tıkla önceki versiyona dönebilirsiniz.
  • Ortam izolasyonu: Staging’de test etmeden production’a geçemezsiniz.
  • Deployment koruması: Production için manuel onay mekanizması kurabilirsiniz.
  • Environment-specific değişkenler: Her ortam için farklı API anahtarları, veritabanı bağlantıları kullanabilirsiniz.

Temel .gitlab-ci.yml Yapısı

Önce basit bir yapıyla başlayalım. Bir Node.js uygulaması için hem staging hem de production deployment’ı olan bir pipeline oluşturalım.

stages:
  - build
  - test
  - deploy_staging
  - deploy_production

variables:
  NODE_ENV: production
  APP_NAME: my-awesome-app

build:
  stage: build
  image: node:18-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test:
  stage: test
  image: node:18-alpine
  script:
    - npm ci
    - npm run test
  coverage: '/Liness*:s*(d+.?d*)%/'

deploy_staging:
  stage: deploy_staging
  image: alpine:latest
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - echo "Staging ortamina deploy ediliyor..."
    - apk add --no-cache openssh-client rsync
    - rsync -avz --delete dist/ deploy@staging-server:/var/www/myapp/
  only:
    - develop

deploy_production:
  stage: deploy_production
  image: alpine:latest
  environment:
    name: production
    url: https://myapp.com
  script:
    - echo "Production ortamina deploy ediliyor..."
    - apk add --no-cache openssh-client rsync
    - rsync -avz --delete dist/ deploy@prod-server:/var/www/myapp/
  only:
    - main
  when: manual

Burada dikkat etmeniz gereken birkaç nokta var. environment bloğu, GitLab’a bu job’ın hangi ortama deploy ettiğini söyler. when: manual ise production deployment’ının otomatik tetiklenmeyeceği, birinin gelip “Evet, deploy et” demesi gerektiği anlamına gelir. Bu özellikle kritik.

Environment Değişkenlerini Yönetmek

Her ortamın kendine özgü değişkenlere ihtiyacı olur. Staging veritabanı ile production veritabanı aynı olmamalı. GitLab CI/CD Variables bölümünden ortam bazlı değişkenler tanımlayabilirsiniz.

GitLab arayüzünde Settings > CI/CD > Variables kısmına gidin. Değişken eklerken “Environment scope” alanını dikkatli doldurun:

  • * (yıldız): Tüm ortamlarda geçerli
  • staging: Sadece staging ortamında geçerli
  • production: Sadece production ortamında geçerli

Pipeline’da bu değişkenlere şu şekilde erişirsiniz:

deploy_staging:
  stage: deploy_staging
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - echo "Veritabani sunucusu: $DB_HOST"
    - echo "API endpoint: $API_URL"
    - ./deploy.sh
  variables:
    DEPLOY_ENV: staging
    LOG_LEVEL: debug

DEPLOY_ENV: staging gibi job seviyesinde tanımlanan değişkenler, GitLab UI’dan tanımlananlarla birleşir. Çakışma durumunda job seviyesindeki değişkenler kazanır.

Gerçek Dünya Senaryosu: Docker ile Kubernetes Deployment

Şimdi daha gerçekçi bir senaryo üzerinden gidelim. Bir microservice’imizi Docker container olarak build edip Kubernetes cluster’ına deploy edelim.

stages:
  - build
  - test
  - security_scan
  - deploy_staging
  - integration_test
  - deploy_production

variables:
  DOCKER_REGISTRY: registry.gitlab.com
  IMAGE_NAME: $CI_REGISTRY_IMAGE
  IMAGE_TAG: $CI_COMMIT_SHORT_SHA

.deploy_template: &deploy_template
  image: bitnami/kubectl:latest
  before_script:
    - kubectl config set-cluster k8s-cluster --server=$K8S_SERVER
    - kubectl config set-credentials gitlab-ci --token=$K8S_TOKEN
    - kubectl config set-context default --cluster=k8s-cluster --user=gitlab-ci
    - kubectl config use-context default

build_image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker build -t $IMAGE_NAME:latest .
    - docker push $IMAGE_NAME:$IMAGE_TAG
    - docker push $IMAGE_NAME:latest

deploy_to_staging:
  <<: *deploy_template
  stage: deploy_staging
  environment:
    name: staging
    url: https://staging.myapp.com
    on_stop: stop_staging
  script:
    - sed -i "s|IMAGE_TAG|$IMAGE_TAG|g" k8s/deployment.yaml
    - kubectl apply -f k8s/deployment.yaml -n staging
    - kubectl rollout status deployment/myapp -n staging --timeout=5m
    - echo "Staging deployment tamamlandi. Image tag: $IMAGE_TAG"
  only:
    - develop

stop_staging:
  <<: *deploy_template
  stage: deploy_staging
  environment:
    name: staging
    action: stop
  script:
    - kubectl delete deployment myapp -n staging --ignore-not-found=true
  when: manual
  only:
    - develop

Burada on_stop özelliği çok işe yarıyor. Staging ortamını durdurmanız gerektiğinde (örneğin maliyet azaltmak için) GitLab UI’dan “Stop Environment” butonuna basarak stop_staging job’ını tetikleyebilirsiniz.

Review Environments: Her Branch İçin Ayrı Ortam

GitLab’ın en güçlü özelliklerinden biri “Review Apps” ya da dinamik environment’lar. Her feature branch için otomatik olarak bir test ortamı oluşturabilirsiniz.

deploy_review:
  stage: deploy_staging
  image: bitnami/kubectl:latest
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.review.myapp.com
    on_stop: stop_review
    auto_stop_in: 2 days
  script:
    - NAMESPACE="review-$CI_COMMIT_REF_SLUG"
    - kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
    - sed -i "s|NAMESPACE|$NAMESPACE|g" k8s/review-deployment.yaml
    - sed -i "s|SUBDOMAIN|$CI_COMMIT_REF_SLUG|g" k8s/review-deployment.yaml
    - sed -i "s|IMAGE_TAG|$CI_COMMIT_SHORT_SHA|g" k8s/review-deployment.yaml
    - kubectl apply -f k8s/review-deployment.yaml -n $NAMESPACE
    - echo "Review app hazir: https://$CI_COMMIT_REF_SLUG.review.myapp.com"
  only:
    - /^feature/.*/
  except:
    - main
    - develop

stop_review:
  stage: deploy_staging
  image: bitnami/kubectl:latest
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  script:
    - NAMESPACE="review-$CI_COMMIT_REF_SLUG"
    - kubectl delete namespace $NAMESPACE --ignore-not-found=true
    - echo "Review environment silindi: $NAMESPACE"
  when: manual
  only:
    - /^feature/.*/

auto_stop_in: 2 days özelliği altın değerinde. Branch ile ilgilenmediğinizde ortam otomatik olarak kapanıyor, Kubernetes kaynaklarınızı gereksiz yere harcamıyorsunuz.

Protected Environments ile Güvenlik

Production ortamına kimin deploy yapabileceğini kontrol etmek istiyorsanız Protected Environments özelliğini kullanmalısınız. Bu özelliği Settings > CI/CD > Protected Environments bölümünden etkinleştirebilirsiniz.

Protected Environment kurulumunda şu ayarları yapıyoruz:

  • Allowed to deploy: Sadece “Maintainer” rolündeki kullanıcılar
  • Required approvals: En az 1 kişinin onayı gerekli
  • Approval rules: Belirli kullanıcı grupları onay verebilir
deploy_production:
  stage: deploy_production
  image: bitnami/kubectl:latest
  environment:
    name: production
    url: https://myapp.com
  script:
    - echo "Production deploy basliyor..."
    - echo "Commit: $CI_COMMIT_SHA"
    - echo "Committer: $CI_COMMIT_AUTHOR"
    - kubectl set image deployment/myapp app=$IMAGE_NAME:$IMAGE_TAG -n production
    - kubectl rollout status deployment/myapp -n production --timeout=10m
    - |
      if [ $? -ne 0 ]; then
        echo "Deployment basarisiz, rollback yapiliyor..."
        kubectl rollout undo deployment/myapp -n production
        exit 1
      fi
    - echo "Production deployment basarili!"
  only:
    - main
  when: manual

Manuel deployment’ı tetiklediğinizde, eğer Protected Environment’ta approval gereksinimi ayarladıysanız, GitLab sizden onay isteyecek. Bu özellik özellikle ekip büyüdükçe kritik hale geliyor.

Deployment Rollback Stratejisi

Her şey yolunda gitmeyebilir. İyi bir rollback stratejisi şart.

#!/bin/bash
# rollback.sh - Production rollback scripti

set -e

NAMESPACE=${1:-production}
DEPLOYMENT=${2:-myapp}
MAX_HISTORY=5

echo "=== Rollback Islemi Basliyor ==="
echo "Namespace: $NAMESPACE"
echo "Deployment: $DEPLOYMENT"

# Mevcut deployment gecmisini goster
echo ""
echo "--- Deployment Gecmisi ---"
kubectl rollout history deployment/$DEPLOYMENT -n $NAMESPACE

# Son basarili revision'i bul
PREVIOUS_REVISION=$(kubectl rollout history deployment/$DEPLOYMENT -n $NAMESPACE | grep -v "REVISION" | tail -2 | head -1 | awk '{print $1}')

echo ""
echo "Onceki revision'a donuluyor: $PREVIOUS_REVISION"

# Rollback yap
kubectl rollout undo deployment/$DEPLOYMENT -n $NAMESPACE --to-revision=$PREVIOUS_REVISION

# Rollback durumunu izle
kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE --timeout=5m

echo ""
echo "=== Rollback Tamamlandi ==="
kubectl get pods -n $NAMESPACE -l app=$DEPLOYMENT

Bu scripti pipeline’a entegre etmek için:

rollback_production:
  stage: deploy_production
  image: bitnami/kubectl:latest
  environment:
    name: production
  script:
    - chmod +x scripts/rollback.sh
    - ./scripts/rollback.sh production myapp
  when: manual
  only:
    - main
  allow_failure: false

Deployment Bildirimler ve Slack Entegrasyonu

Production’a deploy ettikten sonra ekibin haberdar olması gerekiyor. Slack entegrasyonu ekleyelim:

notify_deployment:
  stage: deploy_production
  image: alpine/curl:latest
  environment:
    name: production
  script:
    - |
      PAYLOAD="{
        "text": "*Production Deployment Tamamlandi!* :rocket:",
        "attachments": [
          {
            "color": "#36a64f",
            "fields": [
              {"title": "Proje", "value": "$CI_PROJECT_NAME", "short": true},
              {"title": "Branch", "value": "$CI_COMMIT_REF_NAME", "short": true},
              {"title": "Commit", "value": "$CI_COMMIT_SHORT_SHA", "short": true},
              {"title": "Yapan", "value": "$GITLAB_USER_NAME", "short": true},
              {"title": "Mesaj", "value": "$CI_COMMIT_MESSAGE", "short": false}
            ]
          }
        ]
      }"
    - curl -X POST -H 'Content-type: application/json' --data "$PAYLOAD" $SLACK_WEBHOOK_URL
  needs:
    - deploy_production
  only:
    - main

SLACK_WEBHOOK_URL değişkenini GitLab’ın CI/CD Variables bölümüne eklemek ve Masked işaretlemek unutulmamalı.

Environment Durumunu İzlemek

GitLab’ın Operations > Environments sayfası, tüm ortamlarınızın anlık durumunu gösterir. Ama bunu daha da ileriye taşıyabiliriz. Her deployment’tan sonra sağlık kontrolü yapan bir script ekleyelim:

#!/bin/bash
# health_check.sh

set -e

TARGET_URL=${1:-"https://staging.myapp.com"}
MAX_ATTEMPTS=10
WAIT_SECONDS=15
HEALTH_ENDPOINT="$TARGET_URL/health"

echo "Saglik kontrolu basliyor: $HEALTH_ENDPOINT"

for i in $(seq 1 $MAX_ATTEMPTS); do
  echo "Deneme $i/$MAX_ATTEMPTS..."
  
  HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_ENDPOINT" --connect-timeout 10)
  
  if [ "$HTTP_STATUS" = "200" ]; then
    echo "Uygulama saglikli! HTTP Status: $HTTP_STATUS"
    
    # Versiyon kontrolu
    DEPLOYED_VERSION=$(curl -s "$TARGET_URL/version" | jq -r '.version')
    echo "Calistiriliran versiyon: $DEPLOYED_VERSION"
    echo "Beklenen commit: $CI_COMMIT_SHORT_SHA"
    
    exit 0
  else
    echo "Beklenmedik HTTP status: $HTTP_STATUS"
    
    if [ "$i" -lt "$MAX_ATTEMPTS" ]; then
      echo "$WAIT_SECONDS saniye bekleniyor..."
      sleep $WAIT_SECONDS
    fi
  fi
done

echo "HATA: Uygulama $MAX_ATTEMPTS denemeden sonra saglikli degildir!"
echo "Rollback icin: kubectl rollout undo deployment/myapp -n $NAMESPACE"
exit 1

Bu scripti hem staging hem de production pipeline’larına eklemek, deployment başarısız olduğunda erken uyarı almanızı sağlar.

Tam Pipeline Orneği: Hepsini Bir Arada

Şimdiye kadar anlattıklarımı tek bir .gitlab-ci.yml dosyasında birleştirelim:

stages:
  - build
  - test
  - deploy_review
  - deploy_staging
  - verify_staging
  - deploy_production
  - verify_production

variables:
  IMAGE_NAME: $CI_REGISTRY_IMAGE
  IMAGE_TAG: $CI_COMMIT_SHORT_SHA
  KUBE_NAMESPACE_STAGING: staging
  KUBE_NAMESPACE_PROD: production

include:
  - template: Security/SAST.gitlab-ci.yml

# Build job
build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') 
                   --build-arg VCS_REF=$CI_COMMIT_SHA 
                   -t $IMAGE_NAME:$IMAGE_TAG 
                   -t $IMAGE_NAME:latest .
    - docker push $IMAGE_NAME:$IMAGE_TAG
    - docker push $IMAGE_NAME:latest

# Review app (feature branch)
deploy_review_app:
  stage: deploy_review
  image: bitnami/kubectl:latest
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.review.myapp.com
    on_stop: stop_review_app
    auto_stop_in: 3 days
  script:
    - ./scripts/deploy-review.sh $CI_COMMIT_REF_SLUG $IMAGE_TAG
  rules:
    - if: '$CI_COMMIT_BRANCH =~ /^feature//'

stop_review_app:
  stage: deploy_review
  image: bitnami/kubectl:latest
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  script:
    - kubectl delete namespace review-$CI_COMMIT_REF_SLUG --ignore-not-found=true
  rules:
    - if: '$CI_COMMIT_BRANCH =~ /^feature//'
      when: manual

# Staging deployment
deploy_staging:
  stage: deploy_staging
  image: bitnami/kubectl:latest
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - ./scripts/deploy.sh $KUBE_NAMESPACE_STAGING $IMAGE_TAG
    - bash scripts/health_check.sh https://staging.myapp.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

# Staging dogrulama
smoke_test_staging:
  stage: verify_staging
  image: node:18-alpine
  script:
    - npm install -g newman
    - newman run tests/postman/smoke-tests.json 
        --env-var "baseUrl=https://staging.myapp.com" 
        --reporters cli,junit 
        --reporter-junit-export results/smoke-test-results.xml
  artifacts:
    reports:
      junit: results/smoke-test-results.xml
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

# Production deployment
deploy_production:
  stage: deploy_production
  image: bitnami/kubectl:latest
  environment:
    name: production
    url: https://myapp.com
  script:
    - ./scripts/deploy.sh $KUBE_NAMESPACE_PROD $IMAGE_TAG
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  when: manual

# Production dogrulama
verify_production:
  stage: verify_production
  image: alpine/curl:latest
  script:
    - bash scripts/health_check.sh https://myapp.com
    - bash scripts/notify-slack.sh "production" "success"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  needs:
    - deploy_production

Sık Yapılan Hatalar

Deneyimlerimden derlediğim birkaç kritik nokta:

  • Staging’i production ile aynı tutun: Farklı resource limitleri, farklı konfigürasyonlar sorunlara yol açar. Staging mümkün olduğunca production’ı yansıtmalı.
  • Secret’ları asla kod içine yazmayın: Tüm hassas değerleri GitLab Variables’a koyun ve “Masked” olarak işaretleyin.
  • Deployment timeout’larını ayarlayın: Sonsuz bekleyen bir pipeline sizi yanıltabilir. Her kubectl rollout status komutuna --timeout ekleyin.
  • Artifact’ları temizleyin: expire_in kullanmayı unutmayın, GitLab storage’ınız hızla dolabilir.
  • Review app’leri otomatik durdurun: auto_stop_in özelliğini mutlaka kullanın, aylık cloud faturanız teşekkür edecek.

Sonuç

GitLab Environments, sadece deployment otomasyonu değil, yazılım teslimat sürecinizin tüm görünürlüğünü sağlayan bir kontrol merkezidir. Staging ortamı olmadan production’a geçmek, yarım karanlıkta araba kullanmak gibidir. Review apps ile her özellik branch’i kendi izole ortamında test edilebilir hale gelir ve bu özellikle büyük ekiplerde gerçek bir zaman tasarrufu sağlar.

Protected Environments ve manuel approval mekanizmaları sayesinde “yanlışlıkla production’a push ettim” trajedilerinin önüne geçilir. Rollback stratejisi ve health check scriptleriyle de olası sorunlar anında tespit edilip geri alınabilir.

Bu yapıyı projelerinize uyarlarken küçük başlayın: önce basit bir staging/production ayrımı yapın, ardından review apps ve gelişmiş onay mekanizmalarını ekleyin. CI/CD bir gecede mükemmelleşmez, ama her adımda hem sizin hem de ekibinizin hayatını kolaylaştırır. Eğer bu yazıdaki konularda sorularınız varsa ya da kendi pipeline’ınızda takıldığınız bir nokta varsa yorumlarda paylaşın.

Bir yanıt yazın

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