GitLab CI/CD Nedir: Pipeline Temelleri ve Temel Kavramlar

Yazılım geliştirme dünyasında “bende çalışıyor” bahanesi artık kabul görmüyor. Ekipler büyüdükçe, kod tabanları karmaşıklaştıkça ve deployment sıklığı arttıkça manuel süreçler kaçınılmaz olarak kırılıyor. İşte tam bu noktada GitLab CI/CD devreye giriyor ve yazılım teslimat süreçlerini otomatize ederek hem hızı hem de güvenilirliği artırıyor. Bu yazıda GitLab CI/CD’nin temellerini, pipeline kavramını ve gerçek dünya senaryolarıyla nasıl kullanıldığını ele alacağız.

GitLab CI/CD Nedir

GitLab CI/CD, GitLab’ın yerleşik sürekli entegrasyon ve sürekli teslimat sistemidir. Harici bir araç kurmanıza gerek kalmadan, kod deponuzla aynı platformda pipeline’larınızı tanımlayıp çalıştırabilirsiniz. Jenkins gibi alternatiflerin aksine ayrı bir sunucu kurulumu, eklenti yönetimi veya karmaşık entegrasyon konfigürasyonu gerektirmez.

CI (Continuous Integration) yani sürekli entegrasyon, geliştiricilerin kod değişikliklerini sık sık ana dala entegre etmesi ve her entegrasyonun otomatik testlerle doğrulanması pratiğidir. CD (Continuous Delivery/Deployment) ise doğrulanan kodun otomatik olarak staging veya production ortamlarına gönderilmesidir.

GitLab’da tüm bu süreç tek bir YAML dosyasıyla yönetilir: .gitlab-ci.yml

Temel Kavramlar

Pipeline dünyasına girmeden önce terminolojiyi oturtmak gerekiyor. Bu kavramları kafanızda netleştirmeden YAML dosyalarına bakmak kafa karışıklığı yaratır.

Pipeline

Pipeline, bir veya daha fazla stage (aşama) içeren ve belirli bir tetikleyici tarafından başlatılan otomasyon sürecidir. Bir commit attığınızda, merge request açtığınızda veya bir zamanlayıcı tetiklendiğinde pipeline devreye girer.

Stage

Stage’ler pipeline’ın aşamalarıdır. Örneğin build, test, deploy üç farklı stage olabilir. Stage’ler sırayla çalışır ve bir önceki stage başarısız olursa sonraki çalışmaz.

Job

Job, belirli bir stage içinde çalışan görevdir. Bir stage içinde birden fazla job tanımlayabilirsiniz ve bunlar paralel olarak çalışır. Örneğin test stage’inde hem unit test hem de integration test job’u aynı anda çalışabilir.

Runner

Runner, job’ları fiilen çalıştıran ajandır. GitLab.com’un sağladığı shared runner’ları kullanabilir ya da kendi sunucunuza self-hosted runner kurabilirsiniz. Kurumsal ortamlarda genellikle kendi runner’larınızı kurmak daha mantıklıdır.

Artifact

Job çalışırken üretilen ve sonraki job’lara aktarılabilen dosyalardır. Build çıktıları, test raporları, binary dosyalar artifact olarak tanımlanabilir.

İlk .gitlab-ci.yml Dosyası

En basit pipeline tek bir job içerir. Şunu anlayın: kompleksite sonradan gelir, önce temeli kurmak lazım.

# .gitlab-ci.yml - En basit hali

stages:
  - test

run_tests:
  stage: test
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - python -m pytest tests/

Bu dosyayı projenizin kök dizinine koyup commit attığınızda GitLab otomatik olarak pipeline’ı başlatır. image direktifi Docker imajını belirtir, script ise çalıştırılacak komutları.

Gerçek Bir Pipeline Yapısı

Tek job’lu pipeline’lar pratikte işe yaramaz. Gerçek dünya senaryosunda bir Node.js uygulaması için şu yapıyı düşünelim:

# .gitlab-ci.yml - Node.js uygulaması için kapsamlı pipeline

stages:
  - install
  - lint
  - test
  - build
  - deploy

variables:
  NODE_ENV: "production"
  DOCKER_IMAGE: "registry.gitlab.com/sirketim/uygulama"

# Bağımlılıkları cache'le, her seferinde npm install çekme
cache:
  paths:
    - node_modules/

install_dependencies:
  stage: install
  image: node:18-alpine
  script:
    - npm ci
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 hour

lint_check:
  stage: lint
  image: node:18-alpine
  script:
    - npm run lint
  dependencies:
    - install_dependencies

unit_tests:
  stage: test
  image: node:18-alpine
  script:
    - npm run test:unit -- --coverage
  coverage: '/Liness*:s*(d+.?d*)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
  dependencies:
    - install_dependencies

