GitLab CI Pipeline Optimizasyonu: Caching, DAG ve Paralel Job'lar
GitLab CI/CD pipeline'larınızı optimize etmek, yazılım geliştirme süreçlerinizdeki en kritik iyileştirmelerden biridir. Her gün onlarca commit yapan bir ekipte, 45 dakikalık bir pipeline 75 saatlik bekleme süresi anlamına gelir. Bu makalede, GitLab CI pipeline'larınızı 10 dakikanın altına nasıl indireceğinizi, güçlü optimizasyon tekniklerini ve production-ready konfigürasyon örneklerini inceleyeceğiz.
Pipeline Optimizasyonunun Önemi
Bir CI/CD pipeline'ı ne kadar hızlı çalışırsa, geliştiriciler o kadar çabuk geri bildirim alır ve üretkenlik o kadar artar. DORA araştırmalarına göre, optimize edilmiş pipeline'lar kullanan ekipler 4 kat daha yüksek deployment sıklığına ulaşıyor. GitLab, 30 milyondan fazla geliştirici tarafından kullanılan ve tek uygulamada tam DevOps yaşam döngüsü sunan bir platformdur. Pipeline optimizasyonu sadece zamandan tasarruf sağlamaz; aynı zamanda developer deneyimini iyileştirir ve teknik borç birikimini önler.
Performans Metrikleri ve Hedefler
Pipeline sürelerini ölçmek ve iyileştirmek için öncelikle mevcut durumu analiz etmelisiniz. GitLab'ın pipeline analiz araçları, her job'un ne kadar sürdüğünü ve hangi aşamaların darboğaz oluşturduğunu gösterir. İdeal bir pipeline, 5-10 dakika arasında tamamlanmalıdır. 30 dakikanın üzerindeki pipeline'lar ciddi iyileştirme gerektirir. Öncelikle en uzun süren job'ları belirleyin ve onlara odaklanın.
ROI Hesaplama
Pipeline optimizasyonunun geri dönüşünü hesaplamak kritiktir. Diyelim ki günde 5 commit yapan 20 geliştiriciniz var ve ortalama pipeline süresi 45 dakika. Bu, günde 75 saatlik bekleme süresi demektir. Aynı ekipte pipeline süresini 8 dakikaya düşürürseniz, günde sadece 13 saat bekleme süresi olur. Bu, günde 62 saatlik üretkenlik kazanımı anlamına gelir.
Caching Stratejileri
Cache, pipeline optimizasyonunun temel taşlarından biridir. Her seferinde internetten paket indirmek yerine, daha önce indirilen bağımlılıkları yeniden kullanmak süreyi önemli ölçüde kısaltır. GitLab, birçok cache politikası sunar ve doğru stratejiyi seçmek kritik önem taşır.
Cache Temel Konfigürasyonu
GitLab CI'da cache, job'lar arasında dosya paylaşımını sağlar. En yaygın kullanım, node_modules ve pip cache'lerini saklamaktır. Aşağıdaki örnek, temel bir cache konfigürasyonunu göstermektedir.
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.cache/npm"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm/
- node_modules/
- .cache/
Bu konfigürasyon, her branch için ayrı bir cache oluşturur. Böylece farklı branch'ler birbirini etkilemez. Ancak daha gelişmiş senaryolarda, source file hash'lerini kullanarak daha akıllı cache anahtarları oluşturabilirsiniz.
Cache Policy: Pull, Push ve Pull-Push
GitLab üç cache politikası sunar: pull-push, pull ve push. Pull-push politikası cache'i okur ve yazar, genellikle build job'ları tarafından kullanılır. Pull politikası sadece okur, test job'ları için idealdir ve parallel yazma nedeniyle oluşabilecek race condition'ları önler. Push politikası sadece yazar ve nadiren kullanılır, genellikle cache warming job'ları için.
.cache_template: &cache_template
cache:
key:
files:
- package-lock.json
- requirements.txt
paths:
- .npm-cache/
- .pip-cache/
- node_modules/
- venv/
policy: pull-push
build:
: *cache_template
script:
- npm ci
- npm run build
test:
cache:
: *cache_template
policy: pull
script:
- npm test
Önemli bir kural: her cache anahtarı için sadece bir job pull-push politikası kullanmalıdır. Birden fazla job aynı anda push yapmaya çalışırsa race condition oluşur ve cache bozulabilir.
Dependency Caching
Package manager cache'lerini doğru şekilde yapılandırmak kritiktir. npm için NPM_CONFIG_CACHE, pip için PIP_CACHE_DIR ortam değişkenlerini kullanarak GitLab'ın cache sistemine entegre edebilirsiniz. Alpine tabanlı hafif image'lar kullanmak da indirme sürelerini önemli ölçüde azaltır.
variables:
DOCKER_DRIVER: overlay2
default:
image: node:20-alpine
before_script:
- npm ci --cache .npm --prefer-offline
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm/
- node_modules/
DAG Pipeline: needs Anahtar Kelimesi
Directed Acyclic Graph (DAG) pipeline'lar, job'ların tüm stage'ler tamamlanana beklemesi yerine, sadece ihtiyaç duydukları dependency'ler tamamlandığında çalışmasını sağlar. Bu, pipeline süresini %30-50 oranında azaltabilir.
Traditional vs DAG Yaklaşımı
Traditional GitLab CI pipeline'ları, bir stage'deki tüm job'lar tamamlanana kadar bir sonraki stage'e geçmez. Ancak gerçekte birçok job'un diğer stage'lerdeki job'larla ilişkisi yoktur. needs anahtar kelimesi bu bağımlılıkları kaldırır ve job'ların sadece gerçekten ihtiyaç duydukları job'ların tamamlanmasını beklemesini sağlar.
stages:
- build
- test
- deploy
build-frontend:
stage: build
script:
- npm run build:frontend
artifacts:
paths:
- dist/frontend/
build-backend:
stage: build
script:
- npm run build:backend
artifacts:
paths:
- dist/backend/
lint:
stage: test
needs: ["build-frontend"]
script:
- npm run lint
unit-tests:
stage: test
needs: ["build-backend"]
script:
- pytest tests/unit/
integration-tests:
stage: test
needs: ["build-backend", "build-frontend"]
script:
- pytest tests/integration/
deploy:
stage: deploy
needs: ["lint", "unit-tests", "integration-tests"]
script:
- ./deploy.sh
Bu örnekte, lint job'u build-backend tamamlanana beklemez, sadece build-frontend tamamlandığında başlar. Integration-tests ise hem build-frontend hem build-backend tamamlandığında çalışır. Bu yaklaşımla, 5-10 dakikalık süre tasarrufu sağlanabilir.
DAG Avantajları
DAG pipeline'ların en büyük avantajı, gereksiz bekleme sürelerini ortadan kaldırmasıdır. Büyük monorepo'larda, backend ve frontend build'leri paralel çalışabilir ve testler sadece ilgili build tamamlandığında başlar. Bu, özellikle mikroservis mimarilerinde ve çoklu servisler içeren projelerde büyük fark yaratır.
Parallel Job'lar ve Test Bölme
Testler, çoğu pipeline'ın en uzun süren aşamasıdır. Testleri paralel çalıştırmak, toplam süreyi önemli ölçüde azaltır. GitLab, job bölme ve test bölme için güçlü özellikler sunar.
Parallel Job Konfigürasyonu
parallel anahtar kelimesi, aynı job'un birden fazla örneğini oluşturur. Her örnek, CI_NODE_INDEX ve CI_NODE_TOTAL ortam değişkenlerini alır. Bu değişkenler, job'un kaçıncı örnek olduğunu ve toplam kaç örnek olduğunu gösterir.
unit-tests:
stage: test
parallel: 4
script:
- pytest tests/unit/ --splits 4 --group $CI_NODE_INDEX
artifacts:
reports:
junit: report.xml
Bu konfigürasyon, unit testleri 4 paralel job'a böler. Her job, test setinin dörtte birini çalıştırır. Sonuç olarak, test süresi dörtte birine düşer.
Matrix Job'lar
Matrix job'lar, aynı job'u farklı değişken kombinasyonlarıyla çalıştırır. Örneğin, birden fazla Node.js versiyonu ve veritabanı kombinasyonunda test yapmak istediğinizde matrix kullanabilirsiniz.
test:
stage: test
parallel:
matrix:
- NODE_VERSION: ["18", "20", "22"]
DATABASE: ["postgres", "mysql"]
image: node:${NODE_VERSION}-alpine
services:
- ${DATABASE}:latest
alias: db
script:
- npm ci
- npm run test:integration
Bu örnek, 3 Node versiyonu ve 2 veritabanı kombinasyonuyla toplam 6 paralel job oluşturur. Her kombinasyon ayrı bir container'da çalışır.
E2E Test Paralelizasyonu
E2E testleri genellikle en uzun süren testlerdir. Playwright ve Cypress gibi araçlarla E2E testlerini bölmek önemli performans kazanımları sağlar.
e2e-tests:
stage: test
parallel: 3
script:
- npm ci --prefer-offline
- npx playwright install --with-deps chromium
- npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
Artifact Yönetimi
Artifact'lar, job'lar arasında dosya paylaşımını sağlar. Build çıktıları, test sonuçları ve coverage raporları artifact olarak saklanır ve sonraki job'lara aktarılır. Ancak artifact'ların boyutu ve saklama süresi pipeline performansını etkiler.
Artifact Konfigürasyonu
Artifact'ları sadece gerçekten ihtiyaç duyulan dosyalarla sınırlayın. Gereksiz dosyaları artifact'a eklemek, transfer sürelerini artırır ve depolama maliyetlerini yükseltir.
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
reports:
junit: report.xml
expire_in parametresi, artifact'ların ne kadar süreyle saklanacağını belirler. Production pipeline'ları için daha uzun saklama süreleri, geliştirme pipeline'ları için daha kısa süreler tercih edilebilir.
Runner Optimizasyonu
GitLab Runner, pipeline job'larının çalıştığı arka uçtur. Runner konfigürasyonu, pipeline performansını doğrudan etkiler. Doğru runner seçimi ve yapılandırması, en iyi optimizasyon sonuçlarını verir.
Docker Runner Yapılandırması
Docker executor kullanırken, Docker driver ve TLS ayarlarını optimize etmek önemlidir. DOCKER_DRIVER: overlay2, en iyi performansı sağlar ve DOCKER_TLS_CERTDIR, TLS sertifikalarının nerede saklanacağını belirler.
concurrent: 4
runners:
docker:
privileged: false
volumes:
- /cache
docker:
runtime: runc
helper_image: ubuntu:22.04
Kubernetes Runner'lar
Büyük ölçekli pipeline'lar için Kubernetes runner'lar idealdir. Otomatik ölçeklendirme özelliği, yoğun dönemlerde daha fazla pod başlatır ve boş zamanlarda kaynakları serbest bırakır. Bu, maliyet optimizasyonu sağlar.
runners:
- name: kubernetes
url: https://gitlab.com
token: ${RUNNER_TOKEN}
executor: kubernetes
kubernetes:
namespace: gitlab-ci
cpu_limit: 2
memory_limit: 4Gi
cpu_request: 1
memory_request: 2Gi
service_cpu_limit: 1
service_memory_limit: 1Gi
helper_cpu_limit: 500m
helper_memory_limit: 256Mi
Local Cache Kullanımı
Özel runner'larda, local cache kullanmak performansı önemli ölçüde artırır. Cache, GitLab object storage yerine runner'ın yerel diskinde saklanır. Bu, network gecikmesini ortadan kaldırır.
runners:
- name: local-cache-runner
executor: docker
cache_dir: /cache
docker:
image: docker:20.10.16
volumes:
- /cache:/cache
Image Pull Sürelerini Azaltma
Her job yeni bir container başlattığında, Docker image'ı pull edilir. Bu süre, özellikle büyük image'larda pipeline süresini uzatır. Image pull sürelerini azaltmak için birkaç strateji kullanabilirsiniz.
Hafif Base Image Kullanımı
Alpine tabanlı image'lar, Ubuntu veya Debian'a kıyasla çok daha küçüktür. Node:20 yerine node:20-alpine kullanmak, indirme süresini 10 kat azaltabilir.
default:
image: node:20-alpine
Image Pre-pulling
Runner'ları, pipeline'lar çalışmadan önce yaygın kullanılan image'ları önceden çekecek şekilde yapılandırabilirsiniz. Bu, ilk job'ların başlama süresini azaltır.
Interruptible Jobs
Interruptible özelliği, aynı pipeline'ın yeni bir commit ile tetiklenmesi durumunda önceki job'ları otomatik olarak iptal eder. Bu, gereksiz kaynak kullanımını önler ve pipeline kuyruğunu rahatlatır.
build:
stage: build
interruptible: true
script:
- npm run build
Bu özellik, özellikle sık commit yapan geliştiriciler için faydalıdır. Eski pipeline'lar otomatik olarak iptal edilir ve sadece en son commit'in pipeline'ı çalışır.
Production Pipeline Şablonu
Aşağıda, yukarıdaki tüm optimizasyonları birleştiren production-ready bir pipeline şablonu bulunmaktadır. Bu şablon, 25 dakikalık bir pipeline'ı 6 dakikanın altına indirebilir.
stages:
- build
- test
- deploy
variables:
DOCKER_BUILDKIT: "1"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.cache/npm"
.default_cache: &default_cache
key:
files:
- package-lock.json
- requirements.txt
paths:
- .cache/
- node_modules/
- .venv/
policy: pull
build-frontend:
stage: build
cache:
: *default_cache
policy: pull-push
script:
- npm ci --prefer-offline
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
build-backend:
stage: build
cache:
: *default_cache
policy: pull-push
script:
- python -m venv .venv
- source .venv/bin/activate
- pip install -r requirements.txt
- python setup.py build
artifacts:
paths:
- build/
expire_in: 1 hour
lint:
stage: test
needs: ["build-frontend"]
cache:
: *default_cache
script:
- npm run lint
- npm run type-check
unit-tests:
stage: test
needs: ["build-backend"]
cache:
: *default_cache
parallel: 4
script:
- source .venv/bin/activate
- pip install -r requirements.txt
- python -m pytest tests/unit/ --splits 4 --group $CI_NODE_INDEX
artifacts:
reports:
junit: report.xml
integration-tests:
stage: test
needs: ["build-backend", "build-frontend"]
services:
- postgres:15
- redis:7
variables:
POSTGRES_DB: testdb
POSTGRES_PASSWORD: testpass
cache:
: *default_cache
script:
- source .venv/bin/activate
- pip install -r requirements.txt
- python -m pytest tests/integration/ -x
e2e-tests:
stage: test
needs: ["build-frontend"]
parallel: 3
script:
- npm ci --prefer-offline
- npx playwright install --with-deps chromium
- npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
deploy-staging:
stage: deploy
needs: ["unit-tests", "integration-tests", "e2e-tests"]
script:
- ./deploy.sh staging
environment:
name: staging
rules:
- if: $CI_COMMIT_BRANCH == "main"
Sonuç
GitLab CI pipeline optimizasyonu, sürekli iyileştirme gerektiren bir süreçtir. Bu makalede ele aldığımız teknikler, caching, DAG pipeline'lar, parallel job'lar ve runner optimizasyonu, pipeline sürelerini %50-80 oranında azaltabilir. Önemli olan, mevcut pipeline'ınızı analiz etmek, darboğazları belirlemek ve kademeli olarak iyileştirmeler uygulamaktır. Unutmayın: her dakikalık optimizasyon, günler sonra büyük zaman tasarruflarına dönüşür.