İçeriğe geç
NetDevOps
11 dk okuma DevOps

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.