İçeriğe Atla
Mustafa Erbay
Kariyer · 11 dk okuma · görüntülenme

Yan Projeler İçin CI/CD: 3 Pragmatik Tasarım Tercihi

Yan projelerimde CI/CD süreçlerini nasıl pragmatik yaklaşımlarla kurduğumu ve bu süreçlerde karşılaştığım zorlukları anlatıyorum.

100%

Yan projeler, birçoğumuz için hem öğrenme alanı hem de yeni fikirleri test etme platformu. Ben de yıllardır kendi yan ürünlerimle uğraşırım. Ama ne yazık ki, genellikle bu projelerin CI/CD süreçleri, “manuel SSH ile bağlan, git pull çek, systemctl restart yap” düzeyinde kalıyor. Bu durum, özellikle gecenin bir yarısı bir şeyi düzeltmek zorunda kaldığımda ciddi zaman kayıplarına ve hatalara yol açtı.

Kurumsal projelerde kullandığımız devasa CI/CD pipeline’larını yan projelere taşımak çoğu zaman overkill oluyor. Maliyet, zaman ve karmaşıklık açısından anlamlı değil. Bu yüzden, yan projelerimde hem hızlı, hem güvenilir hem de bakımı kolay olacak şekilde üç ana pragmatik CI/CD tasarım tercihi yaptım. Bu yazıda, bu tercihleri ve nedenlerini kendi deneyimlerimle anlatacağım.

1. Minimalist Git-Triggered Deployment: Push-to-Deploy Basitliği

Manuel deploy’lar, özellikle küçük değişikliklerde bile zamanla birikip ciddi bir yük haline geliyor. Bir ara, kendi yan ürünümün backend’i için her güncellemede sunucuya bağlanıp komutları tek tek çalıştırmak zorunda kalıyordum. Bu durum, hem sıkıcıydı hem de yorgunluk anlarında hatalı komutlar yazma riskini artırıyordu. Bir seferinde yanlışlıkla production veritabanını test veritabanıyla karıştırmış, ciddi bir veri kaybı korkusu yaşamıştım.

Bu sorunu çözmek için en basit ve en hızlı yolu seçtim: git push ile tetiklenen otomatik deploy. Bu sistem, genellikle tek sunuculu, basit web uygulamaları veya API servisleri için ideal. Temelde yaptığım şey, Git repository’sinin post-receive hook’unu kullanarak bir script çalıştırmak. Bu script, yeni kod geldiğinde uygulamayı güncelliyor ve yeniden başlatıyor.

İşte tipik bir post-receive hook’u nasıl görünüyor:

#!/bin/bash
# post-receive hook on your bare Git repository

# The bare repository is usually at /var/repo/myproject.git
# The working directory for your application is at /var/www/myproject

# Git hook variables
read oldrev newrev refname

# Ensure we're pushing to the main branch
if [ "$refname" = "refs/heads/main" ]; then
  echo "Push received for main branch. Deploying..."
  cd /var/www/myproject || exit

  # Pull the latest changes
  unset GIT_DIR # Important for non-bare repo operations
  git pull origin main

  # Install dependencies (if any)
  /usr/bin/pip install -r requirements.txt

  # Run database migrations (if any)
  /usr/bin/python manage.py migrate

  # Restart the application service
  /usr/bin/systemctl restart myproject-backend.service

  echo "Deployment complete for main branch."
else
  echo "Push received for $refname. No deployment triggered."
fi

Bu script, main branch’ine yapılan her push işleminde çalışıyor. Uygulama dizinine gidiyor, en son değişiklikleri çekiyor, bağımlılıkları yüklüyor, veritabanı migrasyonlarını çalıştırıyor ve systemd servisini yeniden başlatıyor. Bu sayede, ben sadece kodu git push origin main ile gönderiyorum ve gerisini sunucu hallediyor. Bu yöntem, daha önce yaşadığım manuel deploy yükünden sonra yan projelerimde büyük bir rahatlık sağladı.

Bu yaklaşımın temel avantajı hız ve basitlik. Dezavantajı ise, tek bir sunucuya bağımlı olması ve rollback’in manuel olarak eski commit’e dönüp tekrar push yapmakla sınırlı olması. Ancak yan projeler için bu trade-off genellikle kabul edilebilir.

2. Containerization ile Ortam Tutarlılığı: Docker Compose Avantajı

