Linux sanal makina host üzerinde koşan Docker compose ya da swarm altyapısını kullanan bir projemiz var. Ürünün farklı modülleri farklı servisler altında container olarak çalışıyor. Veritabanı olarak kullandığımız MongoDB yine container olarak ayağa kalkıyor ve yaşamını sürdürüyor. Farklı deployment senaryolarından birinde MongoDB ve diğer servisler aynı docker node üzerinde çalışıyor.

Problem

Geçenlerde ortamlarımızdan birinde MongoDB container’ın aniden durduğunu farkettik. Önce tabi logları kontrol ettik fakat herhangi bir hata ya da uyarı logu gözükmüyordu. Problem tüm servisleri durdurup yeniden başlattığımızda birkaç gün çalışıp tekrar ediyordu. MongoDB üzerinde CPU/Memory/Loglar ve diğer değerler normal gözükmesine rağmen tüm servisler çalışırkerken MongoDB duruyordu.

Olay Yeri İnceleme

Docker loglarından birşey çıkmayınca Linux host ortamını incelemeye başladık. CPU/Memory değerlerini geçmişe dönük incelerken Monitoring aracımızda tam MongoDB’nin kapanmasına yakın zaman aralığında available/used memory değerlerinin birleştiğini ve sonrasında MongoDB’nin kapandığını farkettik. Sorun aşağı yukarı belli olmuştu sorun sistemdeki mevcut belleğin tükenmesi nedeniyle kapanmasıydı fakat kapanmadan önce bile MongoDB Memory değerleri normal gözüküyordu.

Şüpheli ile İlk Temas

MongoDB bellek değerleri normal gözüktüğüne göre başka bir sebeple durduruluyordu. Bunun için yine monitoring sistemine dönüp diğer servislerin ve host üzerindeki işlemlerin tükettiği değerleri kontrol ettik. O sırada herhangi bir işlem fazla bellek tüketmiyordu fakat diğer bir docker container adı “large_memory” olsun, belleğin büyük çoğunluğunu tüketip MongoDB’nin kapanmasına sebep oluyordu.

Kanıtların Toplanması

Bütün belleği tüketen container ayakta kalırken neden sadece MongoDB kapanıyordu ve bunu kim tetikliyordu? Bunu tespit etmek için Linux host üzerinde kernel loglarını incelemeye başladık. Dmesg ile logları incelerken ilgili zaman aralığına gittik ve aradığımız logu bulduk.

[2156113.520338] Out of memory: Kill process 24363 (mongod) score 52 or sacrifice child
[2156113.520726] Killed process 24363 (mongod), UID 999, total-vm:3079196kB, anon-rss:1292664kB, file-rss:0kB, shmem-rss:0kB

Sebep

Linux out of memory management işlemcisi sistemi sürekli izleyip bellek yetersizliği durumunda hesapladığı skora göre kendine bir kurban seçip bu işlemi sonlandırıyor. Kurbanın nasıl seçildiğine dair detayları bağlantıdan ya da birçok farklı kaynaktan inceleyebilirsiniz fakat çok düz mantık en fazla kim bellek tüketiyorsa onu sonlandıralım mantığı ile çalışmıyor. Sistemde eğer bellek yetersiz kaldıysa, o anda oom_score değerine göre en yüksek hangi işlem ise onu sonlandırıyor. Bu sebepten dolayı malesef veritabanımız MongoDB işlemi mongod sonlanıyor, yani diğer container en fazla belleği tüketse bile, en fazla skora sahip olan mongod işlemi olduğu için o sonlanıyor.

Olay Yeri Tatbikatı

Aynı senaryoyu test ortamında oluşturmak için kolları sıvadım. İlk olarak MongoDB’yi container olarak ayağa kaldırıp sisteminde boşta olan bellek miktarını kontrol ettim. Aşağıdaki gibi bir resim ortaya çıktı

watch free -h

free memory

Ardından docker container içinde çalışan tüm işlemlerin oom_score değerlerini bulacak oom.sh aşağıdaki scripti hazırladım.

#!/bin/bash

