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.
