Hlavní navigace

Inicializace aneb Od Initu k Runitu (3)

Jan Molič 14. 7. 2004

Jsou situace, kdy člověk nemá chuť dělat nic pořádného. Alespoň já velmi rád odkládám důležité věci a místo nich se zabývám "náhradními", no a jednou se mi při té příležitosti podařilo nahradit init. Důvodem byla zvědavost a fakt, že mě ve 23:00 nenapadla smysluplnější činnost, kromě přesouvání adresářů sem a tam (tzv. uklízení disku).

Otázka: Jak je to složité?
Odpověď: Do tří ráno.

Jednou z možných náhrad SystemV initu je runit. Vybral jsem ho, poněvadž je postaven na daemontools, jejichž princip supervize služeb považuji za správný. Jako všechny DJB produkty i daemontools ovšem trpí podstatou své geniality – umí jen minimum funkcí, což by nevadilo, kdyby minimum nestanovil Dan Bernstein. V praxi totiž výsledek bývá na hranici použitelnosti, a asi právě proto napsal Gerrit Pape runit, jímž rozšířil funkce daemontools o runlevely, jednoduchou správu závislostí, a dopsal vlastní init, jinými slovy vytvořil „komplexní balíček“. Jistě, nemusíte nahrazovat celý init, lze použít jen neinitovou část runitu, ale proč být hotov už ve dvě, že…

Runit jako init

V čem se liší runit-init od běžného initu? Otázku bychom mohli položit spíše „Co runit-init neumí?“ Především neimplementuje funkce, které jsou zbytečné a které do initu nepatří, tedy nespouští getty a nereaguje na powerfail. Hmm, řeknete si. Když se mi nespustí getty, znamená to, že se nezobrazí konzole? Nikoli. Runit ke getty přistupuje jako k obyčejné službě, a proto ji nespouští v initové části, nýbrž až pomocí supervizního daemona. Powerfail lze zase řešit na běžné programové úrovni, třeba skriptem, na jehož konci bude init 0, čímž se počítač vypne.

Mám-li zjednodušeně shrnout, co initová část runitu dělá, pak vykonává po sobě tři skripty, jimž odpovídají tři fáze:

  1. bootování

    Spustí skript /etc/runit/1 (tak se jmenuje) a počká na jeho skončení. Jsou provedeny jednorázové akce, například připojení filesystémů, kontroly disků atd.

  2. běh

    O běh se stará /etc/runit/2. V něm se nastartuje obdoba svscanu z daemontools; to jest program, který prochází určitý adresář a spouští supervizního daemona nad všemi jeho podadresáři (prostě spustí služby). Skript by měl běžet, dokud nepožadujeme reboot či vypnutí systému, proto ho v případě pádu runit zrestartuje. Runit navíc reaguje na SIGINT (ctrl-alt-del), a pokud ho obdrží, vykoná skript /etc/runit/ctrlal­tdel, jehož obsah spočívá na nás.

  3. ukončení

    Je-li požadováno ukončení systému nebo fáze 2 doběhla bezchybně, runit spustí /etc/runit/3. V něm se vše povypíná, a když se to nechce vypnout, tak se to zabije. Nakonec počítač vypne či zrestartuje, a kupodivu vypne i můj notebook, což se běžnému initu nějak nedaří (s acpi ani s apm=poweroff).

Konfigurace runitu

Nachází se, světe div se, v /etc/runit (a tahle věta je klišé skoro každého článku). Adresář by měl vypadat takto:

/runsvdir
    /all
    /~current
    /default
    /~previous
    /single
*1
*2
*3
*ctrlaltdel
 reboot
 stopit

K podadresáři runsvdir, kterým runit řeší runlevely, se vrátíme později. Dále vidíme, kromě výše popsaných skriptů1,2,3 (fází běhu) a ctrlaltdel, soubory reboot a stopit, které nemají právo spouštění vlastníkem. Co se stane, když jim toto právo nastavíme a pošleme runitu signál CONT? Runit na událost zareaguje: nastavením práva na souboru stopit přejde do fáze 3, a pokud právo nastavíme i na soubor reboot, pak počítač zrestartuje namísto vypnutí.