“Benim makinemde çalışıyordu!” cümlesi, yazılım dünyasının en eski ve en sık duyulan şikayetlerinden biridir. Yan projelerde bile bu sorunla sıkça karşılaştım. Özellikle farklı kütüphane versiyonları, işletim sistemi farklılıkları veya veritabanı kurulumundaki nüanslar yüzünden bolca zamanımı setup sorunlarına harcadığımı biliyorum. Bir yan ürünümün backend’ini geliştirirken, PostgreSQL versiyon farklılıkları yüzünden lokalde çalışan bir sorgunun test sunucusunda hata vermesi epeyce zamanımı almıştı.

Bu sorunu kökten çözmek için Docker ve Docker Compose kullanıyorum. Tüm uygulama bileşenlerini (backend servisi, veritabanı, Redis gibi cache servisleri) container’lara koymak, ortam tutarlılığını garantiliyor. Nerede çalışırsa çalışsın, aynı bağımlılıklar ve aynı konfigürasyonla çalışıyor.

İşte basit bir docker-compose.yml örneği:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/app
    environment:
      DATABASE_URL: postgres://user:password@db:5432/mydatabase
      REDIS_URL: redis://redis:6379/0
    depends_on:
      - db
      - redis
    # Add resource limits for side projects to prevent runaway processes
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
    restart: always

  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - pgdata:/var/lib/postgresql/data
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
    restart: always

  redis:
    image: redis:7-alpine
    deploy:
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M
    restart: always

volumes:
  pgdata:

Bu konfigürasyon, web servisini (uygulama kodum), db servisini (PostgreSQL) ve redis servisini tanımlıyor. Her biri kendi izole ortamında çalışıyor ve bağımlılıklar depends_on ile belirleniyor. Özellikle deploy.resources.limits ve reservations kısımları, benim gibi VPS üzerinde birçok yan ürün çalıştıran biri için hayati. Tek bir servisteki bir bellek sızıntısı limitsiz bırakıldığında bütün makineyi etkileyebilir; bu limitler, bir servisin diğerlerini aç bırakmasını engelliyor.

Docker Compose, geliştirme ortamında da büyük kolaylık sağlıyor. docker compose up ile tüm ortam saniyeler içinde ayağa kalkıyor. Üretim ortamında da basit bir docker compose up -d ile deploy edebiliyorum. Bu yaklaşım, monolitik bağımlılıkların iç içe geçtiği ortamlarda yaşadığım sıkıntılardan sonra benim için adeta bir nefes alma alanı oldu.

3. Basit Test ve Linting Adımları: Hızlı Geri Bildirim Döngüsü

Yan projelerimizde genellikle kapsamlı test süitleri yazmaya vakit bulamayız. Ancak tamamen testsiz kalmak da büyük riskler doğurur. Tek bir gözden kaçan regex ya da sınır koşulu, hiçbir otomatik kontrol yoksa kullanıcı şikayeti gelene kadar prod’da sessizce yaşayabilir. Manuel testler zahmetli ve unutulabiliyor.

Bu yüzden, CI/CD pipeline’ıma en azından temel linting ve birkaç kritik unit test adımını ekliyorum. Bu adımlar, kod kalitesini artırıyor ve en bariz hataları prod’a gitmeden yakalamamı sağlıyor. Genellikle Python projelerinde flake8 veya black ile kod stilini kontrol ediyor, pytest ile de kritik işlevlerin testlerini çalıştırıyorum.

İşte GitHub Actions üzerinde basit bir test ve linting workflow’u örneği:

name: CI for My Side Project

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run Flake8 Linter
        run: |
          pip install flake8
          flake8 . --max-line-length=120 --exclude .git,__pycache__,venv
      - name: Run Pytest
        run: |
          pip install pytest
          pytest

Bu workflow, main branch’ine yapılan her push veya pull request’te otomatik olarak tetikleniyor. Önce bağımlılıkları yüklüyor, sonra flake8 ile kod stilini kontrol ediyor ve son olarak pytest ile testleri çalıştırıyor. Eğer linting veya testler başarısız olursa, deploy süreci durduruluyor ve bana anında geri bildirim sağlıyor. Böylece, yukarıda bahsettiğim gibi küçük ama kritik hataların prod’a kaçmasının önüne geçmiş oluyorum.

Bu adımlar benim için vazgeçilmez oldu: test edilmeden deploy edilen bir kod parçasının yol açtığı pek çok sorunu, basit bir otomatik kontrol çok daha erken aşamada yakalayabilir.

4. Dağıtım Stratejileri ve Rollback Mekanizmaları: Güvenli Çıkış Yolları

