Jenkins ile Çoklu Branch Pipeline Yönetimi
Büyük bir ekiple çalışıyorsanız ve her developer kendi branch’inde özgürce geliştirme yapıyorsa, bu branch’lerin hepsini ayrı ayrı pipeline ile yönetmek ciddi bir baş ağrısına dönüşebilir. Jenkins’in Multibranch Pipeline özelliği tam da bu noktada hayat kurtarıyor. Tek bir pipeline tanımıyla onlarca branch’i otomatik olarak keşfedip yönetebiliyorsunuz. Bu yazıda gerçek dünya senaryolarıyla Jenkins’te çoklu branch pipeline yönetimini en ince detayına kadar inceleyeceğiz.
Jenkins Multibranch Pipeline Nedir?
Klasik Jenkins job’larında her branch için ayrı bir job tanımlamanız gerekir. Yeni branch açıldığında biri unutur, CI çalışmaz, build kırık kalır. Multibranch Pipeline bu sorunu kökten çözüyor.
Jenkins, repository’nizi düzenli olarak tarar (scan), yeni branch’leri otomatik keşfeder ve her branch için ayrı bir pipeline oluşturur. Branch silindiğinde Jenkins’teki karşılığı da temizlenir. Bütün bu mantık tek bir Jenkinsfile üzerinden yürür.
Temel avantajlar şöyle sıralanabilir:
- Otomatik branch keşfi: Yeni branch açıldığı an pipeline hazır olur
- İzolasyon: Her branch kendi bağımsız build geçmişine sahiptir
- Merkezi konfigürasyon: Tek
Jenkinsfiletüm branch’leri yönetir - Pull Request desteği: GitHub/GitLab PR’larını da otomatik build eder
- Temiz kaynak: Silinen branch’lerin pipeline’ı da temizlenir
Kurulum ve Gereksinimler
Önce Jenkins’in güncel olduğundan ve gerekli plugin’lerin yüklü olduğundan emin olalım.
# Jenkins versiyon kontrolü
java -jar jenkins-cli.jar -s http://localhost:8080 version
# Gerekli plugin'leri CLI üzerinden yüklemek için
java -jar jenkins-cli.jar -s http://localhost:8080
-auth admin:your-api-token
install-plugin workflow-multibranch
install-plugin branch-api
install-plugin git
install-plugin github-branch-source
install-plugin pipeline-stage-view
Jenkins UI üzerinden de Manage Jenkins > Manage Plugins > Available sekmesinden şu plugin’leri aratıp kurabilirsiniz:
- Pipeline: Multibranch: Temel multibranch desteği
- GitHub Branch Source: GitHub entegrasyonu
- GitLab Branch Source: GitLab entegrasyonu
- Bitbucket Branch Source: Bitbucket entegrasyonu
- Pipeline: Stage View: Pipeline görselleştirme
İlk Multibranch Pipeline Job’ını Oluşturmak
Jenkins dashboard’dan New Item diyoruz, bir isim veriyoruz ve Multibranch Pipeline seçiyoruz. Açılan ekranda dikkat etmemiz gereken birkaç kritik ayar var.
Branch Sources bölümünde repository bilgilerini giriyoruz. GitHub kullanıyorsanız credentials olarak Personal Access Token ya da SSH key eklemeniz gerekiyor. Credentials’ı Jenkins’e şu şekilde ekleyebilirsiniz:
# Jenkins CLI ile credential eklemek
cat <<EOF | java -jar jenkins-cli.jar -s http://localhost:8080
-auth admin:your-api-token create-credentials-by-xml
system::system::jenkins _
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
<scope>GLOBAL</scope>
<id>github-credentials</id>
<description>GitHub PAT</description>
<username>your-github-username</username>
<password>ghp_your_personal_access_token</password>
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
EOF
Scan Multibranch Pipeline Triggers ayarında Periodically if not otherwise run seçip interval’ı 1 dakika yapmanızı öneririm. Webhook kurduğunuzda zaten anlık tetiklenecek, bu sadece fallback için.
Jenkinsfile Yapısı ve Branch Bazlı Mantık
İşte burada asıl güç ortaya çıkıyor. Jenkinsfile içinde env.BRANCH_NAME değişkenini kullanarak her branch’e özel davranış tanımlayabilirsiniz.
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'registry.mycompany.com'
APP_NAME = 'myapp'
SLACK_CHANNEL = '#ci-cd-alerts'
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
// Branch adını temizle (feature/my-feature -> feature-my-feature)
env.SAFE_BRANCH_NAME = env.BRANCH_NAME.replaceAll('/', '-')
env.BUILD_TAG = "${env.APP_NAME}-${env.SAFE_BRANCH_NAME}-${env.BUILD_NUMBER}"
echo "Building branch: ${env.BRANCH_NAME}"
echo "Build tag: ${env.BUILD_TAG}"
}
}
}
stage('Unit Tests') {
steps {
sh './gradlew test'
}
post {
always {
junit 'build/test-results/**/*.xml'
}
}
}
stage('Build Docker Image') {
steps {
script {
def image = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${env.BUILD_TAG}")
env.DOCKER_IMAGE = image.id
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
echo "Deploying to staging environment..."
sh """
kubectl set image deployment/${APP_NAME}
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${env.BUILD_TAG}
-n staging
"""
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
timeout(time: 10, unit: 'MINUTES') {
input message: "Production'a deploy edilsin mi?",
ok: "Deploy Et",
submitter: "devops-team"
}
sh """
kubectl set image deployment/${APP_NAME}
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${env.BUILD_TAG}
-n production
"""
}
}
}
}
Bu Jenkinsfile’da when { branch 'develop' } direktifi sayesinde staging deploy sadece develop branch’inde çalışır. Feature branch’lerinde bu stage tamamen atlanır.
Branch Filtreleme ve Keşif Stratejileri
Her branch’i build etmek istemeyebilirsiniz. Özellikle büyük repo’larda deneme amaçlı açılan branch’ler gereksiz kaynak tüketir.
// Jenkins Job DSL ile Multibranch Pipeline programatik oluşturma
multibranchPipelineJob('my-application') {
branchSources {
github {
id('my-app-github')
repoOwner('my-organization')
repository('my-application')
credentialsId('github-credentials')
// Branch keşif stratejisi
traits {
// Sadece PR'ları build et (origin branch değil)
headRegexFilter {
regex('(main|develop|release/.*|feature/.*|hotfix/.*)')
}
// Stale branch'leri görmezden gel (30 gün aktif değilse)
branchDiscoveryTrait {
strategyId(3) // 1: sadece PR olmayan, 2: sadece PR olan, 3: her ikisi
}
}
}
}
orphanedItemStrategy {
discardOldItems {
numToKeep(10)
daysToKeep(30)
}
}
triggers {
periodic(1) // 1 dakikada bir scan
}
}
Branch isimlerini regex ile filtrelemek için Jenkinsfile içinde de bunu yapabilirsiniz:
pipeline {
agent any
stages {
stage('Integration Tests') {
when {
anyOf {
branch 'main'
branch 'develop'
branch pattern: 'release/.*', comparator: 'REGEXP'
branch pattern: 'hotfix/.*', comparator: 'REGEXP'
}
}
steps {
sh './gradlew integrationTest'
}
}
stage('Security Scan') {
when {
not {
branch pattern: 'dependabot/.*', comparator: 'REGEXP'
}
}
steps {
sh 'trivy image ${DOCKER_IMAGE}'
}
}
}
}
Shared Libraries ile Kod Tekrarını Önleme
Çok sayıda repository’niz varsa her birinde aynı Jenkinsfile’ı kopyalamak zorunda kalmak kabustur. Jenkins Shared Libraries bu sorunu çözüyor.
Önce shared library repo’sunuzu oluşturun. Yapısı şu şekilde olmalı:
jenkins-shared-library/
├── vars/
│ ├── buildAndDeploy.groovy
│ ├── runTests.groovy
│ └── dockerBuild.groovy
├── src/
│ └── com/
│ └── mycompany/
│ └── pipeline/
│ └── Utils.groovy
└── resources/
└── scripts/
└── health-check.sh
vars/buildAndDeploy.groovy içeriği:
def call(Map config = [:]) {
def appName = config.appName ?: error("appName zorunludur!")
def registry = config.registry ?: 'registry.mycompany.com'
def namespace = config.namespace ?: 'default'
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: docker
image: docker:24-dind
securityContext:
privileged: true
- name: kubectl
image: bitnami/kubectl:latest
command: ['cat']
tty: true
"""
}
}
stages {
stage('Build') {
steps {
container('docker') {
sh "docker build -t ${registry}/${appName}:${env.BUILD_NUMBER} ."
sh "docker push ${registry}/${appName}:${env.BUILD_NUMBER}"
}
}
}
stage('Deploy') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
container('kubectl') {
script {
def targetNamespace = env.BRANCH_NAME == 'main' ? 'production' : 'staging'
sh """
kubectl set image deployment/${appName}
${appName}=${registry}/${appName}:${env.BUILD_NUMBER}
-n ${targetNamespace}
kubectl rollout status deployment/${appName} -n ${targetNamespace}
"""
}
}
}
}
}
}
}
Bu shared library’yi kullanan minimal bir Jenkinsfile:
@Library('jenkins-shared-library@main') _
buildAndDeploy(
appName: 'payment-service',
registry: 'registry.mycompany.com',
namespace: 'payments'
)
Shared library’yi Jenkins’e tanıtmak için Manage Jenkins > Configure System > Global Pipeline Libraries bölümüne gidin ve repo bilgilerini ekleyin.
Webhook Kurulumu ile Anlık Tetikleme
Periyodik scan yerine webhook kullanmak çok daha verimli. GitHub webhook’u kurmak için:
# GitHub webhook'u curl ile oluşturma
curl -X POST
-H "Authorization: token ghp_your_token"
-H "Content-Type: application/json"
-d '{
"name": "web",
"active": true,
"events": ["push", "pull_request", "create", "delete"],
"config": {
"url": "https://jenkins.mycompany.com/github-webhook/",
"content_type": "json",
"secret": "your-webhook-secret",
"insecure_ssl": "0"
}
}'
https://api.github.com/repos/my-organization/my-application/hooks
Jenkins tarafında webhook secret’ı doğrulamak için Manage Jenkins > Configure System > GitHub > Advanced bölümüne gidin. Secret’ı buraya gireceksiniz.
Paralel Stage’ler ile Build Süresini Kısaltma
Feature branch’lerinde hızlı feedback önemli. Paralel stage’lerle test sürelerini dramatik biçimde düşürebilirsiniz.
pipeline {
agent any
stages {
stage('Parallel Tests') {
parallel {
stage('Unit Tests') {
agent {
label 'java-agent'
}
steps {
sh './gradlew test --tests "*UnitTest*"'
}
post {
always {
junit 'build/test-results/test/*.xml'
}
}
}
stage('Integration Tests') {
when {
anyOf {
branch 'develop'
branch 'main'
branch pattern: 'release/.*', comparator: 'REGEXP'
}
}
agent {
label 'java-agent'
}
steps {
sh './gradlew integrationTest'
}
}
stage('Static Analysis') {
agent {
label 'java-agent'
}
steps {
sh './gradlew sonarqube
-Dsonar.projectKey=my-app
-Dsonar.host.url=https://sonar.mycompany.com
-Dsonar.login=$SONAR_TOKEN'
}
}
}
}
stage('Quality Gate') {
when {
anyOf {
branch 'develop'
branch 'main'
}
}
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
}
Gerçek Dünya Senaryosu: Gitflow ile Tam Pipeline
Ekibiniz Gitflow kullanıyorsa şu branch stratejisi çok işe yarar:
- feature/*: Sadece build ve unit test
- develop: Build, tüm testler, staging deploy
- release/*: Build, tüm testler, UAT deploy, tag oluşturma
- main: Build, smoke test, production deploy (onaylı)
- hotfix/*: Build, kritik testler, hızlı production deploy
pipeline {
agent any
environment {
REGISTRY = credentials('docker-registry-url')
KUBECONFIG = credentials('kubeconfig')
}
stages {
stage('Build') {
steps {
script {
env.IMAGE_TAG = sh(
script: "git rev-parse --short HEAD",
returnStdout: true
).trim()
sh "docker build -t ${REGISTRY}/app:${env.IMAGE_TAG} ."
sh "docker push ${REGISTRY}/app:${env.IMAGE_TAG}"
}
}
}
stage('Tests') {
parallel {
stage('Unit') {
steps { sh 'make test-unit' }
}
stage('Lint') {
steps { sh 'make lint' }
}
}
}
stage('Integration Tests') {
when {
not {
branch pattern: 'feature/.*', comparator: 'REGEXP'
}
}
steps {
sh 'make test-integration'
}
}
stage('Deploy Staging') {
when {
branch 'develop'
}
steps {
sh """
helm upgrade --install app ./helm/app
--namespace staging
--set image.tag=${env.IMAGE_TAG}
--set ingress.host=staging.mycompany.com
--wait --timeout 5m
"""
}
}
stage('Deploy UAT') {
when {
branch pattern: 'release/.*', comparator: 'REGEXP'
}
steps {
sh """
helm upgrade --install app ./helm/app
--namespace uat
--set image.tag=${env.IMAGE_TAG}
--set ingress.host=uat.mycompany.com
--wait --timeout 5m
"""
// Release tag'i oluştur
sh """
git tag -a "rc-${env.BUILD_NUMBER}" -m "Release candidate"
git push origin "rc-${env.BUILD_NUMBER}"
"""
}
}
stage('Deploy Production') {
when {
anyOf {
branch 'main'
branch pattern: 'hotfix/.*', comparator: 'REGEXP'
}
}
steps {
script {
if (env.BRANCH_NAME.startsWith('hotfix/')) {
echo "Hotfix tespiti: Manual onay atlanıyor..."
} else {
timeout(time: 30, unit: 'MINUTES') {
input message: "Production deploy onayı gerekiyor",
ok: "Onayla",
submitter: "release-managers"
}
}
sh """
helm upgrade --install app ./helm/app
--namespace production
--set image.tag=${env.IMAGE_TAG}
--set ingress.host=mycompany.com
--wait --timeout 10m
"""
}
}
}
}
post {
failure {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'danger',
message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER} - Branch: ${env.BRANCH_NAME}"
)
}
success {
script {
if (env.BRANCH_NAME == 'main') {
slackSend(
channel: '#releases',
color: 'good',
message: "Production deploy başarılı: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
}
cleanup {
cleanWs()
}
}
}
Orphaned Branch Temizliği ve Kaynak Yönetimi
Zamanla silinmiş branch’lerin Jenkins’te kalıntıları birikir. Otomatik temizlik için:
# Jenkins Groovy script ile orphaned branch job'larını temizleme
# Manage Jenkins > Script Console'dan çalıştırın
def daysToKeep = 7
def cutoff = new Date() - daysToKeep
Jenkins.instance.getAllItems(WorkflowMultiBranchProject.class).each { project ->
project.items.findAll { branch ->
def lastBuild = branch.getLastBuild()
if (lastBuild == null) return true
def buildDate = new Date(lastBuild.getTimeInMillis())
return buildDate < cutoff
}.each { staleBranch ->
println "Siliniyor: ${staleBranch.fullName}"
staleBranch.delete()
}
}
Job konfigürasyonunda da şu ayarları yapın:
- Discard old items aktif olsun
- Days to keep old items: 30
- Max # of old items to keep: 10
Bu sayede her branch en fazla son 10 build’ini saklasın ve 30 günden eski build’ler otomatik silinsin.
Sorun Giderme: Yaygın Hatalar ve Çözümleri
Multibranch pipeline kurulumunda sıkça karşılaşılan sorunlar:
Branch keşfedilmiyor
- Credentials doğru mu kontrol edin
- Repository URL’i erişilebilir mi test edin
- Scan Multibranch Pipeline Now ile manuel tarama başlatın
- Jenkins log’larında
[BranchDiscovery]satırlarına bakın
Jenkinsfile bulunamıyor
- Branch’in kök dizininde
Jenkinsfile(büyük J) olduğundan emin olun - Job konfigürasyonundaki script path’i kontrol edin
- Default değer
Jenkinsfileolmalı
Webhook çalışmıyor
# Webhook delivery geçmişini kontrol edin
curl -H "Authorization: token ghp_your_token"
https://api.github.com/repos/org/repo/hooks/HOOK_ID/deliveries
| jq '.[0:5] | .[] | {id, event, created_at, status: .status}'
Agent bulunamıyor
// Label yerine any kullanarak test edin
agent any
// Çalışıyorsa agent label'larını kontrol edin
Sonuç
Jenkins Multibranch Pipeline, modern yazılım geliştirme süreçlerinde vazgeçilmez bir araç haline geldi. Tek bir Jenkinsfile ile tüm branch stratejinizi kodlayabilir, her branch’e özel davranış tanımlayabilir ve ekibinizin iş akışını otomatikleştirebilirsiniz.
Kurulumda dikkat etmeniz gereken kritik noktalar şunlar: doğru credential yönetimi, akıllıca branch filtreleme, shared library kullanımıyla kod tekrarından kaçınma ve orphaned branch temizliği. Özellikle büyük ekiplerde shared library olmadan yönetim kaosa dönüşebilir, bu yüzden ortak pipeline mantığını mümkün olan en erken aşamada library’e taşımanızı öneriyorum.
Webhook entegrasyonunu da ihmal etmeyin. Periyodik scan ile başlayabilirsiniz ama production ortamında webhook her zaman daha güvenilir ve anlık feedback sağlar. Geliştiricileriniz push yaptıktan sonra saniyeler içinde build başlıyorsa, CI süreciniz gerçekten işe yarıyor demektir.
