GitLab Container Registry ile Docker Image Yönetimi

Bir müşterimizin CI/CD pipeline’ı tamamen çökmüştü. Sorunun kaynağı Docker Hub’ın rate limiting politikasıydı; anonim pull’lar için saatte 100 istek sınırı, 50 developer’lı bir ekipte dakikalar içinde doluyordu. Çözüm aslında hep elimizin altındaydı: GitLab’ın kendi içinde gelen Container Registry. O günden beri kurumsal projelerde Docker Hub’a bağımlılığı mümkün olduğunca azaltıyorum ve bu yazıda öğrendiklerimi aktarmak istiyorum.

GitLab Container Registry Neden Önemli?

GitLab, sadece bir Git sunucusu değil. Proje kodunuzla aynı çatı altında bir container registry, CI/CD sistemi, issue tracker ve çok daha fazlasını barındırıyor. Container Registry özelliği sayesinde Docker image’larınızı doğrudan GitLab projenizle ilişkilendirilmiş özel bir registry’de saklayabiliyorsunuz.

Bu yaklaşımın en büyük avantajı entegrasyon kolaylığı. CI/CD pipeline’ınız aynı GitLab instance’ında çalışıyor, registry de orada, yetki yönetimi de orada. Docker Hub’da ayrı bir organizasyon kurmak, token yönetmek, Kubernetes’te ayrı secret oluşturmak gibi operasyonel yüklerle uğraşmak yerine tek bir yerden yönetebiliyorsunuz her şeyi.

GitLab.com kullananlar için bu özellik hemen hazır geliyor. Self-hosted GitLab kuruyorsanız birkaç ek yapılandırma adımı gerekiyor, o kısma da değineceğim.

Self-Hosted GitLab’da Registry Yapılandırması

GitLab.com kullanıyorsanız bu bölümü atlayabilirsiniz. Kendi sunucunuzda GitLab çalıştırıyorsanız /etc/gitlab/gitlab.rb dosyasında şu ayarları yapmanız gerekiyor:

# /etc/gitlab/gitlab.rb

registry_external_url 'https://registry.sirketiniz.com'

gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "registry.sirketiniz.com"
gitlab_rails['registry_port'] = "443"
gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"

registry['enable'] = true
registry['registry_http_addr'] = "localhost:5000"

Yapılandırmayı uygulamak için:

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

Registry için ayrı bir subdomain kullanmanızı şiddetle tavsiye ederim. Aynı domain üzerinde farklı port ile çalıştırmak teknik olarak mümkün ama production ortamında sorun çıkarıyor. SSL sertifikanızı da bu subdomain için ayrıca almanız gerekiyor; Let’s Encrypt kullanıyorsanız GitLab bunu otomatik halledebiliyor.

Storage backend olarak varsayılan filesystem yerine S3 uyumlu bir nesne depolama kullanmak uzun vadede çok daha sağlıklı:

registry['storage'] = {
  's3' => {
    'accesskey' => 'AWS_ACCESS_KEY',
    'secretkey' => 'AWS_SECRET_KEY',
    'bucket' => 'gitlab-registry-bucket',
    'region' => 'eu-west-1',
    'secure' => true
  }
}

MinIO kullanıyorsanız regionendpoint parametresini eklemeniz gerekiyor. Bunu atlayan çok kişi görüdm, sonra “neden S3’e yazamıyorum” diye saatlerce uğraşıyorlar.

Registry’ye Bağlanma ve Temel Kullanım

GitLab Container Registry’ye bağlanmak için GitLab kullanıcı adınız ve bir Personal Access Token (PAT) kullanıyorsunuz. Şifrenizi direkt kullanmak yerine read_registry ve write_registry scope’larıyla bir token oluşturmanızı öneririm.

# GitLab.com için
docker login registry.gitlab.com

# Self-hosted için
docker login registry.sirketiniz.com

# Kullanıcı adı: GitLab kullanıcı adınız
# Şifre: Personal Access Token

