Przejdź do treści
Lekcja 100 z 117

Kurs › Moduł 10 › Lekcja 100

Metoda rozwiązywania problemów

⏱ ok. 15 min czytania· moduł 10: Usługi i serwery

Po tej lekcji: kursant ma powtarzalną metodę na awarie, umie znaleźć przyczynę wolnego serwera, procesora na 100%, braku pamięci, pełnego dysku i pełnych i-węzłów, usługi, która nie wstaje, braku SSH i niedziałającej strony, a po wszystkim zapisuje zgłoszenie, zmianę i raport po awarii.

Wstęp

To cała treść zgłoszenia. Która strona? Od kiedy? Nie działa, czyli nie otwiera się, jest wolna, czy pokazuje błąd? Dla wszystkich, czy dla szefa na hotelowym Wi-Fi?

Tu są dwie drogi. Pierwsza: panika, restart wszystkiego, co się da, i modlitwa. Czasem nawet pomaga, tylko nikt potem nie wie dlaczego i za tydzień jest to samo. Druga: metoda. Ta lekcja jest o drugiej drodze. To lekcja setny, więc zbieramy w nim wszystko, co już umiesz, w jedną skrzynkę z narzędziami.

Metoda: jak lekarz na SOR-ze

Dobry lekarz nie zaczyna od operacji. Zaczyna od wywiadu, potem bada, stawia diagnozę, leczy i sprawdza, czy pomógł. Ty robisz to samo:

  1. Wywiad. Co dokładnie nie działa, od kiedy, kogo dotyczy i, najważniejsze pytanie w całej informatyce: co się ostatnio zmieniło? Aktualizacja, nowy wpis w konfiguracji, nowy kolega z uprawnieniami roota.
  2. Odtwórz objaw sam. „Nie działa” z ust użytkownika i błąd, który widzisz na własne oczy, to dwie różne rzeczy.
  3. Hipoteza od najprostszych. Najpierw sprawdzasz, czy kabel jest włożony, a dopiero potem błąd w jądrze.
  4. Jedna zmiana naraz. Zmienisz trzy rzeczy i zadziała, to nie wiesz, która pomogła, a dwie pozostałe mogą być nową bombą z opóźnionym zapłonem.
  5. Sprawdź, że naprawiłeś. Ten sam test, który pokazał awarię, teraz ma pokazać, że jej nie ma.
  6. Zapisz. Co, kiedy, czym znalazłeś, co zrobiłeś.

Na zasoby jest jeszcze ściąga od Brendana Gregga, inżyniera od wydajności, nazywa się metoda USE: dla każdego zasobu, czyli procesora, pamięci, dysku i sieci, sprawdź wykorzystanie (ile czasu był zajęty), nasycenie (ile pracy czeka w kolejce) i błędy. Cztery zasoby, trzy pytania, dwanaście kratek do odhaczenia.

Pierwsza minuta na serwerze

Zanim zaczniesz zgadywać, zrób obchód. Te komendy znasz z poprzednich modułów:

uptime
systemctl --failed
journalctl -p err -b --no-pager | tail -20
free -h
df -h
df -i
top
ss -tlnp

Od jak dawna działa i jakie ma obciążenie, co się wywróciło, jakie błędy są w dzienniku od startu, pamięć, miejsce, i-węzły, kto je procesor, kto czego słucha. Minuta pracy, a w połowie przypadków wiesz już, co się stało.

Serwer jest wolny

Najpierw uptime:

 02:49:25 up 12 min,  0 user,  load average: 1.21, 0.68, 0.35

Trzy liczby to średnie obciążenie z 1, 5 i 15 minut. Load average to kolejka do kas w Biedronce: ile osób stoi albo jest obsługiwanych. Ale sama liczba nic nie mówi, jeśli nie wiesz, ile jest kas. Liczbę kas daje nproc. Load 4 na serwerze z 16 rdzeniami to spacer. Load 4 na dwóch rdzeniach to kolejka do wyjścia ze sklepu. Trzy liczby czytasz jak trend: 1.21, 0.68, 0.35 to „coś się właśnie zaczęło”.

