Szósty artykuł z cyklu Konfigurujemy VPS. Tym razem piszę o automatycznym uruchamianiu programów.
Obiecany w piątej części szczegółowy opis konfiguracji nginx-a będzie niedługo, po prostu przyda mi się do niego mechanizm opisywany poniżej.
Start, stop, restart...
Jak dystrybucje Linuxa uruchamiają serwery? Każdy wie - pliki w
/etc/init.d
, linki symboliczne do nich w /etc/rc*
lub podobnych
katalogach, proces init uruchamiający podlinkowane skrypty przy
zmianie poziomów działania. Tak startuje niemal wszystko, od demonów
wspierających działanie sprzętu, po serwery poczty czy WWW.
Podejście to ma dość oczywistą wadę, istotną zwłaszcza gdy administrator zagląda na serwer od czasu do czasu, a usługi mają działać nieprzerwanie. Proces, który ulegnie awarii, nie jest uruchamiany ponownie.
W przypadku najważniejszych systemowych demonów jest to mniej bolesne (działają bardzo stabilnie, często mają wbudowane własne mechanizmy restartu) - choć i tu w specyficznych przypadkach (choćby brak pamięci) mogą wystąpić awarie. Nasz kod - jeśli takowy mamy - może być bardziej wrażliwy.
Dlatego warto korzystać z narzędzi zapewniających nie tylko uruchomienie takiej czy innej aplikacji, ale też jej restart w razie awarii. Jest ich kilka, poniżej piszę szczegółowo o mojej ulubionej. Przy okazji opiszę też poręczną technikę umożliwiania konfigurowania demonów przez użytkowników nie mających praw administratora.
Runit - podstawy
Moja propozycja to runit. Jest to nieco uporządkowana reimplementacja daemontools. Program wymaga troszkę wysiłku konfiguracyjnego ale za to działa bardzo stabilnie i - co ważne w kontekście tego cyklu - zużywa mikroskopijne ilości pamięci.
Jak działa runit? Monitoruje zadany katalog (w Ubuntu i Debianie
jest to /etc/service
, w starszych ich wersjach /var/service
), w
każdym z jego podkatalogów poszukuje skryptu lub programu o nazwie
run
. Jeśli takowy znajdzie, uruchamia go. Jeśli ten skrypt lub
program z jakiegokolwiek powodu się skończy, runit
uruchomi go
ponownie. I tyle.
W efekcie, jeśli potrzebuję uruchamiać jakiś program, po prostu tworzę
nowy podkatalog w /etc/service
, wkładam tam skrypt run
i mogę być
pewny, że program będzie uruchamiany po reboocie i restartowany w
razie awarii.
Instalacja
W przypadku Debiana i Ubuntu wystarczy
$ sudo apt-get install runit
Po instalacji trzeba zweryfikować, czy główny proces runit
-a wstaje.
Na Ubuntu odpowiada za to skrypt /etc/event.d/runsvdir
, na Debianie
powinien się pojawić wpis dotyczący runit-a w /etc/inittab
.
Uwaga: nie radzę instalować pakietu runit-run
. Pozwala on zastąpić
domyślny proces init przy pomocy runita, wymaga to jednak głębokiej
rekonfiguracji systemu (min. przepisania wszystkich skryptów z
/etc/init.d
na skrypty runita). W tym artykule opisuję używanie
runit-a jako aplikacji pomocniczej.
Można natomiast zainstalować runit-services
:
$ sudo apt-get install runit-services
Ten pakiet zawiera zbiór skryptów runit-a dla różnych standardowych
pakietów (min. cron, ssh i postfix). Są one domyślnie nieaktywne
(instalowane w /etc/sv
), od nas zależy czy je aktywujemy (usuwając
zarazem skrypty z /etc/init.d
). Na pewno przydadzą się jako
przykłady.
Konwencja symlinków
Przy używaniu runit
-a zalecana jest następująca konwencja: zamiast
tworzyć katalogi w /etc/service
, tworzymy je w /etc/sv
, tam
piszemy skrypt run
i dopiero gdy wszystko jest gotowe, robimy
link symboliczny w /etc/service
. Czyli na przykład:
Tworzymy katalog:
$ mkdir /etc/sv/mojprogram
piszemy skrypt:
$ nano /etc/sv/mojprogram/run
ustawiamy prawo wykonywalności:
$ chmod a+x /etc/sv/mojprogram/run
robimy prosty test z palca:
$ cd /etc/sv/mojprogram
$ ./run
$ # ... Ctrl-C
i dopiero na koniec:
$ ln -s /etc/sv/mojprogram /etc/service/mojprogram
W ciągu co najwyżej kilku sekund od powyższej operacji runit zauważy nowy katalog, uruchomi skrypt i zacznie go monitorować.
runit
tworzy w monitorowanym katalogu podkatalogsupervise
zawierający wewnętrznie wykorzystywane pliki.
Nie jest to przymusowe, można stworzyć nowy katalog bezpośrednio pod
/etc/service
, ale runit
zacznie go monitorować od razu, generując
błędy braku pliku run
czy próbując uruchamiać jego niekompletne
wersje. Do tego konwencja symlinka pozwala w razie potrzeby czasowo
usunąć link bez usuwania skryptu.
Pisanie skryptów run
Skrypty run
są zazwyczaj prościutkie, na przykład:
#!/bin/sh
exec python /apps/moj_demon/moj_demon.py
Ważne jest, by uruchamiany program nie przechodził w tło (jeśli to
zrobi, runit uzna iż się zakończył i uruchomi kolejną kopię).
Większość często spotykanych programów posiada odpowiednią opcję
(nazwy są różne, np. -n
, -f
, czy --no-daemon
; czasem mamy osobny
skrypt np. runzope
zamiast zopectl
).
Wykorzystanie exec
nie jest obowiązkowe ale oszczędza pamięć
(zwalniamy zasoby używane przez proces shella).
W powyższym zapisie demon uruchomi się z konta root
. Przykład
ze zmianą użytkownika:
#!/bin/sh
USER=www-data
exec chpst -u$USER perl /apps/mybot/moj_demon.pl
(po prostu dodajemy chpst -uKONTO
przed właściwą komendą). Program
chpst
ma też kilka innych użytecznych opcji, np. ograniczanie
ilości zużywanej pamięci, uruchomionych procesów czy otwartych plików.
Ostatnia użyteczna sztuczka - weryfikowanie, że inna potrzebna usługa działa:
#!/bin/sh
sv start /etc/service/pomocniczy || exit 1
exec /usr/bin/someprogram
Nie zawsze jest to potrzebne, jeśli uruchamiany program przy braku -
powiedzmy - bazy danych się składa, to runit
po chwili odpali go
ponownie.
Zapis logów
runit
obsługuje także zapis do specyficznego, automatycznie
rotowanego logu. Umieszcza tam teksty wypisywane przez monitorowany
program na standardowym wyjściu.
Logowanie włączamy tworząc w katalogu programu (tym, w którym
znajduje się skrypt run
) podkatalog log
, z kolejnym
skryptem run
, o takiej formie:
#!/bin/sh
USER=logowner
DIR=/var/log/myapp
exec chpst -u$USER svlogd -tt $DIR
Bieżący log będzie zapisywany jako plik $DIR/current
,
starsze zrotowane logi będą odkładane obok w tym samym katalogu
(dość dziwne nazwy zawierające zakodowaną datę).
Parametry takie jak moment rotowania czy ilość zachowywanych starych
logów konfigurujemy przy pomocy pliku config
(w katalogu log
).
Przykładowa treść:
# Maksymalny rozmiar (w bajtach) - po jego przekroczeniu
# nastąpi rotowanie
s1000000
# Ilość zachowywanych starych plików (0 = dowolnie wiele)
n10
Szczegółowy opis - patrz man svlogd
.
Aby obsłużyć także wyjście błędu (stderr), należy je przekierować na stdout (w głównym skrypcie). Na przykład tak:
#!/bin/sh
USER=www-data
exec 2>&1
exec chpst -u$USER perl /apps/mybot/moj_demon.pl
(chodzi o magiczną konstrukcję exec 2>&1
)
Zatrzymywanie i restartowanie demonów
Interfejsem do zarządzania usługami monitorowanymi przez runit
jest program sv
. Przykładowe komendy:
$ sudo sv stop /etc/service/mojprogram
$ sudo sv start /etc/service/mojprogram
$ sudo sv restart /etc/service/mojprogram
Pełne zatrzymanie programu (i uniknięcie ponownych uruchomień) to:
$ sudo sv stop /etc/service/mojprogram
$ sudo sv shutdown /etc/service/mojprogram
$ sudo rm /etc/service/mojprogram
(stop programu, stop procesu nadzorującego go, usunięcie symbolicznego linka)
Demony użytkowników
Prosta sztuczka, z której korzystam głównie na desktopie ale może być
poręczna i na serwerze. Możemy łatwo sprawić, by skrypty umieszczone
przez wybranego użytkownika (użytkowników) w podkatalogach jego
$HOME/service/
(czy innego podobnego katalogu) były uruchamiane
jako demony (z jego konta oczywiście) i restartowane w razie potrzeby.
W poniższym przykładzie runit
będzie uruchamiał skrypty z
podkatalogów katalogu /home/appowner/service
(np. /home/appowner/service/mojbackend/run
) - oczywiście z konta
appowner
. Ponadto będzie logował wyjście tych programów
w katalogu /home/appowner/log
.
Aby to uzyskać, tworzymy systemowy skrypt runit-a:
$ sudo mkdir /etc/sv/runit_appowner
$ sudo mkdir /etc/sv/runit_appowner/log
$ sudo nano /etc/sv/runit_appowner/run
$ sudo nano /etc/sv/runit_appowner/log/run
$ sudo chmod a+x /etc/sv/runit_appowner/run
$ sudo chmod a+x /etc/sv/runit_appowner/log/run
$ sudo ln -s /etc/sv/runit_appowner /etc/service/
Skrypt /etc/sv/runit_appowner/run
wygląda tak:
#!/bin/sh
USER=appowner
DIR=/home/appowner/service
exec 2>&1
exec chpst -u$USER runsvdir $DIR
Do rozważenia także limity zasobów, patrz man chpst
. Program
runsvdir
to ten sam program, co używany do nadzorowania
/etc/service
, będzie działał analogicznie (tyle że już nie z konta
root
).
Skrypt /etc/sv/runit_appowner/log/run
wygląda standardowo:
#!/bin/sh
USER=appowner
DIR=/home/appowner/log
exec chpst -u$USER svlogd -tt $DIR
Dodajemy też używane katalogi:
$ mkdir /home/appowner/service /home/appowner/log
$ chown appowner.appowner /home/appowner/service /home/appowner/log
Jak korzysta z tego użytkownik appowner
? Na przykład tak (o ile
zastosuje konwencję symlinków analogiczną do systemowej):
$ mkdir ~/sv
$ mkdir ~/sv/mojdemon
$ nano ~/sv/mojdemon/run
$ chmod a+x ~/sv/mojdemon/run
$ ln -s ~/sv/mojdemon ~/service/mojdemon
Przy tym skrypt ~/sv/mojdemon/run
to po prostu coś takiego:
#!/bin/sh
cd $HOME/myapps/backend
exec python moj_skrypt.py
(czy cokolwiek chcemy uruchomić). Tu nie potrzeba już chpst
, o ile
wystarczy zbiorczy log nie są też potrzebne katalogi log
.
appowner
może też swobodnie używać komend sv start
, sv stop
itp
w odniesieniu do tych katalogów.
Cała ta konwencja jest szczególnie wygodna na desktopie, na serwerze stanowi pewien balans między wygodą a bezpieczeństwem - przy wyższym poziomie paranoi lepiej, by właścicielem katalogów nie był użytkownik, na którego koncie działają aplikacje.
Moje ulubione desktopowe zastosowanie powyższej techniki to utrzymywanie otwartych tuneli SSH. Dla każdego potrzebnego tunelu tworzę katalog
~/service/tunel_costam
, w nim plikrun
:#!/bin/sh # Lokalny port LOCAL=1143 # Zdalny host i port (z punktu widzenia maszyny VIA) REMOTE=localhost:143 # Pośrednik VIA=moj.supersajt.pl VIA_USER=tommy exec ssh -L $LOCAL:$REMOTE -l $VIA_USER -N $VIA
(to akurat przykład udostępnienia zdalnego IMAP z maszyny
moj.supersajt.pl
lokalnie na porcie 1143) i tunel pozostaje otwarty zawsze, gdy maszyna jest uruchomiona.
Wady runita
Runit ma dwie wady: dość nieporęcznie się go monitoruje i czasem bywa zbyt uporczywy.
Najszybszy sposób na ogarnięcie jakie katalogi runit monitoruje i jakie procesy uruchomił to ...
$ pstree -l -a
(programy runsv
odpowiadają pojedynczym poleceniom, programy runsvdir
monitorują katalogi). Komendy sv status
są niezbyt użyteczne.
Co do uporczywości: ilekroć runitowi (dokładniej, programowi runsv
)
nie uda się uruchomić monitorowanego programu, poczeka jedną sekundę i
spróbuje ponownie. Zawsze, nawet gdy skryptu run
nie ma czy nie jest
wykonywalny albo gdy program raportuje błąd w konfiguracji. To z
jednej strony dobrze (gdy tylko usługa będzie mogła wystartować,
wystartuje), z drugiej może generować nadmierne obciążenie lub zalać
logi informacjami o błędzie.
W praktyce jest to szczególnie istotne przy dodawaniu nowych usług,
trzeba dwa razy sprawdzić, że skrypt run
jest napisany poprawnie i
usługa faktycznie startuje.
Supervisor
Dość ciekawym programem funkcjonalnie zbliżonym do runit-a jest supervisor. Zamiast hierarchii katalogów mamy tutaj dość czytelny plik konfiguracyjny, jest także przyjazny program do administrowania, a nawet aplikacja webowa i aplikacja XML-RPC.
Problemy: supervisor sam z siebie zużywa dość dużo pamięci (jest
to spora aplikacja napisana w Pythonie), przynajmniej na VPS jest moim
zdaniem zbyt duży. Można mieć też wątpliwości, czy wbudowanej
funkcjonalności nie jest za wiele (serwis webowy i API XML-RPC na
koncie root
-a...). Wreszcie, program nie jest aktualnie paczkowany
przez istotne dystrybucje, easy_install supervisor
działa ale nie
możemy liczyć na automatyczne aktualizacje.
Upstart
Bodaj najbardziej naturalnym wyborem pod Ubuntu jest upstart, bądź
co bądź domyślny menedżer uruchamiania w tym systemie. Plany rozwoju
Ubuntu przewidują stopniowe przechodzenie z /etc/init.d
na skrypty
upstart-a (w tej chwili /etc/event.d
, w przyszłych wersjach
/etc/init/jobs.d
). Można też z niego korzystać samodzielnie.
Upstart potrafi automatycznie restartować procesy
(dyrektywa respawn
) i daje duże możliwości strojenia konfiguracji
(kolejność uruchamiania procesów, powiązania między nimi, akcje
uruchamiane pod wpływem różnych zdarzeń, częstotliwość i harmonogram
restartów itd itp). Ogólnie: potrafi wszystko to co runit (za
wyjątkiem obsługi logów) i sporo więcej.
Nie opisałem go tutaj, bo na razie jest to rozwiązanie tylko dla
niewielkiej ilości dystrybucji, a trudno wyobrazić sobie
doinstalowanie go jako dodatkowej usługi. Do tego program ciągle
ewoluuje, format konfiguracji zmienia się z wersji na wersję
(ostatnio bolesna jest zmiana znaczenia dyrektywy start
),
a dokumentacja jest szczątkowa.
Dwa przykłady skryptów konfiguracyjnych upstarta - dla wersji 0.3.9 (Ubuntu 8.04):
start on runlevel 2
start on runlevel 3
start on runlevel 4
start on runlevel 5
stop on shutdown
respawn
exec /usr/local/sbin/mojdemon -a -b -f /var/www/data
albo (gdy używamy skryptu):
start on runlevel 2
start on runlevel 3
start on runlevel 4
start on runlevel 5
stop on shutdown
respawn
script
export MYDIR=/var/lib/data
exec python /myapps/test/myscript.py
end script
Po dodaniu nowego skryptu trzeba wydać polecenie:
$ sudo initctl reload
by upstart zauważył zmiany. A listę wszystkich zadań i ich stan wyświetla
$ sudo initctl list
Powyższe przykłady przestaną działać w Upstart 0.5, tam dyrektywa
start on
będzie tylko jedna, przy czym np. aplikację sieciową
możemy skonfigurować na start on interface-up
zamiast odwoływać się
do runlevel-i. Trochę więcej informacji można znaleźć na
blogu autora.
Reasumując: upstart ma duży potencjał i za jakiś czas będzie bardzo ciekawą metodą na zarządzanie startującymi serwerami. Ale ... niech się najpierw ustabilizuje i zyska porządną dokumentację.
Podsumowanie
W tej części, dzięki instalacji runita, zyskaliśmy możliwość wygodnego konfigurowania startujących (i automatycznie restartowanych) demonów i innych procesów pomocniczych.