A runit működése

A Unix/Linux szerverek üzemeltetése wikiből
(Változatok közti eltérés)
a (A nagy egész: struktúra)
a (runit: processzcsoport: link a unix-alapokra)
 
(2 szerkesztő 17 közbeeső változata nincs mutatva)
24. sor: 24. sor:
 
** könnyedén biztosíthatjuk ezeket a tulajdonságokat a felhasználók saját "szolgáltatásainak" - pl.:
 
** könnyedén biztosíthatjuk ezeket a tulajdonságokat a felhasználók saját "szolgáltatásainak" - pl.:
 
*** screen, benne irssi
 
*** screen, benne irssi
*** eggdrop bot (sajnos át kell írni, mert mindenképpen a háttérbe akarja rakni magát -- update 2007. január: úgy tűnik, ez szerencsére már nem igaz, az újabb verzióknak van olyan kapcsolója, amitől az előtérben maradnak)
+
*** irc "bouncer"
  +
*** eggdrop bot
 
*** fetchmail
 
*** fetchmail
 
*** signify
 
*** signify
46. sor: 46. sor:
 
Ezeket az ún. service directorykat általában a /service alá symlinkeljük be, ha az adott szolgáltatást futtatni akarjuk.
 
Ezeket az ún. service directorykat általában a /service alá symlinkeljük be, ha az adott szolgáltatást futtatni akarjuk.
   
A /service könyvtárat figyeli a <tt>runsvdir</tt>, és minden alkönyvtárra indít egy <tt>runsv</tt> folyamatot. Erről rövidsen lesz szó.
+
A /service könyvtárat figyeli a <tt>runsvdir</tt>, és minden alkönyvtárra indít egy <tt>runsv</tt> folyamatot. Erről rövidesen lesz szó.
   
 
== Telepítés ==
 
== Telepítés ==
53. sor: 53. sor:
 
* Debianhoz és Ubuntuhoz van szép runit csomag, ez csak felrakja a runitot, de nem cseréli le az initet - mindenféle körültekintés nélkül telepíthetjük
 
* Debianhoz és Ubuntuhoz van szép runit csomag, ez csak felrakja a runitot, de nem cseréli le az initet - mindenféle körültekintés nélkül telepíthetjük
 
** Van runit-run csomag is, ami le is cseréli az initet - csak óvatosan: reboot után, ha nem vigyázunk, csak a konzolról fogunk tudni belépni, mert nem indul el az sshd
 
** Van runit-run csomag is, ami le is cseréli az initet - csak óvatosan: reboot után, ha nem vigyázunk, csak a konzolról fogunk tudni belépni, mert nem indul el az sshd
  +
*** A runit-run csomagot a Debian unstable-ből 2010. őszén eltávolították; valószínűleg a többi Debian-alapú disztribúcióban sem marad benne sokáig (Ubuntun az Upstart bevezetése óta úgysem lehetett igazán használni).
 
* Nyilván fel lehet rakni forrásból is
 
* Nyilván fel lehet rakni forrásból is
 
** Alapból a djb-féle /package, /command könyvtárstruktúrát használja, aminek ugyan van előnye, de azért eléggé eltér az FHS-től
 
** Alapból a djb-féle /package, /command könyvtárstruktúrát használja, aminek ugyan van előnye, de azért eléggé eltér az FHS-től
130. sor: 131. sor:
   
 
/etc/init.d/rcS
 
/etc/init.d/rcS
/etc/init.d/makedev start
+
/etc/init.d/rc 2
/etc/init.d/systune start
 
/etc/init.d/ud start
 
/etc/firewall/firewall start
 
/etc/init.d/rmnologin start
 
/etc/init.d/xend start
 
/etc/init.d/xendomains start
 
   
 
touch /etc/runit/stopit
 
touch /etc/runit/stopit
140. sor: 141. sor:
 
Ne feledjük, hogy a beállított és exportált környezeti változókat a script gyermekfolyamatai örökölni fogják.
 
Ne feledjük, hogy a beállított és exportált környezeti változókat a script gyermekfolyamatai örökölni fogják.
   
Vegyük észre, hogy a fenti scriptben használhattunk volna rc.d mechanizmust is a szolgáltatások elindítására, ami
+
Vegyük észre, hogy a fenti scriptben a megszokott rc.d mechanizmust is használtuk a szolgáltatások elindítására, ami
 
* kicsit nehezebben átlátható,
 
* kicsit nehezebben átlátható,
 
* egy picit lassúbb, cserébe
 
* egy picit lassúbb, cserébe
149. sor: 150. sor:
 
[...]
 
[...]
 
/etc/init.d/rcS
 
/etc/init.d/rcS
/etc/init.d/rc 2
+
/etc/init.d/makedev start
  +
/etc/init.d/systune start
  +
/etc/init.d/rmnologin start
  +
/etc/init.d/xend start
  +
/etc/init.d/xendomains start
 
[...]
 
[...]
 
</pre>
 
</pre>
  +
  +
... vagyis az egyes meghívandó initscripteket egyenként felsorolhattuk volna.
  +
Ez az 5 jóval kevesebb, mint amennyi a kettes runlevelbe való váltáskor lefutna, úgyhogy még párhuzamosítással sem valószínű, hogy lenne értelme vesződni.
   
 
A /etc/runit/1 után - micsoda meglepetés - a /etc/runit/2 jön:
 
A /etc/runit/1 után - micsoda meglepetés - a /etc/runit/2 jön:
161. sor: 165. sor:
   
 
exec env - PATH=$PATH \
 
exec env - PATH=$PATH \
runsvdir -P /var/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'
+
runsvdir -P /etc/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'
 
</pre>
 
</pre>
   
 
Innentől, amig a runsvdir ki nem lép, monitorozza a szolgáltatásainkat és fut a rendszer.
 
Innentől, amig a runsvdir ki nem lép, monitorozza a szolgáltatásainkat és fut a rendszer.
   