Potem vmstat, który w jednej linijce mówi, kto jest wąskim gardłem:

vmstat 1 3
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 1  0      0 7061680  74940 749932    0    0     0     0  869  784  3 50 47  0  0  0
  • r — procesy w kolejce do procesora. Stale więcej niż rdzeń: brakuje procesora.
  • b — procesy zablokowane, zwykle czekające na dysk.
  • si, so — swap. Jak nie są zerami, brakuje pamięci i system przekłada rzeczy do piwnicy.
  • wa — procesor czeka na dysk. Kucharz z patelnią czeka na ziemniaki z piwnicy.
  • st — hiperwizor zabiera Ci procesor na sąsiadów.

Jak winny jest dysk, iostat -x 1 z pakietu sysstat. Patrzysz na %util, czyli jak długo dysk był zajęty, i na r_await oraz w_await, czyli ile milisekund trwa odczyt i zapis. A historia z wczoraj? sar z lekcji o monitorowaniu.

CPU 100%

Symulujemy:

yes > /dev/null &
ps -eo pid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head -3
  PID USER     %CPU %MEM     ELAPSED CMD
14782 root     97.3  0.0       00:05 yes

Jest winowajca, PID, właściciel, od kiedy działa. W top to samo na żywo, sortowanie po procesorze to P. I teraz pytanie, które odróżnia admina od kogoś z kill -9 w ręku: czy to jest normalne? Kompresja kopii o 2:00 w nocy może jeść procesor, ma prawo. Proces PHP, który od trzech dni stoi na 100%, to raczej pętla bez wyjścia. A proces o dziwnej nazwie z /tmp, który je procesor, to kopacz kryptowalut i właśnie zmieniłeś zgłoszenie z „wydajność” na „bezpieczeństwo”.

Koniec zabawy: kill 14782. Najpierw grzeczna prośba o zamknięcie lokalu, SIGTERM. Wyłączenie prądu, SIGKILL, dopiero gdy grzecznie nie działa.

Brak pamięci

free -h
ps -eo pid,user,%mem,rss,cmd --sort=-%mem | head -5
journalctl -k | grep -i -E 'out of memory|oom'

W free patrzysz na kolumnę available, nie free. Linux używa wolnej pamięci na podręczną pamięć plików i oddaje ją na żądanie, więc małe free to nie alarm.

Gdy pamięci naprawdę zabraknie, jądro wzywa OOM killera. To bramkarz w przepełnionym klubie: wybiera jednego gościa, zwykle tego, który zajmuje najwięcej miejsca, i wyprowadza go bez dyskusji. W dzienniku jądra zostaje wpis „Out of memory: Killed process” z numerem i nazwą ofiary. Klasyczny objaw: „baza danych sama się wyłączyła w nocy”. Sama się nie wyłączyła, wyprowadził ją bramkarz.

Co dalej: znaleźć żarłoka, sprawdzić, czy rośnie stale (wyciek pamięci, wtedy sar -r z historii pokaże równą rosnącą linię), ograniczyć mu pamięć w usłudze systemd (MemoryMax=), dodać RAM albo swap. Swap nie przyspiesza, ale daje czas, zanim bramkarz zacznie wyprowadzać.

Pełny dysk

df -h
sudo du -xh --max-depth=1 / | sort -h | tail

du -x nie wchodzi na inne systemy plików, więc nie liczy /proc ani zamontowanych dysków. Schodzisz piętro niżej w największy folder i powtarzasz. Zwykle trafiasz na /var/log, /var/lib/docker albo czyjąś kopię bazy „na chwilę” sprzed dwóch lat.

A teraz zagadka, na której wyłożył się każdy admin. Zrobiłem mały dysk 96 MB i proces, który pisze do logu 80 MB. Kasuję log:

rm mnt/app.log
df -h mnt
du -sh mnt
/dev/loop1       96M   81M  8.7M  91% /home/eryk/mnt
20K	mnt

