Kariyerimde sayısız problemle karşılaştım, ama çoğu zaman çözümlerin karmaşıklığı değil, basit bir gözden kaçırma beni daha zor duruma soktu. Bir keresinde, kendi geliştirdiğim bir yan ürünün backend’ini Docker üzerinde çalıştırırken, küçük bir detay yüzünden sunucuyu komple kilitledim. O an, “bu kadar da olmaz” dediğim, ama sonradan çok şey öğreten bir deneyimdi.
Bu hatayı yaptığımda, kendi VPS’im üzerinde çalışan ve benim için kritik öneme sahip olan bir servisi ayağa kaldırmaya çalışıyordum. Docker’ın esnekliği ve kolaylığı beni cezbetmişti, ancak bu kolaylığın altında yatan bazı dinamikleri göz ardı ettiğimi fark etmem pahalıya patladı.
Her Şey Nasıl Başladı: Docker’a Olan Güvenim
Yıllarca bare-metal sunucularda çalıştım, her servisin kendi özel systemd unit’i, her şeyin elle optimize edildiği bir düzen içindeydim. Sonra Docker girdi hayatıma, her şeyi konteynerize etmenin cazibesiyle. Geliştirme ortamında müthiş bir kolaylık sağlıyordu, tek bir docker-compose up -d komutuyla tüm bağımlılıklar ayağa kalkıyordu. Bu rahatlık, benim de “production’da da ne kadar kötü olabilir ki?” diye düşünmeme neden oldu.
Kendi yan ürünüm için bir backend servisi geliştiriyordum. FastAPI ile yazılmış, PostgreSQL veritabanı kullanan, Redis ile cache’lenen basit bir mimariydi. Her bileşen ayrı bir Docker konteynerinde çalışacak şekilde tasarlanmıştı. Testler harika gidiyordu, kaynak tüketimi makul görünüyordu ve her şey tıkır tıkır işliyordu. Bu sistemin birkaç ay sorunsuz çalışması, bende yersiz bir güven oluşturmuştu.
O Kritik Gece: Sunucu Neden Cevap Vermedi?
Bir cuma akşamı geç saatlerde, monitoring sistemimden gelen art arda alarmlarla uyandım. İlk başta basit bir ağ kesintisi sandım, ancak sunucuya SSH ile bağlanmaya çalıştığımda hiçbir yanıt alamadım. Web arayüzünden kontrol ettiğimde ise sunucunun tamamen donduğunu gördüm. CPU kullanımı %100, belleğin tamamı tüketilmiş ve disk I/O’su tavan yapmıştı. Sanki bir DDoS saldırısı yemiş gibiydi, ama içeriden geliyordu.
Uzun uğraşlar sonunda sunucuyu yeniden başlatmayı başardım. İlk iş, journalctl -xe komutuna bakmak oldu. Karşılaştığım manzarada, sistem loglarının baş döndürücü bir hızla aktığını gördüm. Özellikle Docker’ın logları, tüm sistemi ele geçirmiş gibiydi. Bellek yetersizliği (OOM-killed) uyarıları, disk doluluk alarmları ve sayısız I/O hatası… Tam bir karmaşaydı.
Suçlu Kimdi? Docker’ın Sessiz Hırsızlığı
Yeniden başlatmanın ardından, docker system df ve docker volume ls komutlarını çalıştırdım. Gördüğüm manzara şuydu: Docker’ın overlay2 depolama sürücüsü, terabaytlarca yer kaplamıştı. Meğerse, o kadar çok konteyner image’i ve ara katman birikmişti ki, disk alanı tamamen dolmuştu. Dahası, konteyner logları da varsayılan olarak json-file sürücüsüyle diskte tutuluyordu ve bu loglar da devasa boyutlara ulaşmıştı.
Sorun, benim docker system prune veya docker volume prune gibi komutları düzenli çalıştırmayı ihmal etmemden kaynaklanıyordu. Geliştirme ortamında bu komutları sıkça kullanırken, üretim ortamında “stabil çalıştığı için” dokunmamıştım. Bu, Docker’ın default konfigürasyonunun zamanla sessizce kaynak tüketmesine izin vermişti. cgroup limitlerini de yeterince sıkı ayarlamadığım için, bir konteynerin anlamsız bir döngüye girip log basması tüm sistemi yormuştu.
# Disk kullanımının dökümünü almak için (çıktı ortama göre değişir)
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# ...
Bu komutun çıktısında Local Volumes satırı diskimi dolduran asıl sorundu; geri kazanılabilir alan handiyse tamamına yakındı. Disk alanının ne kadar kritik olduğunu bilmeme rağmen, bu basit gözden kaçırma yüzünden kendime kızmıştım.
Alınan Dersler ve Sonraki Adımlar
Bu olaydan sonra Docker kullanımımı ciddi şekilde gözden geçirdim. İlk iş, tüm konteynerlere logging driver’ını json-file yerine syslog veya journald olarak ayarlamak oldu. Bu sayede loglar merkezi bir sisteme akacak ve disk doluluğuna yol açmayacaktı. Ayrıca, max-size ve max-file limitlerini de belirledim.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
daemon.json dosyasına bu ayarları ekleyerek, her konteynerin log boyutunu kısıtladım. Ayrıca, düzenli cron işleri ile docker system prune -a ve docker volume prune komutlarını çalıştırmayı otomatikleştirdim. En önemlisi, her konteynere CPU ve bellek (cgroup) limitleri atadım. Bu, bir konteynerin anlamsız bir şekilde kaynak tüketerek tüm sistemi kilitlemesini engelledi.
Bu deneyim bana bir kez daha gösterdi ki, otomasyon ne kadar gelişirse gelişsin, sistem yöneticisinin ve mimarın temel prensipleri gözden kaçırmaması gerekiyor. “Hazır geliyor, kur çalıştır” zihniyeti, özellikle kritik sistemlerde, büyük sorunlara yol açabilir.
Senin de buna benzer, “bu kadar basit bir hata yüzünden mi?” dediğin bir deneyimin oldu mu? Yorumlarda paylaşır mısın?