DAG Pipeline: GitLab CI/CD ile Paralel Job Yönetimi
GitLab CI/CD pipeline’larıyla ciddi ölçekli projeler yönetiyorsanız, er ya da geç şu soruyla yüzleşiyorsunuz: “Bu joblar neden sıra sıra bekliyor ki, aynı anda çalışabilirler.” İşte tam burada DAG (Directed Acyclic Graph) pipeline konsepti devreye giriyor ve hayatınızı ciddi ölçüde kolaylaştırıyor.
DAG Pipeline Nedir?
Klasik GitLab CI/CD pipeline’larında işler stage’lere göre çalışır. Bir stage tamamlanmadan bir sonrakine geçilmez. Bu yaklaşım sade ve anlaşılır olsa da büyük projelerde ciddi bir zaman kaybına yol açar. Örneğin test stage’inizde birbirinden bağımsız 5 farklı job varsa, hepsi paralel çalışır. Ama deploy stage’inizdeki bir job, sadece bir test jobuna bağımlı olsa bile tüm test stage’inin bitmesini beklemek zorundadır.
DAG pipeline bu problemi çözer. DAG, yönlü döngüsüz çizge anlamına gelir ve matematiksel olarak şunu ifade eder: Joblar arasında bağımlılık ilişkileri tanımlarsınız, bir job yalnızca kendisine bağımlı olduğu joblar bittiğinde çalışmaya başlar. Stage sıralaması değil, iş bazlı bağımlılık mantığı geçerlidir.
GitLab 12.2 sürümüyle hayatımıza giren needs keyword’ü bu yapının temelidir. needs ile bir job, kendi stage’i gelmeden önce bile çalışmaya başlayabilir, yeter ki bağımlı olduğu işler tamamlanmış olsun.
Klasik Pipeline vs DAG Pipeline
Farkı somutlaştırmak için bir senaryo düşünelim. Microservice mimarisi kullanan bir e-ticaret uygulaması geliştiriyorsunuz. Projenizde şu bileşenler var:
- auth-service: Kimlik doğrulama servisi
- product-service: Ürün yönetim servisi
- order-service: Sipariş servisi (hem auth hem product’a bağımlı)
- frontend: React uygulaması
Klasik yaklaşımda pipeline’ınız şöyle görünür:
build stage --> test stage --> deploy stage
(tüm build'ler biter) --> (tüm testler biter) --> (deploy başlar)
Bu yaklaşımda auth-service build ve test’i 2 dakikada bitmiş olsa bile, order-service build’i 8 dakika sürüyorsa deploy 8 dakika bekler. DAG’da ise auth-service ve product-service hazır olur olmaz order-service için gerekli adımlar başlar.
Temel needs Kullanımı
Hemen pratik bir örnekle başlayalım. En basit DAG pipeline yapısı şöyle kurulur:
stages:
- build
- test
- deploy
build:auth:
stage: build
script:
- echo "Auth service build ediliyor"
- docker build -t auth-service:$CI_COMMIT_SHA ./auth-service
build:product:
stage: build
script:
- echo "Product service build ediliyor"
- docker build -t product-service:$CI_COMMIT_SHA ./product-service
build:order:
stage: build
script:
- echo "Order service build ediliyor"
- docker build -t order-service:$CI_COMMIT_SHA ./order-service
test:auth:
stage: test
needs:
- build:auth
script:
- echo "Auth service testleri çalışıyor"
- pytest auth-service/tests/
test:product:
stage: test
needs:
- build:product
script:
- echo "Product service testleri çalışıyor"
- pytest product-service/tests/
test:order:
stage: test
needs:
- build:order
- test:auth
- test:product
script:
- echo "Order service testleri çalışıyor"
- pytest order-service/tests/
deploy:staging:
stage: deploy
needs:
- test:auth
- test:product
- test:order
script:
- echo "Staging ortamına deploy ediliyor"
- kubectl apply -f k8s/staging/
Burada dikkat edilmesi gereken nokta: test:auth job’u, build:auth bitmesiyle birlikte hemen başlar. test:product beklemiyor. test:order ise hem auth hem product testlerinin bitmesini bekliyor çünkü bu servislerin hazır olmasına ihtiyaç duyuyor.
Artifact Transferi ile needs Kullanımı
needs keyword’ünün çok işe yarayan bir özelliği var: bağımlı olduğunuz job’dan artifact alabilirsiniz. Varsayılan olarak needs ile bağlandığınız job’ların artifact’ları otomatik indirilir. Bunu kontrol etmek için artifacts parametresini kullanırsınız:
build:frontend:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test:frontend:
stage: test
needs:
- job: build:frontend
artifacts: true
script:
- ls dist/ # build artifact'ı burada mevcut
- npm run test:e2e
deploy:frontend:
stage: deploy
needs:
- job: build:frontend
artifacts: true
- job: test:frontend
artifacts: false # test artifact'larına ihtiyacımız yok
script:
- aws s3 sync dist/ s3://my-bucket/
artifacts: false kullanmak pipeline performansını artırır çünkü gereksiz dosya transferleri önlenir. Özellikle büyük artifact’lar söz konusu olduğunda bu optimizasyon belirgin fark yaratır.
Gerçek Dünya Senaryosu: Monorepo Pipeline
Monorepo yapısında birden fazla servis barındıran bir projede DAG pipeline’ın gücü tam anlamıyla ortaya çıkıyor. Diyelim ki şu yapıya sahibiz:
/
├── services/
│ ├── api-gateway/
│ ├── user-service/
│ ├── notification-service/
│ └── payment-service/
├── shared/
│ └── common-lib/
└── infra/
└── terraform/
Bu yapı için kapsamlı bir DAG pipeline yazalım:
stages:
- validate
- build
- unit-test
- integration-test
- security-scan
- deploy-staging
- smoke-test
- deploy-production
# Shared library önce build edilmeli
build:common-lib:
stage: build
script:
- cd shared/common-lib
- mvn clean package -DskipTests
artifacts:
paths:
- shared/common-lib/target/*.jar
expire_in: 2 hours
# Servisler paralel build, hepsi common-lib'e bağımlı
build:user-service:
stage: build
needs:
- job: build:common-lib
artifacts: true
script:
- cd services/user-service
- mvn clean package -DskipTests
artifacts:
paths:
- services/user-service/target/*.jar
expire_in: 2 hours
build:notification-service:
stage: build
needs:
- job: build:common-lib
artifacts: true
script:
- cd services/notification-service
- mvn clean package -DskipTests
artifacts:
paths:
- services/notification-service/target/*.jar
expire_in: 2 hours
build:payment-service:
stage: build
needs:
- job: build:common-lib
artifacts: true
script:
- cd services/payment-service
- mvn clean package -DskipTests
artifacts:
paths:
- services/payment-service/target/*.jar
expire_in: 2 hours
# Unit testler servis build'leri biter bitmez başlar
unit-test:user-service:
stage: unit-test
needs:
- job: build:user-service
artifacts: true
script:
- cd services/user-service
- mvn test
unit-test:payment-service:
stage: unit-test
needs:
- job: build:payment-service
artifacts: true
script:
- cd services/payment-service
- mvn test
- mvn jacoco:report
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: services/payment-service/target/site/jacoco/jacoco.xml
# Integration test hem user hem payment service'e bağımlı
integration-test:checkout-flow:
stage: integration-test
needs:
- unit-test:user-service
- unit-test:payment-service
- job: build:notification-service
artifacts: true
services:
- postgres:14
- redis:7
script:
- ./scripts/run-integration-tests.sh checkout
needs ile Pipeline Optimizasyonu
DAG’ın asıl gücü, stage sınırlarını aşabilmesinde yatıyor. Bir job, kendi stage’inden önceki herhangi bir job’a needs ile bağlanabilir. Bunu kullanarak ciddi zaman kazanımları elde edebilirsiniz:
stages:
- build
- test
- security
- deploy
build:api:
stage: build
script:
- docker build -t api:$CI_COMMIT_SHA .
- docker push registry.example.com/api:$CI_COMMIT_SHA
# Security scan, test stage'ini beklemeden build biter bitmez başlar
security:container-scan:
stage: security
needs:
- build:api
script:
- trivy image registry.example.com/api:$CI_COMMIT_SHA
security:sast:
stage: security
needs: [] # Hiçbir job'a bağımlı değil, hemen başlar
script:
- semgrep --config=auto src/
test:unit:
stage: test
needs:
- build:api
script:
- pytest tests/unit/
test:integration:
stage: test
needs:
- build:api
script:
- pytest tests/integration/
# Deploy hem security hem testlerin bitmesini bekler
deploy:staging:
stage: deploy
needs:
- security:container-scan
- security:sast
- test:unit
- test:integration
script:
- helm upgrade --install api-staging ./charts/api
--set image.tag=$CI_COMMIT_SHA
--namespace staging
needs: [] kullanımına dikkat edin. Boş array ile bir job’u hiçbir bağımlılığa bağlamıyorsunuz, yani pipeline başlar başlamaz çalışmaya başlıyor. SAST gibi kaynak kodu analiz eden araçlar için bu yaklaşım mantıklı.
needs ile Opsiyonel Job Bağımlılıkları
Bazen bağımlı olduğunuz job koşullu olarak çalışabilir ya da çalışmayabilir. optional: true parametresi bu durumu yönetmenizi sağlar:
build:docker-image:
stage: build
only:
- main
- develop
script:
- docker build -t myapp:$CI_COMMIT_SHA .
test:smoke:
stage: test
needs:
- job: build:docker-image
optional: true
script:
- |
if docker image inspect myapp:$CI_COMMIT_SHA &>/dev/null; then
echo "Docker image mevcut, smoke test çalıştırılıyor"
docker run --rm myapp:$CI_COMMIT_SHA ./smoke-test.sh
else
echo "Docker image yok, temel testler çalıştırılıyor"
./basic-smoke-test.sh
fi
Feature branch’lerde build:docker-image çalışmıyor ama test:smoke çalışmasına devam edebiliyor. optional: true olmasaydı, test:smoke bağımlı job bulunamadığı için başlamazdı.
Paralel Matrix ile DAG Kombinasyonu
parallel:matrix ile DAG’ı birleştirdiğinizde çok güçlü bir yapı elde ediyorsunuz. Birden fazla ortam ya da konfigürasyon için aynı job’u paralel çalıştırabilirsiniz:
stages:
- build
- test
- deploy
build:service:
stage: build
parallel:
matrix:
- SERVICE: [auth, product, order, notification]
script:
- cd services/$SERVICE
- docker build -t $SERVICE:$CI_COMMIT_SHA .
- docker push registry.example.com/$SERVICE:$CI_COMMIT_SHA
artifacts:
reports:
dotenv: services/$SERVICE/build.env
test:service:
stage: test
parallel:
matrix:
- SERVICE: [auth, product, order, notification]
needs:
- job: build:service
parallel:
matrix:
- SERVICE: $SERVICE
script:
- cd services/$SERVICE
- docker run registry.example.com/$SERVICE:$CI_COMMIT_SHA npm test
deploy:production:
stage: deploy
needs:
- job: test:service
parallel:
matrix:
- SERVICE: [auth, product, order, notification]
script:
- ./scripts/deploy-all.sh $CI_COMMIT_SHA
environment:
name: production
when: manual
Bu yapıyla 4 servisin build ve test işleri tamamen paralel çalışıyor. deploy:production ise tüm servislerin testleri geçmesini bekliyor.
DAG Pipeline’da Hata Yönetimi
DAG pipeline’larda hata yönetimi biraz farklı düşünülmeli. Bir job başarısız olduğunda, o job’a needs ile bağlı diğer job’lar otomatik olarak iptal edilir. Bu davranışı yönetmek için birkaç teknik var:
test:critical:
stage: test
needs:
- build:api
script:
- pytest tests/critical/ -v
allow_failure: false # Bu başarısız olursa pipeline durur
test:non-critical:
stage: test
needs:
- build:api
script:
- pytest tests/edge-cases/ -v
allow_failure: true # Bu başarısız olsa bile devam eder
# Cleanup job'u her durumda çalışmalı
cleanup:resources:
stage: .post
needs: []
when: always
script:
- ./scripts/cleanup-test-resources.sh
- docker system prune -f
# Notify job'u sadece başarısızlıkta çalışır
notify:failure:
stage: .post
needs:
- job: test:critical
optional: true
- job: test:non-critical
optional: true
when: on_failure
script:
- |
curl -X POST $SLACK_WEBHOOK
-H 'Content-type: application/json'
--data "{"text":"Pipeline başarısız: $CI_PROJECT_NAME - $CI_COMMIT_BRANCH"}"
.post stage’i pipeline’ın en sonunda çalışan özel bir stage’dir ve cleanup operasyonları için biçilmiş kaftandır.
Performance Profiling: DAG Kazanımını Ölçmek
DAG’ın size ne kadar zaman kazandırdığını somut olarak görmek için GitLab’ın pipeline analytics özelliğini kullanabilirsiniz. Ama bunu script seviyesinde de ölçmek mümkün:
stages:
- benchmark
pipeline:timing-report:
stage: benchmark
when: always
script:
- |
echo "Pipeline başlangıç zamanı: $CI_PIPELINE_CREATED_AT"
START=$(date -d "$CI_PIPELINE_CREATED_AT" +%s 2>/dev/null ||
date -j -f "%Y-%m-%dT%H:%M:%SZ" "$CI_PIPELINE_CREATED_AT" +%s)
END=$(date +%s)
DURATION=$((END - START))
MINUTES=$((DURATION / 60))
SECONDS=$((DURATION % 60))
echo "Toplam pipeline süresi: ${MINUTES}dk ${SECONDS}sn"
# Metrikleri artifact olarak kaydet
cat << EOF > pipeline-metrics.txt
Pipeline ID: $CI_PIPELINE_ID
Branch: $CI_COMMIT_BRANCH
Duration: ${MINUTES}m ${SECONDS}s
Commit: $CI_COMMIT_SHORT_SHA
EOF
artifacts:
paths:
- pipeline-metrics.txt
expire_in: 30 days
Gerçek deneyimlerimden bir örnek vereyim: 12 servisten oluşan bir microservice projesinde klasik stage bazlı pipeline yaklaşık 45 dakika sürüyordu. DAG’a geçtikten sonra bu süre 18 dakikaya indi. Sebep basit: Bağımsız servisler artık birbirini beklemiyor.
Yaygın Hatalar ve Çözümleri
DAG pipeline kurarken en sık yapılan hatalar şunlar:
- Döngüsel bağımlılık oluşturmak: Job A, Job B’ye muhtaç; Job B de Job A’ya muhtaç. GitLab bunu hata verir ama debug etmesi zaman alabilir.
ci lintile kontrol edin.
- Çok derin bağımlılık zinciri: A -> B -> C -> D -> E şeklinde 5 kademeli bağımlılık oluşturursanız, DAG’ın faydası azalır. Bağımlılık grafiğini mümkün olduğunca yatay tutun.
- Artifact boyutlarını göz ardı etmek: Her job’da büyük artifact’lar indiriyorsanız, kazandığınız zamanı network transferine harcarsınız.
artifacts: falsekullanımını ihmal etmeyin.
- needs listesini aşırı büyütmek: Bir job için 10-15 bağımlılık tanımladıysanız, büyük ihtimalle tasarımı yeniden düşünmeniz gerekiyor.
# Hatalı: Gereksiz bağımlılıklar
deploy:api:
stage: deploy
needs:
- build:api
- build:frontend # Deploy için gerekmez
- test:api
- test:frontend # API deploy için gerekmez
- security:api
- lint:api
- lint:frontend # API deploy için gerekmez
# Doğru: Sadece gerçekten gerekli bağımlılıklar
deploy:api:
stage: deploy
needs:
- test:api
- security:api
Sonuç
DAG pipeline, modern yazılım geliştirme süreçlerinde ciddi bir verimlilik silahı. Özellikle microservice mimarileri, monorepo yapıları ve paralel çalışabilecek birden fazla bileşeni olan projelerde farkı ilk pipeline çalıştırmasında hissediyorsunuz.
needs keyword’ü ilk başta basit görünse de olası kullanım kombinasyonları oldukça geniş. optional, artifacts kontrolü, parallel:matrix entegrasyonu ve .pre/.post stage’leriyle birleştiğinde son derece esnek pipeline yapıları kurabilirsiniz.
Pratik önerim: Mevcut bir pipeline’ınızı alın, hangi job’ların birbirinden gerçekten bağımsız olduğunu bir kağıda çizin, bağımlılık grafiğini belirleyin ve needs ekleyerek başlayın. İlk denemede mükemmel olmak zorunda değilsiniz. GitLab’ın pipeline görselleştirme aracı, DAG modunda bağımlılık grafiğini size güzel bir şekilde gösteriyor, oradan hatalarınızı kolayca görüp düzeltebilirsiniz.
CI/CD pipeline’ları sadece “çalışıyor mu” diye bakılacak araçlar değil. Geliştiricilerin günde defalarca beklediği süreçler. Bu bekleme süresini yarıya indirmek, hem bireysel verimliliği hem de takım moralini doğrudan etkiliyor.
