GitLab Runner Kurulumu ve Pipeline Yapılandırması

Üç yıl önce bir müşterimizin CI/CD altyapısını sıfırdan kuruyordum. Ekip GitHub Actions kullanmak istiyordu ama şirket politikası gereği her şeyin on-premise kalması gerekiyordu. GitLab’a geçtik, runner kurulumuna başladım ve o gün öğrendiklerimi bugün hala kullanıyorum. Bu yazıda hem temel kurulumu hem de gerçek bir pipeline’ı nasıl yapılandıracağınızı anlatacağım.

GitLab Runner Nedir ve Neden Önemli?

GitLab Runner, GitLab CI/CD pipeline’larınızın iş yükünü üstlenen, GitLab sunucusundan bağımsız çalışan bir ajan yazılımıdır. .gitlab-ci.yml dosyanızda tanımladığınız her job, bir runner tarafından execute edilir. Runner’ı GitLab’ın işçisi olarak düşünebilirsiniz: GitLab koordinatör rolünü üstlenirken runner fiilen kodu derler, test çalıştırır, Docker image build eder.

Runner’ların üç farklı scope’u var:

  • Shared runners: GitLab.com’un sunduğu, tüm projelerin ortak kullandığı runner’lar
  • Group runners: Bir grup içindeki tüm projelerin kullanabildiği runner’lar
  • Project runners: Sadece tek bir projeye özgü runner’lar

On-premise kurulumda çoğunlukla project veya group runner kurarsınız. Shared runner mantığını kendi altyapınızda da uygulayabilirsiniz ama bunu daha sonra konuşalım.

Kurulum Öncesi Hazırlık

Runner’ı kuracağınız makinenin gereksinimleri projenize göre değişir. Sadece shell komutları çalıştıracaksanız minimal bir VM yeterli. Docker build yapacaksanız disk alanına ve CPU’ya dikkat etmeniz gerekiyor.

Benim önerim: Production runner’larını GitLab sunucusundan ayrı bir makineye kurun. GitLab sunucusu ile runner’ı aynı makineye koymak kısa vadede pratik görünse de büyük bir build geldiğinde GitLab’ın kendisi yanıt veremez hale gelebilir.

Runner makinesinde şunları kontrol edin:

  • Git kurulu olmalı (runner her job öncesinde repo’yu klonlar)
  • Eğer Docker executor kullanacaksanız Docker daemon çalışıyor olmalı
  • GitLab sunucusuna network erişimi olmalı
  • Gerekli portlara güvenlik duvarı izin vermeli (HTTPS için 443)

Linux’ta GitLab Runner Kurulumu

Debian/Ubuntu tabanlı sistemler için resmi repo’yu ekleyip kurulumu yapabilirsiniz:

# GitLab resmi paket deposunu 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

# Servis durumunu kontrol et
sudo systemctl status gitlab-runner

RHEL/CentOS/AlmaLinux için:

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

# Kur
sudo dnf install gitlab-runner

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

Kurulum sonrası gitlab-runner kullanıcısı otomatik oluşturulur. Bu kullanıcının hangi kaynaklara erişeceğini şimdiden düşünmeye başlayın. Docker komutlarını çalıştırması gerekiyorsa docker grubuna eklemeniz gerekecek.

# gitlab-runner kullanıcısını docker grubuna ekle
sudo usermod -aG docker gitlab-runner

# Değişikliğin aktif olması için servisi yeniden başlat
sudo systemctl restart gitlab-runner

Runner’ı GitLab’a Kayıt Ettirme

Bu adım çoğu kişinin takıldığı yer. GitLab arayüzünden registration token almanız gerekiyor. GitLab 15.x ve sonrasında token alma yeri değişti, dikkat edin.

GitLab 16.x ve sonrası için: Settings > CI/CD > Runners bölümüne gidin. “New project runner” butonuna tıklayın, etiketler ve diğer ayarları girin, size bir authentication token verilecek.

Eski yöntem (deprecated ama hala çalışıyor): Settings > CI/CD > Runners > Registration token alanından kopyalayın.

Token’ı aldıktan sonra:

# Interactive kayıt (öğrenme aşaması için iyi)
sudo gitlab-runner register

