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çerlistaging: Sadece staging ortamında geçerliproduction: 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 statuskomutuna--timeoutekleyin. - Artifact’ları temizleyin:
expire_inkullanmayı 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.