A -P kapcsoló hatására saját processzcsoportban indít el minden szolgáltatást.
+
A -P kapcsoló hatására saját [[Unix-alapok#Processz_.C3.A1llapottere|processzcsoportban]] indít el minden szolgáltatást.
 
A "log: sok pont" egy olyan "helyet" hoz létre, ahová a szolgáltatások naplóüzeneteket írhatnak akkor is, ha sem a diszk, sem a hálózat nem elérhető - nevezetesen a runsvdir processz ''nevébe''.
 
A "log: sok pont" egy olyan "helyet" hoz létre, ahová a szolgáltatások naplóüzeneteket írhatnak akkor is, ha sem a diszk, sem a hálózat nem elérhető - nevezetesen a runsvdir processz ''nevébe''.
   
187. sor: 191. sor:
   
 
echo 'Waiting for services to stop...'
 
echo 'Waiting for services to stop...'
sv -w196 force-stop /var/service/*
+
sv -w196 force-stop /etc/service/*
sv exit /var/service/*
+
sv exit /etc/service/*
   
 
echo 'Shutdown...'
 
echo 'Shutdown...'
220. sor: 224. sor:
 
</pre>
 
</pre>
   
=== runvsdir ===
+
=== runsvdir ===
   
 
A runsvdir feladata a runsv-folyamatok menedzselése.
 
A runsvdir feladata a runsv-folyamatok menedzselése.
228. sor: 232. sor:
 
<pre>
 
<pre>
 
exec env - PATH=$PATH \
 
exec env - PATH=$PATH \
runsvdir -P /var/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'
+
runsvdir -P /etc/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'
 
</pre>
 
</pre>
   
A runsvdir odavált a megadott könyvtárba (itt /var/service, amire mutató symlinket a daemontools-hoz szokott rendszergazda létrehoz a gyökérben), és minden (legfeljebb 1000) itt található alkönyvtárhoz és alkönyvtárra mutató symlinkhez indít egy <tt>runsv</tt>-t.
+
A runsvdir odavált a megadott könyvtárba (itt /etc/service, amire mutató symlinket a daemontools-hoz szokott rendszergazda létrehoz a gyökérben), és minden (legfeljebb 1000) itt található alkönyvtárhoz és alkönyvtárra mutató symlinkhez indít egy <tt>runsv</tt>-t.
 
A ponttal kezdődő nevű könyvtárakat kihagyja.
 
A ponttal kezdődő nevű könyvtárakat kihagyja.
 
Ehhez a <tt>runsv</tt>-nek benne kell lennie a PATH-ban.
 
Ehhez a <tt>runsv</tt>-nek benne kell lennie a PATH-ban.
255. sor: 259. sor:
 
Ha létezik a ./down nevű file, a runsv nem indítja el azonnal a ./run-t, csak akkor, ha erre külön megkérjük.
 
Ha létezik a ./down nevű file, a runsv nem indítja el azonnal a ./run-t, csak akkor, ha erre külön megkérjük.
   
A létezik a ./log könyvtár, akkor a runsv nyit egy pipe-ot, a ./run és a ./finish standard outputját abba irányítja
+
Ha létezik a ./log könyvtár, akkor a runsv nyit egy pipe-ot, a ./run és a ./finish standard outputját abba irányítja
 
(ezt megteheti, hiszen az ő gyermekfolyamatai, vagyis tőle öröklik az stdout filedeszkriptort), és a ./log könyvtárban
 
(ezt megteheti, hiszen az ő gyermekfolyamatai, vagyis tőle öröklik az stdout filedeszkriptort), és a ./log könyvtárban
 
található run és finish programokkal pontosan úgy jár el, mint a szolgáltatás hasonló nevű file-jaival.
 
található run és finish programokkal pontosan úgy jár el, mint a szolgáltatás hasonló nevű file-jaival.
A log/run abból a pipe-ból fog olvasni, amiben a ./run ír, tehát a szolgáltatás standard outputja megjelenik a logoló alszolgáltatás
+
A ./log/run abból a pipe-ból fog olvasni, amibe a ./run ír, tehát a szolgáltatás standard outputja megjelenik a logoló alszolgáltatás standard inputján.
standard inputján.
 
   
 
A runsv létrehoz egy ./supervise (és esetleg ./log/supervise, ha van ./log) könyvtárat.
 
A runsv létrehoz egy ./supervise (és esetleg ./log/supervise, ha van ./log) könyvtárat.
 
A supervise könyvtár tartalma:
 
A supervise könyvtár tartalma:
   
* status: bináris file, ami a szolgáltatás állapotával kapcsolatos információkat tartalmazza; kompatibilis a daemontools supervise programjának status file-jával
+
* status: bináris file, ami a szolgáltatás állapotával kapcsolatos információkat tartalmazza
 
* stat: szövegfile. Tartalma lehet:
 
* stat: szövegfile. Tartalma lehet:
 
** "run": a szolgáltatás fut
 
** "run": a szolgáltatás fut
355. sor: 359. sor:
 
Sajnos pl. "reload" akciót nem tudunk csinálni, ezt az sv egyelőre nem támogatja - pedig a check mintájára nem lenne nehéz.
 
Sajnos pl. "reload" akciót nem tudunk csinálni, ezt az sv egyelőre nem támogatja - pedig a check mintájára nem lenne nehéz.
 
Ez nyilván nem jelenti azt, hogy nem tudjuk újraolvastatni az apache-val a konfigurációját, ha az apache amúgy képes erre (mellesleg képes), csak azt, hogy a runit ehhez nem nyújt nekünk segítséget.
 
Ez nyilván nem jelenti azt, hogy nem tudjuk újraolvastatni az apache-val a konfigurációját, ha az apache amúgy képes erre (mellesleg képes), csak azt, hogy a runit ehhez nem nyújt nekünk segítséget.
  +
  +
Példa az <tt>sv status</tt> kimenetére:
  +
  +
<pre>
  +
# sv status /service/*
  +
run: /service/atd: (pid 4557) 1238307s; run: log: (pid 4019) 2687162s
  +
run: /service/cron: (pid 5120) 2687160s
  +
run: /service/dnscache: (pid 4021) 2687162s; run: log: (pid 4017) 2687162s
  +
run: /service/getty-1: (pid 3996) 2687162s
  +
run: /service/getty-2: (pid 3995) 2687162s
  +
run: /service/getty-3: (pid 4000) 2687162s
  +
run: /service/getty-4: (pid 4002) 2687162s
  +
run: /service/getty-5: (pid 4014) 2687162s
  +
run: /service/mdadm: (pid 4062) 2687162s
  +
run: /service/munin-node: (pid 4095) 2687162s; run: log: (pid 4094) 2687162s
  +
run: /service/ntp: (pid 22798) 61327s; run: log: (pid 4076) 2687162s
  +
run: /service/smartmontools: (pid 4191) 2687161s; run: log: (pid 4190) 2687161s
  +
run: /service/socklog: (pid 31695) 1238676s; run: log: (pid 4079) 2687162s
  +
run: /service/ssh: (pid 4028) 2687162s
  +
</pre>
   
 
=== Runlevel-emuláció: runsvchdir ===
 
=== Runlevel-emuláció: runsvchdir ===
360. sor: 384. sor:
 
Ha nagyon a szívünkhöz nőttek a runlevelek, runit mellett is használhatjuk őket; ráadásul tetszőlegesen sok lehet belőlük, és szabadon adhatunk nekik neveket.
 
Ha nagyon a szívünkhöz nőttek a runlevelek, runit mellett is használhatjuk őket; ráadásul tetszőlegesen sok lehet belőlük, és szabadon adhatunk nekik neveket.
   
Az elgondolás lényege az, hogy több /var/service-szerű könyvtárat csinálunk (minden runlevelhez egyet), más-más szolgáltatáskönyvtárak symlinkjeivel, és a runsvdirt rábeszéljük, hogy váltson át egy másikba, és értelemszerűen állítsa le azokat a szolgáltatásokat, amelyeknek az új könyvtárban nincs symlinkje, azokat pedig, amelyeknek az előzőben nem volt, de itt van, indítsa el.
+
Az elgondolás lényege az, hogy több /etc/service-szerű könyvtárat csinálunk (minden runlevelhez egyet), más-más szolgáltatáskönyvtárak symlinkjeivel, és a runsvdirt rábeszéljük, hogy váltson át egy másikba, és értelemszerűen állítsa le azokat a szolgáltatásokat, amelyeknek az új könyvtárban nincs symlinkje, azokat pedig, amelyeknek az előzőben nem volt, de itt van, indítsa el.
   
 
Ehhez
 
Ehhez
366. sor: 390. sor:
 
# létre kell hoznunk valahol (alapértelmezés szerint a /etc/runit/runsvdir/ könyvtárban) egy-egy ilyen runlevel-könyvtárat
 
# létre kell hoznunk valahol (alapértelmezés szerint a /etc/runit/runsvdir/ könyvtárban) egy-egy ilyen runlevel-könyvtárat
 
# ebben létrehozni egy "current" nevű symlinket, ami az aktuális runlevelre mutat
 
# ebben létrehozni egy "current" nevű symlinket, ami az aktuális runlevelre mutat
# a /var/service-t (vagy ami a /etc/runit/2-ben a runsvdir paramétere) le kell cserélnünk egy symlinkre, ami erre a current nevű symlinkre mutat.
+
# a /etc/service-t (vagy ami a /etc/runit/2-ben a runsvdir paramétere) le kell cserélnünk egy symlinkre, ami erre a current nevű symlinkre mutat.
   
 
Ezután a current symlink átállítása runlevel-váltást eredményez.
 
Ezután a current symlink átállítása runlevel-váltást eredményez.
390. sor: 414. sor:
 
** létrehozható file-ok maximális mérete
 
** létrehozható file-ok maximális mérete
 
** core file max. mérete
 
** core file max. mérete
  +
** max. CPU-idő (a nem dokumentált <tt>-t</tt> kapcsolóval :)
 
* új process group létrehozása a program elindítása előtt
 
* új process group létrehozása a program elindítása előtt
 
* standard filedeszkriptorok bezárása (input, output, error szelektíven) a program elindítása előtt
 
* standard filedeszkriptorok bezárása (input, output, error szelektíven) a program elindítása előtt
399. sor: 424. sor:
 
* umask
 
* umask
 
* munkakönyvtár
 
* munkakönyvtár
* max. CPU-idő
 
 
* hard erőforráskorlátok (csak a soft limiteket állítja, de nem tudja őket nagyobbra rakni a hard limiteknél, vagyis ha van valamilyen default hard limit, annál nagyobb softlimitet a chpst nem tud beállítani; előtte ulimittel kell módosítani a hard limitet)
 
* hard erőforráskorlátok (csak a soft limiteket állítja, de nem tudja őket nagyobbra rakni a hard limiteknél, vagyis ha van valamilyen default hard limit, annál nagyobb softlimitet a chpst nem tud beállítani; előtte ulimittel kell módosítani a hard limitet)
 
* supplementary groupok megtartása (eldobja őket)
 
* supplementary groupok megtartása (eldobja őket)
  +
* NSS használata a UID-k/GID-k feloldására (pl. LDAP-ban tárolt felhasználók és csoportok esetén workaroundolni kell)
   
 
A környezeti változók beállítása úgy történik, hogy létrehozunk egy könyvtárat, amiben minden file egy beállítandó környezeti változóról van elnevezve.
 
A környezeti változók beállítása úgy történik, hogy létrehozunk egy könyvtárat, amiben minden file egy beállítandó környezeti változóról van elnevezve.
434. sor: 459. sor:
 
*** t''másodperc'': ha az aktuális napló elérte a megadott kort, és nem üres, akkor rotálunk. Ha 0, nincs idő-alapú rotáció.
 
*** t''másodperc'': ha az aktuális napló elérte a megadott kort, és nem üres, akkor rotálunk. Ha 0, nincs idő-alapú rotáció.
 
*** !''posztprocesszor'': rotációkor a naplót a megadott program (pl. egy tömörítő vagy egy elemző) dolgozza fel. Alapértelmezés szerint nincs utófeldolgozás.
 
*** !''posztprocesszor'': rotációkor a naplót a megadott program (pl. egy tömörítő vagy egy elemző) dolgozza fel. Alapértelmezés szerint nincs utófeldolgozás.
*** u''a.b.c.d[:port]'': a megadott IP-cím megadott portjára is elküldi az üzenetet (udp-vel) (valójában csak az első ''hossz'' byte-ot, a ''hossz'' megadható a parancssorban.
+
*** u''a.b.c.d[:port]'': a megadott IP-cím megadott portjára is elküldi az üzenetet (udp-vel) (valójában csak az első ''hossz'' byte-ot; a ''hossz'' megadható a parancssorban).
 
*** U''a.b.c.d[:port]'': mint a kis u, de ''csak'' udp-n logol, a helyi logfile-ba nem írja bele az üzenetet.
 
*** U''a.b.c.d[:port]'': mint a kis u, de ''csak'' udp-n logol, a helyi logfile-ba nem írja bele az üzenetet.
 
*** p''prefix'': a megadott ''prefix''et odailleszti az összes naplózott üzenet elé. Ez akkor jó, ha udp-n logolunk, és a fogadóoldalon könnyen szét akarjuk válogatni az üzeneteket; a prefix alapján lesz a legegyszerűbb.
 
*** p''prefix'': a megadott ''prefix''et odailleszti az összes naplózott üzenet elé. Ez akkor jó, ha udp-n logolunk, és a fogadóoldalon könnyen szét akarjuk válogatni az üzeneteket; a prefix alapján lesz a legegyszerűbb.
449. sor: 474. sor:
 
A mintaillesztés két metakaraktert ismer:
 
A mintaillesztés két metakaraktert ismer:
 
* A csillag jelentése: tetszőleges sztring, kivéve a csillagot követő karaktert;
 
* A csillag jelentése: tetszőleges sztring, kivéve a csillagot követő karaktert;
* A plusz jelentése: a pluszt követő karakterből egy vagy tetszőleges számú.
+
* a plusz jelentése: a pluszt követő karakterből egy vagy tetszőleges számú.
   
 
Vegyük észre, hogy ez sokkal gyengébb, mint a reguláris kifejezések, cserébe viszont sokkal gyorsabb az illesztés, és az esetek döntő részében még a pluszra sincs szükség.
 
Vegyük észre, hogy ez sokkal gyengébb, mint a reguláris kifejezések, cserébe viszont sokkal gyorsabb az illesztés, és az esetek döntő részében még a pluszra sincs szükség.
476. sor: 501. sor:
 
* a konfigurációjukban a különösen izgalmasnak tartott üzeneteket írassuk ki velük a standard errorra is;
 
* a konfigurációjukban a különösen izgalmasnak tartott üzeneteket írassuk ki velük a standard errorra is;
 
* az uncat folyamatosan olvassa az üzeneteket, és x byte után vagy y másodperc elteltével meghív egy programot, aminek a standard inputján megjelennek ezek az üzenetek
 
* az uncat folyamatosan olvassa az üzeneteket, és x byte után vagy y másodperc elteltével meghív egy programot, aminek a standard inputján megjelennek ezek az üzenetek
** Ez a program lehet pl. a <tt>mail</tt>, vagy egy sms-küldő
+
** Ez a program lehet pl. a <tt>mail</tt>, vagy egy sms-küldő.
   
A megoldás hátránya az, hogy azáltal, hogy egy FIFO-ba esetleg több svlogd is ír egyszerre, az üzenetek elvileg összekeveredhetnek; valamint, ha az uncat leáll, és nincs, ami olvasson a FIFO-ból, azok az svlogd-k, amelyek írni próbálnak bele, blokkolódnak, és ezzel a beléjük naplózó szolgáltatásokat is blokkolják. Így elérhetjük, hogy egy rendszerfelügyeleti eszköz kritikus meghibásodási ponttá (single point of failure) váljon.
+
A megoldás hátránya az, hogy azáltal, hogy egy FIFO-ba esetleg több svlogd is ír egyszerre, az üzenetek elvileg összekeveredhetnek (persze futtathatunk annyi uncatet, ahány svlogd-t); valamint, ha az uncat leáll, és nincs, ami olvasson a FIFO-ból, azok az svlogd-k, amelyek írni próbálnak bele, blokkolódnak, és ezzel a beléjük naplózó szolgáltatásokat is blokkolják. Így elérhetjük, hogy egy rendszerfelügyeleti eszköz kritikus meghibásodási ponttá (single point of failure) váljon.
   
 
Ezt a mechanizmust Gerrit Pape, a runit és a socklog fő fejlesztője, [http://smarden.org/socklog/notify.html socklog-notify]-nak hívja.
 
Ezt a mechanizmust Gerrit Pape, a runit és a socklog fő fejlesztője, [http://smarden.org/socklog/notify.html socklog-notify]-nak hívja.
  +
  +
Érdekesség: a fenti, aránylag összetett működést az svlogd mintegy 45 kB-nyi memóriafoglalással abszolválja.
  +
  +
=== Hogyan jön létre a pipe a szolgáltatás és a naplózóprogram között? ===
  +
  +
Ehhez nézzük meg strace-szel, mit csinál a <tt>runsv</tt>.
  +
  +
Az alábbi a főfolyamat tevékenységét foglalja össze (kicsit zanzásítva):
  +
  +
<pre>
  +
execve("/usr/bin/runsv", ["runsv", "/tmp/svtest"], [/* 63 vars */]) = 0
  +
[...]
  +
chdir("/tmp/svtest") = 0
  +
stat("down", 0x7fff0a8975f0) = -1 ENOENT (No such file or directory)
  +
stat("log", {st_mode=S_IFDIR|0775, st_size=16, ...}) = 0
  +
stat("log/down", 0x7fff0a8975f0) = -1 ENOENT (No such file or directory)
  +
pipe([5, 6]) = 0 # ezt a két FD-t a gyermekfolyamatok öröklik
  +
fcntl(5, F_SETFD, FD_CLOEXEC) = 0
  +
fcntl(6, F_SETFD, FD_CLOEXEC) = 0
  +
[...]
  +
fork() = 5666 # ez lesz majd a logoló processz
  +
[...]
  +
fork() = 5667 # ez lesz maga a szolgáltatás
  +
open("supervise/pid.new", O_WRONLY|O_CREAT|O_TRUNC|O_NONBLOCK, 0644) = 15
  +
write(15, "5667\n", 5) = 5 # bele is írjuk a PID-ját a supervise/pid-be
  +
close(15) = 0
  +
rename("supervise/pid.new", "supervise/pid") = 0
  +
[...]
  +
</pre>
  +
  +
Nézzük most az első forkkal létrehozott processzt:
  +
  +
<pre>
  +
fcntl(5, F_GETFL) = 0 (flags O_RDONLY)
  +
close(0) = 0 # bezárjuk az stdin-t
  +
fcntl(5, F_DUPFD, 0) = 0 # rámásoljuk az 5-ös deszkriptort, ami a pipe egyik vége
  +
close(6) = 0 # bezárjuk a 6-os deszkriptort, ami a pipe másik vége
  +
chdir("./log") = 0
  +
[...]
  +
execve("./run", ["./run"], [/* 63 vars */]) = 0
  +
</pre>
  +
  +
A második forkkal létrehozott processz pedig a következőt csinálja:
  +
  +
<pre>
  +
fcntl(6, F_GETFL) = 0x1 (flags O_WRONLY)
  +
close(1) = 0 # bezárjuk az stdoutot
  +
fcntl(6, F_DUPFD, 1) = 1 # rámásoljuk a 6-os deszkriptort, ami a pipe másik vége (ebbe lehet írni)
  +
close(5) = 0 # becsukjuk a pipe egyik végét (amiből a naplózóprogram olvasni fog)
  +
[...]
  +
execve("./run", ["./run"], [/* 63 vars */]) = 0
  +
