GitHub Actions, CI/CD süreçlerimi yönetmek için sıkça kullandığım bir araç. Özellikle birçok projemde, hem kendi yan ürünlerimde hem de bazı müşteri projelerinde build ve test adımlarını otomatikleştirmek büyük kolaylık sağlıyor. Ancak, zamanla fark ettim ki, GitHub’ın kendi hosted runner’ları, özellikle yoğun iş yükleri ve uzun süren build’ler için maliyetli olabiliyordu. Bir süre sonra, “Acaba bu maliyetleri kendi kontrolüm altına alabilir miyim?” diye düşünmeye başladım.
Bu düşünceyle birlikte, GitHub Actions runner’larımı kendi sanal sunucuma (VPS) taşıma macerasına atıldım. Bu süreç, sadece maliyetleri optimize etmekle kalmadı, aynı zamanda build ortamı üzerinde daha fazla kontrol sahibi olmamı da sağladı. Spesifik yazılım bağımlılıkları, özel ağ konfigürasyonları veya daha yüksek performans gerektiren durumlar için kendi runner’ınızın olması paha biçilmez bir avantaj.
Neden Kendi GitHub Actions Runner’ıma İhtiyaç Duydum?
GitHub’ın sunduğu hosted runner’lar çoğu senaryo için harika bir çözüm. Ancak benim gibi belirli ihtiyaçları olan geliştiriciler veya ekipler için bazı sınırlamaları olabiliyor. Kendi runner’ımı kurmaya iten temel nedenler şunlardı:
Maliyet Optimizasyonu
Bazı projelerimde build süreleri, özellikle büyük bağımlılıkların indirilmesi ve testlerin çalışmasıyla birlikte uzayabiliyordu. Bir üretim ERP’sinin backend’i gibi projelerde, her commit’te çalışan entegrasyon testleri veya AI modelleri için veri hazırlama süreçleri dakikalarca sürebiliyordu. GitHub’ın ücretsiz limitleri dolunca, fazla kullanım için ödeme yapmaya başlıyordum. Kendi VPS’imde, zaten boşta duran kaynakları değerlendirerek bu maliyetleri önemli ölçüde düşürebileceğimi gördüm. Örneğin, kendi yan ürünümün CI/CD pipeline’ı ayda 2000 dakikadan fazla kullanıma ulaşıyordu, bu da GitHub’da belirli bir ücrete tekabül ediyordu.
Özel Ortam ve Bağımlılıklar
Bazen, GitHub’ın varsayılan runner imajlarında bulunmayan özel yazılımlara, belirli bir kütüphane versiyonuna veya custom bir tool’a ihtiyacım oluyordu. Örneğin, eski bir C++ projesini derlerken belirli bir GCC versiyonu veya bazı ağ testleri için özel bir netcat varyantı gerekebiliyordu. Kendi runner’ımda bu tür bağımlılıkları istediğim gibi kurabiliyor, ortamı tamamen kontrol edebiliyordum. Bir müşteri projesinde, güvenlik gereği, buildlerin sadece belirli bir IP aralığından çıkış yapması gerekiyordu; bu da kendi runner kurmamı zorunlu kıldı.
Performans İhtiyaçları
Büyük bir monorepo veya karmaşık derleme süreçleri olan projelerde, daha fazla CPU, RAM veya daha hızlı disk I/O’su gerekebilir. GitHub’ın standart runner’ları belirli bir performans seviyesi sunsa da, kendi sunucumda daha güçlü bir VPS kiralayarak veya mevcut sunucumun boşta duran kaynaklarını kullanarak build sürelerini kısaltabildim. Özellikle Docker imajlarını build ederken veya çok sayıda bağımlılığı indirirken disk I/O’su kritik olabiliyor. Kendi sunucumda NVMe diskler kullanarak bu süreyi önemli ölçüde hızlandırdım.
Hazırlık Aşaması: VPS ve Ön Koşullar
Kendi runner’ımı kurmadan önce, uygun bir VPS seçmeli ve gerekli ön koşulları sağlamalıydım. Bu adımlar, kurulum sürecinin sorunsuz ilerlemesi için kritikti.
VPS Seçimi ve Özellikleri
Genellikle Ubuntu veya Debian tabanlı Linux dağıtımlarını tercih ediyorum. Stabilite, geniş paket desteği ve systemd entegrasyonu açısından bu dağıtımlar işimi kolaylaştırıyor. Bir runner için minimum özellikler projenizin büyüklüğüne göre değişir, ancak genel bir başlangıç için:
- CPU: 2 vCPU
- RAM: 4 GB
- Disk: 60 GB SSD (build cache’leri ve bağımlılıklar için yeterli alan)
Benim tercihim, kullandığım diğer yan ürünlerin backend’ini de barındıran, 4 vCPU, 8 GB RAM ve 160 GB NVMe diske sahip bir VPS oldu. Bu sayede mevcut kaynaklarımdan daha verimli faydalanabildim.
Gerekli Paketlerin Kurulumu
VPS’e SSH ile bağlandıktan sonra, runner yazılımının çalışması için gerekli bazı temel araçları kurdum.
sudo apt update
sudo apt upgrade -y
sudo apt install -y curl git jq
curl: GitHub Actions runner paketini indirmek için.git: Repository’leri klonlamak ve işlem yapmak için.jq: JSON çıktılarını işlemek için (genellikle runner scriptleri içinde kullanılır).
GitHub Actions Runner Kurulumu: Adım Adım
Şimdi gelelim asıl konuya: runner yazılımını indirip kurma ve yapılandırma adımlarına. Bu süreç, GitHub’ın kendi dökümantasyonunda da detaylıca anlatılıyor, ancak benim tecrübelerimle birlikte bazı püf noktalarını paylaşacağım.
1. Runner Token’ı Alma
Öncelikle, GitHub’dan bir runner token’ı almamız gerekiyor. Bu token, runner’ın GitHub ile güvenli bir şekilde iletişim kurmasını sağlar. Token’ı, projenizin veya organizasyonunuzun ayarlarına giderek alabilirsiniz:
- Repository Seviyesinde:
Your repository->Settings->Actions->Runners->New self-hosted runner - Organization Seviyesinde:
Your organization->Settings->Actions->Runners->New self-hosted runner
Burada, işletim sistemi olarak Linux’u seçtiğinizde size adımları ve token’ı verecektir. Token’ı kopyalayın, çünkü kurulum sırasında ihtiyacımız olacak.
2. Runner Dizinini Oluşturma ve İzinleri Ayarlama
Runner’ı çalıştırmak için bir dizin oluşturmak ve bu dizine doğru izinleri vermek önemlidir. Güvenlik prensipleri gereği, runner’ı root kullanıcısı yerine ayrıcalıksız bir kullanıcı ile çalıştırmayı tercih ediyorum. Bu, olası bir güvenlik açığının sistemin tamamına yayılmasını engeller.
sudo mkdir /actions-runner
sudo chown mustafa:mustafa /actions-runner # Kullanıcı adınızı ve grubunuzu buraya yazın
cd /actions-runner
Ben kendi kullanıcı adım olan mustafa’yı kullandım. Siz kendi kullanıcı adınızı kullanmalısınız.
3. Runner Yazılımını İndirme ve Çıkarma
GitHub’ın sağladığı komutları kullanarak runner paketini indirip çıkarabiliriz. Bu komutlar, yukarıda token alırken size gösterilen adımlarda da yer alır.
# Örnek komutlar (güncel versiyonu GitHub sayfasından kontrol edin)
curl -o actions-runner-linux-x64-2.309.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.309.0/actions-runner-linux-x64-2.309.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.309.0.tar.gz
Bu komutlarla, actions-runner dizinine runner yazılımı dosyaları çıkarılacaktır.
4. Runner’ı Yapılandırma
Paketi çıkardıktan sonra, runner’ı GitHub hesabımıza bağlamak için config.sh scriptini çalıştırmamız gerekiyor.
./config.sh --url https://github.com/MustafaErbay/my-repo --token <YOUR_TOKEN> --name my-vps-runner --labels linux,x64,self-hosted --unattended
--url: Repository’nizin veya organizasyonunuzun URL’si. Benim yan ürünlerimden birinin backend projesi içinhttps://github.com/MustafaErbay/my-repogibi bir URL kullandım.--token: Az önce aldığınız token.--name: Runner’a vereceğiniz isim. GitHub Actions arayüzünde bu isimle görünecek.--labels: Runner’a atayacağınız etiketler. Workflow’larınızda bu etiketleri kullanarak belirli runner’ları hedefleyebilirsiniz.linux,x64,self-hostedgibi genel etiketler kullanmak iyi bir başlangıçtır.--unattended: Bu parametre, yapılandırma sırasında ek soru sormadan işlemi tamamlamasını sağlar.
Bu adımı tamamladıktan sonra, GitHub Actions arayüzünde “Runners” sekmesinde yeni runner’ınızı “Idle” durumda görmelisiniz.
systemd ile Yönetim ve Güvenlik
Runner’ı manuel olarak çalıştırmak yerine, onu bir systemd servisi olarak yapılandırmak, sunucu yeniden başlatıldığında otomatik olarak başlamasını ve arka planda sürekli çalışmasını sağlar. Ayrıca, kaynak yönetimi ve log takibi açısından da büyük avantajlar sunar.
1. systemd Unit Dosyası Oluşturma
/etc/systemd/system/actions.runner.service adında bir dosya oluşturdum ve içine aşağıdaki içeriği ekledim:
[Unit]
Description=GitHub Actions Runner
After=network.target
[Service]
ExecStart=/actions-runner/run.sh
WorkingDirectory=/actions-runner
User=mustafa
Group=mustafa
Restart=always
RestartSec=10
LimitNOFILE=1024
LimitNPROC=1024
TimeoutStopSec=5min
# Memory limits (soft limit, sends a warning)
MemoryHigh=4G
[Install]
WantedBy=multi-user.target
UserveGroup: Runner’ın hangi kullanıcı ve grup altında çalışacağını belirtir. Kesinliklerootkullanıcısı olmamalıdır. Kendi kullanıcı adımı kullandım.WorkingDirectory: Runner’ın çalışacağı dizin.ExecStart: Runner’ı başlatan script.Restart=always: Servisin hata durumunda veya durduğunda otomatik olarak yeniden başlatılmasını sağlar. Bu, runner’ın sürekli çalışır durumda kalması için kritik.MemoryHigh=4G: Bu,cgrouptarafından uygulanan yumuşak bir bellek limitidir. Eğer runner’ın kullandığı bellek 4 GB’ı aşarsa, sistem bir uyarı gönderir ve belleği serbest bırakmaya çalışır, ancak süreci doğrudan sonlandırmaz. Bu sayede, birOOM-killeddurumuna düşmeden önce müdahale edebilirim.
2. systemd Servisini Etkinleştirme ve Başlatma
Unit dosyasını kaydettikten sonra, systemd’yi güncellemeli, servisi etkinleştirmeli ve başlatmalıyız:
sudo systemctl daemon-reload
sudo systemctl enable actions.runner.service
sudo systemctl start actions.runner.service
Servisin durumunu kontrol etmek için:
sudo systemctl status actions.runner.service
Yeşil “active (running)” mesajını görmelisiniz.
3. Log Takibi ve Hata Ayıklama
systemd sayesinde, runner’ın loglarını journald ile takip edebilirim. Bu, herhangi bir sorun durumunda hata ayıklama için paha biçilmez bir kaynaktır.
journalctl -u actions.runner.service -f
Bu komut, runner servisine ait logları gerçek zamanlı olarak gösterir. Bir keresinde, runner’ın sürekli kapanıp açıldığını fark ettim. journalctl çıktılarını incelediğimde, bir bağımlılık yüzünden sürekli OOM-killed olduğunu gördüm. Bu, MemoryHigh limitini ayarlamam gerektiğini ve build adımlarımda bellek kullanımını optimize etmem gerektiğini anlamamı sağladı.
CI/CD Pipeline Entegrasyonu ve Testler
Runner’ım artık çalışır durumda olduğuna göre, GitHub Actions workflow’larımda onu kullanmaya başlayabilirim.
Workflow Dosyasını Güncelleme
.github/workflows dizinindeki YAML dosyalarınızda, runs-on anahtar kelimesini kendi runner’ınızın etiketleriyle değiştirmelisiniz.
Örneğin, main.yml dosyamda:
name: CI/CD Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build_and_test:
runs-on: [self-hosted, linux, x64] # Kendi runner etiketlerim
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build project
run: npm run build
Bu şekilde, GitHub, bu workflow’u çalıştırmak için self-hosted, linux ve x64 etiketlerine sahip uygun bir runner arayacaktır.
Basit Bir Test Workflow’u
İlk entegrasyon sonrası, her zaman basit bir test workflow’u çalıştırırım. Bu, runner’ın doğru çalıştığından ve GitHub ile iletişim kurabildiğinden emin olmamı sağlar.
name: Self-Hosted Runner Test
on:
workflow_dispatch:
jobs:
test_runner:
runs-on: [self-hosted, linux, x64]
steps:
- name: Check hostname
run: hostname
- name: Check disk usage
run: df -h
- name: List current user
run: whoami
- name: Display environment variables
run: env
Bu workflow’u manuel olarak çalıştırarak (workflow_dispatch), runner’ımın host adını, disk kullanımını ve hangi kullanıcı altında çalıştığını kontrol edebilirim. Bu, ortamın beklediğim gibi çalıştığını doğrulamak için hızlı bir yoldur.
Performans Karşılaştırması
Kendi runner’ımı kullanmaya başladıktan sonra, build sürelerinde gözle görülür bir iyileşme fark ettim. Özellikle büyük bağımlılıkların indirildiği veya Docker imajlarının build edildiği adımlarda, GitHub’ın hosted runner’larına kıyasla kendi NVMe disklerime sahip VPS’im daha hızlıydı. Örneğin, bir üretim ERP’sinde 15 dakikayı bulan bir build, kendi runner’ımda ortalama 5-7 dakikaya düştü. Bu, hem zaman hem de maliyet açısından önemli bir kazançtı.
Bakım ve Güncelleme
Bir self-hosted runner kurmak sadece başlangıçtır. Onu güncel ve sağlıklı tutmak da önemlidir.
Runner Yazılımını Güncel Tutma
GitHub, runner yazılımını düzenli olarak günceller. Bu güncellemeler, yeni özellikler, performans iyileştirmeleri ve güvenlik yamaları içerebilir. Runner’ımı güncel tutmak için genellikle şu adımları izlerim:
- GitHub Actions “Runners” sayfasında yeni bir versiyon olup olmadığını kontrol ederim.
- Eğer yeni bir versiyon varsa, runner servisini durdururum:
sudo systemctl stop actions.runner.service /actions-runnerdizinine gidip mevcut dosyaları yedeklerim veya silerim.- Yeni versiyonun
tar.gzdosyasını indirip çıkarırım. ./bin/installdependencies.shscriptini çalıştırarak bağımlılıkları güncellerim (bazen gereklidir).- Runner’ı tekrar yapılandırırım (
./config.shveyaunconfig.shsonraconfig.sh). - Servisi tekrar başlatırım:
sudo systemctl start actions.runner.service
Disk Alanı Yönetimi
CI/CD süreçleri sırasında indirilen bağımlılıklar, build çıktıları ve geçici dosyalar zamanla disk alanını doldurabilir. Özellikle Docker imajları ile çalışıyorsanız, Docker’ın kendi cache’i de büyük yer kaplayabilir. Bu nedenle, düzenli olarak disk kullanımını kontrol ederim ve gereksiz dosyaları temizlerim.
df -h # Disk kullanımını kontrol et
sudo du -sh /actions-runner # Runner dizininin boyutunu kontrol et
sudo docker system prune -a # Docker cache'ini temizle (eğer Docker kullanıyorsanız)
Bir keresinde, bir Docker build’i sırasında disk %100 doldu ve runner çöktü. Bu durum, docker system prune komutunu CI/CD pipeline’ımın sonuna eklemem gerektiğini anlamamı sağladı.
Güvenlik Güncellemeleri
VPS’imin işletim sistemi ve kurulu tüm paketlerini düzenli olarak güncel tutmak, güvenlik açısından kritiktir.
sudo apt update
sudo apt upgrade -y
sudo reboot # Kernel güncellemelerinden sonra yeniden başlatma gerekebilir
Kernel modüllerini blacklist’e alma gibi ek güvenlik önlemleri de alıyorum. Örneğin, bir CVE (CVE-2026-31431 gibi) yüzünden algif_aead modülünü blacklist’e almıştım.
Potansiyel Sorunlar ve Çözümleri
Self-hosted runner kullanırken karşılaştığım bazı yaygın sorunlar ve bunlara getirdiğim çözümler şunlardı:
Runner’ın Offline Kalması
Bazen runner, beklenmedik nedenlerle offline duruma düşebilir. Bu durum genellikle ağ kesintileri, sunucu kaynak yetersizliği (OOM-killed) veya runner yazılımındaki bir hata nedeniyle olur.
- Çözüm:
sudo systemctl status actions.runner.serviceile servisin durumunu kontrol ederim. Eğer durmuşsa,journalctl -u actions.runner.service -file logları incelerim. GenellikleRestart=alwayspolitikası sayesinde otomatik olarak yeniden başlar, ancak sürekli kapanıyorsa kök nedeni bulmak gerekir.
OOM-Killed Süreçler
Bellek yetersizliği, özellikle büyük projelerde veya bellek yoğun işlemlerde sıkça karşılaşılan bir durumdur. Ben bu hataya bir keresinde sleep 360 yazıp bir polling-wait mekanizması yerine uzun süreli bir uyku komutu kullanarak düşmüştüm. Runner uzun süre boşta kalıp bellek tüketimi artınca OOM-killed olmuştu.
- Çözüm:
systemdunit dosyasındakiMemoryHighveyaMemoryMaxlimitlerini gözden geçiririm. Workflow adımlarındaki bellek kullanımını analiz eder ve optimize ederim. Örneğin, Node.js bağımlılıklarını kurarkennpm ci --no-optionalkullanarak gereksiz bağımlılıkları atlayabilirim.
Network Sorunları
Firewall ayarları, proxy konfigürasyonları veya DNS sorunları runner’ın GitHub ile iletişim kurmasını engelleyebilir.
- Çözüm:
curl -v https://github.comkomutu ile temel ağ bağlantısını test ederim. Eğer proxy kullanıyorsam, runner’ın ortam değişkenlerineHTTP_PROXYveHTTPS_PROXYayarlarını eklediğimden emin olurum.MTU/MSSuyuşmazlıkları da bazen gizli sorunlara yol açabilir, özellikle VPN topolojileri içinde.ping -M do -s 1472 google.comgibi komutlarla bu durumları test ederim.
Docker Build Issues ve Disk Yangını
Docker kullanan projelerde, build cache’i veya katmanları nedeniyle disk alanı hızla dolabilir. docker build sırasında OOM hataları da yaşanabilir.
- Çözüm: Yukarıda bahsettiğim
docker system prune -akomutu ile düzenli temizlik yaparım. Ayrıca,Dockerfile’ımı optimize ederek katman sayısını azaltır ve gereksiz dosyaları build context’inden çıkarırım (.dockerignore).cgroupile Docker container’larına da bellek ve CPU limitleri uygulayabilirim.
Dosya Bütünlüğü İzleme
Runner dizinindeki dosyaların yetkisiz kişilerce değiştirilmediğinden emin olmak için auditd gibi araçlarla dosya bütünlüğünü izlerim. Bu, özellikle güvenlik hassasiyeti yüksek projelerde önemlidir.
sudo auditctl -w /actions-runner -p rwxa -k github-runner-integrity
Bu kural, /actions-runner dizinindeki tüm yazma, okuma, çalıştırma ve attribute değişikliklerini izler ve github-runner-integrity anahtarı ile loglar.
Sonuç
GitHub Actions runner’ımı kendi VPS’ime taşımak, benim için önemli bir öğrenme süreci oldu. Başlangıçtaki maliyet optimizasyonu hedefime ulaşmanın yanı sıra, CI/CD ortamım üzerinde daha fazla kontrol ve esneklik kazandım. Kendi sunucumun boşta duran kaynaklarını değerlendirerek hem bütçemi korudum hem de projelerimin özel ihtiyaçlarına daha hızlı yanıt verebildim.
Bu süreçte systemd ile servis yönetimi, cgroup ile kaynak limitleri ve journald ile log takibi gibi Linux sistem yönetimi becerilerimi de pekiştirdim. Unutmamalıyız ki, self-hosted bir runner’ın yönetimi, GitHub’ın hosted runner’larına göre daha fazla sorumluluk gerektirir; ancak doğru yapılandırma ve düzenli bakım ile bu sorumlulukların üstesinden gelmek mümkündür.
Bir sonraki adım olarak, birden fazla runner’ı otomatik olarak ölçekleyebilen (auto-scaling) bir yapı kurmayı planlıyorum. Bu sayede, iş yüküne göre dinamik olarak yeni runner’lar başlatıp kapatabileceğim ve kaynak kullanımımı daha da optimize edebileceğim.