Ladění problémů

strace, lsof, /proc, dmesg.

Dřív nebo později něco přestane fungovat — služba nenaběhne, port je „obsazený", disk je plný, proces nejde zabít. Začátečník v tu chvíli hádá a náhodně zkouší příkazy z internetu. Sysadmin měří: zeptá se systému, co se děje, přečte logy a mění vždycky jen jednu věc. Tahle kapitola tě naučí ten druhý přístup — systematické ladění, kde tě k příčině dovede důkaz, ne dojem.


Pár pojmů na úvod

  • Systematický přístup = neházet příkazy naslepo. Nejdřív zjisti co se změnilo, pak přečti logy, pak měň jednu věc a sleduj efekt. Tři pravidla, která ti ušetří hodiny.
  • Log = záznam toho, co program dělal. Když něco spadne, skoro vždy to někam napsal proč — tvým úkolem je to jen najít a přečíst, ne to uhodnout.
  • Syscall (systémové volání) = způsob, jakým proces žádá jádro o práci (otevři soubor, čti ze sítě). Když proces „visí", často čeká právě uvnitř nějakého syscallu — a to se dá odposlechnout.
  • Port = číslo, na kterém služba naslouchá (např. web na 80, SSH na 22). Dva programy nemůžou držet stejný port zároveň — odtud „adresa se už používá".
  • File descriptor (fd) = číselný odkaz na otevřený soubor/socket uvnitř procesu. Dokud ho někdo drží, soubor „žije" (proto nejde odpojit disk nebo uvolnit smazaný soubor).
  • Zombie = proces, který už doběhl, ale rodič si ještě nevyzvedl jeho návratový kód. Nezabere paměť, jen řádek v tabulce — a kill na něj neplatí, protože už neběží.

Zlaté pravidlo: co se změnilo?

Systém, který včera jel a dnes nejede, se nezměnil sám. Než sáhneš po nástrojích, polož si otázky:

  • Nainstaloval/aktualizoval jsem něco? (dnf history, journalctl kolem té doby)
  • Měnil jsem konfiguraci? (kdy naposledy? existuje .bak?)
  • Došlo místo na disku, došla paměť? (o tom níž)
  • Restartoval se stroj a něco nenaběhlo?

🚩 Nejčastější chyba juniora: rovnou googlí chybovou hlášku a lepí příkazy, aniž by věděl, co se vlastně rozbilo. Deset minut přemýšlení „co bylo jinak" často nahradí hodinu bezhlavého zkoušení.

Druhé pravidlo je stejně důležité: měň vždycky jen jednu věc a pak ověř. Když najednou přepíšeš tři řádky konfigurace, restartuješ službu a smažeš cache — a ono to začne jet — nevíš, co pomohlo. Příště budeš zase tápat. Jedna změna → jeden test → závěr.


Čti logy dřív, než začneš hádat

Logy jsou první místo, kam se dívat. V moderních distrech (systemd) je centrální nástroj journalctl.

# poslední řádky logu + „vysvětlivky" u chyb, skoč na konec (-e)
journalctl -xe

# log konkrétní služby (třeba nginx)
journalctl -u nginx

# jen od posledního bootu, sleduj živě (jako tail -f)
journalctl -u nginx -b -f

# co se dělo mezi časy (pozor na timestampy!)
journalctl --since "10:00" --until "10:15"

Pro jádro a hardware (disky, USB, ovladače, OOM killer) je klasika dmesg:

sudo dmesg -T        # -T = lidsky čitelné časy místo sekund od bootu
sudo dmesg -w        # sleduj živě (zapoj USB a koukej, co jádro řekne)

💡 Timestamp je půl řešení. Vždy si najdi v logu čas, kdy problém nastal, a čti řádky kolem něj — ne úplný konec logu. Chyba na konci může být následek, ne příčina. První skutečná chyba bývá o pár řádků výš a o pár vteřin dřív.


„Služba nejede" — status → journalctl

Nejběžnější scénář. Postup je vždy stejný:

# 1) Jaký je stav? (běží? spadla? proč?)
systemctl status nginx

# 2) Přečti log té konkrétní služby — tam je důvod
journalctl -u nginx -e

# 3) Oprav příčinu (např. překlep v configu), pak zkus znovu
sudo systemctl restart nginx

# 4) Ověř, že jede, a případně nastav autostart při bootu
systemctl status nginx
sudo systemctl enable nginx

systemctl status ti řekne active (running), failed, nebo inactive, obvykle i posledních pár řádků logu a exit code. Když vidíš failed, důvod je v journalctl -u <služba>. Typicky: chyba v konfiguraci, obsazený port (viz níž), chybějící soubor, špatná práva.