build_app:
  stage: build
  image: node:18-alpine
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 day
  only:
    - main
    - develop

Bu yapıda dikkat etmeniz gereken birkaç nokta var:

  • cache direktifi node_modules klasörünü saklar, böylece her pipeline çalışmasında npm install yapmak zorunda kalmazsınız
  • artifacts ile dist/ klasörünü sonraki stage’lere aktarıyoruz
  • only ile hangi branch’lerde çalışacağını belirliyoruz
  • dependencies ile hangi önceki job’un artifact’larına ihtiyaç duyulduğunu belirtiyoruz

Environment Variables ve Secrets Yönetimi

Pipeline’larda en kritik konulardan biri hassas bilgilerin güvenli saklanmasıdır. Database şifreleri, API anahtarları, SSH private key’leri asla .gitlab-ci.yml dosyasına yazılmaz.

GitLab’da bu bilgileri Settings > CI/CD > Variables bölümünde saklarsınız. Sonra pipeline içinde direkt kullanabilirsiniz:

deploy_production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d 'r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -H $PRODUCTION_SERVER >> ~/.ssh/known_hosts
  script:
    - ssh deploy@$PRODUCTION_SERVER "cd /var/www/app && git pull && npm install --production && pm2 restart app"
  environment:
    name: production
    url: https://uygulamam.com
  only:
    - main

Burada $SSH_PRIVATE_KEY ve $PRODUCTION_SERVER GitLab Variables’dan geliyor. Protected olarak işaretlenmiş variable’lar sadece protected branch’lerde çalışan job’larda görünür, bu da güvenlik açısından kritik.

Variable türleri şöyle ayrılır:

  • Variable: Normal metin değeri, log’larda maskeleme yapılabilir
  • File: İçerik geçici bir dosyaya yazılır, dosyanın path’i variable olarak atanır (SSL sertifikaları için ideal)
  • Masked: Log çıktısında görünmez ama her formatta maskelenemiyor dikkat
  • Protected: Sadece protected branch/tag’lerde kullanılabilir

Docker ile Pipeline Çalıştırma

Modern CI/CD neredeyse Docker olmadan düşünülemez. Her job farklı bir Docker imajında çalışabilir, bu da bağımlılık çakışmalarını ortadan kaldırır.

# Python ve PostgreSQL gerektiren bir test ortamı

test_integration:
  stage: test
  image: python:3.11-slim
  services:
    - postgres:14-alpine
    - redis:7-alpine
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpass
    DATABASE_URL: "postgresql://testuser:testpass@postgres/testdb"
    REDIS_URL: "redis://redis:6379"
  before_script:
    - pip install -r requirements.txt
    - python manage.py migrate
  script:
    - python -m pytest tests/integration/ -v

services direktifi ilginç bir konsept. Ana container yanında sidecar olarak başka container’lar çalıştırmanızı sağlar. PostgreSQL, Redis, MySQL, Elasticsearch gibi bağımlılıkları bu şekilde test ortamınıza dahil edebilirsiniz. Service container’lar servis adıyla (örneğin postgres, redis) erişilebilir olur.

Koşullu Çalışma: rules ve only/except

Her job’un her durumda çalışmasını istemezsiniz. Production deploy’u sadece main branch’ten yapmalısınız, test coverage raporunu sadece merge request’lerde görmek isteyebilirsiniz.

only/except eski yöntem, rules ise modern ve çok daha esnek yaklaşım:

# rules ile gelişmiş koşul yönetimi

deploy_staging:
  stage: deploy
  script:
    - ./deploy.sh staging
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
      when: on_success
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: never
    - when: never

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
      when: manual
  environment:
    name: production

notify_slack:
  stage: .post
  script:
    - 'curl -X POST -H "Content-type: application/json" --data "{"text":"Deploy tamamlandi: $CI_PROJECT_NAME"}" $SLACK_WEBHOOK_URL'
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: on_success

when: manual özellikle dikkat edin. Bu, job’un otomatik çalışmayacağını, birinin GitLab arayüzünden manuel olarak tetiklemesi gerektiğini belirtir. Production deploy’lar için bu yaklaşım çok yaygın ve yerinde.

Sık kullanılan predefined variable’lar:

  • CI_COMMIT_BRANCH: Commit’in yapıldığı branch adı
  • CI_COMMIT_SHA: Commit hash değeri
  • CI_PIPELINE_SOURCE: Pipeline’ı tetikleyen kaynak (push, merge_request_event, schedule vb.)
  • CI_PROJECT_NAME: Proje adı
  • CI_REGISTRY_IMAGE: Proje’nin GitLab Container Registry adresi
  • CI_COMMIT_TAG: Eğer bir tag üzerindeyse tag adı

Docker Image Build ve Registry’ye Push

GitLab’ın kendi container registry’si var ve CI/CD ile mükemmel entegre çalışıyor:

# Docker imajı build edip GitLab Registry'ye push etme

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"

build_docker:
  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 
        --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') 
        --build-arg GIT_COMMIT=$CI_COMMIT_SHA 
        -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA 
        -t $CI_REGISTRY_IMAGE:latest 
        .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main
    - tags

$CI_REGISTRY, $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD otomatik olarak GitLab tarafından sağlanan variable’lar. Ekstra konfigürasyon gerektirmez.

Include ile Pipeline’ları Modüler Tutmak

Birden fazla proje yönetiyorsanız aynı pipeline kodunu tekrar tekrar yazmak yerine merkezi template’ler kullanmak hayat kurtarır.

# Ana .gitlab-ci.yml - Diğer template'leri include eder

include:
  - project: 'devops/ci-templates'
    ref: main
    file: '/templates/docker-build.yml'
  - project: 'devops/ci-templates'
    ref: main
    file: '/templates/security-scan.yml'
  - local: '.gitlab/ci/deploy.yml'

stages:
  - test
  - build
  - security
  - deploy

# Lokalde tanımlı özel job
custom_test:
  stage: test
  script:
    - echo "Projeye özel test"

Bu yaklaşımın güzelliği şu: Merkezi template’de bir güvenlik tarama aracını güncellerseniz tüm projeler otomatik olarak güncellenen versiyonu kullanır. 50 proje yönetirken bu özellik inanılmaz değerli.

Self-Hosted Runner Kurulumu

GitLab.com’un shared runner’larını kullanmak başlangıç için iyi, ama production ortamında kendi runner’larınızı kurmak çok daha mantıklı. Hem maliyet hem de güvenlik açısından.

# Ubuntu/Debian üzerine GitLab Runner kurulumu

# Repository ekle
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash

# Runner'ı kur
sudo apt-get install gitlab-runner

# Runner'ı kaydet (GitLab > Settings > CI/CD > Runners'dan token al)
sudo gitlab-runner register 
  --url "https://gitlab.com/" 
  --registration-token "BURAYA_TOKEN_GEL" 
  --executor "docker" 
  --docker-image "alpine:latest" 
  --description "production-runner-01" 
  --tag-list "production,docker" 
  --run-untagged="false" 
  --locked="false"

# Servisi başlat ve enable et
sudo systemctl enable gitlab-runner
sudo systemctl start gitlab-runner

# Runner durumunu kontrol et
sudo gitlab-runner status

Runner kayıt işleminde executor seçimi önemli. docker executor her job’u temiz bir container içinde çalıştırır, bu da en güvenli ve tavsiye edilen yöntem. shell executor doğrudan runner’ın shell’inde çalışır, daha hızlı ama daha riskli.

Runner’a tag atamak kritik: Job tanımında tags: [production] yazdığınızda o job sadece bu tag’e sahip runner’larda çalışır. Bu sayede production deploy job’larınızı sadece production ağında bulunan runner’larda çalıştırabilirsiniz.

Pipeline Hata Ayıklama

Pipeline’lar ilk seferinde nadiren düzgün çalışır. Hata ayıklamak için birkaç pratik yöntem:

# Pipeline içinde debug bilgisi toplama

debug_job:
  stage: test
  script:
    # Ortam değişkenlerini listele (hassas bilgileri maskele!)
    - env | grep -v PASSWORD | grep -v SECRET | sort
    # Hangi kullanıcı olduğunu gör
    - whoami
    # Çalışma dizini
    - pwd && ls -la
    # Network erişimini test et
    - curl -f https://api.sirket.com/health || echo "API erişilemiyor"
    # Docker varsa kontrol et
    - docker info 2>/dev/null || echo "Docker mevcut degil"
  when: manual

Job başarısız olduğunda GitLab’da log’ları incelemek ilk adım. Eğer log’lardan anlayamıyorsanız, yukarıdaki gibi debug job’u ekleyip environment’ı inceleyebilirsiniz.

CI_DEBUG_TRACE variable’ını true olarak ayarlarsanız shell trace aktif olur ve her komutun çıktısını görürsünüz. Ama dikkat: Bu modda variable değerleri de görünebilir, production’da asla kullanmayın.

Gerçek Senaryo: Microservice Deployment Pipeline’ı

Eksiksiz bir senaryo görelim. Bir e-ticaret uygulamasının ödeme servisini Kubernetes’e deploy eden pipeline:

# Ödeme servisi için tam pipeline