</pre>
   
 
=== [http://cr.yp.to/libtai/tai64.html A TAI64N időbélyegekről] ===
 
=== [http://cr.yp.to/libtai/tai64.html A TAI64N időbélyegekről] ===
486. sor: 563. sor:
 
TAI: Temps Atomique International. Egy TAI-másodperc az az idő, amely a céziumatom által meghatározott körülmények között kibocsátott sugárzás 9192631770 darab periódusát teszi ki, és nemzetközi szabvány.
 
TAI: Temps Atomique International. Egy TAI-másodperc az az idő, amely a céziumatom által meghatározott körülmények között kibocsátott sugárzás 9192631770 darab periódusát teszi ki, és nemzetközi szabvány.
   
Egy TAI64-időbélyeg 64 bites, és a valós idő egy konkrét másodpercét jelöli meg (a relativisztikus hatásoktól tekintsünk el). Egy 's' időbélyeg értelmezése:
+
Egy TAI64-időbélyeg 64 bites, és a valós idő egy konkrét másodpercét jelöli meg (a relativisztikus hatásoktól tekintsünk el). Egy ''s'' időbélyeg értelmezése:
   
* ha 0 <= 's' < 2^62: az a másodperc, amely 1970 TAI (1970. január elsején UTC szerint majdnem pont éjfél, konkrétan 1969. december 31. 23:59:50) előtt 's' másodperccel kezdődött;
+
* ha 0 <= ''s'' < 2<sup><small>62</small></sup>: az a másodperc, amely 1970 TAI (1970. január elsején UTC szerint majdnem pont éjfél, konkrétan 1969. december 31. 23:59:50) előtt ''s'' másodperccel kezdődött;
* ha 2^62 <= 's' < 2^63: az a másodperc, amely 1970 TAI után 's'-2^62 másodperccel kezdődött.
+
* ha 2<sup><small>62</small></sup> <= ''s'' < 2<sup><small>63</small></sup>: az a másodperc, amely 1970 TAI után ''s''-2<sup><small>62</small></sup> másodperccel kezdődött.
   
A 2^63-nál nagyobb értékeket esetleges bővítések számára tartják fenn. A jelenleg elterjedt kozmológiai elméletek jó része szerint a 2^63 alatti értékek elegendőek a Világegyetem teljes várható élettartamának lefedésére. Ha igazuk van, várhatóan nem lesz szükség a TAI64 bővítésére.
+
A 2<sup><small>63</small></sup>-nál nagyobb értékeket esetleges bővítések számára tartják fenn. A jelenleg elterjedt kozmológiai elméletek jó része szerint a 2<sup><small>63</small></sup> alatti értékek elegendőek a Világegyetem teljes várható élettartamának lefedésére. Ha igazuk van, várhatóan nem lesz szükség a TAI64 bővítésére.
   
A TAI64-et nyolc byte reprezentálja big-endian sorrendben, tehát az utolsó a legkisebb helyiértékű, az első pedig a legnagyobb.
+
A TAI64-et nyolc byte reprezentálja big-endian sorrendben, tehát az utolsó a legkisebb helyiértékű, az első pedig a legnagyobb. (Időszámításunk képzeletbeli nulladik évének -- tehát igazából i.e. 1-nek -- január elsején, közép-európai téli idő szerint 00:00:00-kor pl. TAI64-ben 3ffffff1868b7226 volt az idő. Ma, i.sz. 2012. október 31-én 11 körül, 400000005090f844-et írunk.)
   
 
A TAI64N ezt a nyolcbájtos formátumot kiegészíti egy négybájtos egésszel, amelynek az értéke 0 és 999999999 közé esik, és a megadott másodpercen belül egy adott nanoszekundum sorszámát tartalmazza. A legtöbb unixos alkalmazás számára ez már elegendő pontosságot biztosít. (Egy nanoszekundum alatt a fény kb. 30 centimétert tesz meg.)
 
A TAI64N ezt a nyolcbájtos formátumot kiegészíti egy négybájtos egésszel, amelynek az értéke 0 és 999999999 közé esik, és a megadott másodpercen belül egy adott nanoszekundum sorszámát tartalmazza. A legtöbb unixos alkalmazás számára ez már elegendő pontosságot biztosít. (Egy nanoszekundum alatt a fény kb. 30 centimétert tesz meg.)
   
 
Létezik TAI64NA is, ami további négy bájton az első tizenkét byte által megadott nanoszekundumon belül egy konkrét attoszekundum sorszámát tartalmazza. (Egy attoszekundum alatt a fény kb. 0,0000003 millimétert tesz meg. Ez egy hidrogénatom átmérőjének kb. a háromszorosa.)
 
Létezik TAI64NA is, ami további négy bájton az első tizenkét byte által megadott nanoszekundumon belül egy konkrét attoszekundum sorszámát tartalmazza. (Egy attoszekundum alatt a fény kb. 0,0000003 millimétert tesz meg. Ez egy hidrogénatom átmérőjének kb. a háromszorosa.)
  +
  +
TAI64N-ből a <tt>daemontools</tt> csomag <tt>tai64nlocal</tt> programja készít ember számára olvasható dátumot; tetszőleges dátumból TAI64N-t pedig az alábbi miniscript:
  +
  +
<pre>
  +
date +"16o2 62^%s+2 32^* 42949672960+p" | dc | tr A-Z a-z
  +
</pre>
  +
  +
A GNU date <tt>--date</tt> kapcsolója segítségével bármilyen dátumot átalakíthatunk. A fenti kódban a <tt>dc</tt>-nek átadott kifejezés redundáns (a mágikus 10*2<sup>32</sup>-es konstans szerintem kispórolható belőle). Kap egy virtuális pirospontot, aki optimalizálja. :)
   
 
=== utmpset ===
 
=== utmpset ===
719. sor: 804. sor:
 
</pre>
 
</pre>
   
Ha ezt a run scriptet bemásoljuk egy apache2-svn nevű könyvtárba, és azt belinkeljük a /var/service-be, akkor a script, amikor elindul, az apache2-svn-re jellemző konfigurációt fogja beolvasni a /etc/default/apache2-svn-ből.
+
Ha ezt a run scriptet bemásoljuk egy apache2-svn nevű könyvtárba, és azt belinkeljük a /etc/service-be, akkor a script, amikor elindul, az apache2-svn-re jellemző konfigurációt fogja beolvasni a /etc/default/apache2-svn-ből.
 
A kód és a konfiguráció jól elkülönül; a script vándorolhat gépről gépre anélkül, hogy módosítani kellene. Elég a /etc/default/apache2-* file-okat módosítani.
 
A kód és a konfiguráció jól elkülönül; a script vándorolhat gépről gépre anélkül, hogy módosítani kellene. Elég a /etc/default/apache2-* file-okat módosítani.
   
 
Vegyük észre, hogy itt a sudo miatt eleve nem rootként indul el az apache2, tehát nem fog tudni 1024 alatti porton figyelni, hacsak ezt valahogyan nem engedélyeztük a RUNASUSER számára.
 
Vegyük észre, hogy itt a sudo miatt eleve nem rootként indul el az apache2, tehát nem fog tudni 1024 alatti porton figyelni, hacsak ezt valahogyan nem engedélyeztük a RUNASUSER számára.
   
Az ilyen template-szerű run-scripteket úgy ugyan nem használhatjuk, hogy ugyanazt a könyvtárat több példányban, csak mindig más néven belinkeljük a /var/service alá, mivel mindegyikhez tartozó <tt>runsv</tt> ugyanazt a supervise alkönyvtárat akarná használni; annak viszont természetesen nincs akadálya, hogy a run scriptet az egyes példányok saját könyvtáraiban egy a template run-scriptre mutató symlinkkel helyettesítsük. Így a run scriptet könnyedén tarthatjuk pl. subversion repositoryban.
+
Az ilyen template-szerű run-scripteket úgy ugyan nem használhatjuk, hogy ugyanazt a könyvtárat több példányban, csak mindig más néven belinkeljük a /etc/service alá, mivel mindegyikhez tartozó <tt>runsv</tt> ugyanazt a supervise alkönyvtárat akarná használni; annak viszont természetesen nincs akadálya, hogy a run scriptet az egyes példányok saját könyvtáraiban egy a template run-scriptre mutató symlinkkel helyettesítsük. Így a run scriptet könnyedén tarthatjuk pl. subversion repositoryban.
   
 
=== Logoló run script ===
 
=== Logoló run script ===
782. sor: 867. sor:
   
 
* Mik az előnyei a runitnak a hagyományos System V inittel szemben? Mik a hátrányai?
 
* Mik az előnyei a runitnak a hagyományos System V inittel szemben? Mik a hátrányai?
* Milyen feltételeket kell teljesítenie egy minimális runitos run-scriptnek?
+
* Milyen feltételeket kell teljesítenie egy minimális runitos run-scriptnek? Mire célszerű odafigyelni a megírásakor?
 
* Milyen lehetőségeket biztosít az svlogd a logok által elfoglalt hely mennyiségének menedzselésére?
 
* Milyen lehetőségeket biztosít az svlogd a logok által elfoglalt hely mennyiségének menedzselésére?
 
* Milyen esetekben rotálja a logokat az svlogd?
 
* Milyen esetekben rotálja a logokat az svlogd?

A lap jelenlegi, 2014. június 10., 09:28-kori változata

A daemontools szabad reimplementációja; majdnem minden, ami igaz a runitre, igaz a daemontoolsra is, és fordítva. Az ötlet az, hogy juttassuk érvényre a Unix-filozófiát: egy program egy dolgot csináljon, lehetőleg jól és hatékonyan, de legyen képes más programokkal együttműködni. A runit csomag egy-egy programja tipikusan kevesebb, mint ezer sor C-kódból áll, és sosem nő a memóriafoglalása. Ha a runitot statikusan linkeljük a dietlibc-hez, akkor az 1-es processzként futó program csak kb. 20KB-ot vesz igénybe. A BSD-licenchez hasonló licenc alatt érhető el.

Az initet és az initscripteket váltja ki, valamint segít a naplózás megszervezésében. Be lehet vezetni kicsit, nagyon, és teljesen.

Ha kicsit vezetjük be, akkor megmarad a System V init, de mellette kapunk:

  • Szolgáltatásmenedzsmentet. A szolgáltatások
    • tiszta környezettel indulnak
    • újraindulnak, ha kilépnek
    • opcionálisan megbízható naplózást kapnak
      • naplóüzenet nem vész el naplózás közben
      • méret alapján rotált logok
      • utófeldolgozás lehetősége
      • akkor is működik, ha a szolgáltatás chrootban fut
      • a logolás és a szolgáltatás egymástól függetlenül újraindítható/leállítható
    • kiválasztott felhasználók sudo nélkül is menedzselhetik őket
    • könnyedén lekérdezhető a státuszuk
    • könnyedén küldhető nekik signal
    • könnyedén biztosíthatjuk ezeket a tulajdonságokat a felhasználók saját "szolgáltatásainak" - pl.:
      • screen, benne irssi
      • irc "bouncer"
      • eggdrop bot
      • fetchmail
      • signify
      • akár apache vagy egyéb

Ha nagyon bevezetjük, akkor emellett még az initet is kiváltjuk, de a rendszerindulást továbbra is a hagyományos initscriptjeink vezénylik le, csak az inittab válik feleslegessé. Emellett gyorsabbá válik az indítás és a leállítás, mert sokmindent egyszerre fog csinálni a runit (azért nem kell azzal foglalkoznia, hogy minek mi után kell jönnie, mert ezt az intelligenciát nekünk kell megavlósítanunk a run scriptekben - l. később).

Ha teljesen bevezetjük, akkor az rcS után már csak a runit által menedzselt szolgáltatásaink futnak, vagyis az "rc 2" már nem kell, hogy lefusson.

Tartalomjegyzék

[szerkesztés] 1 Architektúra

  • Minden szolgáltatáshoz tartozik egy könyvtár (directory), amiben:
    • Megvan az a program (általában script), ami az adott szolgáltatást elindítja és futtatja (és run névre hallgat); addig nem léphet ki, amíg a szolgáltatás nem lépett ki
    • A runit létrehoz egy supervise könyvtárat - l. később
    • Lehet egy down nevű file, ami azt jelzi, hogy a szolgáltatás normális állapota az, hogy nem fut
    • Lehet egy log nevű könyvtár, ha a szolgáltatáshoz naplózást is szeretnénk - l. később
    • Lehet egy check nevű program, ami 0-ás visszatérési értékkel lép ki, ha a szolgáltatás működik (ami nem azonos azzal, hogy fut), egyébként nullától különbözővel
    • Lehet egy finish nevű program, amit abban az esetben kell lefuttatni, ha a run kilép

Ezeket az ún. service directorykat általában a /service alá symlinkeljük be, ha az adott szolgáltatást futtatni akarjuk.

A /service könyvtárat figyeli a runsvdir, és minden alkönyvtárra indít egy runsv folyamatot. Erről rövidesen lesz szó.

[szerkesztés] 2 Telepítés

  • Ajánlott verzió: legalább 1.6 (bár a korábbiak is használhatók)
  • Debianhoz és Ubuntuhoz van szép runit csomag, ez csak felrakja a runitot, de nem cseréli le az initet - mindenféle körültekintés nélkül telepíthetjük
    • Van runit-run csomag is, ami le is cseréli az initet - csak óvatosan: reboot után, ha nem vigyázunk, csak a konzolról fogunk tudni belépni, mert nem indul el az sshd
      • A runit-run csomagot a Debian unstable-ből 2010. őszén eltávolították; valószínűleg a többi Debian-alapú disztribúcióban sem marad benne sokáig (Ubuntun az Upstart bevezetése óta úgysem lehetett igazán használni).
  • Nyilván fel lehet rakni forrásból is
    • Alapból a djb-féle /package, /command könyvtárstruktúrát használja, aminek ugyan van előnye, de azért eléggé eltér az FHS-től

[szerkesztés] 3 A runit részei

A runit csomag számos programot tartalmaz; mindegyik valamilyen viszonylag kicsi részfeladatot lát el, és emiatt egyszerű a kódja.

Tekintsük át ezeket felülről lefelé haladva a processz-fában, amely az init lecserélése esetén pl. így nézhet ki:

runit-+-events/0
[...]
      |-runsvdir-+-runsv-+-dnscache
      |          |       `-svlogd
      |          |-runsv-+-run---sleep
      |          |       `-svlogd
      |          |-runsv-+-qmail-send-+-qmail-clean
      |          |       |            |-qmail-lspawn
      |          |       |            `-qmail-rspawn
      |          |       `-svlogd
      |          |-4*[runsv-+-socklog]
      |          |          `-svlogd]
      |          |-runsv---dd
      |          |-runsv---klogd
      |          |-runsv---cron
      |          |-runsv-+-munin-node
      |          |       `-svlogd
      |          |-8*[runsv---getty]
      |          |-runsv-+-ntpd
      |          |       `-svlogd
      |          |-runsv---sshd---sshd---zsh---screen
      |          |-runsv-+-smartd
      |          |       `-svlogd
      |          |-runsv---mdadm
      |          |-runsv---pound---pound---2*[{pound}]
      |          `-runsv-+-svlogd
      |                  `-tcpserver
[...]

[szerkesztés] 3.1 runit-init

Ez a rendkívül egyszerű program, amely a /sbin/initet váltja ki, mindössze 76 sor hosszú.

Ha 1-es a process ID-ja (vagyis ő az init), akkor elindítja (execve() hívással) a runitot.

Ha más a process ID-ja, akkor úgy viselkedik, mint a telinit, tehát mintha runlevelt akarnánk váltani, de csak a 0-ás és a 6-os runlevelt ismeri.

  • 0: rendszerleállítás.
    1. Létrehozza a /etc/runit/stopit file-t (ez csak egy flag-file, a runit megnézi majd, hogy létezik-e és futtható-e)
    2. Futtathatóvá teszi
    3. Elveszi a jogokat a /etc/runit/reboot file-ról
    4. CONT signalt küld az 1-es processznek (ami remélhetőleg a runit, és ettől majd elkezd leállni)
  • 6: újraindítás
    1. Létrehozza a /etc/runit/stopit file-t (ez csak egy flag-file, a runit megnézi majd, hogy létezik-e és futtható-e)
    2. Futtathatóvá teszi
    3. Létrehozza a /etc/runit/reboot file-t (ez csak egy flag-file, a runit megnézi majd, hogy létezik-e és futtható-e)
    4. Futtathatóvá teszi
    5. CONT signalt küld az 1-es processznek (ami remélhetőleg a runit, és ettől majd elkezd leállni)

[szerkesztés] 3.2 runit

Ez a program való arra, hogy 1-es PID-del fusson és az init feladatait ellássa, vagyis:

  • elindítsa a rendszert,
  • leállítsa a rendszert és
  • a kettő között gondoskodjon a szolgáltatások futtatásáról.

Ha init helyett a runit indul, akkor a /etc/runit/1 nevű scriptet indítja el először. Ennek a tartalma lehet pl. ez (a runit-run csomagban is ilyesmi van):

#!/bin/sh
# system one time tasks

PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/bin/X11
export PATH

/etc/init.d/rcS
/etc/init.d/rc 2

touch /etc/runit/stopit
chmod 0 /etc/runit/stopit

Az 1-es script tartalmazza a rendszerindításkor kiadandó parancsok listáját, emiatt nem hordozható korlátozás nélkül.

Ne feledjük, hogy a beállított és exportált környezeti változókat a script gyermekfolyamatai örökölni fogják.

Vegyük észre, hogy a fenti scriptben a megszokott rc.d mechanizmust is használtuk a szolgáltatások elindítására, ami

  • kicsit nehezebben átlátható,
  • egy picit lassúbb, cserébe
  • modulárisabb és
  • könnyebb automatizálni a karbantartását - de erre nem biztos, hogy van igény.

Konkrétan írhattuk volna akár azt is, hogy

[...]
/etc/init.d/rcS
/etc/init.d/makedev start
/etc/init.d/systune start
/etc/init.d/rmnologin start
/etc/init.d/xend start
/etc/init.d/xendomains start
[...]

... vagyis az egyes meghívandó initscripteket egyenként felsorolhattuk volna. Ez az 5 jóval kevesebb, mint amennyi a kettes runlevelbe való váltáskor lefutna, úgyhogy még párhuzamosítással sem valószínű, hogy lenne értelme vesződni.

A /etc/runit/1 után - micsoda meglepetés - a /etc/runit/2 jön:

#!/bin/sh

PATH=/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin

exec env - PATH=$PATH \
runsvdir -P /etc/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'

Innentől, amig a runsvdir ki nem lép, monitorozza a szolgáltatásainkat és fut a rendszer.

A -P kapcsoló hatására saját processzcsoportban indít el minden szolgáltatást. A "log: sok pont" egy olyan "helyet" hoz létre, ahová a szolgáltatások naplóüzeneteket írhatnak akkor is, ha sem a diszk, sem a hálózat nem elérhető - nevezetesen a runsvdir processz nevébe.

Láthatjuk, hogy a 2-es script lényegében semmilyen rendszerspecifikus dolgot nem tartalmaz, hordozható.

Ha kilép a runsvdir, kilép a /etc/runit/2 is; ha a visszatérési értéke 0 volt, elindul a /etc/runit/3, ami a leállításért felelős.

Ha nem 0 volt a 2-es script visszatérési értéke, akkor a runit újra elindítja.

A 3-as script tartalma például a következő lehet:

#!/bin/sh
exec 2>&1

PATH=/sbin:/bin:/usr/sbin:/usr/bin

LAST=0
test -x /etc/runit/reboot && LAST=6

echo 'Waiting for services to stop...'
sv -w196 force-stop /etc/service/*
sv exit /etc/service/*

echo 'Shutdown...'
/etc/init.d/rc $LAST

Elvileg a 3-as script is lehetne rendszerfüggő, mivel a rendszer leállításával kapcsolatos parancsokat kell benne felsorolnunk, azonban gyakran nem az.

Ha

  • megnyomjuk a ctrl-alt-delt (és a kernelt úgy állítottuk be, hogy erről értesítse az initet), vagy
  • INT signalt küldünk az 1-es processznek, akkor

megnézi, létezik és futtatható-e a /etc/runit/ctrlaltdel script; ha igen

  • lefuttatja, majd
  • küld magának egy CONT signalt.

Ha létezik és végrehajtható a /etc/runit/stopit file, akkor a runit a CONT signal hatására leállítja a rendszert. Ha a /etc/runit/reboot is létezik és végrehajtható, akkor leállítás helyett újraindítja a gépet.

A /etc/runit/ctrlaltdel script tartalma pl.:

#!/bin/sh

PATH=/bin:/usr/bin
MSG="System is going down in 14 seconds..."

# echo 'disabled.' ; exit
touch /etc/runit/stopit
chmod 100 /etc/runit/stopit && echo "$MSG" | wall
/bin/sleep 14

[szerkesztés] 3.3 runsvdir

A runsvdir feladata a runsv-folyamatok menedzselése.

Normális körülmények között a /etc/runit/2 indítja:

exec env - PATH=$PATH \
runsvdir -P /etc/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'

A runsvdir odavált a megadott könyvtárba (itt /etc/service, amire mutató symlinket a daemontools-hoz szokott rendszergazda létrehoz a gyökérben), és minden (legfeljebb 1000) itt található alkönyvtárhoz és alkönyvtárra mutató symlinkhez indít egy runsv-t. A ponttal kezdődő nevű könyvtárakat kihagyja. Ehhez a runsv-nek benne kell lennie a PATH-ban. Ha valamelyik runsv kilép, a runsvdir újraindítja.

Ötmásodpercenként megnézi, megváltozott-e a paraméterként kapott könyvtár (mtime, inode, device). Ha megváltozott, akkor megnézi, van-e benne új alkönyvtár (ha igen, indít hozzá runsv-t), vagy megszűnt-e régi (ekkor leállítja az adott runsv-t és a szolgáltatást, ami hozzá tartozott).

A "log: sokpont" helyre a runsvdir stderr-je irányítódik; amit amúgy a standard errorra írna ki, azt helyette beleírja a saját processznevébe, amiben a pontok egy körpuffert képeznek. Negyedóránként mindenképpen kiír ide egy pontot, hogy a régi üzenetek elmúlhassanak, ill. megbecsülhető legyen egy üzenet kora.

[szerkesztés] 3.4 runsv

A runsv feladata egy szolgáltatás (és esetleg egy hozzá tartozó naplózószolgáltatás) monitorozása, elindítása, leállítása.

Egyetlen paramétert kap indításkor, egy könyvtár nevét. Tevékenysége így foglalható össze:

  1. Induláskor odavált, és lefuttatja a ./run programot.
  2. Ha ez kilép, és létezik a ./finish, lefuttatja azt.
  3. Ha a ./finish nem létezik, vagy kilép, újra elindítja a ./run-t.

Ha a scriptek "azonnal" kilépnek, a runsv egy másodpercet vár az újraindítási kísérletek között.

Ha létezik a ./down nevű file, a runsv nem indítja el azonnal a ./run-t, csak akkor, ha erre külön megkérjük.

Ha létezik a ./log könyvtár, akkor a runsv nyit egy pipe-ot, a ./run és a ./finish standard outputját abba irányítja (ezt megteheti, hiszen az ő gyermekfolyamatai, vagyis tőle öröklik az stdout filedeszkriptort), és a ./log könyvtárban található run és finish programokkal pontosan úgy jár el, mint a szolgáltatás hasonló nevű file-jaival. A ./log/run abból a pipe-ból fog olvasni, amibe a ./run ír, tehát a szolgáltatás standard outputja megjelenik a logoló alszolgáltatás standard inputján.

A runsv létrehoz egy ./supervise (és esetleg ./log/supervise, ha van ./log) könyvtárat. A supervise könyvtár tartalma:

  • status: bináris file, ami a szolgáltatás állapotával kapcsolatos információkat tartalmazza
  • stat: szövegfile. Tartalma lehet:
    • "run": a szolgáltatás fut
    • "down": a szolgáltatás nem fut
    • "finish": a szolgáltatáshoz tartozó finish program fut
    • a fentiek még kiegészülhetnek a következőkkel:
      • ", paused": a szolgáltatást felfüggesztettük
      • ", got TERM": a szolgáltatásnak küldtünk TERM signalt, de még nem lépett ki
      • ", want down": a szolgáltatást le szeretnénk állítani, de még nem állt le
      • ", want exit": a runsv-t is le szeretnénk állítani, de ehhez előbb a szolgáltatást kell, az pedig még nem állt le
  • pid: a szolgáltatáshoz tartozó éppen futó program (run vagy finish) process ID-ja
  • control: egy fifo, amibe parancsokat írhatunk (ezt csinálja az sv(8) program, l. később):
    • u: "up". Ha a szolgáltatás nem fut, elindítja; amikor kilép, újraindítja.
    • d: "down". Ha a szolgáltatás fut, kap egy TERM, majd egy CONT signalt (hogy a TERMre akkor is tudjon reagálni, ha fel volt függesztve). Ha a ./run kilép, lefut a ./finish; amikor az is kilép, a runsv nem indítja újra a szolgáltatást.
    • o: "once". Ha a szolgáltatás nem fut, elindítja. Amikor kilép, nem indítja újra (de a finish lefut).
    • p: "pause". Ha a szolgáltatás fut, felfüggeszti (STOP signalt küld neki).
    • c: "continue". Ha a szolgáltatás fut, folytatja (CONT signalt küld).
    • h: "hangup". Ha a szolgáltatás fut, HUP signalt küld neki.
    • a: "alarm". ALARM signal.
    • i: "interrupt". INT signal.
    • q: "quit". QUIT signal.
    • 1: USR1 signal.
    • 2: USR2 signal.
    • t: "terminate". TERM signal. Ha a szolgáltatás "up" állapotban volt, és a TERM hatására kilép, akkor ez lényegében egy újraindítással egyenértékű, mert a runsv újra el fogja indítani.
    • k: "kill". KILL signal.
    • x: "exit":
      • Ha a szolgáltatás fut, kap egy TERM, majd egy CONT signalt.
      • Amikor kilép, lefut a finish.
      • Ha nincs log alszolgáltatás, a runsv kilép.
      • Ha van log, a runsv lezárja a log standard inputját, és megvárja, amíg a log/run is kilép.
      • Lefut a log/finish.
      • A runsv kilép.
      • A log szolgáltatásnak nem lehet exit parancsot küldeni (ill. lehet, de ignorálja).

Fontos, hogy a parancsok pontosan egy karakterből állnak, sorvége-jel sem kell utánuk; tehát ha nem az sv programmal, hanem kézzel akarjuk kiadni őket, és precízek akarunk lenni, akkor echo -n-t használjunk. Ha nem akarunk precízek lenni, az sem baj, mert a runsv nem törődik az ismeretlen parancskarakterekkel.

Gondoljunk arra, hogy ha nem fut a runsv (és így nincs, aki elvegye a fifoba írt karaktert), akkor az echo blokkolódni fog.

Ha a runsv TERM signalt kap, úgy tesz, mintha "x" parancsot kapott volna.

[szerkesztés] 3.4.1 A parancsok hatásának testreszabása

Megtehetjük, hogy a fenti parancsok hatását megváltoztatjuk.

Ehhez létre kell hoznunk a szolgáltatás könyvtárában egy control nevű alkönyvtárat, amiben a parancsokról elnevezett programokat helyezhetünk el. Ha olyan parancsot adunk ki, amelyhez tartozó control program létezik és futtatható, akkor a runsv

  • lefuttatja a control/parancs programot, és megvárja, amíg kilép;
  • ha a visszatérési érték sikeres, a runsv nem kézbesíti a kért signalt a szolgáltatásnak;
  • ha sikertelen, akkor kézbesíti.

A logoló alszolgáltatás vezérlése nem testreszabható.

[szerkesztés] 3.5 sv

Az sv feladata az, hogy információt szolgáltasson a runsv által menedzselt szolgáltatások állapotáról ill. hogy parancsokat adjon a runsv-nek. Képes továbbá korlátozottan emulálni egy SystemV initscript működését.

Indítása:

# sv [-v] [-w sec] command services

# /etc/init.d/service [-w sec] command

A services helyén felsorolhatunk egy vagy több szolgáltatást, amikre hatni szeretnénk; a hozzájuk tartozó runsv-s directory nevét kell megadni.

A command lehet a runsv által ismert egykarakteres parancsok valamelyike (de kiírhatjuk a teljes parancsot is, mert csak az első karaktert nézi), valamint a következők, amelyek a SystemV-initscript-emulációt segítik:

  • start: mint az "up", de megvárja, hogy a szolgáltatás el is induljon (alapértelmezés szerint 7 másodpercet). A szolgáltatáshoz tartozó check scripttel ellenőrzi, hogy elindult-e, vagy, ha ilyen nincs, akkor azt várja meg, hogy epszilonnál hosszabb ideje fusson. Ha 7 (vagy sec) mp után nincs eredmény, kilép, a szolgáltatás pedig továbbra is próbál elindulni.
  • stop: mint a "down", de megvárja, hogy a szolgáltatás le is állon (mint fent).
  • restart: mint egy "down", majd egy "up" egymás után.
  • shutdown: mint az "exit", de megvárja (timeout mint fent), hogy a runsv is kilépjen.
  • force-stop: mint a "stop", de időtúllépés esetén KILL signalt küld a szolgáltatásnak.
  • force-reload: mint egy "term" és egy "continue", de ha nem áll le időben, kap egy KILL signalt is.
  • force-restart: mint a "restart", de ha nem áll le időben, kap egy KILL signalt is.
  • force-shutdown: mint a "shutdown", de ... you know the rest. :)

Emellett van még egy check parancs, ami azt vizsgálja meg, hogy a szolgáltatás az általunk utoljára kért állapotban van-e, ill. ha nincs, akkor a megadott ideig vár arra, hogy oda eljusson.

Az initscript-emuláció értelme az, hogy ha pl. van egy apache szolgáltatásunk, akkor megcsinálhatjuk ezt (debianos példa):

# dpkg-divert --local --rename /etc/init.d/apache
# ln -s /usr/bin/sv /etc/init.d/apache

Így a /etc/init.d/apache segítségével majdnem a szokásos módon kezelhetjük az apache-t: működik a stop, a restart, a start és társai, mert az sv megnézi, milyen néven hívtuk meg, és az olyan nevű szolgáltatást próbálja menedzselni. Sajnos pl. "reload" akciót nem tudunk csinálni, ezt az sv egyelőre nem támogatja - pedig a check mintájára nem lenne nehéz. Ez nyilván nem jelenti azt, hogy nem tudjuk újraolvastatni az apache-val a konfigurációját, ha az apache amúgy képes erre (mellesleg képes), csak azt, hogy a runit ehhez nem nyújt nekünk segítséget.

Példa az sv status kimenetére:

# sv status /service/*
run: /service/atd: (pid 4557) 1238307s; run: log: (pid 4019) 2687162s
run: /service/cron: (pid 5120) 2687160s
run: /service/dnscache: (pid 4021) 2687162s; run: log: (pid 4017) 2687162s
run: /service/getty-1: (pid 3996) 2687162s
run: /service/getty-2: (pid 3995) 2687162s
run: /service/getty-3: (pid 4000) 2687162s
run: /service/getty-4: (pid 4002) 2687162s
run: /service/getty-5: (pid 4014) 2687162s
run: /service/mdadm: (pid 4062) 2687162s
run: /service/munin-node: (pid 4095) 2687162s; run: log: (pid 4094) 2687162s
run: /service/ntp: (pid 22798) 61327s; run: log: (pid 4076) 2687162s
run: /service/smartmontools: (pid 4191) 2687161s; run: log: (pid 4190) 2687161s
run: /service/socklog: (pid 31695) 1238676s; run: log: (pid 4079) 2687162s
run: /service/ssh: (pid 4028) 2687162s

[szerkesztés] 3.6 Runlevel-emuláció: runsvchdir

Ha nagyon a szívünkhöz nőttek a runlevelek, runit mellett is használhatjuk őket; ráadásul tetszőlegesen sok lehet belőlük, és szabadon adhatunk nekik neveket.

Az elgondolás lényege az, hogy több /etc/service-szerű könyvtárat csinálunk (minden runlevelhez egyet), más-más szolgáltatáskönyvtárak symlinkjeivel, és a runsvdirt rábeszéljük, hogy váltson át egy másikba, és értelemszerűen állítsa le azokat a szolgáltatásokat, amelyeknek az új könyvtárban nincs symlinkje, azokat pedig, amelyeknek az előzőben nem volt, de itt van, indítsa el.

Ehhez

  1. létre kell hoznunk valahol (alapértelmezés szerint a /etc/runit/runsvdir/ könyvtárban) egy-egy ilyen runlevel-könyvtárat
  2. ebben létrehozni egy "current" nevű symlinket, ami az aktuális runlevelre mutat
  3. a /etc/service-t (vagy ami a /etc/runit/2-ben a runsvdir paramétere) le kell cserélnünk egy symlinkre, ami erre a current nevű symlinkre mutat.

Ezután a current symlink átállítása runlevel-váltást eredményez. Ezt csinálja meg a runsvchdir parancs, és ráadásul egy az éppen elhagyott runlevelre mutató symlinket is létrehoz "previous" néven. Ez azért jó, mert attól, hogy a runsv processzek TERM signalt küldtek a lelövendő szolgáltatásoknak, még nem biztos, hogy mindegyik le is állt; a "previous" symlinken keresztül követhetjük nyomon a legegyszerűbben, hogy az előző runlevelben futó szolgáltatások státusza hogyan alakul.

[szerkesztés] 3.7 chpst

A chpst feladata az, hogy valamilyen környezetet beállítson, és abban elindítson egy gyermekfolyamatot. Ennek megfelelően a unixos folyamatok állapotterének jelentős részét tetszésünk szerint beállíthatjuk:

  • UID
  • GID
  • supplementary groups ("kiegészítő csoporttagságok")
  • környezeti változók (a daemontools envdir-jéhez hasonló módon; l. lejjebb)
  • chroot
  • nice
  • kölcsönös kizárás (a megadott program csak egy példányban futhasson)
  • erőforráskorlátok:
    • memória (vagy az összes, vagy csak az adatszegmens)
    • megnyitható file-ok száma
    • processzek száma
    • létrehozható file-ok maximális mérete
    • core file max. mérete
    • max. CPU-idő (a nem dokumentált -t kapcsolóval :)
  • új process group létrehozása a program elindítása előtt
  • standard filedeszkriptorok bezárása (input, output, error szelektíven) a program elindítása előtt

Ami hiányzik, pedig nem lenne rossz:

  • scheduler beállítása (lehetne pl. realtime, bár ez nem hordozható)
  • capability-k beállítása (szintén nem hordozható)
  • umask
  • munkakönyvtár
  • hard erőforráskorlátok (csak a soft limiteket állítja, de nem tudja őket nagyobbra rakni a hard limiteknél, vagyis ha van valamilyen default hard limit, annál nagyobb softlimitet a chpst nem tud beállítani; előtte ulimittel kell módosítani a hard limitet)
  • supplementary groupok megtartása (eldobja őket)
  • NSS használata a UID-k/GID-k feloldására (pl. LDAP-ban tárolt felhasználók és csoportok esetén workaroundolni kell)

A környezeti változók beállítása úgy történik, hogy létrehozunk egy könyvtárat, amiben minden file egy beállítandó környezeti változóról van elnevezve. A változó értéke az adott file tartalmának első sora lesz. A nullás karaktereket újsorrá alakítja, a sorvégi szóközöket és tabulátorokat pedig lenyeli. Ha a file nulla byte hosszú, akkor a róla elnevezett változót a chpst törli a környezetből.

A kölcsönös kizárás megvalósításához a chpst megpróbál lockolni egy file-t; ha sikerül, akkor elindítja a megadott programot úgy, hogy az megkapja a lockolt file-t is. Ha az elindított program bezárja az öröklött file-deszkriptort, akkor a kölcsönös kizárás nem biztosított; szerencsére kevés program csinálja ezt.

[szerkesztés] 3.8 svlogd

Az svlogd feladata az, hogy a standard inputon érkező sorokat automatikusan rotált naplófile-okba írja.

A naplózó alszolgáltatások általában az svlogd-t futtatják; az svlogd sokkal okosabb, mint a daemontools multilogja.

A naplózás a következőképpen működik:

  • Az svlogd kilép, ha a standard inputon file végét olvas.
  • Egy svlogd-példány egy vagy több könyvtárba logol.
  • Egy könyvtárba csak egy svlogd-példány logolhat (az apache2-ben versenyhelyzet van ezzel kapcsolatban, ha nem az előtérben futtatjuk és pipe-ba logolunk).
  • Minden könyvtárnak saját konfigurációja van, ami eldöntheti, hogy az adott könyvtárba a bemeneten olvasott sorok közül melyeket kell naplózni és melyeket nem.
  • Egy sor több könyvtárba is naplózódhat.
  • A könyvtárakban a következő file-ok találhatóak:
    • current: az aktuális logfile, amibe éppen ír az svlogd
    • @4000000044b600e22b73217c.s jellegű file-ok: korábbi naplófile-ok, amelyeknek az utófeldolgozása sikeres volt; a nevük a rotáció időpontja tai64n formátumban
    • @4000000044b600e22b73217c.u jellegű file-ok: mint fent, de nem történt utófeldolgozás
    • az utófeldolgozás közben még megjelenhetnek rövid életű .t kiterjesztésű file-ok és egy previous nevű file.
    • config: az svlogd adott könyvtárra vonatkozó konfigurációját tartalmazza. A formátum egyszerű; egykarakteres, sor eleji parancsok, majd a hozzájuk tartozó argumentumok:
      • sméret: a naplófile-okat rotálni kell, ha elérték a megadott méretet. Ha nulla, nincs méret alapján történő rotáció.
      • ndarab: a megadott darabszámú korábbi logfile-t tartunk meg. Ha 0, nem töröl régi logokat.
      • Ndarab: legalább a megadott darabszámú korábbi logfile-t tartunk meg. Ha elfogyott a hely, és ennél több régi log van, az svlogd törli a legrégebbit.
      • tmásodperc: ha az aktuális napló elérte a megadott kort, és nem üres, akkor rotálunk. Ha 0, nincs idő-alapú rotáció.
      •  !posztprocesszor: rotációkor a naplót a megadott program (pl. egy tömörítő vagy egy elemző) dolgozza fel. Alapértelmezés szerint nincs utófeldolgozás.
      • ua.b.c.d[:port]: a megadott IP-cím megadott portjára is elküldi az üzenetet (udp-vel) (valójában csak az első hossz byte-ot; a hossz megadható a parancssorban).
      • Ua.b.c.d[:port]: mint a kis u, de csak udp-n logol, a helyi logfile-ba nem írja bele az üzenetet.
      • pprefix: a megadott prefixet odailleszti az összes naplózott üzenet elé. Ez akkor jó, ha udp-n logolunk, és a fogadóoldalon könnyen szét akarjuk válogatni az üzeneteket; a prefix alapján lesz a legegyszerűbb.
      • -minta: a mintára illeszkedő sorokat nem naplózza (l. lejjebb).
      • +minta: a mintára illeszkedő sorokat mégis naplózza.
      • eminta: a mintára illeszkedő sorokat kiírja a standard errorra (l. lejjebb).
      • Eminta: a mintára illeszkedő sorokat mégsem írja ki a standard errorra.
    • Van még néhány egyéb file (state, newstate, lock), amiket az svlogd saját céljaira használ.

A posztprocesszort az svlogd a háttérben futtatja, úgyhogy közben megy tovább a logolás. Ha viszont a következő rotációkor még fut az előző processzor, az svlogd blokkolódik, amíg ki nem lép; ez blokkolhatja az svlogd-be logoló szolgáltatást is! A posztprocesszort érdemes lehet a tryto felügyelete alatt futtatni; ez néhányszor megpróbálja, de ha egyszer sem sikerül adott idő alatt befejezni, akkor inkább lemond a feldolgozásról és feldolgozás nélkül menti el a logot. A tryto a socklog csomagban található.

A mintaillesztés két metakaraktert ismer:

  • A csillag jelentése: tetszőleges sztring, kivéve a csillagot követő karaktert;
  • a plusz jelentése: a pluszt követő karakterből egy vagy tetszőleges számú.

Vegyük észre, hogy ez sokkal gyengébb, mint a reguláris kifejezések, cserébe viszont sokkal gyorsabb az illesztés, és az esetek döntő részében még a pluszra sincs szükség.

Pl. az a minta, hogy "*PAM_unix[*]: (ssh)*" illeszkedik minden olyan sztringre, amelyben az első nagy P betű után az jön, hogy "AM_unix[", majd egy tetszőleges sztring a következő "]" jelig, amelyet a ": (ssh)" sztring követ, ezután pedig még lehet bármi a sorban. Az a sor, hogy "Péntek: PAM_unix[1234]: (ssh) session opened for user root" nem illeszkedik, mert a "Péntek" P-betűje után nem az jön, hogy "AM_unix[".

A minták segítségével könnyen szétdobálhatjuk pl. a sokféle "daemon" üzenetet aszerint, hogy melyik szolgáltatás naplózta; vagy pl. naplózhatjuk egy könyvtárba az összes, az ssh-val kapcsolatos üzenetet attól függetlenül, hogy "auth" vagy "daemon" syslog facility-vel érkezett-e.

Az svlogd parancssori opcióival megadhatjuk, hogy

  • minden sor elejére illesszen egy tai64n időbélyeget (-t)
  • minden sor elejére illesszen egy lexikografikusan rendezhető, ember számára olvasható UTC timestampet (-tt)
  • cserélje a nem nyomtatható karaktereket valami másra (-r)
  • egyéb karaktereket is cseréljen (pl. szóközöket és tabokat válogatás nélkül aláhúzásra)
  • mekkora puffermérettel dolgozzon. Ezt nagyon nagy log-forgalom esetén érdemes az alapértelmezés szerinti 1024 byte-ról 4, vagy akár 16-64 kilobyte-ra is emelni, ha azt tapasztaljuk, hogy a szolgáltatás arra vár, hogy logolhasson; a legkönnyebben úgy jöhetünk rá, hogy emiatt lassú a naplózás (és így a szolgáltatás), ha strace-eljük az svlogd-t, és azt látjuk, hogy mindig teli pufferrel tér vissza a read() rendszerhívása. Normális esetben több karaktert próbál olvasni, mint amennyi van.

Az svlogd néhány signalra speciálisan reagál:

  • HUP: az összes log-könyvtár konfigurációját újraolvassa, a log-könyvtárakat újra megnyitja; ha valamelyik megszűnt, azt eldobja
  • TERM: feldolgozza a pufferében levő adatokat (eldönti, hova kell logolni az adott sorokat és kiírja őket), megvárja, hogy véget érjenek az esetleg futó posztprocesszorok, majd kilép
  • ALRM: rotálja az összes nem üres logot.

A standard errorra való logolást pl. arra lehet használni, hogy bizonyos üzenetekről emailt kapjunk. A módszer lényege a következő:

  • futtassunk egy uncat programot (a socklog csomagban található) úgy, hogy egy FIFO-ból olvasson;
  • állítsuk be az svlogd-ket úgy, hogy a standard errorjuk ebbe a FIFOba legyen irányítva;
  • a konfigurációjukban a különösen izgalmasnak tartott üzeneteket írassuk ki velük a standard errorra is;
  • az uncat folyamatosan olvassa az üzeneteket, és x byte után vagy y másodperc elteltével meghív egy programot, aminek a standard inputján megjelennek ezek az üzenetek
    • Ez a program lehet pl. a mail, vagy egy sms-küldő.

A megoldás hátránya az, hogy azáltal, hogy egy FIFO-ba esetleg több svlogd is ír egyszerre, az üzenetek elvileg összekeveredhetnek (persze futtathatunk annyi uncatet, ahány svlogd-t); valamint, ha az uncat leáll, és nincs, ami olvasson a FIFO-ból, azok az svlogd-k, amelyek írni próbálnak bele, blokkolódnak, és ezzel a beléjük naplózó szolgáltatásokat is blokkolják. Így elérhetjük, hogy egy rendszerfelügyeleti eszköz kritikus meghibásodási ponttá (single point of failure) váljon.

Ezt a mechanizmust Gerrit Pape, a runit és a socklog fő fejlesztője, socklog-notify-nak hívja.

Érdekesség: a fenti, aránylag összetett működést az svlogd mintegy 45 kB-nyi memóriafoglalással abszolválja.

[szerkesztés] 3.9 Hogyan jön létre a pipe a szolgáltatás és a naplózóprogram között?

Ehhez nézzük meg strace-szel, mit csinál a runsv.

Az alábbi a főfolyamat tevékenységét foglalja össze (kicsit zanzásítva):

execve("/usr/bin/runsv", ["runsv", "/tmp/svtest"], [/* 63 vars */]) = 0
[...]
chdir("/tmp/svtest")                    = 0
stat("down", 0x7fff0a8975f0)            = -1 ENOENT (No such file or directory)
stat("log", {st_mode=S_IFDIR|0775, st_size=16, ...}) = 0
stat("log/down", 0x7fff0a8975f0)        = -1 ENOENT (No such file or directory)
pipe([5, 6])                            = 0	# ezt a két FD-t a gyermekfolyamatok öröklik
fcntl(5, F_SETFD, FD_CLOEXEC)           = 0
fcntl(6, F_SETFD, FD_CLOEXEC)           = 0
[...]
fork()                                  = 5666	# ez lesz majd a logoló processz
[...]
fork()                                  = 5667	# ez lesz maga a szolgáltatás
open("supervise/pid.new", O_WRONLY|O_CREAT|O_TRUNC|O_NONBLOCK, 0644) = 15
write(15, "5667\n", 5)                  = 5	# bele is írjuk a PID-ját a supervise/pid-be
close(15)                               = 0
rename("supervise/pid.new", "supervise/pid") = 0
[...]