🚩 sudo systemctl restart naslepo dokola je past. Když služba padá pořád dokola, restart nic nevyřeší — jen skryje log. Přečti proč padá, teprve pak restartuj.


„Port je obsazený" — ss → lsof → kill

Hláška typu bind: address already in use znamená, že port už někdo drží. Musíš zjistit kdo:

# co poslouchá na portech: -t TCP, -u UDP, -l listening, -p proces, -n čísla místo jmen
ss -tulpn

# cíleně: kdo drží port 8080?
sudo ss -tulpn | grep :8080
# nebo přímo přes lsof
sudo lsof -i :8080

lsof -i :8080 ti vypíše jméno procesu a PID. Teď se rozhodni: je to něco, co tam nemá být (starý zapomenutý proces), nebo legitimní služba, kterou máš vypnout přes systemctl? Pokud je to opravdu zbloudilý proces:

kill <PID>        # slušně požádej proces, ať skončí (SIGTERM)
# ...počkej pár vteřin, ověř...
sudo ss -tulpn | grep :8080
kill -9 <PID>     # AŽ když -15 nezabralo (tvrdé zabití, SIGKILL)

🚩 sudo u ss/lsof potřebuješ, abys viděl procesy cizích uživatelů (jinak u portu neuvidíš jméno ani PID). Bez sudo často dostaneš jen prázdno a myslíš si, že port nikdo nedrží.


„Disk je plný" — df → du → najdi viníka

No space left on device — nejdřív zjisti, který oddíl je plný (ne vždy ten, co čekáš):

df -h              # přehled oddílů: velikost, použito, volno, %, kam jsou připojené

Najdi řádek se 100 % (nebo skoro). Teď se do toho adresáře přesuň a hledej, co žere místo:

# největší položky v aktuálním adresáři, seřazené
du -sh ./* 2>/dev/null | sort -h

# klasický „sestup do díry": najdi tučný adresář, vejdi, opakuj
du -sh /var/* 2>/dev/null | sort -h

Nejčastější viníci: /var/log (rozbující logy), staré Docker image/volume, /tmp, cache, zapomenutý velký soubor po stažení. Pozor na dvě různé věci, které dojdou:

  • Místo (df -h) — bloky na disku.
  • Inody (df -i) — počet souborů. Můžeš mít 50 % místa a přesto „plno", když ti milion malých souborů vyčerpal inody. Když df -h ukazuje volno, ale zápis stejně selže — zkontroluj df -i.

💡 Smazání velkého souboru, který stále drží běžící proces, místo neuvolní — místo se vrátí až proces skončí (nebo restartuješ). Kdo ho drží, zjistíš přes lsof (viz další sekce). Časté u logů, které někdo rm-nul, ale služba do nich pořád píše.


„Nejde odpojit disk / target is busy" — lsof

umount: target is busy znamená, že něco má na tom mountu otevřený soubor nebo tam stojí pracovním adresářem. Musíš najít viníka:

# kdo má něco otevřené pod /mnt/disk?
sudo lsof +D /mnt/disk
# nebo obecněji — které procesy sahají na to zařízení/cestu
sudo fuser -vm /mnt/disk

Uvidíš PID(y) a jména procesů. Nejčastější banalita: ty sám stojíš terminálem v tom adresáři (cd /mnt/disk). Stačí cd ~ a odpojení projde. Jinak procesy slušně ukonči a zkus znovu:

cd ~                       # neblokuj to vlastním shellem
sudo umount /mnt/disk

🚩 umount -l (lazy) mount jen „schová", ale odpojení dokončí až později — není to řešení, jen odklad. Nejdřív zjisti kdo drží, pak to řeš, ne obcházej.


„Proces visí" — strace a /proc

Proces žere CPU nebo naopak nic nedělá a „zamrzl"? Podívej se, co zrovna dělá — jaké syscally volá:

# připoj se k běžícímu procesu a sleduj jeho systémová volání
sudo strace -p <PID>

Když výpis stojí na read(...) nebo futex(...) a nehýbe se, proces na něco čeká (na data, na zámek). Když se řítí openat za openat, něco horečně hledá. Doplňkově tě hodně řekne /proc/<PID>/, kde jádro vystavuje stav procesu jako soubory (přesně to „všechno je soubor"):

ls -l /proc/<PID>/fd        # co má proces otevřeno (soubory, sockety)
cat  /proc/<PID>/status     # stav: běží (R), spí (S), nepřerušitelně čeká (D), zombie (Z)…
cat  /proc/<PID>/cmdline | tr '\0' ' '   # čím přesně byl spuštěn (i s argumenty)

Stav D (uninterruptible sleep) je důležitý: proces čeká na I/O (typicky pomalý/odumřelý disk nebo síťový mount) a nejde ho zabít ani kill -9 — jádro ho pustí, až I/O dopadne. To není chyba procesu, ale toho, na co čeká.


„Proces nejde zabít / zombie"

Dvě různé situace, které vypadají podobně, ale mají jinou příčinu:

  • Zombie (Z) — proces už doběhl, jen na něj rodič nesebral návratový kód. kill neplatí, protože už neběží. Řešení je zabít nebo probrat rodiče (najdeš ho ve status jako PPid); až rodič zombie „vyzvedne" (nebo zemře a převezme ho init), zombie zmizí. Zombie sám o sobě neškodí — jen zabírá řádek v tabulce procesů.
  • D state — proces čeká na I/O a kill -9 na něj neplatí (viz výš). Nezabíjej to dokola — najdi, na jaký disk/mount čeká, a vyřeš to (odumřelý NFS, vadný disk v dmesg).
ps -eo pid,ppid,stat,comm | grep ' Z'   # najdi zombie a jejich rodiče (PPID)

💡 Na výkonové problémy (proces žere 100 % CPU nebo paměť) je samostatná kapitola o výkonutop/htop, ps aux --sort=-%mem. Tady jde o diagnostiku zaseknutí, ne o měření zátěže.


Rozhodovací postup (když nevíš, kde začít)

Klíč není znát nazpaměť všechny přepínače, ale mít postup: co se změnilo → logy → zúžit na druh problému → správný nástroj → jedna změna → ověřit.


Failure modes — časté omyly při ladění

  • Hádání místo měření. „Asi to bude tím…" a rovnou měníš config. Nejdřív se zeptej systému (status, logy, ss, lsof) — důkaz > dojem.
  • Přeinstalace místo diagnózy. Windows reflex „přeinstaluju to". V Linuxu skoro vždy najdeš proč to spadlo a opravíš jednu věc — přeinstalace tě jen připraví o příležitost pochopit příčinu.
  • kill -9 naslepo. SIGKILL nedá procesu šanci uklidit (dopsat soubor, uvolnit zámek) a na D state ani zombie stejně neplatí. Nejdřív slušný kill (SIGTERM), -9 až jako poslední možnost.
  • Ignorování timestampů. Čteš úplný konec logu a řešíš řádek, který je následek. Najdi čas, kdy problém nastal, a čti kolem něj — první skutečná chyba bývá dřív.
  • Měnění víc věcí naráz. Přepíšeš config, smažeš cache a restartuješ zároveň → zajede to → nevíš co pomohlo. Jedna změna, jeden test, jeden závěr.

🛠️ Cvičení

  1. Přečti si, proč něco spadlo. Vyber běžící službu (systemctl list-units --type=service), zjisti její stav přes systemctl status <služba> a pak si zobraz její log přes journalctl -u <služba>. Najdi v logu timestamp posledního startu. Proč je lepší číst log služby než celý journalctl?
  2. Kdo drží port? Spusť ss -tulpn a najdi nějakou naslouchající službu (třeba :22 SSH). Pak zjisti stejný proces přes sudo lsof -i :22. Porovnej, co ti oba nástroje řeknou o PID a jménu procesu.
  3. Najdi žrouta místa. Pomocí df -h zjisti, který oddíl máš nejvíc zaplněný. Přesuň se do jeho adresáře a přes du -sh ./* | sort -h najdi tři největší položky. Zkontroluj i df -i — máš dost inodů?
  4. Nahlédni do běžícího procesu. Vyber PID nějakého svého procesu (ps aux) a podívej se do /proc/<PID>/: co je ve status (řádek State)? Co je v fd/? Sestav z cmdline, čím byl spuštěn.
  5. Odpoj „busy" adresář. Otevři terminál, cd do nějakého adresáře (klidně /tmp), a z jiného terminálu zjisti přes lsof +D /tmp, že tam ten shell „drží". Pak cd ~ a ověř, že už tam nefiguruje.
Náčrt řešení — rozbal, až si cvičení zkusíš sám
  1. Log služby — stav zjistíš systemctl status <služba> (uvidíš active/failed + posledních pár řádků logu), plný log journalctl -u <služba> (přidej -e pro skok na konec, -b jen od posledního bootu). Timestamp startu je řádek Started …. Číst log konkrétní služby je lepší, protože celý journalctl míchá zprávy ze všech procesů — v šumu bys příčinu hledal těžko. Pozor: první skutečná chyba bývá výš, ne úplně na konci.
  2. Kdo drží portss -tulpn vypíše všechny naslouchající sockety (-t TCP, -u UDP, -l listening, -p proces, -n čísla); u portu 22 uvidíš sshd a jeho PID. sudo lsof -i :22 řekne totéž jinou cestou (proces + PID + typ socketu). Bez sudo bys u cizích procesů PID/jméno neviděl. Pozor: dva programy nemůžou držet stejný port — odtud „address already in use".
  3. Žrout místadf -h ukáže procenta zaplnění po oddílech; do nejplnějšího se přesuň a du -sh ./* 2>/dev/null | sort -h seřadí položky od nejmenší po největší (viník je dole). df -i ukazuje inody — můžeš mít volné místo, ale vyčerpané inody (spousta malých souborů) a zápis stejně selže. Pozor: smazaný soubor, který drží běžící proces, místo neuvolní, dokud proces neskončí (najdeš přes lsof).
  4. Do procesu/proc/<PID>/status má řádek State: (např. S (sleeping), R (running), D = čeká na I/O, Z = zombie) a PPid: (rodič); fd/ je seznam otevřených souborů a socketů (symlinky na cíle); cmdline obsahuje spouštěcí příkaz s argumenty oddělenými \0 (čitelně: tr '\0' ' ' < /proc/<PID>/cmdline). Pozor: /proc je virtuální okno jádra, ne soubory na disku.
  5. Busy adresářlsof +D /tmp vypíše procesy, které mají něco otevřené pod tou cestou, včetně shellu, jehož pracovní adresář (cwd) tam stojí. Proto umount hlásí target is busy. Jakmile uděláš cd ~, shell už /tmp nedrží a v lsof zmizí. Pozor: umount -l (lazy) problém neřeší, jen odloží — vždy nejdřív najdi a uvolni viníka.

🧠 Otázky & odpovědi

Jak mám postupovat, když něco přestane fungovat?

Systematicky, ne hádáním. Nejdřív se zeptej co se změnilo — instaloval jsem něco, měnil config, restartoval stroj, došlo místo? Pak přečti logy (journalctl -xe pro systém, dmesg -T pro jádro a hardware) a najdi v nich čas, kdy problém nastal — čti řádky kolem něj, ne jen konec. Teprve pak měň jednu věc a ověř efekt. Když měníš víc věcí naráz a zajede to, nevíš co pomohlo, a příště tápeš zas.

Port hlásí address already in use — jak zjistím kdo ho drží?

Zjistíš, který proces na portu naslouchá, a rozhodneš, jestli tam patří. Použij sudo ss -tulpn (vypíše naslouchající TCP/UDP sockety i s PID a jménem procesu) nebo cíleně sudo lsof -i :8080. Oba ti dají PID. Pokud je to legitimní služba, vypni ji přes systemctl; pokud je to zbloudilý proces, slušně ho ukonči přes kill <PID> a -9 až když měkký kill nezabere. sudo je nutné, jinak u cizích procesů PID neuvidíš a budeš si myslet, že port nikdo nedrží.

Proč nejde odpojit disk — umount hlásí target is busy?

Protože něco má na tom mountu otevřený soubor nebo tam stojí pracovním adresářem. Najdi viníka přes sudo lsof +D /mnt/disk (nebo sudo fuser -vm /mnt/disk) — dostaneš PID a jméno procesu. Nejčastější banalita je, že tam stojíš ty sám terminálem (cd do toho adresáře) — stačí cd ~ a odpojení projde. Jinak ty procesy ukonči a zkus znovu. umount -l (lazy) není řešení, jen odklad — problém tím jen schováš.

Proces nejde zabít ani přes kill -9 — co s tím?

Znamená to jednu ze dvou věcí a kill -9 naslepo nepomůže ani u jedné. Buď je to zombie (stav Z) — proces už doběhl, jen si rodič nevyzvedl jeho návratový kód; kill neplatí, protože už neběží, takže musíš zabrat nebo probrat rodiče (PPid ve status). Nebo je proces ve stavu D (čeká na I/O, typicky pomalý či odumřelý disk nebo síťový mount) — jádro ho pustí, až I/O dopadne, a kill -9 na něj neplatí. Zjisti stav v cat /proc/<PID>/status a řeš příčinu, ne symptom.

Kdy použít strace a co mi vlastně řekne?

Když proces „visí" nebo se chová divně a chceš vidět, co zrovna dělá na úrovni jádra. sudo strace -p <PID> se připojí k běžícímu procesu a vypisuje jeho syscally v reálném čase. Když výpis stojí na read() nebo futex(), proces na něco čeká (data, zámek); když se řítí spousta openat(), něco horečně hledá (třeba chybějící soubor). Doplní ho /proc/<PID>/status (stav procesu), fd/ (co má otevřeno). Je to rozdíl mezi hádáním proč to visí a skutečným pohledem dovnitř.