Başarılı login sonrası image’larınızı push etmek için GitLab’ın belirlediği naming convention’a uymak zorundasınız:

# Image build etme
docker build -t registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:v1.0.0 .

# Tag ekleme
docker tag mevcut-image:latest registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:latest

# Push etme
docker push registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:v1.0.0
docker push registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:latest

# Pull etme
docker pull registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:v1.0.0

Namespace yapısına dikkat edin: registry_host/namespace/proje/image:tag. GitLab’da subgrouplar kullanıyorsanız bu path daha da uzayabiliyor. Otomasyonda bunu dinamik olarak ele almak önemli.

CI/CD Pipeline ile Entegrasyon

İşte işin gerçekten güçlendiği kısım burası. GitLab CI, kendi registry’siyle çalışırken CI_REGISTRY, CI_REGISTRY_IMAGE, CI_REGISTRY_USER ve CI_REGISTRY_PASSWORD gibi önceden tanımlı değişkenler sunuyor. Bu değişkenleri kullanarak pipeline’ınızı temiz tutabiliyorsunuz.

# .gitlab-ci.yml

variables:
  DOCKER_TLS_CERTDIR: "/certs"
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

stages:
  - build
  - test
  - push
  - deploy

build-image:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build --pull -t $IMAGE_TAG .
    - docker push $IMAGE_TAG
  rules:
    - if: $CI_COMMIT_BRANCH

Bu yapıda $CI_REGISTRY_USER ve $CI_REGISTRY_PASSWORD değişkenlerini siz tanımlamıyorsunuz; GitLab otomatik dolduruyor. Bu ayrıntı çok kritik: Token yönetimiyle hiç uğraşmıyorsunuz.

Daha gelişmiş bir senaryo olarak multi-stage pipeline’ı ele alalım. Gerçek bir proje için kullandığım yapıya benzer bir örnek:

# .gitlab-ci.yml - Gelismis versiyon

variables:
  DOCKER_BUILDKIT: "1"
  DOCKER_TLS_CERTDIR: "/certs"
  RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
  COMMIT_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  BRANCH_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG

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

build:
  <<: *docker-login
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  script:
    - |
      docker build 
        --cache-from $RELEASE_IMAGE 
        --build-arg BUILDKIT_INLINE_CACHE=1 
        --tag $COMMIT_IMAGE 
        --tag $BRANCH_IMAGE 
        .
    - docker push $COMMIT_IMAGE
    - docker push $BRANCH_IMAGE
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH

tag-release:
  <<: *docker-login
  stage: push
  image: docker:24.0
  services:
    - docker:24.0-dind
  script:
    - docker pull $COMMIT_IMAGE
    - docker tag $COMMIT_IMAGE $RELEASE_IMAGE
    - docker tag $COMMIT_IMAGE $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
    - docker push $RELEASE_IMAGE
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
  rules:
    - if: $CI_COMMIT_TAG

Burada --cache-from parametresine özellikle dikkat edin. Layer cache’ini registry üzerinden yönetmek, build sürelerini ciddi ölçüde kısaltıyor. Özellikle bağımlılık yükleme adımları tekrar tekrar çalışmaktan kurtulabiliyor.

Image Temizleme ve Retention Policy

Zamanla registry dolmaya başlıyor. Yeterince konuşulmayan ama production’da çok kritik olan bir konu bu. Her commit’te yeni bir image push ediliyorsa disk alanı hızla tükeniyor. GitLab bu sorunu çözmek için Cleanup Policies sunuyor.

GitLab arayüzünden Settings > Packages and registries > Container registry yolunu izleyerek cleanup policy tanımlayabilirsiniz. Ama policy’yi kod olarak tanımlamak çok daha kontrollü bir yaklaşım:

# GitLab API ile cleanup policy tanimlama
curl --request PUT 
  --header "PRIVATE-TOKEN: <your-token>" 
  --header "Content-Type: application/json" 
  --data '{
    "container_expiration_policy_attributes": {
      "cadence": "1d",
      "enabled": true,
      "keep_n": 10,
      "older_than": "30d",
      "name_regex_delete": ".*",
      "name_regex_keep": ".*-stable|latest|main"
    }
  }' 
  "https://gitlab.com/api/v4/projects/<project-id>"