Plik skasowany, du mówi 20 KB, a df dalej 91%. Kto kłamie? Nikt. Proces dalej ma ten plik otwarty. rm zdjął tylko tabliczkę z drzwi, ale w środku ktoś dalej mieszka i płaci czynsz. Szukamy lokatorów bez tabliczki:

sudo lsof -nP +L1
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NLINK NODE NAME
sleep   15295 root    3w   REG    7,1 83886080     0   12 /home/eryk/mnt/app.log (deleted)

+L1 to „pliki z liczbą dowiązań mniejszą niż 1”, czyli skasowane, ale otwarte. NLINK 0, dopisek (deleted). Dwie naprawy. Elegancka: restart tej jednej usługi, która trzyma plik. Awaryjna, gdy restartu zrobić nie możesz: obcięcie pliku przez deskryptor procesu:

ls -l /proc/15703/fd/3
: > /proc/15703/fd/3
df -h mnt
l-wx------ 1 root root 64 Nov  4 02:49 /proc/15703/fd/3 -> /home/eryk/mnt/app.log (deleted)
/dev/loop1       96M   24K   89M   1% /home/eryk/mnt

Na przyszłość logów działających programów się nie kasuje, tylko obcina (: > plik) albo zostawia logrotate. Dziennik systemd przycinasz jego własnym narzędziem: sudo journalctl --vacuum-size=500M.

Pełne i-węzły

Drugi podstęp. Dysk ma miejsce, a system mówi „nie”:

touch: cannot touch 'mnt/nowy.txt': No space left on device
df -h mnt
df -i mnt
/dev/loop1       96M   44K   89M   1% /home/eryk/mnt
/dev/loop1       1008  1008     0  100% /home/eryk/mnt

Miejsca 89 MB, i-węzłów zero. I-węzeł to karta w katalogu bibliotecznym: każdy plik potrzebuje jednej karty, nawet pusty. Szuflada z kartami się skończyła, choć półki stoją puste. Kto zjadł karty?

sudo du --inodes -x -d1 /var | sort -n | tail
1	./lost+found
997	./sesje
999	.

Prawie zawsze winne są miliony malutkich plików: sesje PHP, kolejka poczty, pamięć podręczna aplikacji, której nikt nie sprząta. Usuwasz, a potem ustawiasz sprzątanie, na przykład find z -mtime w cronie.

Usługa nie wstaje

Trzy komendy, zawsze w tej kolejności:

systemctl status nginx
journalctl -u nginx -b --no-pager | tail -30
sudo nginx -t

Status powie, że nie działa i z jakim kodem wyjścia. Dziennik powie dlaczego. Test konfiguracji powie, w której linijce. Każdy poważny program ma taki test: nginx -t, apachectl configtest, sshd -t, named-checkconf, promtool check config. Literówka w SSH:

sudo sshd -t
/etc/ssh/sshd_config: line 132: Bad configuration option: PermitRootLogn
/etc/ssh/sshd_config: terminating, 1 bad configuration options

I w Apache:

AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/sklep.conf:
Invalid command 'Listne', perhaps misspelled or defined by a module not included in the server configuration

Pozostałe typowe powody, gdy konfiguracja jest dobra: port zajęty przez inny proces (ss -tlnp | grep :80), brak uprawnień do pliku albo folderu, pełny dysk, a na RHEL-u SELinux, który nie pozwala usłudze czytać pliku z nietypowego miejsca. Tam zaglądasz do sudo ausearch -m AVC -ts recent. Jeśli status mówi start-limit-hit, usługa padała tyle razy, że systemd przestał próbować. Po naprawie najpierw systemctl reset-failed nginx, potem start.

Brak SSH