Yan projelerde genelde “fire and forget” (dağıt ve unut) yaklaşımıyla deploy yaparız. Ama işler her zaman yolunda gitmez. Hatalı bir deploy sonrası sistemin çökmesi ve geri dönmenin zorluğu, motivasyon düşürücü olabilir. Disk dolması gibi beklenmedik bir koşulda uygulama ayağa kalkmayabilir; bir rollback mekanizması yoksa geri dönmek uzun bir manuel uğraşa dönüşür. Bu tür durumlar, yan projelerde de basit bir “geri alma” stratejisinin neden önemli olduğunu gösteriyor.

Tam teşekküllü Blue-Green veya Canary deploy’lar yan projeler için karmaşık olabilir. Ancak basit bir rsync tabanlı Blue-Green yaklaşımı veya sembolik link değiştirme, nispeten daha güvenli bir dağıtım ve hızlı bir rollback imkanı sunar. Benim tercihim, uygulamanın farklı versiyonlarını tutan iki ayrı dizin ve bunları işaret eden bir sembolik link kullanmak.

İşte basit bir deploy scripti ve ln -s kullanımı:

#!/bin/bash
# Simple Blue-Green style deploy script

APP_DIR="/var/www/myproject"
CURRENT_RELEASE_DIR="$APP_DIR/current"
RELEASE_DIR_A="$APP_DIR/releases/release_a"
RELEASE_DIR_B="$APP_DIR/releases/release_b"
GIT_REPO="/var/repo/myproject.git"

# Determine which release directory is currently active
if [ -L "$CURRENT_RELEASE_DIR" ] && [ "$(readlink "$CURRENT_RELEASE_DIR")" == "$RELEASE_DIR_A" ]; then
  TARGET_RELEASE_DIR="$RELEASE_DIR_B"
  OLD_RELEASE_DIR="$RELEASE_DIR_A"
else
  TARGET_RELEASE_DIR="$RELEASE_DIR_A"
  OLD_RELEASE_DIR="$RELEASE_DIR_B"
fi

echo "Deploying to $TARGET_RELEASE_DIR"