Bu politika şunu yapıyor:

  • cadence: “1d”: Temizleme günde bir çalışıyor
  • keep_n: 10: Her image adı için en son 10 tag tutuluyor
  • older_than: “30d”: 30 günden eski tag’lar siliniyor
  • name_regex_keep: stable, latest veya main içeren tag’lar korunuyor

Manuel temizlik yapmak istediğinizde GitLab API’yi şöyle kullanabilirsiniz:

# Bir projedeki tum repository'leri listeleme
curl --header "PRIVATE-TOKEN: <your-token>" 
  "https://gitlab.com/api/v4/projects/<project-id>/registry/repositories"

# Belirli bir repository'nin tag'larini listeleme
curl --header "PRIVATE-TOKEN: <your-token>" 
  "https://gitlab.com/api/v4/projects/<project-id>/registry/repositories/<repository-id>/tags"

# Belirli bir tag'i silme
curl --request DELETE 
  --header "PRIVATE-TOKEN: <your-token>" 
  "https://gitlab.com/api/v4/projects/<project-id>/registry/repositories/<repository-id>/tags/<tag-name>"

Kubernetes Entegrasyonu

Registry’deki image’ları Kubernetes’te kullanmak için bir imagePullSecret oluşturmanız gerekiyor. Bunu her namespace için ayrı ayrı yapmak zahmetli olduğundan bir script ile otomatize etmek daha mantıklı:

#!/bin/bash
# create-gitlab-secret.sh

NAMESPACE=${1:-default}
GITLAB_HOST="registry.gitlab.com"
GITLAB_USER="deploy-user"
GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx"

kubectl create secret docker-registry gitlab-registry-secret 
  --docker-server=$GITLAB_HOST 
  --docker-username=$GITLAB_USER 
  --docker-password=$GITLAB_TOKEN 
  --namespace=$NAMESPACE 
  --dry-run=client -o yaml | kubectl apply -f -

echo "Secret olusturuldu: $NAMESPACE namespace'inde"

Deployment manifest’inizde de bu secret’a referans vermeniz gerekiyor:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: uygulama
spec:
  template:
    spec:
      imagePullSecrets:
        - name: gitlab-registry-secret
      containers:
        - name: uygulama
          image: registry.gitlab.com/sirket/proje/uygulama:v1.2.3
          imagePullPolicy: Always

Kubernetes’te GitLab Deploy Token kullanmak PAT’a göre daha iyi bir pratik. Deploy token’lar proje veya group bazında oluşturulabiliyor ve sadece registry okuma yetkisi verebiliyorsunuz. Kişiye bağlı olmadığı için o kişi şirketten ayrıldığında secret’ı değiştirmeniz gerekmiyor.

Güvenlik Taraması ile Entegrasyon

GitLab Ultimate veya Gold lisansa sahipseniz Container Scanning özelliği geliyor. Ama açık kaynak araçlarla da benzer işleri yapabilirsiniz. Trivy ile entegrasyon kurmak oldukça basit:

container-scan:
  stage: test
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  variables:
    TRIVY_NO_PROGRESS: "true"
    TRIVY_CACHE_DIR: ".trivycache/"
    FULL_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  script:
    - trivy --version
    - trivy image
        --exit-code 0
        --format template
        --template "@/contrib/gitlab.tpl"
        --output gl-container-scanning-report.json
        $FULL_IMAGE_NAME
    - trivy image
        --exit-code 1
        --severity CRITICAL
        $FULL_IMAGE_NAME
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json
    paths:
      - gl-container-scanning-report.json
    expire_in: 1 week
  cache:
    paths:
      - .trivycache/
  allow_failure: true
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Bu yapıda kritik severity’ye sahip açıklar tespit edilirse pipeline başarısız oluyor. allow_failure: true ile job başarısız olsa bile pipeline’ın devam etmesini sağladım, ama bu kararı takımınızın güvenlik politikasına göre değiştirmelisiniz.