Otázka: Z toho vyplývá, že budu počítač vypínat chmodem?
Odpověď: Jistěže ne, nahrazený /sbin/init udělá právě toto za nás, spustíme-li ho s argumenty

# init 0
# init 6

Runit s běžným initem

Runit je možné provozovat i nad standardním initem, tedy využít jen jeho části vzešlé z daemontools a rozšiřující je. V tom případě upravte /etc/inittab tak, aby sekce sysinit (či bootwait) odpovídala fázi 1, runlevely 2 až 5 fázi 2 a runlevely 1 a 6 fázi 3. Můžete přitom využít konfiguračních skriptů runitu (pozdější náhrada initu by měla spočívat už jen v záměně binárky /sbin/init). Výsledek by mohl vypadat takto (netestováno!):

id:3:initdefault:
si::sysinit:/etc/runit/1
lrun:2345:wait:/etc/runit/2
lhlt:0:wait:/etc/runit/3
lreb:6:wait:/etc/runit/3

Runit jako rozšířené daemontools

Dostáváme se k druhé části runitu, jež může fungovat i nad běžným initem. Co umí?

Služby

Spouštění služeb probíhá na stejném principu jako v daemontools – proces je supervizován, zůstává na popředí. Nepoužívají se žádné pid soubory, z principu jde o stoprocentně spolehlivou metodu a vlastního daemona napíšete i v bashi jako skript o dvou řádcích.

Stoprocentně spolehlivou? No to bych si dal, kdybych něco podobného tvrdil. „Z principu“ neznamená „v praxi“. Supervizní daemon je v tom ale nevinně, očekává totiž proces běžící na popředí. V některých případech se však proces sám detachuje a nelze to zvrátit, pak je nutné použít „wrapper“, který spolehlivost sníží, nicméně ne fatálně (wrapper například využije pid souborů úplně stejným způsobem, jako to dělají init.d skripty).

Supervizní daemon se v runitu nejmenuje supervise, ale runsv, a doplňuje kolekci poslatelných signálů o USR1 a USR2. runsv kromě skriptu run spouští i skript finish, a to následně, pokud finish v adresáři existuje.

K čemu se finish hodí? Při ukončení getty. Po každém přihlášení vytvoří getty záznam ve /var/log/utmp a /var/log/wtmp, na základě čehož příkaz who vypisuje, kdo je v systému přihlášen. Tento záznam getty neodstraňuje, ale odstraní ho až běžný init po jejím skončení. Smyslem souborů je zaznamenat, jak respawnovaná služba (getty) skončila.

Initová část runitu nic podobného neimplementuje, takže kdybychom záznamy neodstraňovali, v systému by „pracovalo“ čím dál víc uživatelů. Z tohoto důvodu má runit program utmpset, kterým záznam z obou souborů odstraníme – utmpset je spuštěn právě ve skriptu finish.

Runlevely

jsou řešeny pomocí podadresářů /etc/runit/run­svdir. Všechny služby se nalézají v podadresáři all, odkud jsou symlinkovány do ostatních podadresářů pojmenovaných podle runlevelů; běžící služby nalezneme v podadresáři current. Ukázalo se praktické symlinkovat current do /service; jednak v rámci kompatibility s daemontools, druhak díky uživatelskému pohodlí (cesta je kratší).

/runsvdir
    /all
    /~current
    /default
    /~previous
    /single

current může vypadat takto:

.
..
apache
atd
axfrdns
dcron
dnscache
getty-1
getty-2
getty-3
getty-4
getty-5
local
mysql
qmail-pop3d
qmail-send
qmail-smtpd
...

runsvdir (obdoba svscanu) pravidelně kontrolujecurren­t, a když objeví nový podadresář (službu), spustí nad ním supervizního daemona runsv. Ale pozor, funguje to i opačně – pokud podadresář z current zmizí, runsvdir službu vypne.

K přechodu do jiného runlevelu tedy stačí zaměnit celý adresář current za jiný. runsvdir pak nastartuje všechny služby, které předchozí current neobsahoval, a ukončí ty, které nejsou v novém, zbytek nechá běžet. Jakým způsobem provést záměnu? Jednoduše:current je symlink, nasměrujeme ho jinam. K tomu existuje nástroj runsvchdir, jenž navíc udělá z current previous, čímž umožní případné vrácení.