SSH to most. Jak most się zawalił, sprawdzasz po kolei, od swojego brzegu:

  1. Czy serwer w ogóle żyje w sieci? ping serwer (pamiętaj, że zapora może ping blokować).
  2. Czy port odpowiada? nc -zv serwer 22. „Connection refused” to dobra wiadomość w złym opakowaniu: serwer żyje i odpowiada, tylko na tym porcie nikt nie słucha, więc sshd nie działa. Jeśli za to nc czeka i kończy się limitem czasu, odpowiedź zjadła zapora albo sieć po drodze. Odmówienie przez okienko i brak okienka to dwie różne diagnozy.
  3. Jeśli port odpowiada, a logowanie nie działa: ssh -v eryk@serwer pokaże, na którym etapie rozmowa się sypie. Klucz odrzucony? Złe uprawnienia ~/.ssh po stronie serwera.
  4. Dalej już tylko z konsoli: konsola w panelu chmury, virsh console na KVM, monitor i klawiatura w serwerowni. Tam: systemctl status sshd [RHEL] lub ssh [Ubuntu], sshd -t, ss -tlnp | grep :22, zapora (firewall-cmd --list-all albo ufw status) i dziennik logowań: /var/log/secure [RHEL] albo /var/log/auth.log [Ubuntu].

I dlatego od modułu o SSH powtarzam: zmieniasz sshd_config albo zaporę, to druga sesja zostaje otwarta, a sshd -t przed restartem.

Strona nie działa

Wracamy do szefa z trzema wykrzyknikami. Idziesz warstwami, od dna:

getent hosts sklep.example.com
nc -zv sklep.example.com 443
curl -sI https://sklep.example.com
sudo ss -tlnp | grep -E ':80|:443'
sudo tail -20 /var/log/nginx/error.log

Nazwa, port, odpowiedź, czy ktoś słucha, co mówi dziennik. Najwięcej mówi kod odpowiedzi w pierwszej linijce curl -I. Postawiłem małą stronę testową:

HTTP/1.0 200 OK
HTTP/1.0 404 File not found
curl: (7) Failed to connect to 127.0.0.1 port 8099 after 0 ms: Couldn't connect to server
ObjawCo znaczyGdzie szukać
nie ma odpowiedzi, Couldn't connectnikt nie słucha albo zaporass -tlnp, systemctl status, zapora
404serwer działa, pliku nie maDocumentRoot, ścieżki, wdrożenie
403jest, ale nie wolnouprawnienia plików, SELinux httpd_sys_content_t
500aplikacja się wywróciłalog błędów serwera i aplikacji
502, 504reverse proxy nie dogadał się z backendemczy backend działa, proxy_pass, PHP-FPM
certyfikat wygasłprzeglądarka krzyczy, curl teżopenssl s_client, certbot

A jeśli u Ciebie działa, a u szefa nie? Wtedy problem jest po drodze: DNS, który jeszcze pamięta stary adres, Wi-Fi w hotelu albo jego przeglądarka. Też diagnoza.

Zgłoszenie, zmiana, dokumentacja, raport po awarii

Awaria naprawiona. Teraz część, którą wszyscy pomijają, a która odróżnia admina od strażaka.

Zgłoszenie (ticket). Każda awaria ma numer w systemie zgłoszeń, czy to Jira, czy GLPI, czy cokolwiek używa firma. W zgłoszeniu zapisujesz na bieżąco: kto zgłosił, objaw, co sprawdziłeś i co z tego wyszło. Wpis „sprawdziłem, nie działa” jest bezużyteczny. Wpis „nc -zv web01 443: Connection refused, nginx nie działa” jest złoty.

Zmiana (change). Każda zmiana na produkcji, także naprawa, ma pięć rzeczy: co zmieniasz, dlaczego, jak sprawdzisz, że działa, jak to cofniesz i kiedy. Plan wycofania to nie biurokracja. To spadochron, który pakujesz na ziemi, a nie w powietrzu. Do tego kopia pliku przed edycją (cp -a plik plik.bak-$(date +%F)) albo, jeszcze lepiej, konfiguracja w Gicie, o którym za chwilę w następnym module.

Dokumentacja. Jak coś naprawiałeś drugi raz, zapisz instrukcję. Trzeci raz robi ją kolega o trzeciej w nocy, z Twoją kartką w ręku zamiast Twojego numeru telefonu.

Raport po awarii (postmortem). Po większej awarii piszesz krótki dokument:

AWARIA: sklep niedostępny, 2026-11-04 08:12-09:05 (53 min)
WPŁYW: klienci nie mogli składać zamówień
OŚ CZASU:
  08:12 monitoring: strona nie odpowiada
  08:20 zgłoszenie od klienta
  08:31 diagnoza: dysk / pełny w 100%, nginx nie może pisać logów
  08:44 przyczyna: usunięty log aplikacji dalej otwarty (lsof +L1)
  09:05 restart aplikacji, miejsce odzyskane, strona działa
PRZYCZYNA: logi aplikacji kasowane ręcznie zamiast rotacji
CO ZADZIAŁAŁO: alarm z monitoringu przyszedł przed klientem
CO NIE: alarm dysku na 90% wyłączony od miesiąca
DZIAŁANIA:
  [ ] logrotate dla aplikacji            - Eryk, do 2026-11-08
  [ ] włączyć alarm dysku na 80% i 90%   - Eryk, do 2026-11-06

Najważniejsza zasada raportu: szukamy przyczyny, nie winnego. Jeśli za każdą pomyłkę ktoś dostaje po głowie, ludzie zaczynają ukrywać pomyłki, a ukryta pomyłka wraca jako większa awaria. Pytanie „dlaczego system pozwolił to zrobić?” jest lepsze niż „kto to zrobił?”. Odpowiadasz „dlaczego” kilka razy z rzędu, aż dojdziesz do czegoś, co da się naprawić na stałe.

Jak to działa pod spodem

Load average w Linuksie liczy procesy w stanie R, czyli gotowe do pracy lub pracujące, oraz w stanie D, czyli w nieprzerywalnym oczekiwaniu, zwykle na dysk albo zasoby sieciowe jak NFS. Dlatego wysoki load przy pustym procesorze to często dysk albo zawieszony udział sieciowy, a nie procesor. Kolejka w Biedronce rośnie też wtedy, gdy kasjerka czeka na kierownika z kluczykiem.

Plik w Linuksie to i-węzeł z danymi i jedna lub więcej nazw, czyli dowiązań. rm usuwa nazwę i zmniejsza licznik dowiązań. Jądro zwalnia i-węzeł i bloki dopiero wtedy, gdy licznik dowiązań spadnie do zera i żaden proces nie ma pliku otwartego. Dlatego df, który pyta system plików o wolne bloki, widzi zajęte miejsce, a du, który chodzi po nazwach w folderach, go nie widzi. Otwarte pliki procesu jądro pokazuje w /proc/PID/fd/ i to tamtędy lsof je znajduje.

OOM killer wybiera ofiarę według punktacji, którą widzisz w /proc/PID/oom_score. Najwięcej punktów ma ten, kto zajmuje najwięcej pamięci. Usługa może się osłonić, ustawiając OOMScoreAdjust= w pliku jednostki systemd, ale nie rób z tego nawyku. Ktoś musi wyjść z klubu, a jak wszyscy mają karty VIP, bramkarz wyprowadzi ochroniarza.

Ściąga flag

KomendaFlagaCo robi
uptimeczas pracy i load average 1/5/15 min
vmstat1 5procesor, pamięć, swap, dysk co sekundę
iostat-x 1obciążenie dysków, %util, r_await, w_await
ps-eo ... --sort=-%cpu / --sort=-%memnajwięksi zjadacze procesora i pamięci
systemctl--failedusługi, które padły
journalctl-p err -bbłędy od startu
journalctl-u USŁUGA -bdziennik jednej usługi
journalctl-kdziennik jądra (OOM, dyski)
journalctl--vacuum-size=500Mprzycięcie dziennika
df-h / -imiejsce / i-węzły
du-xh --max-depth=1kto zajmuje miejsce, bez innych dysków
du--inodes -x -d1kto zajmuje i-węzły
lsof-nP +L1skasowane, ale otwarte pliki
nc-zv HOST PORTczy port odpowiada
curl-sI / -vkod odpowiedzi / cała rozmowa
ss-tlnpkto słucha na jakim porcie
sshd, nginx-ttest konfiguracji

⚠ Typowy błąd

Literówka w konfiguracji Apache, a admin od razu:

sudo systemctl restart apache2
Job for apache2.service failed because the control process exited with error code.
See "systemctl status apache2.service" and "journalctl -xeu apache2.service" for details.