Nézzük most az első forkkal létrehozott processzt:

fcntl(5, F_GETFL)                       = 0 (flags O_RDONLY)
close(0)                                = 0	# bezárjuk az stdin-t
fcntl(5, F_DUPFD, 0)                    = 0	# rámásoljuk az 5-ös deszkriptort, ami a pipe egyik vége
close(6)                                = 0	# bezárjuk a 6-os deszkriptort, ami a pipe másik vége
chdir("./log")                          = 0
[...]
execve("./run", ["./run"], [/* 63 vars */]) = 0

A második forkkal létrehozott processz pedig a következőt csinálja:

fcntl(6, F_GETFL)                       = 0x1 (flags O_WRONLY)
close(1)                                = 0	# bezárjuk az stdoutot
fcntl(6, F_DUPFD, 1)                    = 1	# rámásoljuk a 6-os deszkriptort, ami a pipe másik vége (ebbe lehet írni)
close(5)                                = 0	# becsukjuk a pipe egyik végét (amiből a naplózóprogram olvasni fog)
[...]
execve("./run", ["./run"], [/* 63 vars */]) = 0

[szerkesztés] 3.10 A TAI64N időbélyegekről

TAI: Temps Atomique International. Egy TAI-másodperc az az idő, amely a céziumatom által meghatározott körülmények között kibocsátott sugárzás 9192631770 darab periódusát teszi ki, és nemzetközi szabvány.

Egy TAI64-időbélyeg 64 bites, és a valós idő egy konkrét másodpercét jelöli meg (a relativisztikus hatásoktól tekintsünk el). Egy s időbélyeg értelmezése:

  • ha 0 <= s < 262: az a másodperc, amely 1970 TAI (1970. január elsején UTC szerint majdnem pont éjfél, konkrétan 1969. december 31. 23:59:50) előtt s másodperccel kezdődött;
  • ha 262 <= s < 263: az a másodperc, amely 1970 TAI után s-262 másodperccel kezdődött.

A 263-nál nagyobb értékeket esetleges bővítések számára tartják fenn. A jelenleg elterjedt kozmológiai elméletek jó része szerint a 263 alatti értékek elegendőek a Világegyetem teljes várható élettartamának lefedésére. Ha igazuk van, várhatóan nem lesz szükség a TAI64 bővítésére.

A TAI64-et nyolc byte reprezentálja big-endian sorrendben, tehát az utolsó a legkisebb helyiértékű, az első pedig a legnagyobb. (Időszámításunk képzeletbeli nulladik évének -- tehát igazából i.e. 1-nek -- január elsején, közép-európai téli idő szerint 00:00:00-kor pl. TAI64-ben 3ffffff1868b7226 volt az idő. Ma, i.sz. 2012. október 31-én 11 körül, 400000005090f844-et írunk.)