# Clean target directory and clone/pull latest code
rm -rf "$TARGET_RELEASE_DIR"/*
mkdir -p "$TARGET_RELEASE_DIR"
git clone "$GIT_REPO" "$TARGET_RELEASE_DIR" # Or git pull if directory exists and is a repo

cd "$TARGET_RELEASE_DIR" || exit

# Install dependencies, run migrations, build frontend etc.
/usr/bin/pip install -r requirements.txt
/usr/bin/python manage.py migrate --noinput
npm install && npm run build # If you have a frontend

# Test if the new version is working (e.g., health check)
# A simple curl to a health endpoint could work here
# curl -f http://localhost:8001/health || { echo "Health check failed!"; exit 1; }

# Switch the symlink
ln -snf "$TARGET_RELEASE_DIR" "$CURRENT_RELEASE_DIR"
echo "Switched to new release: $(readlink "$CURRENT_RELEASE_DIR")"

# Restart application service (using systemd for example)
/usr/bin/systemctl restart myproject-backend.service

echo "Deployment finished. Old release is in $OLD_RELEASE_DIR for rollback."

Bu script, release_a ve release_b adında iki ayrı dizin arasında geçiş yaparak deploy yapıyor. current sembolik linki, her zaman aktif olan versiyonu gösteriyor. Yeni versiyon hazırlandığında, önce TARGET_RELEASE_DIR’a deploy ediliyor, test ediliyor ve ardından current linki yeni versiyona işaret edecek şekilde değiştiriliyor. Eğer bir sorun olursa, current linkini manuel olarak eski OLD_RELEASE_DIR’a geri çevirerek hızlı bir rollback yapabilirim.

Bu yöntem, uygulamanın atomik olarak güncellenmesini sağlıyor ve sıfıra yakın downtime ile deploy yapmama olanak tanıyor. Kendi yan ürünümün backendi için bu yapıyı kullanıyorum ve bir hata durumunda kısa sürede eski versiyona dönebiliyorum.

5. Observability ve İzleme: Ne Oluyor, Nasıl Anlarım?

Dağıtım sonrası sistemin durumunu bilmemek, karanlıkta uçmaya benzer. Yan projelerde, genellikle Prometheus/Grafana gibi kapsamlı izleme çözümlerini kurmak için vaktimiz veya kaynağımız olmaz. Ancak bu, hiçbir şeyi izlemeyeceğimiz anlamına gelmez. Hataları geç fark etmek, kullanıcı deneyimini kötü etkilediği gibi, bazen ciddi veri sorunlarına da yol açabilir. Çoğu performans düşüşü en başta sessizdir; journalctl ile logları incelemek, beklenenden uzun süren bir sorguyu erkenden ortaya çıkarabiliyor.

Benim yan projeler için pragmatik observability yaklaşımım, Linux’un yerleşik araçlarını ve basit loglamayı kullanmak üzerine kurulu. Uygulamalarımı systemd servisleri olarak çalıştırıyorum ve loglarını journald’e yönlendiriyorum.

İşte basit bir systemd unit dosyası:

[Unit]
Description=My Side Project Backend
After=network.target postgresql.service redis.service

[Service]
User=myuser
WorkingDirectory=/var/www/myproject/current
ExecStart=/usr/bin/python /var/www/myproject/current/app.py
Restart=always
StandardOutput=journal
StandardError=journal
# Add memory limits for the service
MemoryHigh=256M
MemoryMax=512M

[Install]
WantedBy=multi-user.target

Bu systemd unit’i, uygulamamın loglarını doğrudan journald’e gönderiyor (StandardOutput=journal). Böylece, journalctl -u myproject-backend.service -f komutuyla canlı logları takip edebiliyorum. Ayrıca, MemoryHigh ve MemoryMax gibi cgroup limitlerini kullanarak, uygulamanın beklenenden fazla bellek tüketmesini engelliyorum. Eğer uygulama bu limitleri aşarsa, systemd tarafından otomatik olarak yeniden başlatılıyor ve journald’e ilgili uyarı düşüyor.

PostgreSQL gibi servislerin performansını izlemek için de benzer şekilde basit yöntemler kullanıyorum. Örneğin, pg_stat_activity tablosunu sorgulayarak uzun süren sorguları bulmak veya log_min_duration_statement ayarını kullanarak belirli bir süreden uzun süren sorguları loglara yazdırmak, PostgreSQL WAL bloat gibi sorunları erken fark etmeme yardımcı oluyor. Redis’te ise CONFIG GET maxmemory-policy ve INFO memory komutlarıyla OOM eviction policy seçimlerimin doğru çalışıp çalışmadığını kontrol ediyorum.

Bu basit araçlar, Prometheus gibi kapsamlı sistemlerin sunduğu dashboard ve alarmların yerini tutmasa da, bir sorun olduğunda hızlıca ne olup bittiğini anlamamı sağlıyor. Benim için bu, yan projelerde yeterli oluyor.

6. Güvenlik Perspektifinden CI/CD: Zayıf Noktaları Kapatmak

Yan projelerde güvenlik genellikle sonradan akla gelen bir konu olur. “Kim benim küçük yan projemle uğraşacak ki?” düşüncesi yaygındır. Ancak bu, büyük bir yanılgıdır. Korumasız bir API endpoint’ine gelen aşırı istekler servisi rahatlıkla durdurabilir; fail2ban kuralları ve Nginx’te rate limiting gibi basit önlemler, yan projelerde bile temel güvenlik adımlarının ne kadar önemli olduğunu gösteriyor.

CI/CD sürecime temel güvenlik kontrollerini entegre etmek, bu tür riskleri azaltmama yardımcı oluyor. Tam teşekküllü bir güvenlik denetimi yerine, kolay uygulanabilir ve yüksek etki yaratan birkaç adıma odaklanıyorum.

İşte CI/CD’ye eklediğim bazı temel güvenlik adımları:

  1. Bağımlılık Taraması: Kullandığım kütüphanelerde bilinen zafiyetler olup olmadığını kontrol ediyorum. Python projelerinde pip-audit aracı bu iş için harika.

    # CI/CD pipeline'da bağımlılık taraması adımı
    pip install pip-audit
    pip-audit -r requirements.txt

    Bu komut, requirements.txt dosyasındaki tüm bağımlılıkları bilinen CVE’lere karşı tarar ve varsa uyarı verir. Eski bir kütüphanedeki kritik bir zafiyetin farkına geç varmanın ne kadar pahalıya patlayabileceğini gördükten sonra bu adımı atlamıyorum.

  2. Temel Güvenlik Konfigürasyonları: Uygulama seviyesinde rate limiting, JWT/OAuth2 patterns için best practices’i takip ediyorum. Nginx kullanıyorsam, basit limit_req direktifleri ile API’me gelen istekleri sınırlıyorum.

    # Nginx'te rate limiting örneği
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
    
    server {
        # ...
        location /api/ {
            limit_req zone=mylimit burst=20 nodelay;
            # ...
        }
    }

    Bu konfigürasyon, belirli bir IP’den saniyede 10 isteği sınırlar ve 20 isteğe kadar “burst” (patlama) imkanı tanır. Bu, basit DDoS saldırılarına veya aşırı kullanıma karşı ilk savunma hattımdır.

  3. Kernel Modülü Karalistesi: Nadiren de olsa, bazı kernel modülleri güvenlik zafiyetlerine yol açabilir veya gereksiz kaynak tüketebilir. Kendi VPS’imde algif_aead gibi kritik olmayan kernel modüllerini blacklist’e alarak bu modüller üzerinden gelebilecek olası zafiyetlere karşı ek bir katman oluşturuyorum.

    # /etc/modprobe.d/blacklist.conf
    blacklist algif_aead

    Bu, genel sistem güvenliğini artırmak için yaptığım küçük ama etkili bir adımdır.

Bu adımlar, yan projelerimdeki CI/CD sürecine entegre ettiğim ve beni birçok baş ağrısından kurtaran pragmatik güvenlik önlemleri. Tam teşekküllü bir güvenlik ekibinin yapacağı kadar kapsamlı olmasalar da, “olur o kadar” deyip geçmek yerine alınması gereken minimum önlemlerdir.

Sonuç

Yan projeler, özellikle tek kişilik ordu gibi çalıştığımızda, çoğu zaman “yetişsin de nasıl olursa olsun” mantığıyla ilerler. Ancak saha tecrübem, bu pragmatik CI/CD yaklaşımlarının uzun vadede hem zamanımı kurtardığını hem de stresimi azalttığını gösterdi. Manuel deploy’ların risklerini, ortam tutarsızlığının getirdiği baş ağrılarını ve güvensiz kodun yol açtığı sorunları bizzat yaşadığım için, bu üç temel tasarım tercihini (minimalist git-triggered deployment, containerization ile ortam tutarlılığı ve basit test/linting adımları) her yeni yan projemde uyguluyorum.

Bu adımlar, büyük kurumsal sistemlerdeki karmaşık CI/CD süreçlerinin birer mini versiyonu gibi düşünülebilir. Her biri, minimum eforla maksimum fayda sağlayarak projenin daha sürdürülebilir ve daha az sorunlu olmasını amaçlar. Deneyimlerime göre, bu yaklaşımlar sayesinde yan projelerim hem daha hızlı gelişiyor hem de daha stabil çalışıyor. Bir sonraki yazımda, yan projelerimde kullandığım basit izleme ve alerting mekanizmalarına daha derinlemesine değineceğim.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

Yan projelerimde CI/CD süreçlerini kurarken nelere dikkat etmeliyim?
Benim deneyimime göre, yan projelerimde CI/CD süreçlerini kurarken maliyet, zaman ve karmaşıklık açısından dikkatli olmak gerekiyor. Kurumsal projelerde kullandığımız devasa CI/CD pipeline'larını yan projelere taşımak çoğu zaman overkill oluyor. Bu yüzden, hızlı, güvenilir ve bakımı kolay olacak şekildeminimalist bir yaklaşım benimsemek daha uygun oluyor.
Push-to-Deploy basitliği nedir ve nasıl uygulanabilir?
Push-to-Deploy basitliği, `git push` ile tetiklenen otomatik deploy demektir. Ben bu sistemi, tek sunuculu, basit web uygulamaları veya API servisleri için ideal buluyorum. Uygulamak için Git repository'sinin `post-receive` hook'unu kullanarak bir script çalıştırmak yeterli. Bu script, yeni kod geldiğinde uygulamayı güncelliyor ve yeniden başlatıyor.
Yan projelerimde CI/CD süreçlerinde hata oluştuğunda ne yapmalıyım?
Hata oluştuğunda, önce sakin kalmak ve hatayı analiz etmek gerekiyor. Ben, bir seferinde yanlışlıkla production veritabanını test veritabanıyla karıştırmıştım ve ciddi bir veri kaybı korkusu yaşamıştım. O yüzden, önce geri yükleme noktaları oluşturmak ve sonra hatayı düzeltmeye çalışmak önemli. Ayrıca, hata oluştuğunda, günlüğe kaydetmek ve izlemek de faydalı oluyor.
CI/CD pipeline'larında minimalist bir yaklaşım benimsemek avantajlı mı?
Evet, benim deneyimime göre, CI/CD pipeline'larında minimalist bir yaklaşım benimsemek avantajlı oluyor. Bu yaklaşım, özellikle yan projeler için maliyet, zaman ve karmaşıklık açısından daha uygun oluyor. Ayrıca, minimalist bir yaklaşım, hataları daha kolay analiz etmeye ve düzeltmeye olanak tanıyor. Ben, yan projelerimde minimalist bir yaklaşım benimseyerek, daha hızlı ve güvenilir bir şekilde CI/CD süreçlerini kurabildim.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar