Self-hosted Runner: Kendi Sunucunda GitHub Actions Kurulumu

GitHub Actions’ı ilk keşfettiğimde aklıma gelen ilk soru şu olmuştu: “Peki ya kendi sunucumu kullanabilir miyim?” GitHub’ın sağladığı hosted runner’lar çoğu senaryo için yeterli, ancak özel ağ gereksinimleri, performans ihtiyaçları veya maliyet optimizasyonu söz konusu olduğunda self-hosted runner kavramı hayat kurtarıcı hale geliyor. Bu yazıda sıfırdan bir self-hosted runner kurulumunu, güvenlik pratiklerini ve gerçek dünya senaryolarını ele alacağız.

Self-Hosted Runner Neden Kullanılır?

GitHub’ın ücretsiz planında aylık 2000 dakika CI/CD süresi veriliyor. Aktif bir ekiple çalışıyorsanız bu süre hızla tükeniyor. Ama asıl mesele sadece maliyet değil.

Şirket içi bir sistemle entegrasyon kuruyorsunuz diyelim. Veritabanınız dışarıya açık değil, VPN arkasında. GitHub’ın kendi runner’larından bu sisteme erişmenin yolu yok. Ya da GPU gerektiren ML model eğitimi pipeline’ınız var ve her build için özel donanım istiyorsunuz. İşte tam bu noktalarda self-hosted runner devreye giriyor.

Kısaca özetlersek neden tercih edildiğini:

  • Özel ağ erişimi: Şirket içi servisler, veritabanları, artifact repository’ler
  • Maliyet kontrolü: Kendi sunucunuzu kullanarak GitHub dakika limitini aşmama
  • Performans: Özelleştirilmiş donanım, daha fazla CPU/RAM/disk
  • Önceden kurulu araçlar: Özel yazılımlar, lisanslı araçlar, şirket sertifikaları
  • Compliance gereksinimleri: Kodun dışarıya çıkmaması gereken durumlar
  • Uzun süreli build’ler: GitHub hosted runner’larda 6 saatlik limit var, self-hosted’da bu kısıt yok

Kurulum Öncesi Hazırlık

Runner kuracağımız sunucunun bazı temel gereksinimleri karşılaması gerekiyor. Ben bu örnek için Ubuntu 22.04 LTS üzerinde çalışacağım.

# Sistem güncellemelerini yap
sudo apt update && sudo apt upgrade -y

# Temel bağımlılıkları kur
sudo apt install -y curl wget git unzip build-essential

# Runner için ayrı bir kullanıcı oluştur (root çalıştırma!)
sudo useradd -m -s /bin/bash github-runner
sudo usermod -aG sudo github-runner

# Runner dizinini oluştur
sudo mkdir -p /opt/github-runner
sudo chown github-runner:github-runner /opt/github-runner

Önemli not: Runner’ı asla root kullanıcısıyla çalıştırma. Güvenlik açısından ayrı bir kullanıcı oluşturmak kritik. Birisi repo’ya kötü amaçlı bir workflow dosyası push ederse, runner’ın sisteme verebileceği zarar minimize edilmiş olur.

GitHub’dan Token Alma ve Runner Kurulumu

GitHub arayüzünden token almak için şu yolu izle:

  • Repo sayfası > Settings > Actions > Runners > New self-hosted runner

Burada hem token’ı hem de kurulum komutlarını göreceksin. Ben kurulum adımlarını biraz daha detaylı ele alacağım:

# github-runner kullanıcısına geç
sudo su - github-runner
cd /opt/github-runner

# Runner paketini indir (versiyonu GitHub'dan kontrol et, güncel sürümü kullan)
curl -o actions-runner-linux-x64-2.317.0.tar.gz -L 
  https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-linux-x64-2.317.0.tar.gz

# SHA256 doğrulaması yap
echo "9e883d210df8c6028aff475475a457d380353f9d01877d51cc01a17b2a91161d 
  actions-runner-linux-x64-2.317.0.tar.gz" | shasum -a 256 -c

# Paketi aç
tar xzf ./actions-runner-linux-x64-2.317.0.tar.gz

# Runner'ı yapılandır
./config.sh --url https://github.com/KULLANICI/REPO 
  --token BURAYA_TOKEN_YAZ 
  --name "production-runner-01" 
  --labels "self-hosted,linux,production,ubuntu-22.04" 
  --work /opt/github-runner/_work

Yapılandırma sırasında birkaç soru soruyor. Runner grubunu varsayılan bırakabilirsin, isim ve etiketler için kendi değerlerini gir. Etiketler workflow’larda runner seçiminde kullanılacak, bu yüzden anlamlı isimler ver.

Systemd Servisi Olarak Çalıştırma

Runner’ı manuel başlatmak yerine systemd servisi olarak ayarlamak çok daha sağlıklı. Sunucu yeniden başladığında otomatik devreye girer.

# Servis kurulumunu runner dizininden yap (root ile)
sudo /opt/github-runner/svc.sh install github-runner

# Servisi başlat
sudo systemctl start actions.runner.KULLANICI-REPO.production-runner-01

# Otomatik başlatmayı aktif et
sudo systemctl enable actions.runner.KULLANICI-REPO.production-runner-01

# Durumu kontrol et
sudo systemctl status actions.runner.KULLANICI-REPO.production-runner-01

Servis adı biraz uzun görünüyor, bunu bir değişkene atayabilirsin ya da alias tanımlayabilirsin. Logları görmek için:

# Servis loglarını takip et
sudo journalctl -u actions.runner.KULLANICI-REPO.production-runner-01 -f

# Son 100 satırı göster
sudo journalctl -u actions.runner.KULLANICI-REPO.production-runner-01 -n 100

Runner online olduktan sonra GitHub arayüzünde yeşil nokta ile “Idle” durumunda görünmesi gerekiyor. Bu noktaya geldiysen kurulum başarılı demektir.

İlk Workflow Dosyası

Runner hazır, şimdi bunu kullanan bir workflow yazalım. Basit bir Node.js uygulaması deploy eden örnek:

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build-and-deploy:
    runs-on: [self-hosted, linux, production]
    
    steps:
      - name: Kodu checkout et
        uses: actions/checkout@v4

      - name: Node.js kur
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Bağımlılıkları yükle
        run: npm ci

      - name: Test çalıştır
        run: npm test

      - name: Build al
        run: npm run build
        env:
          NODE_ENV: production

      - name: Uygulamayı deploy et
        run: |
          pm2 stop myapp || true
          pm2 delete myapp || true
          pm2 start dist/index.js --name myapp
          pm2 save

runs-on satırına dikkat et. Hem self-hosted hem de tanımladığımız özel etiketleri veriyoruz. Bu sayede birden fazla runner varsa doğru olanı seçiyor.

Gerçek Dünya Senaryosu: Özel Ağda Veritabanı Migrasyonu

Diyelim ki üretim veritabanın dışarıya açık değil ve her deploy’da migration çalıştırman gerekiyor. Runner sunucun bu veritabanına erişebiliyor ama GitHub’ın sunucuları erişemiyor. Bu senaryo self-hosted runner’ın en güçlü olduğu alan.

# .github/workflows/deploy-with-migration.yml
name: Deploy with Database Migration

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Unit testleri çalıştır
        run: |
          npm ci
          npm test

  deploy:
    needs: test
    runs-on: [self-hosted, linux, production]
    environment: production
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Bağımlılıkları yükle
        run: npm ci --production

      - name: Veritabanı migration çalıştır
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

      - name: Uygulamayı güncelle
        run: |
          pm2 reload myapp --update-env
        env:
          NODE_ENV: production
          API_SECRET: ${{ secrets.API_SECRET }}

      - name: Health check
        run: |
          sleep 10
          curl -f http://localhost:3000/health || exit 1

Burada test job’ı GitHub’ın kendi runner’larında, deploy job’ı ise senin sunucunda çalışıyor. Bu yaklaşım hem maliyet optimizasyonu sağlıyor hem de her işin en uygun ortamda çalışmasını garantileyiyor.

Runner Grupları ve Organizasyon Düzeyinde Kullanım

Birden fazla repo’yu yöneten bir organizasyondasın. Her repo için ayrı ayrı runner kurmak yerine organizasyon düzeyinde runner grupları tanımlayabilirsin.

Organizasyon ayarlarından (Settings > Actions > Runner groups) gruplar oluşturabilir, hangi repo’ların hangi runner grubuna erişebileceğini belirleyebilirsin. Bu yapı özellikle şu durumlar için değerli:

  • Production runner grubu: Sadece main branch’ten tetiklenen workflow’lara açık, kısıtlı erişim
  • Staging runner grubu: Geliştiricilerin serbestçe kullanabildiği, daha düşük kaynaklı makineler
  • GPU runner grubu: Sadece ML ekibinin repo’larına açık, özel donanımlı sunucular

Organizasyon düzeyinde runner kurarken config komutunu biraz farklı kullanıyorsun:

# Organizasyon düzeyinde runner yapılandırması
./config.sh --url https://github.com/ORGANIZASYON_ADI 
  --token ORGANIZASYON_TOKEN 
  --name "org-runner-prod-01" 
  --labels "self-hosted,linux,production" 
  --runnergroup "production-runners"

Güvenlik: En Kritik Konu

Self-hosted runner kurduğunda güvenlik konusunu ciddiye almak zorundasın. GitHub hosted runner’lardan farklı olarak bu makine senin sorumluluğunda ve workflow her tetiklendiğinde bu sunucuda kod çalışıyor.

Fork repository riski: Public repo’larda self-hosted runner kullanıyorsan çok dikkatli ol. Herhangi biri fork açıp pull request gönderebilir ve eğer workflow fork’lardan tetikleniyorsa, gönderilen kod senin sunucunda çalışır.

# Fork PR'larından gelen workflow'ları kısıtla
on:
  pull_request_target:
    branches: [main]

jobs:
  deploy:
    # Sadece organizasyon üyelerinin PR'larında çalış
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: [self-hosted, linux]

Secrets yönetimi: Runner sunucusuna koymaman gereken şeyler:

  • Hardcoded şifreler veya API key’ler
  • Runner process’inin okuyabileceği kritik dosyalar
  • Üretim veritabanına direkt bağlantı bilgileri (bunu GitHub Secrets üzerinden ver)

Firewall kuralları: Runner sunucunun dışarıya erişimini sınırla. Sadece gerekli servislere izin ver:

# UFW ile temel kural seti
sudo ufw default deny outgoing
sudo ufw default deny incoming

# SSH erişimine izin ver
sudo ufw allow 22/tcp

# GitHub API ve runner bağlantısı için
sudo ufw allow out 443/tcp
sudo ufw allow out 80/tcp

# İç ağ servislerine erişim (örnek: veritabanı)
sudo ufw allow out to 192.168.1.100 port 5432

sudo ufw enable

Runner’ı düzenli güncelle: Runner yazılımı kendini otomatik güncelleyebilir ama bunu takip etmek iyi pratik:

# Runner versiyonunu kontrol et
/opt/github-runner/run.sh --version

# Güncelleme için servisi durdur, yeni versiyonu indir, yeniden yapılandır
sudo systemctl stop actions.runner.*
# Yeni versiyonu indir ve kurulum adımlarını tekrarla

Paralel Runner Kurulumu ve Ölçeklendirme

Tek bir sunucuda birden fazla runner çalıştırabilirsin. Bu özellikle paralel job’lar için faydalı. Her runner farklı bir dizinde çalışmalı:

# İkinci runner için dizin oluştur
sudo mkdir -p /opt/github-runner-02
sudo chown github-runner:github-runner /opt/github-runner-02

# github-runner kullanıcısına geç
sudo su - github-runner
cd /opt/github-runner-02

# Paketi tekrar indir veya kopyala
cp -r /opt/github-runner/* /opt/github-runner-02/
cd /opt/github-runner-02

# Farklı isimle yapılandır
./config.sh --url https://github.com/KULLANICI/REPO 
  --token YENI_TOKEN 
  --name "production-runner-02" 
  --labels "self-hosted,linux,production" 
  --work /opt/github-runner-02/_work

# Servisi kur ve başlat
sudo /opt/github-runner-02/svc.sh install github-runner
sudo systemctl start actions.runner.KULLANICI-REPO.production-runner-02

Yüksek yük senaryolarında bunu otomatize etmek için Terraform veya Ansible kullanabilirsin. Özellikle AWS, GCP ya da Azure üzerinde spot/preemptible instance’larla dinamik runner ölçeklendirmesi yapmak mümkün, ama bu konu ayrı bir yazıyı hak ediyor.

Runner Bakımı ve İzleme

Runner’ın sağlıklı çalışıp çalışmadığını izlemek için birkaç pratik yöntem:

# Runner log dosyasının konumu
ls /opt/github-runner/_diag/

# En son log dosyasını izle
tail -f /opt/github-runner/_diag/Runner_*.log

# Disk kullanımını kontrol et (_work dizini şişebilir)
du -sh /opt/github-runner/_work/

# Work dizinini periyodik temizlik için cron job
# /etc/cron.d/cleanup-runner
0 3 * * * github-runner find /opt/github-runner/_work -maxdepth 2 
  -name "*.log" -mtime +7 -delete

Build artifact’larının birikmesi ciddi disk problemi yaratabilir. Özellikle Docker image build ediyorsan:

# Docker temizliği için cron job ekle
# Haftada bir eski image ve container'ları temizle
0 2 * * 0 github-runner docker system prune -af --filter "until=168h"

Runner’ın GitHub ile bağlantısını Prometheus + Grafana ile izlemek isteyenler için github-runner-exporter gibi üçüncü parti araçlara bakabilir. Ama basit bir kontrol için şu script işe yarıyor:

#!/bin/bash
# /usr/local/bin/check-runner.sh

RUNNER_SERVICE="actions.runner.KULLANICI-REPO.production-runner-01"
STATUS=$(systemctl is-active $RUNNER_SERVICE)

if [ "$STATUS" != "active" ]; then
    echo "Runner DOWN! Yeniden başlatılıyor..."
    systemctl restart $RUNNER_SERVICE
    # Burada Slack veya e-posta bildirimi ekleyebilirsin
    curl -X POST -H 'Content-type: application/json' 
      --data '{"text":"Runner yeniden başlatıldı!"}' 
      $SLACK_WEBHOOK_URL
fi

Bu script’i her 5 dakikada bir cron ile çalıştır:

*/5 * * * * root /usr/local/bin/check-runner.sh

Docker ile İzole Çalışma Ortamı

Her job’ın temiz bir ortamda çalışmasını istiyorsan container action’larını kullanabilirsin. Runner Docker kurulu olmalı:

# Docker kurulumu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker github-runner

Workflow’da Docker kullanımı:

jobs:
  build:
    runs-on: [self-hosted, linux]
    container:
      image: node:20-alpine
      options: --user root
    
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: npm ci && npm run build

Container kullanmak, runner’ın dosya sistemi kirlenmeden her build’in temiz ortamda çalışmasını sağlar. Bu özellikle birden fazla proje için aynı runner’ı kullandığında çok değerli.

Sık Karşılaşılan Sorunlar

Runner “Offline” görünüyor: Servis çalışıyor mu kontrol et, GitHub API’ye erişim var mı diye firewall kurallarını gözden geçir.

Permission denied hataları: Runner kullanıcısının gerekli dizinlere erişimi var mı? Deployment dizininin sahipliğini kontrol et.

Job stuck/askıda kalıyor: Workflow içinde interaktif komut bekleyen bir şey varsa askıda kalabilir. Tüm komutların non-interactive çalıştığından emin ol.

Disk doldu: _work dizini temizliği yaptın mı? Docker kullanıyorsan docker system prune gerekebilir.

# Hızlı tanı için
sudo su - github-runner
cd /opt/github-runner
./run.sh --check

Sonuç

Self-hosted runner kurmak başlangıçta biraz zahmetli görünebilir ama sağladığı esneklik buna değiyor. Özel ağ gereksinimleri, maliyet optimizasyonu veya özelleştirilmiş donanım ihtiyaçları olan ekipler için bu yaklaşım neredeyse zorunlu hale geliyor.

En kritik nokta güvenlik. Public repo kullanıyorsan özellikle dikkatli ol, runner’ı ayrı bir kullanıcıyla çalıştır, firewall kurallarını sıkı tut ve Secrets yönetimini GitHub’ın kendi mekanizmasına bırak. Bakım tarafında ise disk temizliği ve runner güncellemelerini göz ardı etme, bu iki konu zamanla ciddi baş ağrısı yaratıyor.

Sistemin oturtulduktan sonra “bu neden çalışmıyor?” diye saatler harcamak yerine CI/CD pipeline’larını iyileştirmeye vakit ayırabiliyorsunuz. Ve açıkçası, kendi sunucunda dönen bir pipeline’ın yeşil tik vermesi hosted runner’dan biraz daha tatmin edici geliyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir