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:
-
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.
-
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/ctrlaltdel, jehož obsah spočívá na nás.
-
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/runsvdir. 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ě kontrolujecurrent, 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/runsvdir/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.