Geçen Salı sabaha karşı 04:12’de, uptime monitöründen gelen “Service Down” bildirimiyle uyandım. Kendi VPS’imde, 1 GB RAM kapasiteli o minik makinede tam 13 tane Docker container’ı yan yana, huzur içinde (!) çalışıyordu. En azından ben öyle sanıyordum.
SSH ile bağlanmaya çalıştığımda terminalin öylece asılı kalması, aslında içeride büyük bir yangın olduğunun ilk işaretiydi. Makineye ancak panel üzerinden VNC ile girebildiğimde karşımda gördüğüm manzara tanıdıktı: kcompactd0 süreci %92 CPU tüketiyor, sistem nefes alamıyor ve kernel arkada bulduğu her şeyi öldürüyordu.
1 GB RAM ile 13 Container Yönetmenin Bedeli
Kendi projelerimi (hesapciyiz.com, spamkalkani.com ve bu blog dahil) aynı VPS üzerinde barındırıyorum. Astro, Node.js, SQLite ve Nginx gibi araçlarla kurduğum bu ekosistem normalde oldukça verimli çalışıyor. Ancak o gece, GitHub Actions üzerinden tetiklediğim bir Astro build süreci her şeyi altüst etti.
Normalde 200-300 MB RAM tüketen bir build süreci, o an içerideki SQLite sorgularıyla birleşince aniden 2.5 GB RAM talep etmeye başladı. 1 GB fiziksel RAM’i olan bir makinede bu, doğrudan felaket demek. Kernel, belleği defragmantasyon (parçalanmışlığı giderme) yapmaya çalışan kcompactd0 sürecini öne sürdü ama fiziksel limitler buna izin vermedi.
Semptomlar: SSH Neden Cevap Vermedi?
Makine kilitlendiğinde sadece web sitelerim değil, yönetim arayüzlerim de gitti. journalctl kayıtlarına baktığımda sshd: accept: out of memory hatasını gördüm. Kernel, yeni bir bağlantı kabul etmek için gereken o minicik bellek alanını bile ayırmayı reddediyordu.
Bu durum, 28 Nisan’da yaşadığım disk %100 doluluk probleminden daha beterdi. Disk dolduğunda en azından çalışan süreçler bir şekilde devam ediyordu, bellek bittiğinde ise kernel “Survivor” moduna geçiyor ve rastgele (veya puanına göre) süreçleri öldürmeye başlıyor.
Katmanlı Savunma: OOM Killer’ı Evcilleştirmek
Bu krizden sonra sistem mimarisini değiştirmeye karar verdim. “Olur o kadar” diyerek geçiştirebileceğim bir durum değildi çünkü bu VPS’te 4 farklı yan ürünüm çalışıyor. İlk iş olarak kernel’ın bu durumlarda nasıl davranacağını belirleyen parametrelere el attım.
Aşağıdaki tabloda, o gece uyguladığım ve sistemin stabilitesini artıran kernel parametre değişimlerini görebilirsiniz:
| Parametre | Eski Değer | Yeni Değer | Amaç |
|---|---|---|---|
vm.swappiness | 60 | 10 | Diske yazma sıklığını azalt, RAM’i sonuna kadar kullan. |
vm.overcommit_memory | 0 | 1 | Bellek taleplerine daha esnek (ama riskli) yanıt ver. |
vm.vfs_cache_pressure | 100 | 200 | Cache’i daha hızlı boşaltarak RAM’de yer aç. |
Docker Kaynak Limitlerini Belirlemek
Docker container’larını “saldım çayıra” mantığıyla çalıştırmak, 1 GB RAM’li bir ortamda yapılabilecek en büyük hata. Eğer bir container (örneğin bir Next.js app) memory leak yaparsa, tüm VPS’i yanına alıp götürüyor. Bunu engellemek için docker-compose.yml dosyalarımda katı limitler kullanmaya başladım.
services:
nextjs-app:
image: my-nextjs-app:latest
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
restart_policy:
condition: on-failure
Bu kısıtlama sayesinde, herhangi bir uygulama 256 MB üzerine çıkmaya çalıştığında sadece o container etkileniyor. Tüm sistemin kilitlenmesi yerine sadece sorunlu olan uygulama OOMKilled statüsüne düşüyor ve restart_policy sayesinde temiz bir bellek alanı ile yeniden başlıyor.
Swap Yönetimi ve Disk Yangını
Swap alanı, VPS operasyonlarında can kurtaran simididir ama yanlış yapılandırıldığında celladınız olur. Eskiden 1 GB olan swap alanımı 4 GB’a çıkardım. Ancak bunu yaparken diski yormamak için vm.swappiness değerini 10’a çektim.
# Sağlam bir swap oluşturma reçetesi
sudo dd if=/dev/zero of=/swapfile bs=1M count=4096
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Pipeline Reliability: Build Süreçlerini Korumak
Astro build sırasında yaşadığım OOM hatası bana şunu öğretti: Build süreci ile production trafiği aynı anda aynı kaynakları kullanamaz. Özellikle GitHub Actions üzerinden self-hosted runner kullanıyorsanız, build başladığı an CPU ve RAM tavan yapar.
Bunu çözmek için bir “preflight resource guard” yazdım. Pipeline çalışmadan önce makinedeki anlık RAM durumunu kontrol ediyor, eğer %20’den az boş yer varsa build’i başlatmıyor ve bana bir Telegram uyarısı atıyor. Ayrıca _work/_temp dizinindeki eski build kalıntılarını silmek için bir temizlik adımı ekledim. Geçmişte bu dizinlerin 33 GB build cache ile dolup diski patlattığını acı bir tecrübeyle öğrenmiştim.
Kernel Güvenliği ve Gereksiz Yükler
Sistemi rahatlatmak sadece limit koymakla değil, gereksiz yükleri atmakla da ilgili. Örneğin, kullanmadığım kernel modüllerini blacklist’e alarak hem güvenlik açığını kapattım hem de kernel’ın daha “lean” çalışmasını sağladım. CVE-2026-31431 gibi kernel modülü tabanlı zafiyetlerden korunmak için algif_aead gibi modülleri devre dışı bıraktım.
Sonuç ve Gelecek Planları
1 GB RAM ile bu kadar işi döndürmek bir nevi sanat ama riskleri de büyük. Şu an sistem stabil; 13 container tıkır tıkır çalışıyor, swap kullanımı minimumda ve kcompactd0 artık uykusunda. Ancak bu, bir sonraki krizin gelmeyeceği anlamına gelmiyor.
Önümüzdeki günlerde, bu VPS’teki SQLite veritabanlarını merkezi bir Postgres container’ına taşımayı planlıyorum. Her container’ın içinde ayrı bir SQLite operasyonu dönmesi, disk IO ve bellek yönetimi açısından verimsizleşmeye başladı. Bir sonraki yazıda, bu göç sürecinde yaşadığım performans kazanımlarını ve muhtemel “yine patladık” anlarını anlatacağım.