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.

Bir yanıt yazın

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