5. týždeň

Týždeň 5 – Cvičenie: Nechaj systém pracovať za teba

Cvičenie

Ciele

  1. Pozri sa, čo na tvojom systéme beží a čo ho zaťažuje
  2. Vyrob si zombie proces a zbav sa ho
  3. Over rozdiel medzi slušným a tvrdým ukončením
  4. Napíš si vlastnú službu
  5. Nechaj ju spadnúť a sleduj, čo urobí systém
  6. Naplánuj úlohu klasickým plánovačom
  7. Naplánuj tú istú úlohu časovačom a porovnaj ich

Úvod

Na prednáške sme hovorili o procesoch, o tom, ako sa z programu stane služba, a o dvoch spôsoboch plánovania. Dnes si to postavíš.

Na konci budeš mať vlastnú službu, ktorá sa po páde sama zdvihne, a tú istú úlohu naplánovanú dvoma spôsobmi — takže uvidíš rozdiel na vlastných výstupoch, nie z tabuľky.

Všetko robíš vo svojej virtuálke. Celé to trvá zhruba hodinu a pol. Nie je to za body, mentori to s tebou preberú.

Postup

Krok 1: Pozri sa, čo na tvojom systéme beží a čo ho zaťažuje

ps aux --sort=-%cpu | head -10
ps aux --sort=-%rss | head -10
uptime
nproc

Úloha 1.1

Zapíš si tri procesy s najväčšou spotrebou procesora a tri s najväčšou spotrebou pamäte. Potom si zapíš tri čísla priemerného zaťaženia a počet jadier.

Úloha 1.2

Odpovedz si na otázku, či je tvoj systém zaťažený. Nestačí sa pozrieť na číslo — porovnaj ho s počtom jadier a porovnaj aj tie tri hodnoty navzájom.

Teraz si pozri strom procesov:

pstree -p | head -30

Úloha 1.3

Nájdi v strome svoje vlastné prihlásenie. Zapíš si reťaz od procesu číslo jeden až po shell, v ktorom práve píšeš.

Poznámka: Pekne v ňom vidno, prečo program spustený z terminálu skončí spolu s terminálom. Je jeho dieťaťom.

Krok 2: Vyrob si zombie proces a zbav sa ho

Zombie sa dá vyrobiť zámerne. Stačí program, ktorý si neprevezme výsledok svojho dieťaťa.

# zombie.py
import os, time

if os.fork() == 0:
    os._exit(0)          # dieta hned skonci
else:
    print("rodic ma PID", os.getpid())
    time.sleep(120)      # rodic si vysledok neprevezme
python3 zombie.py &
ps aux | grep -w Z
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'

Úloha 2.1

Nájdi zombie proces vo výpise. Zapíš si jeho číslo, číslo jeho rodiča a písmeno, ktorým je označený jeho stav.

Úloha 2.2

Teraz skús zombie ukončiť a zapíš si, čo sa stalo:

kill -9 <cislo zombie procesu>
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'

Úloha 2.3

Zombie je stále tam. Ukonči teraz rodiča a pozri sa znova.

kill <cislo rodica>
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'

Poznámka: Zombie zmizol až s rodičom. Nedá sa ukončiť, lebo už skončil — drží len svoje číslo, kým si rodič neprevezme výsledok. A keď rodič skončí, dieťa si adoptuje proces číslo jeden, ktorý výsledky preberá spoľahlivo.

Krok 3: Over rozdiel medzi slušným a tvrdým ukončením

Napíš si program, ktorý na slušnú žiadosť zareaguje a po sebe uprace.

# slusny.py
import signal, sys, time

def uprac(signum, frame):
    with open("uprataneb.txt", "w") as f:
        f.write("stihol som po sebe upratat\n")
    sys.exit(0)

signal.signal(signal.SIGTERM, uprac)
print("bezim, PID", __import__("os").getpid())
while True:
    time.sleep(1)
rm -f uprataneb.txt
python3 slusny.py &
kill %1
cat uprataneb.txt

Úloha 3.1

Zapíš si, či súbor vznikol.

Teraz to isté natvrdo:

rm -f uprataneb.txt
python3 slusny.py &
kill -9 %1
cat uprataneb.txt

Úloha 3.2

Zapíš si, či súbor vznikol tentoraz, a vysvetli si, prečo je ten rozdiel taký, aký je.

Poznámka: Predstav si, že namiesto zápisu do súboru ide o databázu, ktorá má dokončiť rozpísanú transakciu. Práve preto sa vždy začína slušnou žiadosťou.

Krok 4: Napíš si vlastnú službu

Najprv skript, ktorý bude služba spúšťať. Nech robí niečo, čo je vidieť.

# /usr/local/bin/hliadka.sh
#!/bin/bash
while true; do
    echo "hliadka: $(date '+%H:%M:%S'), volna pamat $(free -m | awk '/^Mem:/{print $7}') MB"
    sleep 10
done
sudo nano /usr/local/bin/hliadka.sh
sudo chmod +x /usr/local/bin/hliadka.sh
/usr/local/bin/hliadka.sh        # over, ze funguje, potom ukonci cez Ctrl a C

Teraz definícia služby:

sudo nano /etc/systemd/system/hliadka.service
[Unit]
Description=Hliadka volnej pamate
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/hliadka.sh
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now hliadka
systemctl status hliadka
journalctl -u hliadka -f        # ukonci cez Ctrl a C

Úloha 4.1

Zapíš si z výpisu stavu dva riadky — ten, ktorý hovorí o autoštarte, a ten, ktorý hovorí o aktuálnom stave.

Úloha 4.2

Odpovedz si, prečo sa výstup skriptu objavuje v systémovom denníku, hoci ho skript len vypisuje na obrazovku.