# Tek satırda kayıt (otomasyon için)
sudo gitlab-runner register 
  --non-interactive 
  --url "https://gitlab.sirketiniz.com/" 
  --token "glrt-xxxxxxxxxxxx" 
  --executor "docker" 
  --docker-image "alpine:latest" 
  --description "production-docker-runner" 
  --tag-list "docker,production" 
  --run-untagged="false" 
  --locked="false"

Executor seçimi kritik bir karar. En çok kullanılanlar:

  • shell: Komutları doğrudan runner makinesinin shell’inde çalıştırır. Basit ama riskli, job’lar izole değil
  • docker: Her job için temiz bir container başlatır. En çok tercih edilen yöntem
  • docker+machine: Talep üzerine bulut VM’i başlatır, enterprise senaryolarda işe yarar
  • kubernetes: Job’ları K8s pod’ları olarak çalıştırır, büyük ölçekli yapılar için ideal

Runner Yapılandırması: config.toml

Runner kaydedildikten sonra /etc/gitlab-runner/config.toml dosyası oluşur. Bu dosyayı iyi anlarsanız runner’ı istediğiniz gibi şekillendirebilirsiniz.

concurrent = 4
check_interval = 0
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "production-docker-runner"
  url = "https://gitlab.sirketiniz.com/"
  id = 12
  token = "glrt-xxxxxxxxxxxx"
  token_obtained_at = 2024-01-15T10:00:00Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    MaxUploadedArchiveSize = 0
  [runners.docker]
    tls_verify = false
    image = "alpine:latest"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
    network_mtu = 0

Burada dikkat etmeniz gereken birkaç parametre var:

  • concurrent: Aynı anda kaç job çalışacağını belirler. Runner makinenizin CPU ve RAM kapasitesine göre ayarlayın
  • privileged: Docker içinde Docker build yapmanız gerekiyorsa true yapmanız gerekir, ama güvenlik riskini göz önünde bulundurun
  • volumes: Host’tan container’a mount edilecek dizinler. Cache dizini buraya eklenmeli

İlk .gitlab-ci.yml Dosyası

Artık runner hazır, pipeline yazabiliriz. Basit bir Node.js projesi için örnek:

# .gitlab-ci.yml
image: node:18-alpine

stages:
  - install
  - test
  - build
  - deploy

variables:
  NODE_ENV: "production"
  CACHE_DIR: ".npm"

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .npm/
    - node_modules/

install_dependencies:
  stage: install
  script:
    - npm ci --cache $CACHE_DIR --prefer-offline
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 hour

run_tests:
  stage: test
  script:
    - npm run test:unit
    - npm run test:integration
  coverage: '/Liness*:s*(d+.?d*)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

build_app:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  only:
    - main
    - develop

deploy_staging:
  stage: deploy
  image: alpine:latest
  script:
    - apk add --no-cache openssh-client rsync
    - eval $(ssh-agent -s)
    - echo "$STAGING_SSH_PRIVATE_KEY" | tr -d 'r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - rsync -avz --delete dist/ [email protected]:/var/www/uygulama/
  environment:
    name: staging
    url: https://staging.sirket.com
  only:
    - develop

Bu pipeline’da dikkat edin: cache bloğu node_modules ve npm cache’ini saklar, böylece her build’de sıfırdan indirme yapmaz. artifacts ile stage’ler arası dosya geçişini yönetiyoruz.

Gerçek Dünya Senaryosu: Docker Image Build ve Push

Çoğu ekibin ihtiyacı olan şey: kod commit edildiğinde otomatik Docker image build edip registry’e push etmek.

# Docker build ve push pipeline'ı
stages:
  - test
  - build
  - push
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  LATEST_TAG: $CI_REGISTRY_IMAGE:latest

.docker_auth: &docker_auth
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

unit_tests:
  stage: test
  image: python:3.11-slim
  script:
    - pip install -r requirements-dev.txt
    - pytest tests/unit/ -v --junitxml=report.xml
  artifacts:
    reports:
      junit: report.xml

build_image:
  stage: build
  image: docker:24-dind
  services:
    - docker:24-dind
  <<: *docker_auth
  script:
    - docker build
        --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
        --build-arg VCS_REF=$CI_COMMIT_SHA
        --build-arg VERSION=$CI_COMMIT_TAG
        -t $IMAGE_TAG
        -t $LATEST_TAG .
    - docker push $IMAGE_TAG
    - docker push $LATEST_TAG
  only:
    - main
    - tags

