Pages

Thursday, April 19, 2012

Въведение в daemontools (DJB Way)

Софтуера, написан от професор Daniel J. Bernstein е трудно разбираем за много хора и може би за това не е толкова популярен. В тази статия ще се опитам да обясня концепията, стояща зад неговите програми.

Bernstein е приел модела на UNIX, тоест програмите да са колкото се може по-малки, да вършат строго определена работа, да комуникират посредством unix pipes. Ето пример, който всеки администратор използва:

# ps ax|grep httpd|wc -l
      12
Типичен pipeline, където изхода от програмата ps се подава на входа на програмата grep, която пък отбира редовете които съдържат httpd, като след това се подава на програмата wc, която преброява колко реда съдържат httpd и отпечатва стойността. В случая - в момента имаме 12 активни процеса на httpd.

Кодът на програмите му е написан на ANSI C, компилира се на всякакви OS поддържащи POSIX модела.

Ще започнем с daemontools, които са основата на неговият тип софтуер. Daemontools представлява пакет от малки полезни програмки, които следят и поддържат даден процес да бъде активен. Добър аналог на тази система е Services в Windows XP/7.

Инсталацията е опростена максимално:

# wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
# zcat daemontools-0.76.tar.gz|tar xvf -
# cd admin/daemontools-0.76
# package/install

 Програмата добавя ред в /etc/inittab:

SV:123456:respawn:/usr/bin/svscanboot

който указва, че ако процесът svscanboot завърши по някакъв начин, трябва да се стартира наново.
Всъщност svscanboot е просто един скрипт:

#!/bin/sh
# WARNING: This file was auto-generated. Do not edit!

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

exec </dev/null
exec >/dev/null
exec 2>/dev/null

svc -dx /service/* /service/*/log

env - PATH=$PATH svscan /service 2>&1 | \
env - PATH=$PATH readproctitle service errors: ........................................................................................................
.......................................................................................................................................................
.................................................................................................................................................

Скриптът изпълнява svscan, с опция директорията с /services, като изхода на svscan  е пренасочен към друга програмка - readproctitle. Ако има грешки при стартирането на някой service, то съобщението за грешка ще го получи readproctitle, който  от своя старна ще го отпечата на мястото на точките. Това ни дава възможността да проследим грешките от /service ето така:

`--# ps ax|grep readproc
  173 ttyE0- IW    0:00.01 readproctitle service errors: ..................................................................................................................................................
Както се вижда - грешки няма. Ето пример - когато има грешки (промених нарочно run файла в който се стартира multilog -> multilog_proba_za_error) :

`--# ps aux|grep readproc
root       172  0.0  0.0    16   500 ttyE0- S     2Mar12  0:00.02 readproctitle service errors: ...s not exist\nsetuidgid: fatal: unable to run multilog_proba_za_error: file does not exist\nsetuidgid: fatal: unable to run multilog_proba_za_error: file does not exist\nsetuidgid: fatal: unable to run multilog_proba_za_error: file does not exist\nsetuidgid: fatal: unable to run multilog_proba_za_error: file does not exist\nsetuidgid: fatal: unable to run multilog_proba_za_error: file does not exist\n

Структура на директориите при daemontools. Всеки отделен service се пуска от отделна поддиректория на основната за daemontools - /service (при Debian например е /etc/service).


Да кажем, че искате да пуснете програма през daemontools, като целта е програмата да се следи дали не е приключила, и ако е - да се пусне наново. За целта създаваме директория в удобно за нас място (Примера е пускане на sshd през daemontools).


mkdir -p /etc/svc/sshd
mkdir -p /etc/svc/sshd/log

Всяка една от тези 2 директории трябва да съдържа по един run - файл, в който се осъществява самото стартиране на програмата. Изискването е програмата да НЕ се пуска във фонов режим (background/daemon).
Daemontools ще се погрижи тя да си отиде във фонов режим. Ако програмата записва в логовете през syslog - системата, трябва да потърсите опция която указва грешките, и логовете да се пишат в stderr или stdout. Ако не направите това, то multilog - програмката няма да може да прихване и запише логовете в log - директорията.

/etc/svc/sshd/run - файла, в който стартираме самия процес.
#!/bin/sh
exec 2>&1
exec /usr/local/sbin/sshd -D -e
 
Забележка:
Опциите на sshd са, за да се спазят изискванията на daemontools (текста отдолу е изваден от man страницата на sshd).