Gerçek Dünya Senaryosu: Monorepo Yönetimi

Birçok uygulamanın tek bir repoda toplandığı monorepo yapısında her servis için ayrı image yönetmek karmaşıklaşabiliyor. Bu durumda GitLab CI’ın changes kuralını kullanarak yalnızca değişen servislerin image’ını build etmek büyük bir verimlilik sağlıyor:

# Monorepo icin ornek yapi
# services/api, services/worker, services/frontend dizinleri var

.build-template: &build-template
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

build-api:
  <<: *build-template
  variables:
    SERVICE: api
    CONTEXT_PATH: services/api
  script:
    - docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA $CONTEXT_PATH
    - docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA
  rules:
    - changes:
        - services/api/**/*
        - .gitlab-ci.yml

build-worker:
  <<: *build-template
  variables:
    SERVICE: worker
    CONTEXT_PATH: services/worker
  script:
    - docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA $CONTEXT_PATH
    - docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA
  rules:
    - changes:
        - services/worker/**/*
        - .gitlab-ci.yml

build-frontend:
  <<: *build-template
  variables:
    SERVICE: frontend
    CONTEXT_PATH: services/frontend
  script:
    - docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA $CONTEXT_PATH
    - docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA
  rules:
    - changes:
        - services/frontend/**/*
        - .gitlab-ci.yml

Bu yapıda API koduna dokunmadıysanız API image’ı build edilmiyor, gereksiz CI dakikaları harcanmıyor ve registry şişmiyor.

Dikkat Edilmesi Gereken Noktalar

Yaygın yapılan hataları da paylaşmak istiyorum, çünkü bunları ya bizzat yaşadım ya da başkalarının yaşadığını gördüm.

  • docker:dind kullanırken TLS dikkat: Eski örnekler DOCKER_TLS_CERTDIR: "" ile TLS’yi devre dışı bırakıyordu. Bunu yapmayın, güvenlik açığı yaratıyor.
  • Image tag olarak sadece latest kullanmak: Hangi sürümün nerede çalıştığını takip edemezsiniz. Commit SHA veya semantik versiyon mutlaka ekleyin.
  • Registry temizleme politikasını kurmayı ertelemek: “Sonra ayarlarım” diyerek ertelenen cleanup policy, bir gün disk dolduğunda gece yarısı acil müdahale gerektiriyor.
  • Tüm image’ları public yapmak: Özel uygulamanızın Dockerfile’ındaki konfigürasyon bilgileri, base image seçimleri rakipleriniz tarafından görülebilir. Varsayılan olarak private bırakın.
  • Build context’ini küçük tutmamak: .dockerignore dosyası oluşturmayı ihmal etmek build sürelerini gereksiz uzatıyor ve image boyutunu şişiriyor.

Sonuç

GitLab Container Registry, CI/CD ekosisteminizi tek çatı altında birleştirmenin en temiz yollarından biri. Kurumsal yapılarda üçüncü taraf registry bağımlılığını azaltmak hem operasyonel karmaşıklığı düşürüyor hem de güvenlik yüzeyini küçültüyor. Cleanup policy ile depolama maliyetlerini kontrol altında tutmak, Trivy gibi araçlarla güvenlik taramasını pipeline’a entegre etmek ve Kubernetes’te deploy token kullanmak kısa vadede birkaç saat daha fazla iş gibi görünse de uzun vadede sizin yerinize saatlerce sorun çözüyor.

Kendi registry’nizi yönetmek başlangıçta göz korkutucu gelebilir, ama GitLab bu süreci gerçekten kolaylaştırmış. CI değişkenlerinin otomatik enjeksiyonu, proje bazlı yetki yönetimi ve API üzerinden her şeyin otomasyon içine alınabilmesi, bu aracı sysadmin’ler için gerçekten kullanılabilir kılıyor. Başlamak için en iyi zaman hala şimdi.

Bir yanıt yazın

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