Burada docker:24-dind (Docker in Docker) kullandık. Runner’da privileged = true olması gerekiyor bu yaklaşım için. Alternatif olarak Kaniko veya Buildah kullanabilirsiniz, privileged gerektirmiyor.

Cache Stratejileri ve Optimizasyon

Pipeline’ların yavaş çalışması en büyük şikayetlerden biri. Cache’i doğru kullanmak build sürenizi ciddi ölçüde kısaltır.

# Gelişmiş cache stratejisi
cache:
  - key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull-push
  - key: pip-$CI_COMMIT_REF_SLUG
    paths:
      - .pip-cache/
    policy: pull

# Sadece cache'i güncelleyen özel job
update_cache:
  stage: .pre
  script:
    - npm ci
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: push
  only:
    changes:
      - package-lock.json

policy: pull kullandığınızda job cache’i yazar ama okumaz. pull-push hem okur hem yazar. only: changes ile sadece belirli dosyalar değiştiğinde cache güncelleme job’ını çalıştırabilirsiniz.

Runner Etiketleri ve Job Yönlendirme

Birden fazla runner’ınız varsa hangi job’ın hangi runner’da çalışacağını etiketlerle kontrol edersiniz. Örneğin GPU gerektiren ML job’larını sadece GPU’lu makinelere yönlendirebilirsiniz.

# Farklı runner'lara iş yönlendirme
stages:
  - build
  - test
  - ml_train

build_backend:
  stage: build
  tags:
    - docker
    - linux
  script:
    - ./gradlew build

integration_tests:
  stage: test
  tags:
    - docker
    - high-memory
  script:
    - ./gradlew integrationTest
  resource_group: integration_tests

train_model:
  stage: ml_train
  tags:
    - gpu
    - cuda
  script:
    - python train.py --epochs 100
  timeout: 4 hours

resource_group kullanımına dikkat edin: aynı anda sadece bir job o resource group’ta çalışabilir. Integration testleri gibi paylaşılan kaynakları kullanan işler için hayat kurtarıcı.

Gizli Değişken Yönetimi

Pipeline içinde şifre, API anahtarı gibi hassas bilgileri asla .gitlab-ci.yml dosyasına yazmayın. GitLab’ın CI/CD Variables özelliğini kullanın.

Settings > CI/CD > Variables bölümünden değişken ekleyebilirsiniz. Değişken tiplerini doğru kullanın:

  • Variable: Normal metin değişken, loglarda maskelenir
  • File: İçerik geçici bir dosyaya yazılır, dosyanın path’i değişkene atanır (kuberentes config, SSL sertifikası için ideal)
deploy_production:
  stage: deploy
  script:
    # File tipinde değişken kullanımı
    - export KUBECONFIG=$KUBE_CONFIG_FILE
    - kubectl apply -f k8s/
    
    # Masked değişken kullanımı
    - echo $DATABASE_URL | grep -o 'postgresql://[^@]*@' # şifreyi göstermez
    
    # Vault entegrasyonu varsa
    - export DB_PASS=$(vault kv get -field=password secret/myapp/db)
  environment:
    name: production
  when: manual
  only:
    - main

when: manual kullanımı production deploy’lar için önerim. Otomatik deploy staging’e kadar gider, production için birinin onaylaması gerekir.

Runner Performans Sorunlarını Gidermek

Sahadan bir kaç senaryo: Runner’ınız job almıyor, queue’da iş birikmiş. İlk kontrol noktalarınız:

# Runner durumunu kontrol et
sudo gitlab-runner status

# Runner'ın GitLab ile iletişimini test et
sudo gitlab-runner verify

# Detaylı loglara bak
sudo journalctl -u gitlab-runner -f

# Hangi job'ların çalıştığını gör
sudo gitlab-runner list

Disk dolması yaygın bir sorun. Docker executor kullanıyorsanız eski image’lar ve volume’lar birikir:

# Kullanılmayan Docker kaynaklarını temizle
docker system prune -af --volumes

