Jenkins Nedir? Controller, Agent, Executor ve Ne Zaman GitHub Actions Yerine Jenkins
Jenkins, işleri planlayan bir controller ile build'leri executor'larında koşan agent'lardan oluşan self-hosted bir CI/CD sunucusudur. Hudson'dan bugüne geçmişi, workspace ve label kavramları, eşleşmeyen label'ın build'i nasıl queue'da beklettiği, plugin ekosisteminin bedeli, Freestyle ve Pipeline, LTS ve weekly hattı ve Jenkins'in GitHub Actions'a göre ne zaman doğru seçim olduğu.
Sürüm notu: Bu yazıdaki örnekler Jenkins 2.580.2 LTS (Long-Term Support), Java 21, Docker 29 ve Gitea 28 ile denenmiştir.
Önce Tahmin Et
Bir Jenkins kurulumunda controller'ın kendi üzerinde sıfır executor'ı var, iki agent'ın toplam üç executor'ı var ve agent'ların label'ları linux docker ile linux ssh. Birisi pipeline'a agent { label 'windows' } yazıp build'i başlatıyor. Ne olur? Build hemen kırmızıya mı döner, controller "nasılsa boştayım" deyip işi kendisi mi üstlenir, yoksa başka bir şey mi olur? Cevabı yazının ortasında gerçek bir build log'uyla göreceğiz.
Jenkins Nereden Geldi?
Jenkins'in hikâyesi Hudson ile başlıyor. Kohsuke Kawaguchi, Sun Microsystems'ta çalışırken kendi derdine çare olsun diye bir CI (Continuous Integration, sürekli entegrasyon) sunucusu yazdı: her commit'ten sonra kodu derleyen, testleri koşan ve kimin build'i kırdığını herkese gösteren bir araç. Oracle 2010'da Sun'ı satın aldıktan sonra isim hakkı üzerine anlaşmazlık çıktı ve topluluk 2011'in başında projeyi Jenkins adıyla devam ettirmeye karar verdi. Hudson bir süre daha yaşadı, ama topluluk ve plugin'ler Jenkins'le gitti.
İkinci kırılma 2016'da Jenkins 2 ile geldi. O güne kadar Jenkins'teki işlerin çoğu web arayüzünde tıklanarak kurulan Freestyle job'lardı: bir textarea'ya shell komutu, bir checkbox'a "testleri yayınla". Jenkins 2, Pipeline'ı ve onu tanımlayan Jenkinsfile'ı merkeze aldı. Build'in tarifi artık uygulama koduyla aynı repository'de duran, review edilen, geçmişi olan bir dosya. Bu serinin geri kalanı tamamen bu fikrin üzerine kurulu.
Kemik Yapı: Controller, Agent, Executor, Workspace
Jenkins'i anlamak için dört kavram yeter. Controller (eski adıyla master) Jenkins'in beynidir: web arayüzünü ve REST (Representational State Transfer) API'sini (Application Programming Interface) sunar, job tanımlarını, credential'ları, plugin'leri ve build geçmişini JENKINS_HOME klasöründe tutar, build queue'sunu yönetir ve hangi işin nerede koşacağına karar verir. Agent (eski adıyla slave) build'in gerçekten çalıştığı makinedir: fiziksel sunucu, VM (Virtual Machine), container ya da Kubernetes pod'u olabilir. Agent controller'a Remoting adlı bir kanal üzerinden bağlanır ve controller'ın gönderdiği komutları koşar.
Executor, bir agent'ın aynı anda koşabileceği build sayısıdır; agent başına bir sayı olarak ayarlanır. Workspace ise build'in agent üzerindeki çalışma klasörüdür: kod buraya checkout edilir, derleme çıktıları buraya düşer. Bu yazı için kurduğum ortamda controller'ın REST API'sinden düğümleri sordum:
curl -s -u "admin:$JENKINS_ADMIN_PASSWORD" \
"http://localhost:18400/computer/api/json?tree=busyExecutors,totalExecutors,computer%5BdisplayName,numExecutors,offline%5D"
{
"_class": "hudson.model.ComputerSet",
"busyExecutors": 0,
"computer": [
{
"_class": "hudson.model.Hudson$MasterComputer",
"displayName": "Built-In Node",
"numExecutors": 0,
"offline": false
},
{
"_class": "hudson.slaves.SlaveComputer",
"displayName": "docker-agent-1",
"numExecutors": 2,
"offline": false
},
{
"_class": "hudson.slaves.SlaveComputer",
"displayName": "ssh-agent-1",
"numExecutors": 1,
"offline": false
}
],
"totalExecutors": 3
}
Üç düğüm görüyorsun. "Built-In Node" controller'ın kendisi ve executor sayısı bilerek sıfır: controller build koşmaz, yalnız yönetir. Bunun nedenini kurulum yazısında konuşacağız; kısaca, controller'da koşan bir build JENKINS_HOME'a, yani bütün secret'lara ve job tanımlarına dokunabilir. Diğer ikisi agent: docker-agent-1 iki build'i aynı anda koşabilir, ssh-agent-1 bir tane. Class adlarındaki MasterComputer ve SlaveComputer, Jenkins'in master/slave terminolojisini controller/agent ile değiştirmesinden önceki döneme ait; Java API'si geriye dönük uyumluluk için eski adları koruyor.