-D When this option is specified, sshd will not detach and does not become a daemon. This allows easy monitoring of sshd.
-e When this option is specified, sshd will send the output to the standard error instead of the system log.
exec 2>&1 - указва всички грешки отпечатани на stderr (2) да се пренасочат към stdout (1)

/etc/programa1/sshd/log/run - файла, в който стартираме процеса, който запазва логовете на sshd
#!/bin/sh
exec setuidgid root multilog t /var/log/sshd
В случаят програмката multilog се пуска с правата на потребител root, t означава да добави timestamp (във формат tai64 който ще бъде обяснен по-долу) в началото на всеки ред, и накрая се указва директорията, в която ще се съхраняват логовете. Multilog има още няколко допълнителни опции, които са полезни като например: n (брой log файлове) и s (размер на log файловете). Ако променим реда ето така:
exec setuidgid root multilog t s500000 n50 /var/log/sshd
то всеки log файл ще има размер 500,000 bytes и максималния брой на log - файловете ще е 50. Когато се запълнят всичките 50 файла с по 500 000 bytes, се изтрива най-старият файл и се създава нов.

Логовете се записват под формата на timestamp като име на файл освен текущият, който е current. Ето извадка от списък с логове:
-rwxr--r-- 1 qmaill nogroup 16775307 Mar 14 06:13 @400000004f601b0c36890194.s
-rwxr--r-- 1 qmaill nogroup 16775283 Mar 17 22:00 @400000004f64ed533284f5e4.s
-rwxr--r-- 1 qmaill nogroup 16775332 Mar 21 16:57 @400000004f69ec6132814c64.s
-rwxr--r-- 1 qmaill nogroup 16775234 Mar 26 16:32 @400000004f706ffb12df90dc.s
-rwxr--r-- 1 qmaill nogroup 16775255 Mar 30 07:42 @400000004f7539d52ef51c4c.s
-rwxr--r-- 1 qmaill nogroup 16775226 Apr  2 17:13 @400000004f79b3fb24fd9cb4.s
-rwxr--r-- 1 qmaill nogroup 16775247 Apr  4 12:27 @400000004f7c140f3760cbc4.s
-rw-r--r-- 1 qmaill nogroup 10108940 Apr 18 15:42 current
Единственото, което остана за да стартираме ssh service през daemontools е да направим symbolic link на директорията, която създадохме (/etc/svc/sshd/) към /service/. Ето така:
# ln -s /etc/svc/sshd/ /services/sshd
след което този новосъздаден от нас service ще се стартира автоматично.

Може би забелязахте, че пропуснах да обясня някои от използваните програмки като setuidgid или пък svc. Нека обърнем малко внимание и на допълнителните програмки, които идват с daemontools.

svc - служи за контрол на services за които се грижи supervise. С тази програмка на практика изпращате различни сигнали към даденият service.

-u (up) Стартира service. Ако не е стартиран - го стартира. Ако процесът по някаква причина завърши - процесът се рестартира.
-d (down) Спира service. Ако е стартиран, му изпраща сигнал TERM. След като е спрян, service не се рестартира.
-o (once) Стартира се само веднъж. При спиране на service не се рестартира.
-p (pause) Изпраща сигнал STOP..
-t (terminate) Изпраща сигнал TERM. На практика с тази опция се рестартира даден service. Процесът получава TERM - сигнал който би трябвало да го спре и след няколко секунди supervise ще го вдигне отново.
-k (kill) Изпраща сигнал KILL.
Има и още опции, и за повече информация погледнете man - страницата, или на http://cr.yp.to/daemontools/svc.html. Примери:

svc -t /service/* - рестартира всички services.
svc -d /service/qmail-send/ - спира даденият service.
svc -u /service/qmail-send/ - стартира даденият service.

svok - проверява дали даден service е стартиран. Връща стойност 0 ако service е активен, и 100  - ако service не е активен. Може да се използва в скритове.

svstat - показва текущия статус на даден service(s). Може да съдържа произволен брой аргументи, като всеки от тях е път към даден service. Поддържа и wildcards. Пример:

# svstat /service/qmail-send/ /service/qmail-smtpd/
/service/qmail-send/: up (pid 3540) 1891374 seconds
/service/qmail-smtpd/: up (pid 3539) 1891374 seconds

или

# svstat /service/*/log
/service/qmail-send/log: up (pid 7695) 6134915 seconds
/service/qmail-smtpd/log: up (pid 15281) 3457319 seconds

setuidgid - Стартира процес с права на друг потребител. Ако даден service не са му нужни root права, можем да го пуснем с друг потребител с ограничени права. Примерът от по-горе:

#!/bin/sh
exec setuidgid root multilog t /var/log/sshd

