CI/CD Nedir: Yazılım Geliştirmede Otomasyon Rehberi
Bir sabah geldiniz, üretim ortamında uygulama çalışmıyor. Geçen gece bir geliştirici “küçük bir düzeltme” push’ladı, kimse test etmedi, kimse review yapmadı. Sonuç: 3 saatlik kesinti, sinirli müşteriler ve bir sürü “ben yapmadım” toplantısı. Bu senaryo tanıdık geliyor mu? İşte CI/CD tam olarak bu tür durumları önlemek için var.
CI/CD Nedir, Ne Değildir
CI/CD’yi “otomatik deployment aracı” olarak tanımlayanları sık görürüm. Bu tanım eksik. CI/CD bir kültür değişimidir, araçlar bu kültürü hayata geçirmenin yollarıdır.
Continuous Integration (Sürekli Entegrasyon): Geliştiricilerin kod değişikliklerini sık sık (günde birden fazla kez) ortak bir branch’e entegre etmesi ve her entegrasyonun otomatik build ve test süreçleriyle doğrulanması pratiğidir.
Continuous Delivery (Sürekli Teslimat): CI’ın üzerine inşa edilir. Her başarılı build’in üretim ortamına deploy edilebilir halde tutulmasını sağlar. Deploy kararı hâlâ manuel olabilir.
Continuous Deployment (Sürekli Dağıtım): Continuous Delivery’nin bir adım ötesi. Manuel onay olmadan, testleri geçen her değişiklik otomatik olarak üretime gider.
Hangisini seçeceğiniz, ekibinizin olgunluk seviyesine ve iş gereksinimlerinize bağlı. Fintech veya sağlık sektöründeyseniz Continuous Delivery daha uygun olabilir. SaaS ürün geliştiriyorsanız Continuous Deployment’a geçiş mantıklı.
Temel Kavramlar ve Pipeline Anatomisi
Bir CI/CD pipeline’ı birbirini takip eden aşamalardan oluşur. Her aşama başarısız olursa sonraki aşama çalışmaz. Bu fail-fast prensibi, sorunları mümkün olan en erken noktada yakalamayı sağlar.
Tipik bir pipeline şu aşamalardan geçer:
- Source: Kod değişikliği tetikleyici olur (git push, PR oluşturma)
- Build: Kod derlenir, bağımlılıklar çekilir
- Test: Unit testler, integration testler, güvenlik taramaları
- Artifact: Build çıktısı paketlenir ve saklanır
- Deploy to Staging: Test ortamına deployment
- Integration/E2E Tests: Gerçek ortama yakın testler
- Deploy to Production: Üretim ortamına deployment
Her aşamanın ne kadar sürdüğü kritik. 45 dakika süren bir pipeline, geliştiricilerin CI’ı atlamaya çalışmasına yol açar. Hedef 10-15 dakikanın altında tutmak.
GitHub Actions ile Pratik Başlangıç
En yaygın kullanılan araçlardan biri GitHub Actions. Minimal bir yapılandırmayla işe koyulalım:
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Kodu çek
uses: actions/checkout@v4
- name: Node.js kurulumu
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Bağımlılıkları yükle
run: npm ci
- name: Testleri çalıştır
run: npm test
- name: Build al
run: npm run build
Bu basit yapı bile ekibinize çok şey kazandırır. PR açıldığında testler otomatik koşar, main branch’e bozuk kod gitmez.
Jenkins ile Daha Karmaşık Senaryolar
Kurumsal ortamlarda Jenkins hâlâ çok yaygın. Özellikle on-premise gereksinimler ve gelişmiş özelleştirme ihtiyacı olduğunda tercih ediliyor. Bir Jenkinsfile örneğine bakalım:
// Jenkinsfile
pipeline {
agent {
docker {
image 'maven:3.9-openjdk-17'
args '-v /root/.m2:/root/.m2'
}
}
environment {
DOCKER_REGISTRY = 'registry.sirket.com'
APP_NAME = 'uygulama-adi'
VERSION = "${env.BUILD_NUMBER}"
}
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test -Dtest=UnitTests'
}
}
stage('Integration Tests') {
steps {
sh 'mvn test -Dtest=IntegrationTests'
}
}
}
}
stage('Docker Build & Push') {
steps {
withCredentials([usernamePassword(
credentialsId: 'registry-credentials',
usernameVariable: 'REG_USER',
passwordVariable: 'REG_PASS'
)]) {
sh """
docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} .
docker login ${DOCKER_REGISTRY} -u ${REG_USER} -p ${REG_PASS}
docker push ${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}
"""
}
}
}
stage('Deploy to Staging') {
steps {
sh """
kubectl set image deployment/${APP_NAME}
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}
-n staging
kubectl rollout status deployment/${APP_NAME} -n staging
"""
}
}
}
post {
failure {
slackSend(
channel: '#devops-alerts',
color: 'danger',
message: "Pipeline başarısız: ${env.JOB_NAME} - Build #${env.BUILD_NUMBER}"
)
}
success {
echo "Pipeline tamamlandı: ${env.BUILD_NUMBER}"
}
}
}
Parallel stage kullanımına dikkat edin. Unit ve integration testleri paralel çalıştırarak toplam süreyi düşürüyoruz. Büyük projelerde bu fark saatler yaratabilir.
Docker ile Tekrarlanabilir Build Ortamları
“Bende çalışıyor” problemi sysadminlerin kabusu. Docker, build ortamını standartlaştırır. Bir uygulama için çok aşamalı Dockerfile:
# Dockerfile
# Build aşaması
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production aşaması
FROM node:20-alpine AS production
WORKDIR /app
# Güvenlik için root olmayan kullanıcı
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s
CMD wget -q -O /dev/null http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
Multi-stage build iki kritik fayda sağlar: Final image boyutu küçülür, build araçları production image’a taşınmaz. Güvenlik açısından da önemli.
GitLab CI ile Tam Entegre Pipeline
GitLab CI, özellikle self-hosted GitLab kullanan ekipler için son derece güçlü. .gitlab-ci.yml dosyasıyla tüm pipeline tanımlanır:
# .gitlab-ci.yml
stages:
- build
- test
- security
- deploy-staging
- deploy-production
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build:
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 -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- main
- develop
unit-tests:
stage: test
image: python:3.11
script:
- pip install -r requirements-dev.txt
- pytest tests/unit/ --cov=src --cov-report=xml
coverage: '/TOTAL.*s+(d+%)$/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
security-scan:
stage: security
image: aquasec/trivy:latest
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG
allow_failure: false
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
environment:
name: staging
url: https://staging.uygulamam.com
script:
- kubectl config set-cluster staging --server=$KUBE_URL
- kubectl set image deployment/app app=$IMAGE_TAG -n staging
- kubectl rollout status deployment/app -n staging --timeout=5m
only:
- develop
deploy-production:
stage: deploy-production
image: bitnami/kubectl:latest
environment:
name: production
url: https://uygulamam.com
script:
- kubectl set image deployment/app app=$IMAGE_TAG -n production
- kubectl rollout status deployment/app -n production --timeout=10m
when: manual
only:
- main
when: manual satırına dikkat. Production deployment için manuel onay gerekiyor. Bu Continuous Delivery yaklaşımı, kritik ortamlarda çok tercih ediliyor.
Güvenlik Taramaları ve SAST/DAST
CI/CD pipeline’ına güvenlik testlerini dahil etmek “DevSecOps” anlayışının temelidir. Birçok ekip bunu hâlâ ihmal ediyor ve üretimde açık bulunduğunda panikliyor.
# Trivy ile container güvenlik taraması
trivy image --format json --output trivy-report.json myapp:latest
# Sonuçları filtrele, sadece HIGH ve CRITICAL göster
trivy image
--severity HIGH,CRITICAL
--ignore-unfixed
--exit-code 1
myapp:latest
# OWASP ZAP ile basit DAST taraması
docker run -t owasp/zap2docker-stable zap-baseline.py
-t https://staging.uygulamam.com
-r zap-report.html
-I
Güvenlik taramaları pipeline’ı yavaşlatır diye devre dışı bırakmak ciddi hata. Bunu ayrı bir paralel aşama olarak çalıştırırsanız toplam süreye etkisi minimuma iner.
Ortam Değişkenleri ve Secrets Yönetimi
Secrets yönetimi CI/CD’nin en çok ihmal edilen konularından biri. .env dosyasını repoya koymak veya pipeline log’larına şifre yazdırmak güvenlik felaketi.
# HashiCorp Vault ile secret çekme örneği
# Pipeline içinde kullanım
export VAULT_ADDR='https://vault.sirket.com'
export VAULT_TOKEN=$(cat /vault-token)
# Uygulama için gerekli secret'ları çek
DB_PASSWORD=$(vault kv get -field=password secret/uygulama/db)
API_KEY=$(vault kv get -field=key secret/uygulama/api)
# Ortam değişkeni olarak kullanım, log'a yazdırma
export DB_PASSWORD
export API_KEY
# Uygulama deployment'ı
kubectl create secret generic app-secrets
--from-literal=db-password="$DB_PASSWORD"
--from-literal=api-key="$API_KEY"
-n production
--dry-run=client -o yaml | kubectl apply -f -
GitHub Actions veya GitLab CI kullanıyorsanız, platform’un kendi secret store’unu kullanın. Hassas değerleri hiçbir zaman pipeline çıktısında görünür hale getirmeyin.
Gerçek Dünya: Rollback Stratejisi
Pipeline başarıyla tamamlandı, production’a deploy ettiniz. 5 dakika sonra hata oranı fırladı. Ne yapacaksınız?
Önceden hazırlanmış bir rollback stratejisi olmaması, kesinti süresini 10 dakikadan 2 saate çıkarabilir. Kubernetes üzerinde hızlı rollback:
# Mevcut deployment geçmişini gör
kubectl rollout history deployment/uygulama -n production
# Son deployment önceki versiyona geri al
kubectl rollout undo deployment/uygulama -n production
# Belirli bir versiyona geri al
kubectl rollout undo deployment/uygulama
--to-revision=3
-n production
# Rollback durumunu takip et
kubectl rollout status deployment/uygulama -n production
# Mevcut pod'ları ve versiyonları kontrol et
kubectl get pods -n production
-o jsonpath='{range .items[*]}{.metadata.name}{"t"}{.spec.containers[0].image}{"n"}{end}'
Rollback’i de CI/CD pipeline’ının bir parçası yapabilirsiniz. Deployment sonrası otomatik smoke test çalıştırıp başarısız olursa rollback tetikleyebilirsiniz. Bu pattern’i production’da kullanan ekiplerden çok olumlu geri bildirim aldım.
Pipeline Performansını İyileştirme
Yavaş pipeline geliştiricileri sinir eder ve CI’ı atlatmaya iter. Hız optimizasyonu için bazı pratik teknikler:
Cache kullanımı: Bağımlılıkları her seferinde sıfırdan çekmek ciddi zaman kaybettirir. GitHub Actions’ta:
# Akıllı cache kullanımı
- name: npm cache
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
hashFiles kullanımı önemli. Lock dosyası değişmediğinde cache’den alır, değişince yeniden oluşturur. Akıllı cache stratejisi tek başına 3-5 dakika kazandırabilir.
Test paralelleştirme: Test süiti büyüdükçe paralel çalıştırmak şart.
Docker layer cache: Her build’de base image’ı çekmemek için:
docker build
--cache-from $DOCKER_REGISTRY/myapp:latest
--build-arg BUILDKIT_INLINE_CACHE=1
-t $DOCKER_REGISTRY/myapp:$VERSION
.
Değişiklik tabanlı tetikleme: Monorepo kullanıyorsanız sadece değişen servisin pipeline’ını tetikleyin. Tüm servisleri her push’ta build etmek kaynakları boşa harcar.
Monitoring ve Observability
Pipeline başarılı ama uygulama düzgün çalışıyor mu? Deployment sonrası izleme CI/CD’nin ayrılmaz parçası.
Pipeline içinde temel health check:
#!/bin/bash
# post-deploy-check.sh
UYGULAMA_URL="https://uygulamam.com"
BEKLENEN_HTTP_KODU=200
TIMEOUT=300 # 5 dakika
ARALIK=10 # 10 saniyede bir kontrol
baslangic_zamani=$(date +%s)
echo "Deployment sonrası health check başlıyor..."
while true; do
http_kodu=$(curl -s -o /dev/null -w "%{http_code}"
--max-time 10
"$UYGULAMA_URL/health")
if [ "$http_kodu" -eq "$BEKLENEN_HTTP_KODU" ]; then
echo "Uygulama sağlıklı çalışıyor (HTTP $http_kodu)"
exit 0
fi
gecen_sure=$(( $(date +%s) - baslangic_zamani ))
if [ "$gecen_sure" -ge "$TIMEOUT" ]; then
echo "HATA: Timeout aşıldı. HTTP kodu: $http_kodu"
echo "Rollback tetikleniyor..."
kubectl rollout undo deployment/uygulama -n production
exit 1
fi
echo "Bekleniyor... (${gecen_sure}s geçti, HTTP: $http_kodu)"
sleep $ARALIK
done
Bu script, deployment sonrası uygulamanın ayağa kalkmasını bekler. Başaramazsa otomatik rollback yapar ve pipeline’ı başarısız işaretler.
Araç Seçimi: Neyi, Ne Zaman Kullanmalı
Hangi CI/CD aracını seçeceğiniz sorusu çok soruluyor. Net bir cevap vermek gerekirse:
- GitHub Actions: GitHub kullanıyorsanız ve ekip küçükse ilk tercih. Ekstra altyapı gerektirmez, kurulumu hızlıdır.
- GitLab CI: Self-hosted GitLab varsa en doğal entegrasyon. Kurumsal ortamlarda popüler.
- Jenkins: Esneklik ve özelleştirme şartsa, on-premise zorunluysa. Yönetim maliyeti yüksek ama güçlü.
- ArgoCD: Kubernetes-native GitOps yaklaşımı istiyorsanız. Jenkins veya GitHub Actions ile birlikte kullanılır.
- Tekton: Cloud-native, Kubernetes üzerinde CI/CD pipeline’ları için. Öğrenme eğrisi dik.
Araç seçiminde en önemli kriter ekibin benimsemesi. En iyi araç, ekibin kullandığı araçtır.
Sonuç
CI/CD yolculuğu büyük ve karmaşık görünebilir. Ama şunu net söyleyeyim: Mükemmel pipeline’ı ilk günden kurmaya çalışmayın. Küçük başlayın.
İlk hafta sadece otomatik testleri koşturun. İkinci hafta build otomasyonu ekleyin. Bir ay sonra staging’e otomatik deploy. Üç ay sonra production deployment’ı düşünün.
En büyük hata, CI/CD’yi “devops ekibinin işi” olarak görmek. Geliştiriciler pipeline yazmalı, test yazmalı, sorunları çözmeli. Sysadmin ve DevOps mühendislerinin rolü altyapıyı sağlamak, en iyi pratikleri yaymak ve ekibe rehberlik etmek.
Pipeline bir kez kurulup unutulan bir sistem değil. Ekip büyüdükçe, uygulama karmaşıklaştıkça pipeline da evrilmeli. Düzenli retrospektifler yapın: pipeline ne kadar sürüyor, hangi aşamada en çok başarısız oluyoruz, neler iyileştirilebilir?
Kesintisiz delivery hedefi uzun bir yolculuk. Ama her adımda hem ekip hem de kullanıcılar bunun faydasını görüyor. Bir sabah ofise gelip “gece push ettim, sabah canlıya çıktı, hiçbir sorun yok” diyebilmek gerçekten güzel bir his.