# Cron job ile otomatik temizlik ekle
echo "0 2 * * 0 docker system prune -af --volumes >> /var/log/docker-cleanup.log 2>&1" | sudo crontab -u gitlab-runner -

Runner’ın concurrent değeri yanlış ayarlanmışsa job’lar sıraya girer. top veya htop ile CPU ve memory kullanımını izleyin, concurrent değerini buna göre ayarlayın.

Çoklu Ortam için Pipeline Şablonu

Gerçek bir proje genellikle dev, staging ve production ortamlarına sahiptir. Bunu temiz bir şekilde yönetmek için:

# Yeniden kullanılabilir job şablonları
.deploy_template: &deploy_config
  image: bitnami/kubectl:latest
  before_script:
    - kubectl config use-context $KUBE_CONTEXT
  script:
    - envsubst < k8s/deployment.yaml | kubectl apply -f -
    - kubectl rollout status deployment/$APP_NAME -n $NAMESPACE

deploy_dev:
  <<: *deploy_config
  stage: deploy
  variables:
    KUBE_CONTEXT: "gitlab-agent/dev-cluster"
    NAMESPACE: "development"
    APP_NAME: "myapp-dev"
    REPLICAS: "1"
  environment:
    name: development
    url: https://dev.sirket.com
  only:
    - develop

deploy_staging:
  <<: *deploy_config
  stage: deploy
  variables:
    KUBE_CONTEXT: "gitlab-agent/staging-cluster"
    NAMESPACE: "staging"
    APP_NAME: "myapp-staging"
    REPLICAS: "2"
  environment:
    name: staging
    url: https://staging.sirket.com
  only:
    - main

deploy_production:
  <<: *deploy_config
  stage: deploy
  variables:
    KUBE_CONTEXT: "gitlab-agent/prod-cluster"
    NAMESPACE: "production"
    APP_NAME: "myapp"
    REPLICAS: "5"
  environment:
    name: production
    url: https://sirket.com
  when: manual
  only:
    - tags

YAML anchor (&deploy_config) ve alias (<<: *deploy_config) kullanımı kod tekrarını önler. Her ortam kendi değişkenlerini override eder, temel deploy mantığı paylaşılır.

Güvenlik Önerileri

Runner güvenliği konusu genellikle atlanıyor. Birkaç kritik nokta:

  • Shell executor kullanıyorsanız gitlab-runner kullanıcısını sudo yetkisiyle bırakmayın
  • Privileged mode’u sadece gerçekten gerektiğinde açın, mümkünse Kaniko veya Buildah alternatiflerini değerlendirin
  • Runner token’larını düzenli olarak rotate edin, Settings > CI/CD > Runners üzerinden yapabilirsiniz
  • protected flag’ini production ortam değişkenlerine mutlaka ekleyin, sadece protected branch’lerde görünür olsun
  • Runner makinesine SSH erişimini kısıtlayın, sadece sistem yöneticileri erişebilmeli
  • Pipeline loglarını düzenli gözden geçirin, hassas bilgi sızıntısı olup olmadığını kontrol edin

Sonuç

GitLab Runner kurulumu teknik olarak basit görünebilir ama iyi bir CI/CD altyapısı inşa etmek detaylara dikkat gerektirir. Executor seçimi, cache stratejisi, gizli değişken yönetimi ve güvenlik konfigürasyonu, pipeline’larınızın hem hızlı hem de güvenilir çalışmasını doğrudan etkiler.

Başlangıç için Docker executor ile single runner’dan başlayın. Ekibinizin CI/CD kültürü olgunlaştıkça group runner’lara, gerekirse Kubernetes executor’a geçiş yapabilirsiniz. Her değişikliği küçük adımlarla yapın ve her adımda pipeline metriklerinizi izleyin. Build süresi, başarısızlık oranı ve kuyruk süresi izlemeniz gereken temel göstergeler.

Bir şeyi baştan doğru yaparsanız sonradan büyük zaman kazanırsınız: runner makinelerini cattle gibi yönetin, pet gibi değil. Her runner aynı şekilde kurulabilmeli, configuration management aracınız (Ansible, Terraform) ile. O müşterimizin altyapısında bugün hala Ansible playbook’larımız çalışıyor, runner makineleri değişse de pipeline’lar dakikalar içinde hazır oluyor.

Bir yanıt yazın

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