A TAI64N ezt a nyolcbájtos formátumot kiegészíti egy négybájtos egésszel, amelynek az értéke 0 és 999999999 közé esik, és a megadott másodpercen belül egy adott nanoszekundum sorszámát tartalmazza. A legtöbb unixos alkalmazás számára ez már elegendő pontosságot biztosít. (Egy nanoszekundum alatt a fény kb. 30 centimétert tesz meg.)

Létezik TAI64NA is, ami további négy bájton az első tizenkét byte által megadott nanoszekundumon belül egy konkrét attoszekundum sorszámát tartalmazza. (Egy attoszekundum alatt a fény kb. 0,0000003 millimétert tesz meg. Ez egy hidrogénatom átmérőjének kb. a háromszorosa.)

TAI64N-ből a daemontools csomag tai64nlocal programja készít ember számára olvasható dátumot; tetszőleges dátumból TAI64N-t pedig az alábbi miniscript:

date +"16o2 62^%s+2 32^* 42949672960+p" | dc | tr A-Z a-z

A GNU date --date kapcsolója segítségével bármilyen dátumot átalakíthatunk. A fenti kódban a dc-nek átadott kifejezés redundáns (a mágikus 10*232-es konstans szerintem kispórolható belőle). Kap egy virtuális pirospontot, aki optimalizálja. :)

[szerkesztés] 3.11 utmpset

Kis segédprogram, a getty jellegű szolgáltatások finish scriptjéből kell meghívni; a bejelentkezési naplóban rögzíti, hogy az adott terminálon véget ért a login session. Ezt amúgy az init csinálná, de a runitban nincs benne az ezzel kapcsolatos kód.

[szerkesztés] 4 A nagy egész

Hogyan is érdemes egy runitos rendszert felépíteni?

Törekedjünk arra, hogy

  • minden szolgáltatásunkat a runit monitorozza, és
  • lehetőleg a standard outputra logoljanak, így
    • mindegyiknek egyéni svlogdje lehessen, és
    • ne függjenek egy rendszerszintű naplózószolgáltatástól.
  • A szolgáltatásaink, ha valami bajuk van, akkor ne megálljanak, hanem kilépjenek, hogy a runit újraindíthassa őket.

Az svlogd-k naplófile-jai külön logikai köteten legyenek, hogy semmi ne használhassa el előlük a helyet. Ez azért fontos, mert önmagukban az svlogd logjainak méretére (ha sehol sem nulla sem az n, sem az s érték) mindig van érvényes felső becslésünk, tehát ha más nem ír oda, és elég helyet csináltunk, akkor a hely biztosan nem fogy el. Ha elfogy a hely, az azért baj, mert blokkolódhatnak a szolgáltatásaink.

A rendszerszintű naplózást célszerű a socklogra bízni (erről később lesz szó).

Mindig törekedjünk a kód és a konfiguráció szétválasztására, hogy a scriptjeink hordozhatóak legyenek.

[szerkesztés] 4.1 run scriptek írása

Egy szolgáltatást futtató run script alapreceptje a következő:

  1. standard error átirányítása a standard outputra (hogy a logban jelenjen meg);
  2. a szolgáltatás elindítása az exec paranccsal olyan módon, hogy a meghívott parancssor csak akkor térjen vissza, amikor kilép a szolgáltatás.

Egy minimális run script valahogy így fest (rinetd):

#!/bin/sh
exec 2>&1
exec rinetd -f

Ha egy új run scriptet írunk, akkor kiindulhatunk a szolgáltatás initscriptjéből. Nézzünk egy példát, mondjuk az apache2-t. Az initscript a következő (csak az indításért felelős részt hagytam benne):

#!/bin/bash -e
#
# apache2               This init.d script is used to start apache2.
#                       It basically just calls apache2ctl.

ENV="env -i LANG=C PATH=/usr/local/bin:/usr/bin:/bin"

#edit /etc/default/apache2 to change this.
NO_START=0

set -e
if [ -x /usr/sbin/apache2 ] ; then
        HAVE_APACHE2=1
else
        exit 0
fi

. /lib/lsb/init-functions

test -f /etc/default/rcS && . /etc/default/rcS
test -f /etc/default/apache2 && . /etc/default/apache2
if [ "$NO_START" != "0" -a "$1" != "stop" ]; then 
        [ "$VERBOSE" != "no" ] && log_warning_msg "Not starting apache2 - edit /etc/default/apache2 and change NO_START to be 0.";
        exit 0;
fi

APACHE2="$ENV /usr/sbin/apache2"
APACHE2CTL="$ENV /usr/sbin/apache2ctl"