stages:
  - validate
  - test
  - build
  - scan
  - deploy-staging
  - test-staging
  - deploy-production

variables:
  SERVICE_NAME: "payment-service"
  K8S_NAMESPACE_STAGING: "ecommerce-staging"
  K8S_NAMESPACE_PROD: "ecommerce-production"

# Staging deploy
deploy_to_staging:
  stage: deploy-staging
  image: bitnami/kubectl:latest
  before_script:
    - echo $KUBECONFIG_STAGING | base64 -d > /tmp/kubeconfig
    - export KUBECONFIG=/tmp/kubeconfig
  script:
    - kubectl set image deployment/$SERVICE_NAME
        $SERVICE_NAME=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
        -n $K8S_NAMESPACE_STAGING
    - kubectl rollout status deployment/$SERVICE_NAME
        -n $K8S_NAMESPACE_STAGING
        --timeout=5m
  environment:
    name: staging
    url: https://staging.odeme.sirket.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

# Staging smoke test
smoke_test_staging:
  stage: test-staging
  image: alpine/curl:latest
  script:
    - sleep 10
    - curl -f https://staging.odeme.sirket.com/health
    - curl -f https://staging.odeme.sirket.com/api/v1/status
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

# Production deploy - manuel onay gerektirir
deploy_to_production:
  stage: deploy-production
  image: bitnami/kubectl:latest
  before_script:
    - echo $KUBECONFIG_PRODUCTION | base64 -d > /tmp/kubeconfig
    - export KUBECONFIG=/tmp/kubeconfig
  script:
    - kubectl set image deployment/$SERVICE_NAME
        $SERVICE_NAME=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
        -n $K8S_NAMESPACE_PROD
    - kubectl rollout status deployment/$SERVICE_NAME
        -n $K8S_NAMESPACE_PROD
        --timeout=10m
  environment:
    name: production
    url: https://odeme.sirket.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
      allow_failure: false

Bu pipeline’da staging’e otomatik, production’a manuel onaylı deploy yapılıyor. Staging’e deploy sonrasında smoke test çalışıyor ve başarısız olursa production deploy’u zaten mevcut olmayan bir önceki stage başarısız olduğu için çalışmayacak.

Pipeline Performansını Artırma

Uzun süren pipeline’lar geliştirici verimliliğini düşürür. Bazı pratik optimizasyonlar:

  • Cache kullanımı: npm, pip, maven gibi paket yöneticisi cache’lerini saklayın. 5 dakikalık install süresi 30 saniyeye düşebilir.
  • Paralel job’lar: Bağımsız testleri farklı job’lara bölün, aynı anda çalışsınlar.
  • needs direktifi: Varsayılan davranış stage’lerin sırayla çalışmasıdır. needs ile stage sırasını kısmen atlayıp bir job bitince başka stage’deki job’u başlatabilirsiniz.
  • İnce Docker imajları: node:18 yerine node:18-alpine kullanmak pull süresini ciddi düşürür.
  • Shallow clone: Büyük repolarda GIT_DEPTH: 1 ile sadece son commit’i çekerek clone süresini kısaltın.
# Performans optimizasyonu örnekleri

variables:
  GIT_DEPTH: "1"  # Shallow clone

fast_test:
  stage: test
  cache:
    key: "$CI_COMMIT_REF_SLUG"
    paths:
      - .cache/pip
      - venv/
  variables:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  script:
    - python -m venv venv
    - source venv/bin/activate
    - pip install -r requirements.txt
    - pytest -x -q
  needs: []  # Önceki stage bitmeden çalışmaya başla

Sonuç

GitLab CI/CD, yazılım teslimat süreçlerini standartlaştırmak ve otomatize etmek için güçlü bir platform sunuyor. Tek bir YAML dosyasıyla test, build, güvenlik taraması ve deployment’ı yönetebiliyorsunuz.

Başlarken küçük tutun: Tek stage, birkaç job, temel testler. Pipeline çalıştıktan sonra kademeli olarak ekleyin. Docker image build, registry entegrasyonu, multi-environment deployment gibi özellikler zaman içinde eklenir.

En çok dikkat etmeniz gereken konular şunlar: Variable ve secret güvenliği asla ihmal etmeyin, her job için uygun Docker imajı seçin, artifact’ları gerektiğinden uzun süre saklamayın (depolama maliyeti artar), ve runner’larınızı düzenli güncelleyin.

Pratik açıdan bakarsanız GitLab CI/CD’yi gerçekten öğrenmenin tek yolu bir proje üzerinde uygulamak. Mevcut bir projeniz varsa .gitlab-ci.yml ekleyerek başlayın, ilk pipeline çalıştığında geri kalan her şey daha mantıklı oturmaya başlıyor.

Bir yanıt yazın

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