Disk, Memory ve CPU: df, du, free, vmstat, iostat, OOM Killer ve "Disk Doldu" Vakası
Açık bir log dosyasını rm ile silmek yer açmaz; process dosyayı tutar, lsof +L1 onu gösterir ve truncate alanı geri verir. Bookstore'un log diskinin dolduğu gerçek bir vakayla df, du ve inode'ları, free'de available sütununu ve page cache'i, OOM killer'ı ve systemd'nin MemoryMax sınırını, load average'ı core sayısıyla okumayı ve vmstat, pidstat, iostat ile CPU ile I/O beklemesini ayırmayı gösteriyoruz.
Sürüm notu: Bu yazıdaki komutlar Ubuntu Server 24.04.5 LTS (kernel 6.8, systemd 255, procps 4.0.4, sysstat 12.6.1, lsof 4.95) çalıştıran 2 CPU'lu, 1 GB RAM'li bir test VM'inde; Bookstore.Api .NET SDK 10.0.402 ile derlenip ASP.NET Core 10.0.15 runtime'ıyla çalıştırılmıştır.
Önce Tahmin Et
Gece yarısı alarm geliyor: sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) log diski %100 dolu. Sunucuya girip suçluyu buluyorsun, 9.9 MB'lık bir api.log, ve siliyorsun:
$ sudo rm /var/log/bookstore/api.log
$ df -h /var/log/bookstore
Filesystem Size Used Avail Use% Mounted on
/dev/loop0 11M 9.9M 0 100% /var/log/bookstore
Dosya gitti, ama disk hâlâ dolu. Yer neden geri gelmedi ve nasıl geri alırsın? Cevap vaka bölümünde.
Bu yazıda sunucunun üç kaynağına bakıyoruz: disk (df, du, inode'lar, silinmiş ama açık dosyalar), bellek (free, page cache, OOM (Out Of Memory; belleğin tükenmesi) killer, cgroup limitleri) ve CPU ile I/O (uptime, vmstat, pidstat, iostat). Process, virtual memory ve scheduling'in teorisi İşletim Sistemi Temelleri serisinde; burada "sunucu yavaş" ya da "disk doldu" dendiğinde hangi komutla neye bakacağın var.
Disk: df, du ve Inode'lar
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 97M 1004K 96M 2% /run
/dev/vda1 19G 2.5G 16G 14% /
tmpfs 481M 8.0K 481M 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/vda16 881M 117M 703M 15% /boot
/dev/vda15 105M 6.2M 99M 6% /boot/efi
tmpfs 97M 12K 96M 1% /run/user/1000
$ sudo du -xh -d1 /var 2>/dev/null | sort -h | tail -n 6
48K /var/tmp
1.3M /var/crash
114M /var/log
253M /var/lib
342M /var/cache
710M /var
df (disk free) dosya sistemi başına toplam, kullanılan ve boş alanı verir; -h okunur birimler. tmpfs satırları RAM'de duran sanal dosya sistemleri, disk değil. İlk soru her zaman "hangi dosya sistemi doldu": / mu, ayrı bir /var ya da log diski mi? Sonra du (disk usage) ile içeri inilir: -x başka dosya sistemlerine geçme, -d1 yalnızca bir seviye derinlik, sort -h insan okunur boyutları doğru sıralar. Büyük klasörü bulunca aynı komutu onun içinde tekrarla. Aynı işi etkileşimli yapan ncdu (sudo apt install ncdu) yüzlerce klasörlü bir sunucuda daha hızlıdır.
Disk iki şekilde dolar: byte'lar ya da inode'lar. Her dosya, boyutundan bağımsız bir inode (dosyanın metadata kaydı) kullanır ve inode sayısı dosya sistemi oluşturulurken sabitlenir. Milyonlarca küçük dosya üreten bir uygulama alan dolmadan inode'ları bitirebilir. Kitap kapağı thumbnail'ları için küçük bir cache dosya sistemiyle denedim:
$ sudo -u bookstore bash -c 'for i in $(seq 1 400); do : > /var/lib/bookstore/covers-cache/cover-$i.jpg || { echo "stopped at $i"; break; }; done'
bash: line 1: /var/lib/bookstore/covers-cache/cover-246.jpg: No space left on device
stopped at 246
$ sudo df -h /var/lib/bookstore/covers-cache
Filesystem Size Used Avail Use% Mounted on
/dev/loop0 4.0M 32K 3.4M 1% /var/lib/bookstore/covers-cache
$ sudo df -i /var/lib/bookstore/covers-cache
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/loop0 256 256 0 100% /var/lib/bookstore/covers-cache
Hata mesajı aynı, "No space left on device", ama df -h %1 dolu diyor. df -i gerçeği gösteriyor: 256 inode'un hepsi kullanılmış. Bunu bilerek az inode'la oluşturdum; gerçek sunucularda sebep genelde temizlenmeyen session dosyaları, mail queue'su ya da cache klasörleridir. "Disk dolu" alarmında df -h ve df -i'ye birlikte bak.
Vaka: Gece Yarısı "No space left on device"
Vakayı hızlı üretmek için lab'da /var/log/bookstore'u 16 MB'lık ayrı bir dosya sistemine bağladım; log'lar için ayrı disk ya da partition production'da da yaygındır ve böyle bir diskin dolması en sık görülen biçimdir. Senaryo şu: biri birkaç gün önce bir sorunu ararken Bookstore.Api'yi bir drop-in ile journal yerine dosyaya yazdırmış (StandardOutput=append:/var/log/bookstore/api.log, altıncı yazıdaki gibi), sonra release klasöründeki appsettings.json'da log seviyesini Trace'e çekmiş ve ikisini de unutmuş. Bu dosya için logrotate kuralı da yok. Normal trafik birkaç dakikada diski doldurdu. İlk belirti log'da değil, başka bir işte görünüyor:
$ sudo -u bookstore bash -c "echo backup-started >> /var/log/bookstore/db-backup.log"
bash: line 1: echo: write error: No space left on device
$ df -h | grep -E "Filesystem|bookstore|vda1"
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 19G 2.6G 16G 14% /
/dev/vda16 881M 117M 703M 15% /boot
/dev/vda15 105M 6.2M 99M 6% /boot/efi
/dev/loop0 11M 9.9M 0 100% /var/log/bookstore
$ sudo ls -lh /var/log/bookstore
total 9.9M
-rw-r--r-- 1 root root 9.9M Nov 14 17:00 api.log
-rw-rw-r-- 1 bookstore bookstore 0 Nov 14 17:01 db-backup.log
drwx------ 2 root root 16K Nov 14 16:53 lost+found
$ sudo tail -n 2 /var/log/bookstore/api.log | cut -c1-120
<7>Microsoft.AspNetCore.Server.Kestrel.Connections[2] Connection id "0HNQ2E1M8KG2Q" stopped.
<7>Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets[7] Connection id "0HNQ2E1M8KG2Q" se
$ grep -A3 LogLevel /opt/bookstore/current/appsettings.json
"LogLevel": {
"Default": "Trace",
"Microsoft.AspNetCore": "Trace"
}
Disk dolunca uygulama çökmedi; API 200 dönmeye devam etti, yalnızca log yazamıyordu. Asıl zarar diski paylaşan diğer işlere oluyor: yedek dosyası yazılamadı. <7> önekli satırlar Debug ve Trace (altıncı yazıdaki syslog seviyeleri) ve dosyanın neredeyse tamamı onlar. Tahmindeki adım burada geliyor: dosyayı sildim ve df hiç değişmedi. Sebebi Linux'ta silmenin ne yaptığı: rm dosyanın adını siler. Bir process dosyayı hâlâ açık tutuyorsa veri diskte kalır ve son açık file descriptor kapanana kadar alan geri verilmez.
$ sudo lsof -nP +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
dotnet 8778 bookstore 1w REG 7,0 10321920 0 12 /var/log/bookstore/api.log (deleted)
dotnet 8778 bookstore 2w REG 7,0 10321920 0 12 /var/log/bookstore/api.log (deleted)
dotnet 8778 bookstore 6w REG 7,0 10321920 0 12 /var/log/bookstore/api.log (deleted)
dotnet 8778 bookstore 7w REG 7,0 10321920 0 12 /var/log/bookstore/api.log (deleted)
dotnet 8778 bookstore 8u REG 0,1 1006768128 0 126 /memfd:doublemapper (deleted)
dotnet 8778 bookstore 147w REG 7,0 10321920 0 12 /var/log/bookstore/api.log (deleted)
dotnet 8778 bookstore 148w REG 7,0 10321920 0 12 /var/log/bookstore/api.log (deleted)
$ sudo ls -l /proc/$(systemctl show -p MainPID --value bookstore-api)/fd/1
l-wx------ 1 bookstore bookstore 64 Nov 14 16:57 /proc/8778/fd/1 -> /var/log/bookstore/api.log (deleted)
lsof +L1 link sayısı 1'den az olan, yani adı silinmiş ama hâlâ açık dosyaları listeler. dotnet process'i silinen api.log'u birden çok file descriptor'da tutuyor: 1 stdout, 2 stderr (systemd ikisini aynı dosyaya bağladı) ve .NET'in console için açtığı kopyalar. memfd:doublemapper satırı disk değil (DEVICE 0,1): .NET runtime'ının JIT (Just-In-Time; kodu çalışma anında derleyen bileşen) için bellekte tuttuğu bir eşleme; bu listede görürsen görmezden gel. Alanı geri almanın iki yolu var: process'i yeniden başlatmak ya da dosyayı açık olduğu yerden sıfırlamak:
$ sudo truncate -s 0 /proc/$(systemctl show -p MainPID --value bookstore-api)/fd/1
$ df -h /var/log/bookstore
Filesystem Size Used Avail Use% Mounted on
/dev/loop0 11M 24K 9.9M 1% /var/log/bookstore
/proc/<pid>/fd/1 silinmiş dosyaya hâlâ giden tek yol; truncate onu sıfırladı ve 9.9 MB anında geri geldi, uygulama da yeniden başlamadı. Gece yarısı ilk müdahale bu olabilir. Doğru sıralama da şu: önce yer aç (rm değil, truncate -s 0 /var/log/bookstore/api.log ya da yukarıdaki gibi), sonra sebebi düzelt. Kalıcı çözüm dört adım ve hepsini yaptım: appsettings.json'u release'teki hâline döndür (Default: Information, Microsoft.AspNetCore: Warning), drop-in'i silip uygulamayı journal'a geri döndür (journal'ın SystemMaxUse sınırı var), /var/log/bookstore için altıncı yazıdaki copytruncate'li logrotate kuralını yerine koy, ve bunu sunucuda elle değil deploy ile yap ki bir sonraki release aynı ayarları getirsin. Sonrasında lsof +L1 yalnızca memfd satırını gösterdi.
Bu vakada disk dolmadan önce alarm gelmesi gerekirdi: %80'de uyarı, %90'da acil. Disk, inode ve journal boyutu monitoring'in ilk satırlarıdır.
Bellek: free, Page Cache ve available
$ free -h
total used free shared buff/cache available
Mem: 960Mi 265Mi 719Mi 7.4Mi 104Mi 694Mi
Swap: 0B 0B 0B
$ cat /var/tmp/catalog-export.bin > /dev/null
$ free -h
total used free shared buff/cache available
Mem: 960Mi 288Mi 412Mi 7.4Mi 404Mi 671Mi
Swap: 0B 0B 0B
300 MB'lık bir dosyayı bir kez okudum. free sütunu 719'dan 412'ye düştü, buff/cache 104'ten 404'e çıktı. Kernel okuduğu dosyayı page cache'te (disk verisinin bellekteki kopyası) tutuyor; bir sonraki okuma diskten değil RAM'den gelir. Bu bellek boşta durmaktan iyidir ve bir uygulama isteyince kernel onu hemen geri verir. Bu yüzden bakman gereken sütun free değil available: yeni process'lere swap'e düşmeden verilebilecek tahmini bellek, 694'ten 671'e yalnızca biraz düştü. "Sunucunun belleği dolmuş, free 50 MB" alarmlarının çoğu bu yanlış okumadan gelir. Bu VM'de swap yok (bulut image'larında yaygın); swap varsa vmstat'taki si/so sütunları sürekli sıfırdan büyükse bellek gerçekten yetmiyor demektir. WSL (Windows Subsystem for Linux; Windows içinde Linux çalıştıran katman) 2 kullanıyorsan free, Windows'un WSL'e ayırdığı belleği gösterir; sınırı .wslconfig'teki memory ayarı belirler.
OOM Killer: Bellek Bitince Kim Ölür?
Bellek gerçekten bittiğinde kernel bir process'i seçip öldürür: OOM killer. Hangisini seçeceği process'in bellek kullanımına ve oom_score_adj ayarına bağlı; adayların puanı /proc/<pid>/oom_score'da:
$ for p in $(pgrep -x dotnet) $(pgrep -o -x nginx) $(pgrep -o -x sshd) $(pgrep -x systemd-journal); do printf "%-16s pid=%-6s rss=%6s kB oom_score=%s\n" $(cat /proc/$p/comm) $p $(awk "/VmRSS/{print \$2}" /proc/$p/status) $(cat /proc/$p/oom_score); done
dotnet pid=9143 rss= 59488 kB oom_score=706
nginx pid=702 rss= 1488 kB oom_score=667
sshd pid=807 rss= 8252 kB oom_score=0
systemd-journal pid=314 rss= 14772 kB oom_score=510
Puanların mutlak değerinden çok sırası önemli: en yüksek olan önce gider. Bu sunucuda en çok belleği kullanan Bookstore.Api en riskli aday. sshd'nin puanı 0 çünkü sshd açılışta kendi oom_score_adj'ini -1000 yapıyor: bellek bitse bile sunucuya girebilmen için. journald da -250 ile korunuyor. Bellek tüketen bir process çalıştırdım:
$ python3 hog.py 50; echo "exit=$?"
bash: line 1: 9938 Killed python3 hog.py 50
exit=137
$ journalctl -k -b --no-pager --grep "Out of memory" | tail -n 1 | cut -c1-200
Nov 14 17:04:16 bookstore-dev kernel: Out of memory: Killed process 9938 (python3) total-vm:789152kB, anon-rss:770560kB, file-rss:2304kB, shmem-rss:0kB, UID:1000 pgtables:1576kB oom_score_adj:0
$ systemctl is-active bookstore-api nginx ssh
active
active
active
hog.py döngüde 50 MB'lık bloklar ayıran üç satırlık bir script. 750 MB'a ulaştığında kernel onu SIGKILL ile öldürdü (çıkış kodu 137 = 128 + 9). Process kendi hatasını yazamaz; iz yalnızca kernel log'unda kalır ve altıncı yazıdaki journalctl -k bunu gösterir. Bu sefer şanslıydık, en çok bellek kullanan hog'du. Hog'un yerinde bir rapor job'ı olsaydı ve bellek yavaş yavaş dolsaydı, kernel en yüksek puanlı process'i, yani Bookstore.Api'yi seçebilirdi. Bir .NET process'i sebepsiz yere yeniden başlıyorsa ve uygulama log'unda hiçbir şey yoksa ilk bakılacak yer burası.
Bunun önüne geçmenin yolu her service'e kendi bellek sınırını vermek. systemd bunu cgroup'larla (process gruplarının kaynaklarını sınırlayan kernel mekanizması) yapar. Aynı script'i 100 MB sınırlı bir scope içinde çalıştırdım:
$ sudo systemd-run --scope -p MemoryMax=100M -p MemorySwapMax=0 python3 hog.py 10; echo "exit=$?"
Running as unit: run-rdf0a50a36ab84448bc85be99d754cb16.scope; invocation ID: 5fc4a609e7a64e96a9c3503a69ed6766
bash: line 1: 10475 Killed sudo systemd-run --scope -p MemoryMax=100M -p MemorySwapMax=0 python3 hog.py 10
exit=137
$ journalctl -k -b --no-pager --grep "Memory cgroup out of memory" | tail -n 1 | cut -c1-200
Nov 14 17:10:48 bookstore-dev kernel: Memory cgroup out of memory: Killed process 10476 (python3) total-vm:123532kB, anon-rss:102016kB, file-rss:6528kB, shmem-rss:0kB, UID:0 pgtables:272kB oom_score_a
Bu sefer mesaj "Memory cgroup out of memory": sistemin geri kalanında bol bellek varken process kendi sınırına çarptı ve yalnızca o grubun içinden biri öldürüldü. Bookstore.Api'nin unit'ine (repoda deploy/linux/systemd/bookstore-api.service) iki satır ekledik:
TimeoutStopSec=30
MemoryHigh=384M
MemoryMax=512M
$ systemctl status bookstore-api --no-pager | grep -E "Active|Memory"
Active: active (running) since Sat 2026-11-14 17:03:54 UTC; 4s ago
Memory: 30.2M (high: 384.0M max: 512.0M available: 353.7M peak: 30.2M)
MemoryHigh yumuşak sınır: aşılınca kernel bu gruptan belleği agresif geri almaya başlar ve process yavaşlar ama ölmez. MemoryMax sert sınır: aşılırsa OOM killer yalnızca bu service'in içinde çalışır, Nginx ve SSH (uzak bağlantı protokolü) korunur, systemd de Restart=always ile uygulamayı yeniden başlatır. Değerler bu 1 GB'lık lab VM'ine göre; kendi sunucunda uygulamanın normal yükteki tepe kullanımını (peak) birkaç gün izleyip onun üzerine pay bırakarak seç.
CPU: Load Average, vmstat ve pidstat
İki CPU'lu sunucuda üç tane sonsuz döngü başlattım ve bir dakikadan biraz fazla bekledim:
$ uptime
17:12:28 up 40 min, 1 user, load average: 2.26, 1.51, 1.28
$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
4 0 0 591000 74728 157936 0 0 871 2179 1263 7 31 12 56 0 0 0
3 0 0 591000 74728 157936 0 0 0 0 2013 538 100 0 0 0 0 0
3 0 0 591000 74728 157936 0 0 0 0 2016 529 100 0 0 0 0 0
3 0 0 591000 74728 157936 0 0 0 0 2006 529 100 0 0 0 0 0
3 0 0 591000 74728 157936 0 0 0 0 2015 524 100 0 0 0 0 0
$ pidstat -u 1 1 | grep -E "Average|UID" | sort -k8 -rn | head -n 5
Average: 1000 10664 67.26 0.00 0.00 32.74 67.26 - sh
Average: 1000 10663 67.26 0.00 0.00 32.74 67.26 - sh
Average: 1000 10665 61.06 0.00 0.00 38.94 61.06 - sh
Average: 1000 10672 0.00 3.54 0.00 7.08 3.54 - pidstat
Average: UID PID %usr %system %guest %wait %CPU CPU Command
Load average son 1, 5 ve 15 dakikanın ortalaması ve core sayısıyla birlikte okunur: 2 core'da 2.0 "tam dolu", 2.26 "işlerin bir kısmı sırada bekliyor" demek. 1 dakikalık değer yavaş yükselir, döngüler başladıktan bir dakika sonra bile henüz 3'e ulaşmamış. Linux'ta load average, I/O bekleyen D state'teki process'leri de sayar (dördüncü yazı); bu yüzden yüksek load her zaman CPU sorunu değildir. Ayrımı vmstat yapar: r çalışmak için sıra bekleyen process sayısı (3, core sayısından fazla), us kullanıcı kodu (%100), id boşta (%0), wa I/O beklemesi (%0). İlk satır açılıştan beri ortalama olduğu için yok sayılır. pidstat -u hangi process'in CPU kullandığını verir; %wait sütunu process'in CPU için ne kadar sırada beklediğini gösteriyor, üç process iki core'u paylaştığı için her biri zamanın üçte birine yakınında beklemiş. Bulut VM'lerinde st (steal) sütununa da bak: hypervisor'un VM'den aldığı CPU zamanı; sürekli yüksekse sorun senin uygulamanda değil, VM'in boyutunda ya da komşularında.
Suçlu bir .NET process'i çıktığında Linux araçlarının işi biter: hangi thread'in, hangi method'un CPU harcadığını dotnet-counters ve dotnet-trace söyler. Bunu Debugging ve Problem Çözme serisinin hang ve yavaşlık yazısı ve ilerideki .NET'te Profiling ve Diagnostics serisi anlatıyor.
Disk I/O: iostat ve pidstat -d
Her yazmadan sonra diske flush (oflag=direct,dsync) yapan küçük yazmalar başlattım; her satırdan sonra fsync yapan bir log yazıcısı ya da veritabanı buna benzer:
$ vmstat 1 4
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
2 0 0 696036 29428 112220 0 0 961 2078 1229 7 32 13 55 0 0 0
0 1 0 694272 32104 112288 0 0 0 9380 3778 5360 0 5 74 21 0 0
0 1 0 688476 36200 112376 0 0 0 14344 5324 8235 1 2 76 22 0 0
0 1 0 684444 40088 112468 0 0 0 16328 5909 9406 2 2 75 21 0 0
$ iostat -dx 2 2 vda | tail -n 3
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util
vda 0.50 2.00 0.00 0.00 1.00 4.00 2880.50 26884.00 3840.50 57.14 0.18 9.33 1.00 65590.00 0.00 0.00 0.50 65590.00 1920.50 0.12 0.74 96.65
$ pidstat -d 1 1 | grep -E "Average.*(UID|dd)"
Average: UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
Average: 1000 10351 0.00 3146.67 0.00 0 dd
Bu sefer tablo farklı: r 0, b 1 (I/O bekleyen, D state'teki process), wa %21 ve CPU'nun dörtte üçü boşta. iostat -x (sysstat paketinde) cihaz başına ayrıntı verir; ilk rapor açılıştan beri ortalama olduğu için ikinci raporu okudum. Önemli sütunlar: w/s saniyedeki yazma isteği (2880), w_await bir yazmanın ortalama bekleme süresi (ms), aqu-sz ortalama kuyruk uzunluğu ve %util cihazın meşgul olduğu zaman oranı (%96.65). %util tek kuyruklu eski diskler için anlamlıdır; paralel çalışan SSD (flash bellekli disk) ve NVMe disklerde %100 görmek cihazın sınırına geldiği anlamına gelmez, asıl ölçü await'in artmasıdır. pidstat -d yazmanın hangi process'ten geldiğini söyler. Gerçek bir vakada bu tabloyu "Bookstore yavaş" şikâyetiyle aynı saatte görüyorsan, diski paylaşan bir yedek ya da log işi şüphelidir.
sysstat Ubuntu'da kurulu gelir ama geçmiş toplama kapalıdır (/etc/default/sysstat'ta ENABLED="false"). true yapıp servisi açarsan sar ile "dün gece 02:30'da disk ne durumdaydı" sorusunu geriye dönük cevaplayabilirsin; olaydan sonra canlı komutlar o anı göstermez.
Üretimde Dikkat
Disk dolmalarının bilinen kaynakları sırasıyla: rotate edilmeyen uygulama log'u, sıkıştırılmayan ya da silinmeyen yedekler, Docker'ın image, build cache ve container log'ları (/var/lib/docker; docker system df ile bakılır, ilerideki Docker serisi), ve apt cache'i (/var/cache/apt). Her biri için bir sınır koy: logrotate kuralı, yedeklerde saklama süresi, Docker'da log driver'ın max-size ayarı.
Kaynak sınırlarını unit'te yaz. MemoryMax'ın yanında CPUQuota= bir service'in CPU payını, LimitNOFILE= açık dosya sayısını sınırlar. Sınırı olmayan bir service bütün sunucuyu kendisiyle birlikte batırabilir; sınırlı olan yalnızca kendisini yeniden başlatır.
Ölç, sonra karar ver. Load average, available bellek, disk ve inode doluluğu, await monitoring'de olmalı; bu yazıdaki komutlar alarm geldikten sonra "neden" sorusunu cevaplamak için. Geçmişi olmayan bir sunucuda "her zaman böyle miydi" sorusuna cevap veremezsin.
Sık Düşülen Tuzaklar
Açık bir log dosyasını rm ile silip yer açıldığını sanmak. Tahminde gördüğümüz gibi alan process kapanana kadar geri gelmez. Dolu bir diskte yer açarken truncate -s 0 kullan; rm yaptıysan lsof +L1 ile kimin tuttuğunu bul.
free sütununa bakıp bellek alarmı vermek. Page cache "kullanılmış" görünür ama boştur; available'a bak. Aynı sebeple echo 3 > /proc/sys/vm/drop_caches ile cache'i boşaltmak production'da bir çözüm değil, yalnızca bir sonraki okumaları yavaşlatır; bu yazıda yalnızca page cache'i göstermek için bir kez kullandım.
Load average'ı core sayısı olmadan okumak. 4 load, 2 core'lu sunucuda sorun, 16 core'lu sunucuda boşta demek. nproc ile karşılaştır ve vmstat ile CPU mu I/O mu olduğuna bak.
OOM killer'ı uygulama hatası sanmak. Process log yazmadan ölür, systemd onu yeniden başlatır ve geriye yalnızca journalctl -u'da "Main process exited, code=killed, status=9/KILL" satırı kalır. Bunu gördüğünde journalctl -k --grep "Out of memory"'e bak.
Yalnızca df -h'ye bakmak. Inode'lar da tükenir; alan %1 doluyken "No space left on device" görüyorsan df -i.
Küçük Bir Deneme
★☆☆ Kendi sunucunda sudo du -xh -d1 / 2>/dev/null | sort -h | tail ile en büyük klasörleri bul, sonra en büyüğünün içinde aynı komutu tekrarla. df -i ile inode doluluğuna da bak.
★★☆ Bir shell'de exec 3>>/var/tmp/big.log ile dosyayı açık tut ve dd if=/dev/zero bs=1M count=100 >&3 ile içine 100 MB yaz. Başka bir terminalden dosyayı rm ile sil, df'in değişmediğini ve lsof +L1'in onu gösterdiğini doğrula, sonra truncate ile alanı geri al.
★★★ Bookstore.Api'nin unit'indeki MemoryMax'ı geçici olarak 80M'ye indir, rapor endpoint'ine paralel istekler göndererek sınırı zorla ve journalctl -u bookstore-api ile journalctl -k'da ne gördüğünü not et. systemctl show -p NRestarts kaç kez yeniden başladığını söylesin.
Disk, bellek ve CPU sorunlarını teşhis etmeyi gördük. Bir sonraki yazıda sunucuyu dışarıya karşı sertleştiriyoruz: SSH ayarları, ufw, Fail2ban, otomatik güncellemeler ve en az yetki.