# Stupid hack to keep lintian happy. (Warrk! Stupidhack!).
case $1 in
        start)
                [ -f /etc/apache2/httpd.conf ] || touch /etc/apache2/httpd.conf
                # ssl_scache shouldn't be here if we're just starting up.
                [ -f /var/run/apache2/ssl_scache ] && rm -f /var/run/apache2/*ssl_scache*
                # /var/run and /var/lock could be on a tmpfs
                [ ! -d /var/run/apache2 ] && mkdir /var/run/apache2
                [ ! -d /var/lock/apache2 ] && mkdir /var/lock/apache2
                # Make sure /var/lock/apache2 has the correct permissions
                chown www-data /var/lock/apache2

                log_begin_msg "Starting apache 2.0 web server..."
                if $APACHE2CTL startssl; then
                        log_end_msg 0
                else
                        log_end_msg 1
                fi

Ugye emlékszünk, milyen hosszú volt a leállításért felelős rész - mindazt nem kell megírnunk, a runit elintézi (kivéve, ha nagyon buta dolgokat csinál a szolgáltatásunk - pl. a szülőfolyamat ki tud lépni anélkül, hogy az összes gyermekét kiléptetné).

Csak érdekességképpen nézzük meg, mit is importálunk a /lib/lsb/init-functions file-ból (nem másolom be mind a 290 sort):

  • start_daemon() - egy wrapper a start-stop-daemon köré
  • pidofproc() - megpróbálja megtippelni egy szolgáltatás PIDjét - de pl. vakon bízik a pidfile tartalmában...
  • killproc() - mint a pidofproc, de még meg is próbálja kilőni a megtalált processzt
  • log_use_fancy_output() - a csicsaképességet detektálja; a többi függvény gyakran hívogatja, ez viszont külső programot is hív => nesze neked, sebesség
  • log_success_msg(): echo "$@" - emiatt aztán érdemes volt külön függvényt írni - idővel majd bizonyára ez is csicsa lesz
  • log_warning_msg() - csicsa
  • get_lsb_header_val() - az initscriptben elhelyezett kommentekből információt kibányászó segédfüggvény pl. komplikált függőségkezeléshez
  • log_daemon_msg() - csicsa annak érdekében, hogy ugyanaz az initscript futhasson különböző disztribúciók alatt, és az outputja igazodjon az adott disztribúció initscript-look'n'feeljéhez. Hja kérem, fontos dolgok...
  • log_progress_msg() - csicsa
  • log_end_msg() - csicsa
  • log_action_msg(): echo "$@." - csicsa-placeholder
  • log_action_begin_msg(): echo -n "$@..." - dettó
  • log_action_cont_msg(): echo -n "$@..." - dettó
  • log_action_end_msg() - csicsa

Mérleg: 10 db csicsázófüggvény, 2 db esetleges PID-tippelő, 1 db értelmesnek nevezhető függvény. Az apache2 elindításához szigorúan véve egyiknek sincs köze.

Az a bizonyos /usr/sbin/apache2ctl, amit az initscript meghív, szintén egy shellscript. 103 sor hosszú, de "start" paraméter esetén a tevékenysége így foglalható össze:

/usr/sbin/apache2 -k $@

"startssl" esetén ezt csinálja:

/usr/sbin/apache2 -k start -DSSL

Írjunk első közelítésben egy egyszerű run scriptet a fentiek alapján:

#!/bin/bash
exec 2>&1

#edit /etc/default/apache2 to change this.
NO_START=0
MAXFILES=1024

test -f /etc/default/apache2 && . /etc/default/apache2 # beolvassuk a Debian-féle konfigot a kompatibilitás érdekében

# adjunk jogokat az apacheadmin csoportnak a szolgáltatás menedzselésére:
chown :apacheadmin ./supervise ./supervise/control
chmod g+w ./supervise/control
chmod g+x ./supervise 

[ -x /usr/sbin/apache2 ] || exec sv down .	# ha nincs apache bináris, leállítjuk magunkat
[ "$NO_START" != "0" ] && exec sv down .	# ha a configfile szerint nem kell futnunk, akkor lépjünk ki

[ -f /etc/apache2/httpd.conf ] || touch /etc/apache2/httpd.conf	# a debianos initscript létrehozta ezt a file-t, ha nem létezett, tegyünk hát így mi is
[ -f /var/run/apache2/ssl_scache ] && rm -f /var/run/apache2/*ssl_scache*	# a debianos scriptből átemelve

[ ! -d /var/run/apache2 ] && mkdir /var/run/apache2				# szintén
[ ! -d /var/lock/apache2 ] && mkdir /var/lock/apache2				# szintén

# Make sure /var/lock/apache2 has the correct permissions
chown www-data /var/lock/apache2						# szintén

ulimit -H -n $MAXFILES								# a chpst csak a softlimiteket állítja, a hardot nekünk kell
exec chpst -o $MAXFILES /usr/sbin/apache2 -k start -DSSL -DNO_DETACH		# ezt már az apache2ctl-ből puskáztuk

Ez így már működik, de nem túl hordozható. Bele van drótozva a www-data user, az apacheadmin group, és egy csomó könyvtár helye. Nem választottuk szét a kódot és a konfigurációt.

Próbáljuk meg még egyszer, kicsit általánosabban:

#!/bin/bash
exec 2>&1

SVNAME=$(basename $(pwd))	# találjuk ki, hogy hívják a szolgáltatást, amiben futunk (pl. apache2-svn)
SVCONFIG=/etc/default/$SVNAME	# ez lesz az erre a példányra vonatkozó configfile

# néhány default:
MAXFILES=1024
RUNASUSER=www-data
SERVERROOT=/etc/$SVNAME
APACHECONFIGFILE=$SERVERROOT/apache2.conf
ADMINGROUP=apacheadmin
VARRUNGROUP=root
VARRUNMODE=700
SSL="-DSSL"

# betöltjük a debian-configot; ez is felülbírálhatja a fentieket, mondjuk gépszinten:
[ -r /etc/default/apache2 ] && . /etc/default/apache2
# betöltjük a specifikus configot (ez átírhatja a fenti defaultokat):
[ -r $SVCONFIG ] && . $SVCONFIG

chown :$ADMINGROUP ./supervise ./supervise/control
chmod g+rw ./supervise/control
chmod g+x ./supervise 

[ -x /usr/sbin/apache2 ] || exec sv down .
[ "$NO_START" != "0" ] && exec sv down . # A debian-konfiguráció használ egy NO_START változót, támogassuk mi is

[ -f $SERVERROOT/httpd.conf ] || touch $SERVERROOT/httpd.conf
rm -f /var/run/$SVNAME/*ssl_scache* 2>/dev/null # minek megnézni, hogy létezik-e? anélkül is törölhetjük

# A /var alatti könyvtárakat konfigurálható jogokkal hozzuk létre:
mkdir -p /var/run/$SVNAME /var/lock/$SVNAME
chown $RUNASUSER:$VARRUNGROUP /var/run/$SVNAME /var/lock/$SVNAME
chmod $VARRUNMODE /var/run/$SVNAME /var/lock/$SVNAME

# Állítsuk be a hard erőforráskorlátot, mert azt a chpst nem tudja:
ulimit -H -n $MAXFILES

# Csúf, de az apache2 esetében sajnos szükség lehet rá;
# mentségünk, hogy csak az adott userként futó apache2-ket lőjük le,
# ilyen userként pedig akkor csak azok fussanak, amik hozzánk tartoznak.
pkill -u $RUNASUSER apache2

exec chpst -o $MAXFILES \
	sudo -u $RUNASUSER \
	/usr/sbin/apache2 -d "$SERVERROOT" \
	$SSL \
	-DNO_DETACH \
	-f "$APACHECONFIGFILE"

# azért sudo, mert az belép a megfelelő supplementary groupokba, a chpst pedig eldobja őket;
# nekem a shadow csoporttagság miatt kellett

Ha ezt a run scriptet bemásoljuk egy apache2-svn nevű könyvtárba, és azt belinkeljük a /etc/service-be, akkor a script, amikor elindul, az apache2-svn-re jellemző konfigurációt fogja beolvasni a /etc/default/apache2-svn-ből. A kód és a konfiguráció jól elkülönül; a script vándorolhat gépről gépre anélkül, hogy módosítani kellene. Elég a /etc/default/apache2-* file-okat módosítani.

Vegyük észre, hogy itt a sudo miatt eleve nem rootként indul el az apache2, tehát nem fog tudni 1024 alatti porton figyelni, hacsak ezt valahogyan nem engedélyeztük a RUNASUSER számára.

Az ilyen template-szerű run-scripteket úgy ugyan nem használhatjuk, hogy ugyanazt a könyvtárat több példányban, csak mindig más néven belinkeljük a /etc/service alá, mivel mindegyikhez tartozó runsv ugyanazt a supervise alkönyvtárat akarná használni; annak viszont természetesen nincs akadálya, hogy a run scriptet az egyes példányok saját könyvtáraiban egy a template run-scriptre mutató symlinkkel helyettesítsük. Így a run scriptet könnyedén tarthatjuk pl. subversion repositoryban.

[szerkesztés] 4.2 Logoló run script

Egy minimális, svlogd-t indító run script valahogy így fest:

#!/bin/sh
exec chpst -u log svlogd /var/log/sv/szolgaltatasneve

Ezzel az a baj, hogy ha a szolgáltatást egy új gépre másoljuk, ott még esetleg nincs meg ez a könyvtár, és ebben az esetben az svlogd csődöt mond. Egy kicsit feltupírozott template logolóscript festhet pl. így:

#!/bin/sh
cd ..
SVNAME=$(basename $(pwd))
LOGDIR=/var/log/sv/$SVNAME
LOGDIRMODE=700
LOGDIRGROUP=root
LOGUSER=log
CONFIG=/etc/default/$SVNAME
SVLOGDOPTS="-t"
ROTSIZE=50000
ROTNUM=50
POSTPROC="gzip -9"

# rendszerszintű svlogd-defaultok
[[ -r /etc/default/svlogd ]] && . /etc/default/svlogd
# erre a konkrét szolgáltatásra vonatkozó beállítások:
[[ -r $CONFIG ]] && . $CONFIG

mkdir -p $LOGDIR

chown $LOGUSER:$LOGDIRGROUP $LOGDIR
chmod $LOGDIRMODE $LOGDIR

cd $LOGDIR || exit 1
if [[ ! -f config ]]; then
        cat <<EOF >config
s$ROTSIZE
n$ROTNUM
!tryto -pP $POSTPROC
EOF
fi

exec chpst -u $LOGUSER svlogd $SVLOGDOPTS $LOGDIR

[szerkesztés] 5 Ajánlott irodalom

[szerkesztés] 6 Potenciális zh-kérdések

  • Mik az előnyei a runitnak a hagyományos System V inittel szemben? Mik a hátrányai?
  • Milyen feltételeket kell teljesítenie egy minimális runitos run-scriptnek? Mire célszerű odafigyelni a megírásakor?
  • Milyen lehetőségeket biztosít az svlogd a logok által elfoglalt hely mennyiségének menedzselésére?
  • Milyen esetekben rotálja a logokat az svlogd?
  • Mi alapján tudja az svlogd szétválogatni a naplóüzeneteket?
  • Hogyan valósíthatunk meg runlevel-váltást runitos rendszerben?
  • Hová naplózhatjuk a runitos szolgáltatásaink naplózásával kapcsolatos hibaüzeneteket? Hogyan?
  • Mire jó, hogy az svlogd bizonyos naplóüzeneteket szelektíven a standard errorra is ki tud írni?
  • Mire kell odafigyelni, ha posztprocesszort írunk az svlogd-hez?
  • Hogyan működik a runitban a System V initscriptek emulációja?
  • Hogyan adhatók meg a runitban a szolgáltatások egymástól való függései? (Hogyan biztosítható, hogy a függő szolgáltatás csak a szükséges szolgáltatás után indulhasson el?)

A zh második részéhez (ahol egy a4-es lapnyi segédeszköz használható):

  • Adott az alábbi System V initscript és az általa indított szolgáltatás dokumentációjának releváns része. Írjon ez alapján olyan run-scriptet, amely ugyanezt a szolgáltatást a runit számára menedzselhető módon indítja el! Az eredeti script sorszámozott sorait nem kell újra leírnia, elég rájuk szám szerint hivatkozni.
  • Adott az alábbi run-script. Mit rontottunk el benne?
Személyes eszközök