GitLab ile Çoklu Proje Pipeline Entegrasyonu
Birden fazla projeyi aynı anda yönetmek, modern yazılım geliştirme süreçlerinin en zorlu taraflarından biri. Özellikle mikroservis mimarisine geçiş yaptıktan sonra “şu servisin pipeline’ı patladı mı, bu servis deploy edildi mi?” diye repo repo gezmek gerçekten sinir bozucu. GitLab’ın çoklu proje pipeline entegrasyonu tam da bu noktada hayat kurtarıcı oluyor. Bu yazıda, farklı GitLab repolarındaki pipeline’ları birbirine bağlamayı, tetikleme mekanizmalarını ve gerçek dünya senaryolarında nasıl çalıştığını detaylıca ele alacağız.
Çoklu Proje Pipeline Nedir?
Tek bir uygulama bile olsa, genellikle birden fazla repo ile çalışıyorsunuz. Frontend, backend, veritabanı migration’ları, Helm chart’ları, belki ayrı bir shared library reposu. Bu bileşenlerin birbirine bağımlı olduğu durumda, birinin değişmesi diğerlerinin de test edilmesini ya da deploy edilmesini gerektirebilir.
GitLab’ın Multi-project Pipeline özelliği, bir projedeki pipeline’ın başka bir projedeki pipeline’ı tetiklemesine olanak tanıyor. Bu sayede:
- Upstream projeler downstream projeleri tetikleyebiliyor
- Downstream’den upstream’e durum bilgisi akabiliyor
- Pipeline görselleştirmesi birleşik olarak takip edilebiliyor
- Değişken paylaşımı pipeline’lar arasında yapılabiliyor
Temel Kavramlar ve Mimari
Başlamadan önce birkaç terimi netleştirmek lazım:
Upstream Pipeline: Tetikleyen pipeline. Yani “ben değiştim, şimdi sen de çalış” diyen taraf.
Downstream Pipeline: Tetiklenen pipeline. Upstream’den gelen komutla başlayan taraf.
Bridge Job: Upstream pipeline içinde tanımlanan ve downstream’i tetikleyen özel iş tipi. trigger: anahtar kelimesiyle tanımlanır.
Pipeline Token: Downstream projeyi tetiklemek için kullanılan kimlik doğrulama mekanizması.
Mimarinin özü şu şekilde çalışıyor: A projesi kendi pipeline’ını çalıştırıyor, belirli bir aşamada B projesinin pipeline’ını tetikliyor, gerekirse B bitmeden A devam etmiyor.
Trigger Token ile Manuel Tetikleme
En basit yöntemden başlayalım. Downstream projeye gidip Settings > CI/CD > Pipeline triggers bölümünden bir token oluşturuyorsunuz. Bu token ile HTTP API üzerinden tetikleme yapabilirsiniz.
# Downstream projeyi API üzerinden tetikleme
curl -X POST
--fail
-F token=glptt-xxxxxxxxxxxxxxxxxxxxxxxxxx
-F ref=main
"https://gitlab.example.com/api/v4/projects/42/trigger/pipeline"
Bu yaklaşım basit ama yönetimi zorlaşabiliyor, özellikle token rotation meselesinde. Daha güçlü yöntem olarak trigger: keyword’ünü kullanan bridge job’lara geçelim.
Bridge Job ile Downstream Tetikleme
GitLab CI’da trigger: direktifi ile downstream pipeline’ı doğrudan .gitlab-ci.yml içinden yönetebilirsiniz. Upstream projenizin .gitlab-ci.yml dosyasına şöyle bir stage ekleyebilirsiniz:
# upstream-service/.gitlab-ci.yml
stages:
- test
- build
- trigger-downstream
unit-tests:
stage: test
script:
- echo "Upstream testler çalışıyor"
- npm test
build-image:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
trigger-integration-tests:
stage: trigger-downstream
trigger:
project: mygroup/integration-test-suite
branch: main
strategy: depend
variables:
UPSTREAM_IMAGE_TAG: $CI_COMMIT_SHA
UPSTREAM_PROJECT: $CI_PROJECT_NAME
DEPLOY_ENV: staging
Burada dikkat edilmesi gereken birkaç nokta var:
strategy: depend kullandığınızda, upstream pipeline downstream bitmeden tamamlanmış sayılmaz. Yani downstream başarısız olursa upstream da başarısız görünür. strategy: depend yazmazsanız upstream downstream’i ateşleyip devam eder, sonucunu beklemez.
variables bloğu ile downstream’e değişken geçirebilirsiniz. Bu çok kritik, özellikle hangi image’ı deploy edeceğinizi bildirmeniz gerektiğinde.
Downstream Tarafında Değişkenleri Almak
Downstream projenin .gitlab-ci.yml dosyası da upstream’den gelen değişkenleri normal pipeline değişkeni gibi kullanabilir:
# downstream integration-test-suite/.gitlab-ci.yml
stages:
- prepare
- integration-test
- report
prepare-environment:
stage: prepare
script:
- echo "Upstream project: $UPSTREAM_PROJECT"
- echo "Test edilecek image: $UPSTREAM_IMAGE_TAG"
- |
if [ -z "$UPSTREAM_IMAGE_TAG" ]; then
echo "HATA: UPSTREAM_IMAGE_TAG değişkeni boş geldi!"
exit 1
fi
- echo "IMAGE_TAG=$UPSTREAM_IMAGE_TAG" >> build.env
artifacts:
reports:
dotenv: build.env
run-integration-tests:
stage: integration-test
needs: [prepare-environment]
script:
- docker pull $CI_REGISTRY/mygroup/upstream-service:$UPSTREAM_IMAGE_TAG
- docker compose -f docker-compose.test.yml up --abort-on-container-exit
environment:
name: $DEPLOY_ENV
dotenv artifact tipi kullanarak değişkenleri job’lar arasında iletebilir, needs: ile de bağımlılık zinciri oluşturabilirsiniz.
Gerçek Dünya Senaryosu: Mikroservis Deploy Zinciri
Diyelim ki şöyle bir yapınız var: user-service, order-service ve notification-service isminde üç mikroservisiniz var. order-service her deploy edildiğinde entegrasyon testlerinin çalışmasını ve sonrasında notification-service‘in de güncellenmesini istiyorsunuz.
# order-service/.gitlab-ci.yml
stages:
- lint
- test
- build
- deploy
- downstream
variables:
IMAGE_NAME: $CI_REGISTRY_IMAGE
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
lint-check:
stage: lint
image: python:3.11
script:
- pip install flake8
- flake8 src/
run-tests:
stage: test
image: python:3.11
services:
- name: postgres:15
alias: db
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
DATABASE_URL: postgresql://testuser:testpass@db/testdb
script:
- pip install -r requirements.txt
- pytest tests/ -v --cov=src --cov-report=xml
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
when: always
build-and-push:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker build -t $IMAGE_NAME:latest .
- docker push $IMAGE_NAME:$IMAGE_TAG
- docker push $IMAGE_NAME:latest
only:
- main
deploy-staging:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/order-service order-service=$IMAGE_NAME:$IMAGE_TAG -n staging
- kubectl rollout status deployment/order-service -n staging --timeout=120s
environment:
name: staging
only:
- main
trigger-notification-service:
stage: downstream
trigger:
project: mygroup/notification-service
branch: main
strategy: depend
variables:
ORDER_SERVICE_VERSION: $IMAGE_TAG
TRIGGER_REASON: "order-service deployment"
TARGET_ENV: staging
only:
- main
needs:
- job: deploy-staging
artifacts: false
Bu yapıda notification-service reposunda da bir CI dosyası olacak ve ORDER_SERVICE_VERSION değişkenini kullanarak uyumlu versiyonu deploy edecek.
Parent-Child Pipeline Yapısı
Farklı bir kullanım senaryosu da parent-child pipeline yapısı. Bu, aynı repo içinde pipeline’ı alt dosyalara bölmek için kullanılıyor. Büyük monorepo’larda çok işe yarıyor.
# .gitlab-ci.yml (parent)
stages:
- triggers
trigger-frontend:
stage: triggers
trigger:
include: ci/frontend.gitlab-ci.yml
strategy: depend
rules:
- changes:
- frontend/**/*
- ci/frontend.gitlab-ci.yml
trigger-backend:
stage: triggers
trigger:
include: ci/backend.gitlab-ci.yml
strategy: depend
rules:
- changes:
- backend/**/*
- ci/backend.gitlab-ci.yml
trigger-infrastructure:
stage: triggers
trigger:
include: ci/infra.gitlab-ci.yml
strategy: depend
rules:
- changes:
- terraform/**/*
- k8s/**/*
Bu sayede frontend/*/ altında değişiklik olmadığında frontend pipeline’ı hiç çalışmıyor. Büyük monorepolarda pipeline sürelerini dramatik biçimde kısaltıyor.
Dinamik Child Pipeline
GitLab 12.9 ile gelen özelliklerden biri de dinamik child pipeline. Bir script çalıştırıp sonucuna göre pipeline YAML’ı oluşturabiliyorsunuz:
# .gitlab-ci.yml
generate-pipeline:
stage: .pre
image: python:3.11
script:
- python scripts/generate_pipeline.py > generated-pipeline.yml
- cat generated-pipeline.yml
artifacts:
paths:
- generated-pipeline.yml
run-dynamic-pipeline:
stage: test
trigger:
include:
- artifact: generated-pipeline.yml
job: generate-pipeline
strategy: depend
# scripts/generate_pipeline.py içeriği (bash ile örnek)
#!/usr/bin/env python3
import os
import yaml
changed_services = os.environ.get('CHANGED_SERVICES', '').split(',')
jobs = {}
for service in changed_services:
service = service.strip()
if not service:
continue
jobs[f'test-{service}'] = {
'stage': 'test',
'script': [
f'cd services/{service}',
'npm test'
]
}
pipeline = {
'stages': ['test'],
**jobs
}
print(yaml.dump(pipeline))
Bu yaklaşım özellikle değişen servisleri dinamik olarak tespit edip sadece onları test etmek istediğinizde çok güçlü.
Pipeline Görünürlüğü ve İzleme
Çoklu pipeline’ları yönetirken görünürlük çok önemli. GitLab’ın pipeline görselleştirme ekranında upstream-downstream ilişkisi görünür ama bunu daha iyi takip etmek için birkaç yöntem var.
Pipeline’lar arası durum kontrolü API ile:
#!/bin/bash
# check-downstream-status.sh
GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx"
GITLAB_URL="https://gitlab.example.com"
PROJECT_ID="42"
PIPELINE_ID="$1"
if [ -z "$PIPELINE_ID" ]; then
echo "Kullanim: $0 <pipeline_id>"
exit 1
fi
MAX_WAIT=300
ELAPSED=0
INTERVAL=15
echo "Pipeline $PIPELINE_ID bekleniyor..."
while [ $ELAPSED -lt $MAX_WAIT ]; do
STATUS=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN"
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/pipelines/$PIPELINE_ID"
| python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
echo "$(date '+%H:%M:%S') - Durum: $STATUS"
case $STATUS in
"success")
echo "Pipeline basariyla tamamlandi!"
exit 0
;;
"failed"|"canceled")
echo "Pipeline basarisiz veya iptal edildi: $STATUS"
exit 1
;;
"running"|"pending"|"created")
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
;;
*)
echo "Bilinmeyen durum: $STATUS"
exit 1
;;
esac
done
echo "Zaman asimi: $MAX_WAIT saniye icinde tamamlanamadi"
exit 1
Güvenlik: Access Token ve İzinler
Çoklu proje pipeline’larında güvenlik genellikle atlanıyor. Bazı kritik noktalar:
CI/CD değişkenleri ile token yönetimi:
Downstream projeyi tetiklemek için upstream’in yetkisi olması gerekiyor. GitLab bunu iki şekilde çözüyor:
- Pipeline trigger tokens: Downstream projede oluşturulan, sadece tetikleme için kullanılan tokenlar. Settings > CI/CD > Pipeline triggers altında yönetilir.
- Project access tokens: Daha geniş yetki gerektiren durumlar için. API erişimi sağlar ama fazla yetki vermeyin.
Önerilen yaklaşım:
# Downstream'i trigger token ile tetikleme (güvenli yöntem)
trigger-with-token:
stage: downstream
image: curlimages/curl:latest
script:
- |
PIPELINE_ID=$(curl -s -X POST
-F "token=$DOWNSTREAM_TRIGGER_TOKEN"
-F "ref=main"
-F "variables[UPSTREAM_SHA]=$CI_COMMIT_SHA"
-F "variables[UPSTREAM_JOB_ID]=$CI_JOB_ID"
"https://gitlab.example.com/api/v4/projects/$DOWNSTREAM_PROJECT_ID/trigger/pipeline"
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Downstream pipeline ID: $PIPELINE_ID"
echo "DOWNSTREAM_PIPELINE_ID=$PIPELINE_ID" >> pipeline.env
artifacts:
reports:
dotenv: pipeline.env
DOWNSTREAM_TRIGGER_TOKEN değişkenini upstream projenin Settings > CI/CD > Variables bölümüne masked ve protected olarak ekleyin.
Yaygın Sorunlar ve Çözümleri
Sorun: Downstream pipeline tetiklenmiyor
İlk kontrol edilecek şey, upstream projenin downstream projeye erişimi var mı. GitLab’da Settings > CI/CD > Token Access bölümüne bakın ve downstream projenin upstream’e izin verip vermediğini kontrol edin. GitLab 15.9’dan itibaren bu güvenlik katmanı varsayılan olarak aktif.
# CI/CD Token Access izin kontrolü için API
curl --header "PRIVATE-TOKEN: $ADMIN_TOKEN"
"https://gitlab.example.com/api/v4/projects/DOWNSTREAM_ID/ci/cd_variables"
Sorun: Değişkenler downstream’e ulaşmıyor
trigger: ile geçirilen değişkenler sadece o downstream’e gidiyor, nested downstream’lere gitmiyor. Nested yapılarda değişkenleri her katmanda tekrar geçirmeniz gerekiyor.
Sorun: strategy: depend kullanınca upstream çok uzun sürüyor
Downstream’in bitmesini beklemek pipeline sürenizi uzatır. Bunu minimize etmek için downstream pipeline’ı optimize edin ya da gerçekten beklemek zorunda olmadığınız durumlarda strategy: depend kaldırın. Alternatif olarak notification-based yaklaşım kullanabilirsiniz.
Gelişmiş Senaryo: Shared Library ve Çoklu Servis Entegrasyonu
Büyük organizasyonlarda genellikle paylaşılan CI template’leri olan bir ci-templates reposu olur. Tüm servisler buradan include yaparak kendi pipeline’larını oluşturur:
# order-service/.gitlab-ci.yml - Shared template kullanan versiyon
include:
- project: 'myorg/ci-templates'
ref: main
file:
- '/templates/python-service.yml'
- '/templates/docker-build.yml'
- '/templates/kubernetes-deploy.yml'
variables:
SERVICE_NAME: order-service
PYTHON_VERSION: "3.11"
K8S_NAMESPACE: production
DOWNSTREAM_PROJECTS: "myorg/notification-service,myorg/analytics-service"
# Template'den gelen job'ları override edebilirsiniz
unit-tests:
extends: .python-test-template
variables:
TEST_PATH: tests/unit
COVERAGE_MIN: "85"
# Kendi özel job'larınızı ekleyebilirsiniz
trigger-dependents:
stage: .post
trigger:
project: myorg/notification-service
branch: main
strategy: depend
variables:
TRIGGERING_SERVICE: $SERVICE_NAME
SERVICE_VERSION: $CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
Bu yapı sayesinde tüm servisler aynı temel pipeline mantığını kullanıyor, değiştirilmesi gereken sadece ci-templates reposu oluyor. Upstream template değiştiğinde downstream projeler de güncel kalıyor.
Pipeline Optimizasyon İpuçları
Çoklu pipeline yönetirken performans kritik önem taşıyor:
Paralel tetikleme kullanın:
# Sıralı yerine paralel tetikleme
trigger-service-a:
stage: downstream
trigger:
project: mygroup/service-a
strategy: depend
trigger-service-b:
stage: downstream
trigger:
project: mygroup/service-b
strategy: depend
# İkisi de aynı stage'de olduğu için paralel çalışır
Cache ve artifact yönetimi:
Downstream pipeline’lara büyük artifact göndermek hem yavaşlatır hem bant genişliği harcar. Bunun yerine artifact’ı registry’e push edip sadece tag/sürüm bilgisini değişken olarak geçirin.
Koşullu tetikleme:
trigger-performance-tests:
trigger:
project: mygroup/performance-tests
strategy: depend
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
- if: $CI_PIPELINE_SOURCE == "schedule"
when: always
- when: never
Böylece her push’ta değil, sadece main’e merge edildiğinde ya da zamanlanmış çalışma sırasında downstream tetikleniyor.
Sonuç
GitLab’ın çoklu proje pipeline entegrasyonu, mikroservis dünyasında dağınık deploy süreçlerini tek bir tutarlı zincire bağlamak için güçlü bir araç. trigger: direktifi ile bridge job’lar oluşturmak, strategy: depend ile downstream bağımlılıklarını yönetmek ve değişkenleri pipeline’lar arasında aktarmak; başlangıçta karmaşık görünse de birkaç pratikten sonra alışılagelen bir pattern haline geliyor.
Dikkat etmeniz gereken en kritik noktalar: güvenlik için token’ları düzgün yönetin, downstream’e sadece ihtiyaç duyulan değişkenleri geçirin, strategy: depend kullanımını gerçekten gerekli olduğu yerlere sınırlı tutun ve büyüyen pipeline ağını görselleştirip izleyin.
Küçük bir monorepo’dan başlayarak parent-child pipeline yapısını denemenizi, ardından gerçek çoklu proje senaryolarına geçmenizi öneririm. Pipeline’larınız ne kadar karmaşık olursa olsun, iyi tanımlanmış tetikleme kuralları ve net değişken stratejisiyle yönetilebilir hale gelecektir.