стартира multilog с правата на потребител root. Както се вижда, не е нужно multilog да се стартира с root права, и за целта можем да направим следното: създаваме нов потребител (примерно sshlog), променяме притежателя на директорията /var/log/sshd на sshlog (chown sshlog /var/log/sshd), и след това променяме и реда ,с който се стартира multilog, за да стане така:

#!/bin/sh
exec setuidgid sshlog multilog t /var/log/sshd

Така процесът за log - файловете ще е стартиран с ограничените права на потребителя sshlog.

envuidgid - Стартира процес с обкръжението на даден акаунт.

softlimit - Стартира процес с ограничени ресурси като максимална памет която може да заема, големина на създадените файлове и т.н. (аналог на ulimit). Пример:

# softlimit -m 2000000 /bin/sh
# mc
mc: error while loading shared libraries: libslang.so.2: failed to map segment from shared object: Cannot allocate memory

стартира /bin/sh с ограничено използване на памет от 2 000 000 bytes. При опит да се стартира нещо, което заема памет (като midnight commander) се вижда какъв е резултата.
Тази опция я използвам при qmail за лимитиране на големината на получените съобщения.

tai64nlocal - Чете от stdin, търси редове, които започват с @ и след тях има tai64 timestamp, като ги преобразува във формат: YYYY-MM-DD HH:MM:SS.SSSSSSSSS. Пример:

оригинален timestamp от log файл на multilog
@400000004eef68ef060b533c status: local 0/10 remote 2/20

# echo "@400000004eef68ef060b533c status: local 0/10 remote 2/20"|tai64nlocal
2011-12-19 18:40:05.101405500 status: local 0/10 remote 2/20
TAI (Temps Atomique International) е международен стандарт за измерване на време, който за момента не се използва в масовите операционни системи (http://cr.yp.to/proto/utctai.html).
Tai64 е имплементация, направена от Dan Bernstein, която се използва в неговите програми (http://cr.yp.to/proto/tai64.txt).

fghack - anti-backgrounding tool. Ако имате програма, която винаги се старира във фонов режим (background/daemon), с тази програмка можете да "излъжете" тя да се стартира във foreground режим. (не съм го пробвал а и DJB казва, че не работи във всички случаи)

pgrphack - понякога има програми, които при изход изпращат TERM - сигнал не до процес - ID (pid), а до груповия ID (gid), при което сигналът се получава и от svscan, от което последствията не са много приятни. За пример е даден pppd програмата, която трябва да се пуска точно с pgrphack. (това лично аз не съм го тествал).

Ползвани ресурси: http://cr.yp.to/daemontools.html

Tuesday, April 17, 2012

Debian - автоматичен update и dpkg опцията hold.

Случвало ли ви се е да направите upgrade при който някои от пакетите да ви развалят конфигурацията. Ето какво често се случва.

Стандартна инсталация на Debian при която някои от пакетите са преправени от мен. Dovecot-imapd пакета нямаше поддръжка за vpopmail затова ми се наложи да го rebuild с тази опция. Всичко работи до момента в който направите upgrade. Upgrade процесът премахва ръчно преправените пакети на dovecot и слага оригиналните. В този случай задължително е да ползвате опцията на dpkg - HOLD. Това прави така наречените HOLD пакети да не бъдат обновявани при цялостен upgrade на системата.

Следните 2 пакета не искам да бъдат обновявани при upgrade  на системата:

root@server:~# dpkg -l | grep dove
ii  dovecot-common                    1:1.2.15-7     secure mail server that supports mbox and maildir mailboxes
ii  dovecot-imapd                      1:1.2.15-7       secure IMAP server that supports mbox and maildir mailboxes

За целта е нужно да се направи следното:

root@server:~# echo dovecot-common hold | dpkg --set-selections
root@server:~# echo dovecot-imapd hold | dpkg --set-selections

Ето и разликата:

root@server:~# dpkg -l | grep dove
hi  dovecot-common                    1:1.2.15-7     secure mail server that supports mbox and maildir mailboxes
hi  dovecot-imapd                      1:1.2.15-7       secure IMAP server that supports mbox and maildir mailboxes

Забележете буквата 'h' в началото на пакетите. Подобна информация можем да получим и чрез:

root@server:~# dpkg --get-selections | grep hold
dovecot-common                                hold
dovecot-imapd                                   hold

Остава само да сложите в crontab-а един ред за автоматичен update (apt-get update && apt-get -y dist-upgrade) и машината спокойно може да я забравите някъде зазидана след поредния ремонт...