Geçen ayın sonlarına doğru, kendi GitHub Actions runner’ımda bir derleme işi başlattım. Normalde 10-15 dakika süren bir işti, ama bu sefer bir türlü bitmedi. İş takılı kalmış gibiydi, runner’dan hiç yanıt alamıyordum. SSH ile sunucuya bağlanmaya çalıştığımda ise bağlantı kabul etmiyordu. Daha önce VPS’imde yaşadığım sshd’nin accept edemediği OOM senaryolarına benziyordu, ama bu sefer RAM kullanımım normaldi.
Biraz kurcalayınca anladım ki, runner’ımın kalbi atmayı bırakmıştı. GitHub Actions paneline baktığımda runner’ın “Offline” olduğunu gördüm. İşin ilginç yanı, sunucunun kendisi ayaktaydı, diğer Docker container’larım sorunsuz çalışıyordu. Tek sorun, CI runner’ımdı.
Sorunun Köküne İnmek: Bir Temizlik Scripti Cinayeti
Runner’ın neden öldüğünü anlamak için sunucuya konsol üzerinden bağlandım. İlk iş dmesg çıktılarına bakmaktı. Orada şaşırtıcı bir şey yoktu, çekirdek seviyesinde bir hata veya OOM killer tetiklenmesi görünmüyordu. systemctl status github-runner komutuyla servisin durumunu kontrol ettiğimde ise daha da ilginç bir tabloyla karşılaştım: Servis active (exited) durumdaydı ve loglarda hiçbir hata mesajı yoktu. Sanki biri servisi düzgünce kapatmış gibiydi.
İşte tam bu noktada, aklıma geçen hafta eklediğim o “masum” temizlik scripti geldi. Kendi VPS’imde 13’ten fazla Docker container yönetiyorum ve disk alanı zaman zaman kritikleşebiliyor. Özellikle Docker’ın build cache’i ve kullanılmayan imajlar, 33 GB’lık build cache ve 23 GB’lık unused image’lar ile diskimi %100’e kadar doldurabiliyor. Bu yüzden, _work dizinindeki eski build çıktılarını ve gereksiz dosyaları temizlemek için bir script yazmıştım.
Katil Script ve Kurban Runner
Yazdığım script basitçe _work dizinindeki belirli yaşın üzerindeki dosyaları siliyordu. Ancak bir küçük detayı gözden kaçırmıştım: Runner’ın kendisi de _work dizini altında çalışıyordu ve _temp gibi geçici dizinler, hatta bazı durumlarda runner’ın kendi binary’leri veya yapılandırma dosyaları bile bu kapsamda olabiliyordu. Daha önce GitHub Actions runner’da _work/_temp içindeki dizinleri silmenin acısını yaşamıştım, ama bu sefer daha da ileri gitmiştim.
Scriptin içindeki find komutunun maxdepth veya prune gibi parametrelerini yeterince dikkatli kullanmamıştım. Hedefim sadece build artifact’ları iken, script, runner’ın kendisi için de hayati olan bazı dosyaları silmişti. Sonuç: Runner servisi, çalışmaya devam etmek için gerekli dosyalara erişemeyince sessiz sedasız kapanmıştı. Bu da tıpkı Astro build’imin 2.5 GB RAM yiyip OOM olması gibi, bir kaynak yönetimi felaketiydi ama bu sefer disk ve dosya sistemi kaynaklıydı.
# Hatalı temizlik scriptinden bir kesit (basitlestirilmis hali)
# Bu komut, _work dizini altındaki 7 günden eski tüm dosyaları siliyordu.
# Ancak runner'ın kendi çalışma dosyaları da bu kapsamdaydı.
find /home/runner/_work/ -type f -mtime +7 -delete
find /home/runner/_work/ -type d -empty -delete
Bu komut, /home/runner/_work/ altındaki 7 günden eski her dosyayı sildiği için runner’ın kendi koşum dosyalarını da kapsadı; sonuç olarak servis sessizce çöktü. Dersim açık oldu: cleanup script’lerinde mutlaka whitelisted path veya find -prune ile koruma katmanı şart, kör find -delete üretimde asla.