printf "%-50s %8s %8s\n" "Image" "PID" "OOM_SCORE"
docker ps --format " " |
while read -r line; do
  id=$(echo "$line" | cut -d ' ' -f1)
  img=$(echo "$line" | cut -d ' ' -f2)
  topResult=$(docker top "$id" -o pid | grep -v "PID")

  for r in $topResult;
  do
    score=$(cat /proc/"$r"/oom_score)
    printf "%-50s %8s %8s\n" "$img" "$r" "$score"
  done
done | sort -k3 -n -r

Linux bir işlemin oom skorunu /proc/pid/oom_score altında tutuyor. Yukarıdaki script çalıştığında o anda bulunan tüm docker containerlar içinde çalışan tüm işlemleri azalan sıralı şekilde tüm container imajlarının adlarını ve yanlarında OOM skor değerlerini yazacak.

Sıra geldi yük oluşturmaya yani bitene kadar bellek tüketmeye. large_memory container benzeri bir bellek kullanmak için aşağıdaki gibi basit bir container oluşturdum.

[host]docker run --name large_memory -d -t bash
[host]docker exec -it large_memory bash
[bash]for i in {1..12}; do cat /dev/zero | head -c 1000m | tail & done

jobs

Yukarıda container’ı interaktif modda ayağa kaldırıp run sonrasında kapanmasın diye ilk başta -d -t parametreleri ile çalıştırıyoruz. Ardından container içine girip for döngüsü ile her biri 1000MB tüketecek background job oluşturuyoruz.

cat /dev/zero | head -c 1000m | tail &

ile bellek tüketecek iş parçacığı oluşturuyoruz. Tail komutu son satıra gelene kadar tüm verileri belleğinde tuttuğu için cat /dev/zero ile sürekli null gönderip head -c 1000m bunu 1000MB ile sınırlıyorum.

1000MB tüketen 12 tane işi özellikle oluşturdum çünkü bellekte 12GB civarı bir yer var bunun hepsini tüketmek istiyorum. 1000MB olmasının sebebi ise, bu testi yaptığım anda mongod yaklaşık 1.3GB değer tüketiyordu, yük için oluşturduğum işlemler daha fazla bellek tüketirse OOM tarafından sonlandırılmasını istemiyorum.

Tatbikat Bitiyor

Birkaç dakika sonra, oluşturduğumuz yeni container içindeki işler tüm belleği tüketiyor ve bellekte yer kalmıyor ve neredeyse MongoDB’nin 10 katı bellek tüketiyor.

stats

Fakat o sırada oom.sh scriptini çalıştırdığımızda aşağıda gördüğünüz gibi mongod en tepede yani bellek kalmadığında bir süre sonra sonlandırılacak işlem olarak gözüküyor.

oom scores

Hemen sonrasında aynı gerçek ortamda yaşadığımız gibi test ortamında da MongoDB kapanıyor ve bekletiğimiz gibi kernel mesajlarında aynı uyarıyı alıyoruz.

dmesg

Sonuç

Senaryoyu test ortamında simule etmemizin mutluğunu yaşıyoruz. Gerçek ortamda yaşadığımız problem,MongoDB ile alakalı olmamasına rağmen diğer bir container içinde çok fazla küçük küçük process oluşturulup bunların toplamda fazla bellek tüketmesi ve Linux OOM yönetimi tarafından sonlandırılmaya en uygun aday olarak mongod seçilip sonlandırılmasıydı.

Bu sorunu kullandığımız bir kütüphane sebebiyle yaşadığımızı tespit ettik ve sorunu biraz farklı bir şekilde process’leri kendimiz öldürerek aştık.

Bazen container içinde oluşan işlemler tamamen izole gibi düşünülse de, böyle bir durum yok. Aynı kernel üzerinde çalışıp aynı belleği paylaşıyorlar, dolayısıyla host işletim sistemi ile aradaki ilişkiyi iyi bilmek gerekiyor. Fırsat bulursam host ile docker arasındaki ilişkiyi farklı açılardan değerlendirip buraya da yazmak istiyorum.


<
Previous Post
Unix Araçları ile REST Api Performans Testi
>
Next Post
SSH Reverse Tunnel - Pratik Bir Kullanım Senaryosu