Krok 5: Nechaj ju spadnúť a sleduj, čo urobí systém

systemctl show hliadka -p MainPID
sudo kill -9 <to cislo>
sleep 8
systemctl status hliadka

Úloha 5.1

Zapíš si, či sa služba znova spustila a za ako dlho. Porovnaj to s hodnotou, ktorú si nastavil v definícii.

Teraz si vyskúšaj, čo sa stane pri trvalej chybe:

sudo sed -i 's|^ExecStart=.*|ExecStart=/usr/local/bin/neexistuje.sh|' /etc/systemd/system/hliadka.service
sudo systemctl daemon-reload
sudo systemctl restart hliadka
sleep 30
systemctl status hliadka
journalctl -u hliadka -n 20

Úloha 5.2

Zapíš si, do akého stavu sa služba dostala a čo o tom hovorí denník.

Poznámka: Systém to po niekoľkých neúspešných pokusoch vzdá a nechá službu v stave zlyhania. Je to zámer — nekonečné pokúšanie by len zaťažovalo stroj a nikto by si chyby nevšimol.

Úloha 5.3

Vráť pôvodnú cestu späť a over, že služba znova beží.

Krok 6: Naplánuj úlohu klasickým plánovačom

# /usr/local/bin/zapis.sh
#!/bin/bash
echo "$(date '+%F %T') spustene ako $(whoami), PATH=$PATH" >> /tmp/plan.log
sudo nano /usr/local/bin/zapis.sh
sudo chmod +x /usr/local/bin/zapis.sh
crontab -e

Pridaj riadok, ktorý úlohu spustí každú minútu:

* * * * * /usr/local/bin/zapis.sh
sleep 130
cat /tmp/plan.log

Úloha 6.1

Zapíš si, čo je v tom súbore. Všimni si najmä hodnotu posledného údaja a porovnaj ju s tým, čo ti vypíše echo $PATH v termináli.

Poznámka: Toto je ten rozdiel v prostredí, o ktorom bola reč na prednáške. Preto sa v naplánovaných úlohách vždy používajú úplné cesty.

Úloha 6.2

Odstráň riadok z plánovača, aby ti úloha nebežala do konca semestra.

Krok 7: Naplánuj tú istú úlohu časovačom a porovnaj ich

Potrebuješ dva súbory — službu a časovač s rovnakým menom.

sudo nano /etc/systemd/system/zapis.service
[Unit]
Description=Zapis riadku do denniku

[Service]
Type=oneshot
ExecStart=/usr/local/bin/zapis.sh
sudo nano /etc/systemd/system/zapis.timer
[Unit]
Description=Spusti zapis kazdu minutu

[Timer]
OnCalendar=*:0/1
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now zapis.timer
systemctl list-timers zapis.timer
sleep 130
journalctl -u zapis.service -n 10

Úloha 7.1

Zapíš si, čo ukáže výpis časovačov — kedy naposledy bežal a kedy pobeží nabudúce.

Úloha 7.2

Porovnaj, kde si hľadal výstup pri klasickom plánovači a kde pri časovači. Zapíš si ten rozdiel, je to hlavný dôvod, prečo časovače vznikli.

Teraz si over dobehnutie zameškanej úlohy:

sudo systemctl stop zapis.timer
# pockaj aspon tri minuty
sudo systemctl start zapis.timer
journalctl -u zapis.service -n 5

Úloha 7.3

Zapíš si, či sa úloha po zapnutí spustila okamžite, alebo až v najbližšom plánovanom čase. Potom si odpovedz, čo by sa stalo pri klasickom plánovači.

Úloha 7.4

Nakoniec časovač aj službu vypni, aby ti nebežali ďalej:

sudo systemctl disable --now zapis.timer

Záver

Čo budeš mať po tomto cvičení pripravené pre mentora:

  • Tri najnáročnejšie procesy podľa procesora a podľa pamäte, tri hodnoty priemerného zaťaženia a počet jadier.
  • Reťaz procesov od čísla jeden po svoj shell.
  • Číslo zombie procesu, číslo jeho rodiča a to, čo sa stalo pri pokuse ukončiť ho.
  • Výsledky oboch pokusov o ukončenie programu — či súbor vznikol a prečo.
  • Definíciu svojej služby a dva riadky z výpisu jej stavu.
  • Čas, za ktorý sa služba po páde sama zdvihla, a stav, do ktorého sa dostala pri trvalej chybe.
  • Obsah denníka z klasického plánovača a hodnotu prostredia, ktorú tam našiel.
  • Výpis časovača a výsledok pokusu o dobehnutie zameškanej úlohy.

A odpovede na tieto otázky:

  • Prečo sa zombie proces nedá ukončiť a čo s ním teda urobiť?
  • Prečo je rozdiel medzi slušným a tvrdým ukončením pri databáze vážnejší než pri tvojom skripte?
  • Prečo sa výstup tvojho skriptu objavil v systémovom denníku, hoci ho skript len vypisuje?
  • Čím sa líšilo prostredie naplánovanej úlohy od tvojho terminálu a čo z toho plynie pre písanie takých úloh?

Doplňujúce úlohy

Úloha A.1

Pridaj do svojej služby nastavenie, aby bežala pod vlastným účtom bez prihlasovacieho shellu namiesto roota. Over, že stále funguje, a zamysli sa, ktorý princíp z minulého týždňa si tým naplnil.

Úloha A.2

Doplň časovaču nastavenie náhodného odstupu a over vo výpise časovačov, že sa plánovaný čas posunul. Vysvetli, prečo sa to na jednom stroji neoplatí a na stovke strojov áno.