# runsvchdir default
# runsvchdir single
# runsvchdir previous

Skript druhé fáze inicializace (/etc/runit/2) nastaví current na default a spustí nad ním runsvdir.

runsvchdir default
runsvdir /etc/runit/runsvdir/current

Závislosti

Daemontools závislosti služeb vůbec neřeší a spouští je zároveň. DJB předpokládá, že služba spadne, nebude-li mít všechny potřebné prostředky. Supervizní daemon ji totiž vzápětí zrestartuje. Zdá se vám to divné? Možná, ale v praxi metoda funguje docela dobře. Problémy nastávají v případě, kdy se potřebných prostředků chronicky nedostává a služba se restartuje a restartuje… Restart přitom nemusí být nenáročný na CPU. Částečně lze problém řešit skriptem checkrespawn (viz předchozí díl), ale mnohem lépe pomocí svwaitup.

svwaitup počká, dokud vyjmenované služby neběží alespoň určitou dobu. Přidáme jej na začátek skriptu run. V následujícím příkladě apache i mysql musejí běžet minimálně tři sekundy

#!/bin/sh
cd /etc/runit/runlevels/all
svwaitup -s 3 ./mysql ./apache

Pomocí svwaitup nelze zaručit, aby se ukončením určité služby ukončily i všechny na ní závislé, případně se jejím restartem zrestartovaly. Osobně však nevidím důvod, proč restartovat služby závislé na síti při restartu sítě, jak to defaultně dělají initsystémy některých distribucí. Setkal jsem se snad jen s jedinou službou, která „nedostatek sítě“ nepřežila (Snort). Jinými slovy, v praxi nastávají dvě možnosti: buď závislá služba přežije, nebo spadne; v obou případech to nevadí, poněvadž ji runsv zrestartuje a skript run se opět zasekne naswvaitup.

Víc runit neumí. Jeho síla spočívá právě v jednoduchosti; proč vytvářet složité systémy, když nejsou potřeba? DJBware tím má, myslím, hodně společného s extrémním programováním.

Pokud jste dočetli až sem a stále máte chuť runit vyzkoušet, ukážeme si, jak na kompilaci.

Kompilace

Kompilace se nese v duchu DJBware, takže no configure, no make and no make install.
Stáhněte si runit-1.0.3.tar.gz, případně novější verzi ze stránek runitu, někam ji rozbalte a přesuňte se do podadresářerunit-1.0.3

# cd admin/runit-1.0.3/

kompilaci proveďte následujícím příkazem

# package/compile

a o úspěchu pošlete zprávu autorovi :-)

# mail pape-runit-1.0.3@smarden.org < compile/sysdeps

Tip: Máte-li nainstalovanou knihovnu dietlibc, lze runit zkompilovat staticky s ní (namísto glibc), výsledné binárky budou menší. Stačí před kompilací editovat soubory src/conf-cc a src/conf-ld, nejlépe spuštěním

# echo 'diet -Os gcc -O2 -Wall' >src/conf-cc
# echo 'diet -Os gcc -s -Os -pipe' >src/conf-ld

Instalace

je stejně jednoduchá jako kompilace

# package/install

Instalace vytvoří adresář /command (DJB ho používá místo binů a sbinů), do nějž zkopíruje obsah lokálního adresáře command, kde se nacházejí jednotlivé prográmky. Dále vytvoří kompatibilní symlinky z /command do /usr/local/bin a nainstaluje manuálové stránky. Pokud chcete používat /command, nezapomeňte ho uvést do proměnné PATH. Klidně ale můžete obsah adresáře command zkopírovat do /sbin.

A nyní – „zlatý hřebíček na dortu“ – náhrada initu. Zazálohujte /sbin/init přejmenováním na /sbin/init.sysv; kdyby se cokoli přihodilo, budete schopni i nadále použít původní init pomocí parametru jádra

init=/sbin/init.sysv

Initová část runitu sestává ze dvou souborů, runit arunit-init. Oba symlinkujte z /command do /sbin (pokud tam nejsou přímo nakopírované), ale runit-init pod názvem init.