Przed chwilą strona działała na starej konfiguracji. restart zatrzymał działający serwer i nie umiał uruchomić nowego. Awaria zrobiona własnoręcznie, w trzy sekundy. Systemd uprzejmie podpowiada, gdzie szukać, i warto posłuchać: status i journalctl -xeu pokażą ten sam AH00526 z numerem linijki.

Zasada: najpierw test, potem reload. sudo apachectl configtest && sudo systemctl reload apache2. && sprawi, że przeładowanie wykona się tylko wtedy, gdy test przejdzie, a reload w Apache i Nginx wczytuje nową konfigurację bez wyłączania tego, co działa. Drugi wariant tego błędu to restart jako pierwsza reakcja na każdą awarię. Czasem pomaga, ale zawsze kasuje dowody: stan procesów, otwarte pliki, zapełnioną pamięć. Zanim zrestartujesz, zrób obchód z pierwszej minuty i zapisz, co widzisz.

✎ Zadanie

Na maszynie wirtualnej zepsuj i napraw trzy rzeczy: 1) zapełnij dysk plikiem, który usuniesz, ale proces będzie go dalej trzymał otwarty; 2) zrób literówkę w konfiguracji Apache i zrestartuj usługę; 3) zamknij port 80 w zaporze. Dla każdej awarii zanotuj, jakimi komendami ją znalazłeś.

Pokaż rozwiązanie

Trzy awarie, trzy śledztwa. Najpierw dysk zapchany plikiem, który „nie istnieje”.

dd if=/dev/zero of=wielki.log bs=1M count=200
(exec 3<wielki.log; sleep 600) &    # proces trzyma plik otwarty
rm wielki.log
df -h /                             # miejsce dalej zajęte
sudo lsof +L1                       # otwarte pliki bez nazwy na dysku
sleep   25945  eryk  3r  REG  254,0 209715200  0  952579 /home/eryk/wielki.log (deleted)

+L1 znaczy: pokaż pliki, które mają mniej niż jeden link, czyli zero nazw. Plik usunięty z katalogu, ale proces dalej trzyma go za rękę, więc jądro nie zwalnia miejsca. du go nie widzi, df go liczy. Naprawa: zrestartować proces albo, gdy nie wolno, opróżnić plik przez jego uchwyt:

sudo ls -l /proc/25945/fd | grep deleted
sudo truncate -s 0 /proc/25945/fd/3     # albo:  : > /proc/25945/fd/3

U mnie po tym lsof pokazał rozmiar 0. Druga awaria, literówka w Apache:

sudo systemctl restart apache2          # [RHEL] httpd
sudo systemctl status apache2
sudo journalctl -xeu apache2
sudo apachectl configtest
AH00526: Syntax error on line 12 of /etc/apache2/sites-enabled/000-default.conf:
Invalid command 'DokumentRoot', perhaps misspelled or defined by a module not included in the server configuration

Apache podaje plik i numer linijki. Mało który program jest tak uprzejmy. Trzecia: zamknięty port 80. Z drugiej maszyny curl http://serwer/ wisi i kończy się po czasie. Na serwerze:

sudo ss -ltnp | grep ':80 '                 # czy Apache w ogóle słucha?
curl -I http://localhost/                   # lokalnie działa?
sudo ufw status numbered                    # [Ubuntu]
sudo firewall-cmd --list-all                # [RHEL]

Jeśli lokalnie działa, a z zewnątrz nie, to winny jest ochroniarz z listą okienek, czyli zapora. Notatki z tych trzech śledztw to Twój pierwszy raport po awarii. A dziś dowiesz się, gdzie takie notatki i skrypty trzymać, żeby nie zginęły.

Co dalej

W raporcie po awarii padło słowo, które zmienia życie admina: Git. W następnej lekcji zaczynamy nowy moduł: Git dla admina, czyli historia każdej zmiany w konfiguracji i skryptach, commit, diff, gałęzie, klucz SSH do GitLaba i GitHuba, a do tego etckeeper, który pilnuje /etc za Ciebie.