En basit pipeline'ı çalıştırınca bu kavramların hepsi log'da görünür:
pipeline {
agent { label 'linux' }
stages {
stage('Hello') {
steps {
sh 'echo "Hello from $(hostname) as $(whoami)"'
}
}
}
}
Started by user admin
[Pipeline] Start of Pipeline
[Pipeline] node
Running on docker-agent-1 in /home/jenkins/agent/workspace/hello
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Hello)
[Pipeline] sh
+ hostname
+ whoami
+ echo Hello from 4248414140b8 as jenkins
Hello from 4248414140b8 as jenkins
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
label 'linux' iki agent'a da uyuyor; controller boş executor'ı olan docker-agent-1'i seçti ve build'e /home/jenkins/agent/workspace/hello workspace'ini verdi. sh adımı agent container'ında, jenkins kullanıcısıyla çalıştı. Pipeline'ın kendisi, yani Groovy kodunun yorumlanması ise controller'da oldu; agent'a yalnız sh komutu gitti. Bu ayrım üçüncü yazıda CPS (Continuation-Passing Style) tuzaklarını konuşurken önem kazanacak.
Label Uymazsa Ne Olur?
Baştaki sorunun cevabı. Aynı ortamda agent { label 'windows' } isteyen bir pipeline başlattım:
Started by user admin
[Pipeline] Start of Pipeline
[Pipeline] node
Still waiting to schedule task
There are no nodes with the label ‘windows’
Build ne kırmızıya döndü ne de controller işi üstlendi: queue'da bekledi. Queue API'si de aynı şeyi söylüyor:
{"_class":"hudson.model.Queue","items":[{"_class":"hudson.model.Queue$BuildableItem","why":"There are no nodes with the label ‘windows’"}]}
Jenkins burada haklı: o label'a sahip bir agent beş dakika sonra bağlanabilir ve build o zaman koşar. Ama pratikte bu, sonsuza kadar bekleyen bir build demek. Pipeline'daki options { timeout(...) } bile seni kurtarmaz, çünkü declarative pipeline'da o timeout agent bulunduktan sonra başlar. Build'i elle abort ettim; log'un sonu Finished: ABORTED oldu. Label'ı yanlış yazmak, Jenkins'e yeni gelen birinin ilk gün düştüğü tuzaklardan biri.
Plugin Ekosistemi: Gücü ve Bedeli
Jenkins çekirdeği şaşırtıcı derecede küçüktür. Git'ten checkout, Pipeline'ın kendisi, Docker içinde build, credential binding, test raporu, coverage, GitHub entegrasyonu: hepsi plugin. Update center'da binin üzerinde plugin var ve neredeyse her araç için bir tane bulursun. Jenkins'in yirmi yıla yakın ayakta kalmasının sebebi bu.
Bedeli de buradan geliyor. Bu seri için hazırladığım controller image'ında plugins.txt'e 17 plugin yazdım; bağımlılıklarıyla birlikte kurulan sayı:
$ docker exec jenkwork-jenkins sh -c 'ls /var/jenkins_home/plugins/*.jpi | wc -l'
96
17 istedim, 96 geldi. Her biri ayrı bir sürüm, ayrı bir bakımcı ve ayrı bir güvenlik geçmişi demek. Jenkins'in yayımladığı security advisory'lerin büyük bölümü çekirdekle değil plugin'lerle ilgili ve bazı plugin'lerin bakımcısı yıllar önce ilgisini kaybetmiş. Bir Jenkins'i devraldığında ilk iş, kurulu plugin listesine bakıp "bunların hangisini gerçekten kullanıyoruz?" diye sormaktır. Plugin'leri sürümüyle sabitlemeyi ve controller'ı kod olarak yönetmeyi on birinci yazıda göreceğiz.
Freestyle mı, Pipeline mı?
Eski bir Jenkins'te muhtemelen yüzlerce Freestyle job göreceksin. Freestyle job'un tarifi controller'daki config.xml'de yaşar; kimin neyi ne zaman değiştirdiğini bilmezsin, aynı işi başka bir branch için kopyalamak "Copy job" butonudur, iki job arasındaki fark da ancak XML (Extensible Markup Language) diff'iyle görülür. Pipeline'da tarif Jenkinsfile'dadır: pull request'te review edilir, branch'le birlikte değişir, geri alınabilir.
Pipeline'ın iki syntax'ı var. Declarative Pipeline (pipeline { ... } ile başlayan) yapılandırılmış ve sınırlı bir dil; Jenkins onu çalıştırmadan önce doğrulayabilir ve kuralları belli olduğu için okunması kolay. Scripted Pipeline (node { ... } ile başlayan) düz Groovy'dir; esnek ama disiplinsiz. Yeni bir iş için cevap neredeyse her zaman Declarative Pipeline; esneklik gerektiğinde içine script { } bloğu açarsın. Freestyle'ı yeni işte kullanmak için bugün bir sebep yok; mevcut Freestyle job'ları Pipeline'a taşımayı son yazıda ele alacağız.
LTS mi, Weekly mi?
Jenkins iki sürüm hattıyla yayımlanır. Weekly hattı her hafta yeni bir sürüm çıkarır (bu yazıyı yazarken Docker Hub'daki son weekly 2.589). LTS (Long-Term Support) hattında ise topluluk her 12 haftada bir yakın tarihli bir weekly sürümü baseline olarak seçer; o baseline'ın üzerine yaklaşık dört haftada bir .1, .2, .3 düzeltme sürümleri gelir. Bu yazıdaki örneklerin koştuğu 2.580.2, 2.580 baseline'ının ikinci düzeltme sürümü ve 28 Ekim 2026'da çıktı.
Production'da kural basit: LTS kullan. Weekly'de yeni özellikler daha erken gelir ama her hafta bir şey değişir ve plugin'ler weekly'nin yeni API'lerine hızlıca bağımlı hâle gelebilir. Docker image tag'lerinde de bu ayrım görünür: jenkins/jenkins:lts-jdk21 LTS hattını, jenkins/jenkins:latest weekly hattını takip eder.
Ne Zaman Jenkins, Ne Zaman Değil?
Asıl soru bu. Kodun GitHub'daysa, ekibin küçükse ve build'lerin standart bir Linux makinede koşuyorsa, GitHub Actions neredeyse her zaman daha az iş çıkarır: sunucu, upgrade, plugin, yedek, disk yok. Jenkins'i seçmenin iyi sebepleri şunlar:
| Durum | Jenkins | GitHub Actions |
|---|---|---|
| İnternete kapalı (air-gapped) ağ, kurum içi kaynak kodu | Doğal kullanım alanı | GitHub Enterprise Server ve self-hosted runner gerekir |
| Karma SCM (Bitbucket, GitLab, Azure Repos, kurum içi Git) | Hepsine plugin var | GitHub merkezli |
| Özel donanım (GPU, imza cihazı, test cihazı) | Kendi agent'ın, kendi label'ın | Self-hosted runner ile mümkün |
| Regülasyon: log ve artifact kurum içinde kalmalı | Her şey kendi diskinde | Ek yapılandırma gerekir |
| Küçük ekip, kod GitHub'da | Bakım yükü fazla | Genellikle doğru seçim |
Bir şeyi açık söyleyeyim: Jenkins'in maliyeti lisans değil, bakım. Birinin LTS upgrade'lerini takip etmesi, plugin'leri güncellemesi, agent disklerini temizlemesi ve controller'ı yedeklemesi gerekiyor. Kurumsal bir ortamda bu iş zaten bir platform ekibinin elindeyse Jenkins çok iyi bir araç; kimsenin elinde değilse, iki yıl sonra kimsenin dokunmaya cesaret edemediği bir sunucuya dönüşür. GitHub Actions tarafını ilerideki GitHub Actions serisinde, tool'dan bağımsız pipeline düşüncesini de ilerideki CI/CD Temelleri serisinde ayrıntılı konuşacağız.
Bu Serideki Ortam
Seri boyunca her şeyi gerçekten çalıştırıyoruz. Ortam, tek bir compose.yaml ile ayağa kalkan beş container'dan oluşuyor: controller (jenkins/jenkins:2.580.2-lts-jdk21 üzerine plugin'leri eklenmiş bir image), WebSocket ile bağlanan bir inbound agent, SSH (Secure Shell) ile bağlanan bir agent, agent'ların Docker içinde build alabilmesi için ayrı bir Docker daemon'ı (Docker-in-Docker) ve GitHub'ın yerine yerel bir Git sunucusu olarak Gitea. Örnek uygulama sitenin tanıdık Bookstore'u: .NET 10 ile yazılmış Bookstore.Api ve Angular 22 ile yazılmış bookstore-web. Dokuzuncu yazıda bunlara deploy hedefi olarak systemd çalıştıran iki sunucu daha ekleniyor. Kurulumun kendisi bir sonraki yazının konusu.
Üretimde Dikkat
Controller tek bir noktadır. Jenkins'in açık kaynak sürümünde controller için yerleşik bir yüksek erişilebilirlik (high availability) çözümü yok; controller düştüğünde queue durur, webhook'lar boşa gider. Bu yüzden iki şey hayati: JENKINS_HOME'un düzenli ve test edilmiş yedeği, ve controller'ı yeniden kurmayı dakikalara indiren bir tarif (Configuration as Code ile plugins.txt).
Controller'ın kaynaklarını izle. Build log'ları, test raporları, arşivlenen artifact'lar controller diskine yazılır; build geçmişini sınırlamazsan disk bir gün dolar. Pipeline'ın Groovy kısmı controller'ın JVM'inde (Java Virtual Machine) çalıştığı için, pipeline'da ağır işleri (büyük JSON (JavaScript Object Notation) parse, uzun döngüler) Groovy'de yapmak bütün Jenkins'i yavaşlatır; ağır iş agent'ta sh içinde yapılmalı.
Executor sayısını CPU'ya (Central Processing Unit) ve belleğe göre ayarla. Dört çekirdekli bir agent'a sekiz executor verirsen iki .NET build'i ile bir Angular build'i aynı anda koşar ve hepsi yavaşlar. Genel başlangıç noktası çekirdek başına bir executor veya daha azı; sonra build sürelerine bakarak ayarla.
Sık Düşülen Tuzaklar
Birincisi, controller'da build koşmak. Varsayılan kurulumda Built-In Node'un executor'ları vardır ve label belirtilmemiş her iş orada koşar. Bu hem güvenlik açığı hem de performans sorunu.
İkincisi, label'ı yanlış yazmak veya var olmayan bir label istemek. Yukarıda gördüğün gibi build hata vermez, queue'da sessizce bekler. "Jenkins yavaş" şikâyetlerinin bir kısmı aslında hiç başlamamış build'lerdir.
Üçüncüsü, plugin kurmayı ucuz sanmak. Her plugin bir bağımlılık ağacı, bir upgrade riski ve bir saldırı yüzeyi getirir. "Şu işi de bir plugin yapıyormuş" demeden önce aynı işi sh içinde bir komutla yapıp yapamayacağına bak.
Dördüncüsü, Jenkins'i "kurduk, bitti" diye görmek. LTS hattı her dört haftada bir düzeltme, her on iki haftada bir yeni baseline getiriyor; Java gereksinimi de değişiyor (2.555.1'den beri Java 21 zorunlu). Takip edilmeyen bir controller birkaç yıl içinde upgrade edilemez hâle gelir.
Küçük Bir Deneme
★☆☆ Elindeki bir Jenkins'te (veya bir sonraki yazıda kuracağın yerel ortamda) /computer/api/json'u çağır. Built-In Node'un executor sayısı kaç? Sıfır değilse, son bir haftada orada hangi build'lerin koştuğunu bulmaya çalış.
★★☆ Kurulu plugin listesini çıkar (/pluginManager/api/json?depth=1) ve doğrudan kullandığın plugin'leri, yalnızca bağımlılık olarak gelenlerden ayır. Kaç plugin'i doğrudan istiyorsun?
★★★ Kendi ekibin için yukarıdaki tabloyu doldur: kodunuz nerede, build'ler hangi donanımı istiyor, log ve artifact'ların nerede kalması gerekiyor, Jenkins'in bakımını kim yapacak? Cevaplara göre Jenkins mi GitHub Actions mı daha mantıklı, bir paragrafla gerekçelendir.
Bir sonraki yazıda bu ortamı sıfırdan kuruyoruz: Docker ile Jenkins LTS, Java 21, setup wizard'ın ne yaptığı, ilk admin kullanıcısı, plugin seçimi ve production'a çıkmadan önce kapatılması gereken kapılar.