# mv /sbin/init /sbin/init.sysv
# ln -s /command/runit /sbin/runit
# ln -s /command/runit-init /sbin/init

Funguje to tak, že runit-init sám sebe nahradí programem runit, jestliže je spuštěn s PID 1, tedy při inicializaci. V ostatních případech slouží jako nástroj k ukončení/restartu počítače

# init 0
# init 6

Srovnání s daemontools

Co prográmky v runitu dělají a co umějí navíc oproti těm v daemontools?

  • chpst = softlimit + envdir + setuidgid + envuidgid + setlock

    • Omezuje spouštěný program co do maximální použitelné paměti, otevřených souborů a počtu procesů, umí nastavit uživatele, pod nímž má program běžet, a dále proměnné prostředí na základě speciálního adresáře (co soubor, to proměnná).
    • Pape nejspíš došel k závěru, že dělení víceméně podobných funkcí do jednotlivých programů je opravdu extrémní DJBware, proto spojil funkce do jednoho.
      #!/bin/sh
      chpst -m 1000000 -o 40 -p 40 -e ./nastaveni /usr/sbin/apache2
    
  • runsv = supervise

    • supervizní daemon
    • dokáže poslat i signály USR1 a USR2
    • POZOR! V adresáři služby vytváří podadresář supervise, nikoli runsv, pročež se supervise zůstává kompatibilní.
  • runsvstat = svstat + svok

    • ukazuje, zda služba běží, či ne, a jak dlouho
    • s argumentem -l vypíše i status logovacího daemona služby
    # svstat -l /service/apache
      /service/apache: run (pid 9454) 929235 seconds
      log: run (pid 3128) 2803742 seconds
    
  • runsvctrl = svc

    • ovládá supervizní daemon
    • narozdíl od svc přijímá příkaz ve formě textu (up, down, pause, cont, hup, alarm,…) a umí poslat i signály USR1 a USR2 (1, 2)
    # runsvctrl down /service/apache
    
  • svlogd = multilog

    • Loguje standardní vstup do souborů, rotuje je podle aktuální velikosti a umí je třídit do několika souborů podle obsahu. Volitelně je použitelný ke každé službě, kdy loguje její standardní výstup a běží nezávisle na jejích restartech, aby zaručil kontinitu logu.
    • má drobná vylepšení při třídění dle obsahu
    #!/bin/sh
    svlogd /var/log/mujlog/
    
  • runsvchdir

    • změní runlevel
    • v daemontools není
    • POZOR! natvrdo dosazuje cestu do /etc/runit/run­svdir/current
    # runsvchdir single
    
  • svwaitdown

    • Čeká, dokud vyjmenované služby běží, případně je zabije po překročení časového limitu. Použíií především ve skriptu /etc/runit/3 při ukončování systému.
    • v daemontools není
      #!/bin/sh
      svwaitdown -xk -t15 /etc/runit/runsvdir/current/getty-*
    
  • svwaitup

    • čeká, dokud vyjmenované služby neběží, běžet přitom musí alespoň určitou dobu
    • v daemontools není
    #!/bin/sh
    cd /etc/runit/runlevels/all
    svwaitup -s 3 mysql apache
    
  • utmpset

    • odloguje uživatele z /var/log/utmp a /var/log/wtmp po skončení getty, použit ve skriptu finish
    • v daemontools není

Příklady skriptů

Uvádím příklady skriptů, které jsou přizpůsobeny mé oblíbené distribuci Gentoo (Flame! :-)).

/etc/runit/1

První fáze běhu. Využil jsem startovacího skriptu /sbin/rc, který se chová podle proměnné RUNLEVEL. Tu nastavuje běžný init, runit nikoli, takže musíme chování initu simulovat.

#!/bin/sh
PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin
RUNLEVEL="S" /sbin/rc sysinit
RUNLEVEL="1" /sbin/rc boot

/etc/runit/2

Druhá fáze nastaví defaultní runlevel default a pak spustírunsvdir. Tečky mají zvláštní funkci – runsvdir je nahrazuje různými hláškami, které se pak objevují při vylistování procesů.

