Seri · The Quiet Machine · Bölüm 9
Bir Windows Oyun Bilgisayarında GitHub Actions Self-Hosted Runner
Bu proje için her CI koşusu (backend, Android, web) sıfırdan yeni bir GitHub VM’i başlatıyor ve NuGet ile Gradle’ın internetinin yarısını her seferinde yeniden indiriyordu. Box ise bu sırada öylece duruyordu: Ollama bellekte, embedding sorgularını yanıtlıyor, başka hiçbir şey yapmıyordu. Çözüm, aynı box üzerinde çalışan bir self-hosted GitHub Actions runner oldu.
Box boşta dururken, başka bir yerdeki kiralık bir makine her koşuda, kendinden önceki koşuyu hiç hatırlamamanın bedelini ödüyordu.
Böylece box’a ikinci bir iş düştü.
CI’ı neden bir oyun bilgisayarına devrettim
Gerekçe, en iyi anlamıyla sıkıcı. Bu box’ta WSL2’ye on iki thread ve 26GB RAM veren bir Ryzen 5 3600 var, D: sürücüsünde de 846GB boş alan — hepsi de günün büyük bölümünde hiçbir şey yapmıyor.
GitHub-hosted bir runner’ın cache’i ve ısınmış bir dependency deposu yok; job’lar arasında, açıkça upload edip tekrar download etmediğin hiçbir şey hayatta kalmıyor. Kendi box’ımdaki bir runner ise NuGet, Gradle ve npm cache’lerini build’ler arasında diskte tutuyor. Backlog’un kendi bütçesi tam da bunun için 50–100GB ayırıyor.
Soğuk bulut CI’ı ile sıcak yerel cache arasında kıyasıya bir yarış yok.
Bunu dışarı açmanın da fazladan bir maliyeti yok. Runner yalnızca outbound: GitHub’ı iş için long-poll ediyor, hiçbir zaman inbound bir port açmıyor — projenin daha önceki bölümlerindeki SSH tüneli ve Telegram bot’uyla aynı şekilde.
iOS bu makineye hiç uğramıyor; Linux için Xcode olmadığından backend, Android ve web burada build ediliyor, iOS ise her zaman olduğu gibi Mac’te build edilmeye devam ediyor.
GPU’nun neden hiç haberi olmadı
Bu runner yalnızca CPU kullanıyor, build sırasını da bu belirledi. Her adım (restore, compile, test) Ryzen çekirdeklerinde çalışıyor; GPU’ya hiç dokunmuyor.
Bunun önemi şurada: GPU’nun zaten tam zamanlı bir kiracısı var. Ollama, kartın 12GB’ının kabaca 11.6GB’ını sabitleyerek RTX 3060 üzerinde bellekte duruyor, memory framework’e ve günlük yerel inference’a hizmet ediyor.
Bol GPU isteyen bir servisin ortaya çıkması, baştan sona bir pazarlık demekti: bir modeli unload et, ötekini yükle, takas sırasında hiçbir şeyin timeout olmamasını um. Runner’ı register etmeden önce ve sonra VRAM sayısına baktım:
VRAM: 11919MiB / 12288MiB — before
VRAM: 11919MiB / 12288MiB — after registering the runner
Megabaytına kadar değişmemiş. Backlog’da hâlâ bekleyen görsel üretim servisinden önce bunu ilk sıraya koymamın bütün gerekçesi bu: CPU işi ile GPU işi aynı on bir küsur gigabaytı paylaşmıyor, dolayısıyla ortada zamanlanacak bir şey yok.
Gerçek kurulum
Mekanik kısım sıradandı, zaten mesele de bu: Linux runner’ı indir, bağımlılıklarını kur, org’a register et. Adı anılmaya değer tek pürüz: runner işin altında bir .NET binary’si, .NET da Linux’ta globalization desteği için libicu istiyor. Bunu atlarsan ilk açılışta sessizce başarısız oluyor.
Register etmek bir kere insan istiyor: login’in arkasından taze üretilen, yaklaşık bir saat geçerli, tek kullanımlık bir org seviyesi token. Part 4’teki Tailscale pre-auth key ile aynı şekil: tarayıcı isteyen araç bir tane almak zorunda; hiçbir script tıklamanın yerini tutmuyor.
Sonra, bu serinin Part 5’te Tailscale’de zaten zor yoldan öğrendiği o ders: interaktif çalışan bir process reboot’tan sağ çıkmıyor. Onun yerine arka plan servisi olarak kuruldu; böylece reboot sonrası login gerekmeden kendi kendine geri geliyor. Ekran görüntüsü yerine doğrudan API’den doğruladım: online, boşta, bir job bekliyor.
$0’a çalışıyor, çünkü donanım zaten benim.
Mac runner’ı: register edildi, bilerek kapalı bırakıldı
CI’ın bir de iOS ayağı gerekiyor, iOS da yalnızca Mac’te build ediliyor. Aynı runner binary’si — osx-arm64, sürüm 2.336.0 — Mac’e kuruldu ve aynı org’a mac-ios etiketiyle register edildi.
Sonra onu bilerek offline bıraktım, bozuk değil hazır beklesin diye. Bu, Part 6’daki aynı ölçülülük: disablesleep‘i yapabilirim diye genele çevirmek yerine yalnızca AC ile sınırlı tutmuştum.
Sebep disk matematiği. Bir iOS build’inin DerivedData‘sı yaklaşık 20GB tutuyor; Mac’imde ise kabaca 44GB boş alan var. Bu, disk şikayet etmeye başlamadan önce iki ya da üç gözetimsiz build demek, kendi host’unun diskini sessizce dolduran bir runner da hiç runner olmamasından beter.
Çözüm hazır bekliyor, henüz devreye alınmadı: runner’ın .env‘i üzerinden bağlanan bir ACTIONS_RUNNER_HOOK_JOB_COMPLETED hook’u, bir job bittiği anda DerivedData‘yı silen bir cleanup script’ine işaret ediyor. iOS CI istendiğinde tek gereken ./run.sh ya da servis kurulumu kadar bir mesafe.
(O zamandan beri devreye girdi: DerivedData cleanup hook’u kendini kanıtladı, mac-ios artık box’ın runner’ının yanında status=online gösteriyor — o tarihte iki runner.)
Bunu güvenli kılan ayarlar
Self-hosted bir runner, bir workflow’un söylediği her şeyi kendi donanımımda, gerçek ağ erişimiyle çalıştırıyor. Buradaki asıl güvenlik sınırı, WSL2’de yaptığım şeylerden çok, org’un Actions ayarları:
- İzinli action’lar: kendi org’umun action’ları artı güvenilir üçüncü taraf action’lardan oluşan seçilmiş bir allow-list — “hepsine izin ver” değil, çünkü bu herhangi bir public action’ın box’ımda incelenmeden çalışmasına izin verirdi.
- Varsayılan
GITHUB_TOKENizinleri: salt okunur. Yazma erişimi her job için ayrı ayrı, açıkça talep edilmek zorunda. Actions ayrıca kendi pull request’ini onaylayamıyor. - Fork PR workflow’ları: her dış katkıcı için onay şart, fork workflow’ları da private bir repoda hiçbir zaman otomatik çalışmıyor. “Yabancının PR’ı” ile “yabancının kodunun makinemde çalışması” arasındaki tek ayar bu.
- Live debugging: açık. Genel tavsiye bir takım için bunu kapalı tutmak yönünde; ama bir workflow’u tetikleyebilecek tek kişinin ben olduğum private bir org’da, başarısız bir job’a SSH ile girebilmek, o hatayı kaybetmekten daha iyi.
- Artifact ve log saklama süresi: 90 günlük varsayılandan 14–30 güne düşürüldü. Solo bir projenin hiçbir zaman ihtiyaç duymayacağı kadar birikmesine gerek yok.
- GitHub-hosted runner’lar: etkin bırakıldı, org’un harcama limiti $0 olarak ayarlandı. Bunları kapatmak ileride isteyebileceğim bir kapıyı kapatır; bütçeyi sıfırlamaksa kapıyı açık tutup aynı zamanda buradan hiçbir zaman faturalandırılmayı mekanik olarak imkânsız kılıyor.
Box’ın yeni işlerinin geri kalanı
Runner’ın yanı sıra iki servis daha devreye girdi: box’ın kendi servislerini izleyen, biri düştüğü an Telegram’a alarm düşüren Uptime Kuma; bir de Mac ile box arasında, ikisi de bir cloud drive’a dokunmadan, dosya taşıyan Syncthing.
Sırada iki servis daha var, henüz kurulmadı. faster-whisper, runner gibi yalnızca CPU kullanıyor: Ryzen üzerinde int8, sıfır GPU çakışması.
Yerel görsel üretimi için ComfyUI ise tam tersi bir durum: 4–8GB VRAM istiyor, Ollama’nın coder modeli bellekteyken ise yarım gigabaytın epeyce altında boş alan kalıyor. İki modlu bir anahtar gerekiyor: coder modelini unload et, ComfyUI’a 12GB’ın tamamını bırak, üret, sonra coder’ı tekrar yükle.
Runner “önce bunu yap” sırasını tam da o VRAM sorusunu hiç sormak zorunda kalmadığı için kazandı. Ondan sonraki her şey soruyor.
Windows Update için ayda bir kez açılan bir makine, artık zaten yakmakta olduğu elektriğin maliyetine yazılım üretiyor.