#!/bin/sh
PATH=/command:/usr/local/bin:/usr/local/sbin:/bin: /sbin: /usr/bin: /usr/sbin:/usr/X11R6/bin
runsvchdir default > /dev/null
exec env - PATH=$PATH \
runsvdir /etc/runit/runsvdir/current 'log: ......... .................. .................. .................. .................. .................. ..............¨ 

(Pozn. red.: mezery v pathu a v tečičkách přidány kvůli sazbě –Johanka)

/etc/runit/3

Vypne všechny služby. Opět jsem využil Gentoo /sbin/rc skriptu.

#!/bin/sh
exec 2>&1
PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin

echo 'Cekam na ukonceni vsech getty...'
svwaitdown -xk -t15 /etc/runit/runsvdir/current/getty-*

echo 'Cekam na ukonceni sluzeb...'
svwaitdown -xk -t20 /etc/runit/runsvdir/current/*

/sbin/rc shutdown

echo 'Ukoncuji...'

skript run v adresáři getty-1

#!/bin/sh
exec /sbin/agetty 38400 tty1 linux

skript finish v adresáři getty-1

#!/bin/sh
exec utmpset -w tty1

Závěr

Co dodat? Runit používám na serveru od začátku k oboustranné spokojenosti (ani já ani server jsme si zatím nestěžovali), a pokud jsem i vás „nahlodal“ k vyzkoušení,

přeji hodně štěstí ;-)

Samozřejmě budu rád za jakékoli dotazy.

Ještě chci alespoň zmínit program socklog, který napsal Pape přímo pro runit. Nahrazuje syslog, a jelikož je obyčejnou službou, loguje také pomocí svlogu.

Odkazy:

stránky Gerrita Papeho
daemontools

Autor je lektorem společnosti OKsystem, spol. s.r.o.

Našli jste v článku chybu?

15. 7. 2004 9:22

Pavel Mlcoch (neregistrovaný)

Podle me, pokud budou sluzby stale psane jen pro sysvinit tak nemelo cenu do deba pridavat runit. Jen nevim jak zajistit podporu obou. deb zavislost sysvinit|runit by se musela resit tak ze se do vsech baliku s init-scripty pridaji ke skriptum sysvinit jeste skripty pro runit a spoustet se budou v zavislosti na nainstalovanem, nebo se vsechny skrypty presunou primo do baliku se sysvinit. prvni me prijde rozumejsi ale toho se asi dlouho nedockame, obzvlast kdyz nektere sluzby neprinutime fungovat…

8. 9. 2004 11:05

haad (neregistrovaný)

mam taky problem s runit rozbehal som ho ale ako nahle dam init 0 alebo init 6 tak pri konci runlevelu 3 dostanem Kernel panic unable to kill init! a cely system mi zamrzne a musim ho natvrdo restartovat

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

120na80.cz: Rakovina oka. Jak ji poznáte?

Rakovina oka. Jak ji poznáte?

Vitalia.cz: Baletky propagují zdravotní superpostel

Baletky propagují zdravotní superpostel

120na80.cz: Stoná vaše dítě často? Upravte mu jídelníček

Stoná vaše dítě často? Upravte mu jídelníček

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Podnikatel.cz: 10 tipů, jak dostat lidi do restaurace

10 tipů, jak dostat lidi do restaurace

Vitalia.cz: Proč vás každý zubař posílá na dentální hygienu

Proč vás každý zubař posílá na dentální hygienu

Vitalia.cz: Dáte si jahody s plísní?

Dáte si jahody s plísní?

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Lupa.cz: Seznam mění vedení. Pavel Zima v čele končí

Seznam mění vedení. Pavel Zima v čele končí

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Podnikatel.cz: Na poslední chvíli šokuje výjimkami v EET

Na poslední chvíli šokuje výjimkami v EET

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

DigiZone.cz: Flix TV má set-top box s HEVC

Flix TV má set-top box s HEVC

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Vitalia.cz: Tesco: Chudá rodina si koupí levné polské kuře

Tesco: Chudá rodina si koupí levné polské kuře

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

DigiZone.cz: ČT má dalšího zástupce v EBU

ČT má dalšího zástupce v EBU