jak pracować z nazwami DNS, gdy potrzebne są adresy IP?

Od dość dawna wiem, że nazwy DNS-owe są wspaniałą rzeczą i że administrator powinien ich używać zawsze. Ale co zrobić, gdy da się dostarczyć jedynie adres IP?

Na taką właśnie sytuację trafiłem (dość dawno temu) przy okazji cfengine 2.x. Klucze publiczne poszczególnych serwerów są umieszczane w /var/cfengine/ppkeys w formacie root-10.20.121.15.pub. Nie ma nazwy DNS, prawda?

Na szczęście rozwiązanie jest proste: składować klucze publiczne w katalogu /var/cfengine/ppkeys/hosts, a w samym /var/cfengine/ppkeys trzymać wygenerowaną listę symlinków:

[root@cfmaster ppkeys]# pwd
/var/cfengine/ppkeys
[root@cfmaster ppkeys]# ls
hosts/                     root-10.20.130.247.pub@
localhost.priv             root-10.20.18.11.pub@
localhost.pub              root-10.20.24.3.pub@
Makefile                   root-10.16.2.135.pub@
root-10.20.137.6.pub@      root-10.16.2.71.pub@
[...]
[root@cfmaster ppkeys]# ls hosts/
audi.atled.pl.pub          taunus.atled.pl.pub
dodge.atled.pl.pub         trabant.atled.pl.pub
ferrari.atled.pl.pub       us000149.atled.pl.pub
[...]
[root@cfmaster ppkeys]# ls -l root-10.20.130.247.pub
lrwxrwxrwx 1 root root 31 Mar  7 10:49 root-10.20.130.247.pub -> uv000255.atled.pl.pub
[root@cfmaster ppkeys]#

W ten sposób, gdy trafiła mi się zmiana adresu IP któregoś serwera, nie zastanawiałem się, któremu kluczowi zmienić nazwę. Po prostu regenerowałem symlinki.

[root@cfmaster ppkeys]# cfrun lexus
cfrun(0):         .......... [ Hailing lexus.atled.pl ] ..........
WARNING - You do not have a public key from host lexus.atled.pl = 10.20.121.219
          Do you want to accept one on trust? (yes/no)

--> ^C
[root@cfmaster ppkeys]# rm *(@)
[root@cfmaster ppkeys]# make
for pubkey in hosts/*.pub; do host=${pubkey#*/}; host=${host%.pub}; ln -sf $pubkey root-`getent hosts $host | awk '{print $1}'`.pub; done
[root@cfmaster ppkeys]# cfrun lexus

cfrun(0):         .......... [ Hailing lexus.atled.pl ] ..........

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cfengine:lexus:
Executing script /usr/bin/svn update --quiet --config-dir /var/cfengine/svn/config...(timeout=90,uid=-1,gid=-1)
[...]

Dla wyjaśnienia, w zsh glob *(@) oznacza wszystkie pliki, które są symlinkami.

Podobnej techniki używam do utrzymywania katalogu z certyfikatami X.509 (takiego, który można podać jako CA path). Wrzucam pliki z nazwami czytelnymi dla człowieka (np. jarowit.net.cert.pem), a makefile mi regeneruje symlinki (nazwa symlinka jest ustalana komendą openssl x509 -noout -hash -in file.cert.pem).

Podsumowując, nie używaj identyfikatorów, które ci nic nie mówią. Używaj czytelnych nazw, a identyfikatory sobie generuj, czy to usługą katalogową, czy za pomocą haszowania. To oszczędza później kłopotu, gdy identyfikator się zmieni z jakiegoś powodu.

Wysłany: 07 marca 2012, 12:36:24; 7 komentarzy

zarządzanie wieloma serwerami: model pracy i narzędzia

Obiecywałem sobie, że napiszę piękny, duży artykuł o cfengine, który będzie wprowadzać czytelnika do wszystkiego: do składni reguł (CFE 2.x), do narzędzi, których używam, do sposobów pracy, a nawet będzie służył za cfengine cheat sheet. Dupa, raczej nic z tego nie wyjdzie. Jestem zbyt leniwy.

W zamian za to stworzę parę mniejszych artykułów. Coś na wzór pisania na wiki: najpierw szkic artykułu, a potem po jednej, po dwie informacje. Małe przyrostowe edycje są zawsze łatwiejsze w przeprowadzeniu niż jeden big bang. Dziś chciałem przede wszystkim pokazać mój model pracy.

Przy czterech setkach serwerów wiadomo, że potrzebny jest jakiś system automatyzacji pracy. Na rynku jest wiele różnych, z których niektóre opierają się na jednoczesnej pracy -- mniej lub bardziej interaktywnej -- na wielu serwerach (ClusterSSH, PSSH, Pdsh), inne na wysyłaniu skryptów na serwery docelowe (Spacewalk, zdaje się że IBM Tivoli), a jeszcze inne na ściąganiu napisanego w dedykowanym języku skryptu ze zdalnego serwera. Popularnymi przykładami są cfengine i Puppet.

Najbardziej udanymi są (moim zdaniem) cfengine i Puppet. Są niezawodne i samodzielne. Rozumiem przez to, że operator przygotowuje zestaw reguł do wykonania i wywiesza w Wiadomym Miejscu™ i może zaufać swojej infrastrukturze, że ta wykona zlecone zadania i reguły nie wywalą się na obecności jakiegoś nadmiarowego katalogu. Jest to istotnie różna sytuacja od dystrybuowania skryptów shellowych do wykonania. Skrypty trzeba pisać defensywnie, pamiętając za każdym razem, że nowo tworzony katalog może już istnieć, że komenda cd może się nie powieść i o podobnych, mało ciekawych, a mogących spowodować spore zniszczenia szczegółach.

Skoro mamy niezawodny mechanizm aplikowania plików konfiguracyjnych i wykonywania innych zadań administracyjnych, pora przyjrzeć się zarządzanym serwerom. W wśród czterystu serwerów większość da się przydzielić do grup na podstawie funkcji, jaką serwery pełnią i na podstawie usług, jakie mają włączone. W obrębie grup funkcyjnych zestaw usług będzie podobny, więc operacje do wykonania (zatem i reguły cfengine) nie będą się zbytnio różnić. W obrębie grup usługowych podobne będą pliki konfiguracyjne (jeśli będą miały dużo dużych różnic, to pora zastanowić się, czy na pewno tak powinny wyglądać; to duży kłopot, gdy każdy serwer jest inaczej skonfigurowany).

Konfigi danej usługi będą (powinny być) do siebie podobne na wszystkich serwerach. Mogą się różnić szczegółami, ale będą miały podobną strukturę. Dlaczego by nie zastosować systemu szablonów i utrzymywać jednego tylko pliku?

W tym miejscu na arenę wkracza cfgen. To narzędzie dostaje listę serwerów (poszczególne serwery są reprezentowane przez pliki z parametrami) i zestaw szablonów dla poszczególnych usług, a wynikiem jego działania jest zbiór gotowych konfigów, po jednym zestawie dla każdego serwera. cfgen jako języka szablonów używa Template::Toolkita, a parametry serwerów są zapisane w formacie YAML. cfgen posługuje się pojęciem usługi: lista usług oczekiwanych na serwerze jest zdefiniowana w jego parametrach. Tej listy cfgen używa do wygenerowania konfigów (niepotrzebne nie są wypełniane, bo i po co?), a przy okazji dostarcza ją do szablonów, czego można użyć do wypełnienia listy klas w szablonie reguł dla cfengine.

Puppet jest zintegrowany z systemem szablonów ERB. Nie podoba mi się to, ponieważ ERB ma brzydką składnię (przez pociąg rubystów do wsadzania Rubyego gdzie popadnie, również tam, gdzie nie jest to wygodne). Poza tym, skoro to nie jest zewnętrzne narzędzie (jak na przykład jest to zrobione z Facterem), trudno jest je wymienić albo użyć z innym systemem zarządzania serwerami.

Mamy już system dystrybucji konfiguracji (wraz z wykonywaniem operacji na serwerach), mamy system szablonów, mamy listę serwerów z ich parametrami. Przydałoby się teraz zapisać szablony i parametry (które razem wzięte nazywam źródłami) w systemie kontroli wersji. W sumie niegłupim byłoby wersjonować również gotowe konfigi (choć nie jest to niezbędne; każdy punkt w czasie można wygenerować wypełniając szablony parametrami).

Jeśli oba, źródła i gotowe konfigi, znajdują się w różnych repozytoriach, przydatnym będzie narzędzie do przebudowywania zmian. cronbuilder powstał właśnie jako takie uniwersalne narzędzie. Potrafi sobie poradzić z gitem i z Subversion (w dowolnej kombinacji source/destination). Osobiście uważam, że do źródeł lepszy jest git, a do wygenerowanych konfigów SVN.

Serwery zarządzane teraz mogą zostać skonfigurowane tak, żeby używać kopii roboczej repozytorium z gotowymi konfigami. Zysk z tego jest taki, że widać z daleka, która maszyna ma przestarzałe reguły (co jest objawem jakichś problemów). Oczywiście można się też zdać na metodę transportową cfengine.

Przydaje się też parę procedur RPC (xmlrpcd), głównie przy rejestracji nowego serwera w sieci CFE (trzeba mu przecież utworzyć plik z parametrami i wygenerować konfigi). Aktualnie i procedura rejestracji, i skrypt uruchamiany na rejestrowanym serwerze są napisane specjalnie dla mojej sieci, ale przynajmniej skrypt mam zamiar przepisać jako bardziej uniwersalne narzędzie.

Na koniec przydałby się system zbierania informacji o zadaniach wykonanych na poszczególnych serwerach. Wprawdzie cfengine jest całkiem samodzielny, ale przecież zdarzają się problemy, z którymi sobie nie poradzi. Napisałem zestaw skryptów i procedur XML-RPC do transportowania logów audytowych cfengine, ale to nie jest eleganckie rozwiązanie. Lepszym pomysłem jest Fluentd, daemon służący do transportu logów. Niestety nie znalazłem go w momencie pisania systemu -- stąd sznurek i taśma klejąca, ale planuję zastąpić moją konstrukcję gotowym klockiem. W każdym razie, logi lądują w bazie danych, skąd są wyciągane w postaci raportów (domowej roboty narzędzie webowe).


Podsumujmy:

  • mam listę znanych serwerów
  • każdy serwer ma listę usług, których się po nim oczekuje (i parametrów dla tych usług)
  • każdej usłudze przysługuje zestaw plików konfiguracyjnych (a raczej szablonów) i reguł cfengine
  • zmiany w szablonach i parametrach są rejestrowane w systemie kontroli wersji
  • z wykonania reguł logi są zbierane i agregowane w bazie danych, skąd są wyciągane jako raporty
  • mam też (pomysł na) narzędzie do rejestracji serwerów w sieci

Cóż, ledwie opisałem swój pomysł na architekturę systemu, a już zajmuje tyle miejsca. Ile by zajął pełnoprawny artykuł o cfengine?

Wysłany: 25 stycznia 2012, 19:22:22; 3 komentarze

monitorowanie: mniej znaczy lepiej

Dobrze jest mieć monitoring. Jeszcze lepiej, jeśli monitoring sprawdza jak najmniej.

Postawiłem w pracy Nagiosa, który monitoruje niewiele, niecałe dwadzieścia serwerów, a każdy z serwerów ma koło ośmiu sprawdzanych usług (w tym uptime, który ma być nie mniejszy niż 3 dni). Nie używałem żadnego autowykrywacza usług i serwerów -- uważam taki soft nie tylko za zbędny, ale wręcz za szkodliwy dla mojego zdrowia.

Przyznam że trochę mi zajęło wypracowanie takiego podejścia. Niekoniecznie działo się to w kolejności, jaka znalazła się w tym wpisie i niekoniecznie przez zacytowane materiały; starałem się przedstawić ciąg przyczynowo-skutkowy, a nie moją historię.

Podstawą monitoringu jest pierwsza, najbardziej najważniejsza zasada: every alert requires an action. Nie wolno zignorować żadnego ostrzeżenia z systemu monitoringu. Jeśli zaczniesz te powiadomienia ignorować, to prosta droga do katastrofy, gdy kiedyś się pojawi informacja, na którą trzeba zareagować. Zasada jest wzięta wprost z wpisu na blogu Server Fault, choć wykoncypowałem ją wcześniej.

Akcja może być dwojaka, albo powiadomienie dotyczy rzeczywistego zdarzenia i trzeba na to zdarzenie zareagować, albo to false positive spowodowany zbyt niskim progiem dla ostrzeżeń. Informacja o przepełnianiu się dysku na serwerze onyx.atled.pl może być spowodowana tym, że miejsca rzeczywiście zaczyna brakować; wtedy trzeba się zalogować na ten serwer, sprawdzić co dużo zajmuje i na przykład to usunąć. Może być i tak, że zwyczajnie dane się bardzo rozrosły, więc trzeba kupić drugi dysk. Może być jeszcze inaczej -- zajętość dysku jest w normie. Wtedy to próg jest zbyt niski, więc trzeba go podnieść. W każdym z przypadków operator musi jakoś zareagować na ostrzeżenie.

Zasada pierwsza zakłada, że system monitoringu już działa i jest prawidłowo skonfigurowany, to znaczy nie zasypuje operatora ostrzeżeniami o nieistotnych zdarzeniach (np. o niedostępności kamery IP z magazynu, którego nikt nie używa ani nawet o nim nie pamięta). Typowo w dashboardzie nie powinny się znajdować żadne ostrzeżenia, co nazywam stanem green screen. Nie jest to łatwe do osiągnięcia, wymaga samodyscypliny i mnóstwa mrówczej pracy. Zabbix i Zenoss dostarczają narzędzi do automatycznego wykrywania usług. Po użyciu nagle się okazuje, że Zabbix czy Zenoss sprawdzają kilkaset parametrów, o których istnieniu operator do tej pory nawet nie wiedział. Trzeba naprawdę dużo systematycznej pracy, żeby wszystkim tym parametrom ustawić progi.

A może tak podejść do sprawy odwrotnie? Ja wiem co mam w sieci. Wiem co mnie interesuje. Reszta usług -- zakładam -- jest nieistotna. Jeśli okaże się, że o jakimś parametrze nie wiedziałem albo zapomniałem, to go dodam -- po pierwszym razie, gdy mnie ugryzie w dupę. Zaczynam z małą liczbą parametrów, ale za to tych naprawdę ważnych. W miarę upływu czasu będę uzupełniać system monitoringu o kolejne usługi, a każda będzie ważna.

Dzięki temu liczba powiadomień może się utrzymywać na niskim poziomie, a dzięki małej liczbie powiadomień mogę obsłużyć każde jedno.

Przykład z życia: wspomniany już mój Nagios w pracy.

  • Zaczynał ze sprawdzaniem load average (choć nie był to znowu taki istotny parametr) i zajętości dysku.
  • Potem rozszerzyłem moją instalację o sprawdzanie kolejki MTA i szybkości odpowiedzi SMTP z localhosta (MTA działa w ogóle? będzie przesyłać e-maile?). Rozszerzyłem, bo się okazało, że czasem Exim mi się zatykał i e-maile przestawały przychodzić.
  • Dodałem monitorowanie uptime'u. Jeden z serwerów dość regularnie się rebootował. Dzięki temu pluginowi udało się namierzyć regularności w rebootach. Jako skutek uboczny pojawiło się monitorowanie temperatury (to była jedna z hipotez o przyczynie rebootów).
  • Zdefiniowałem sprawdzanie działania cfengine. Czasem (raz na tydzień na dwadzieścia serwerów) mój cfengine przestawał działać na którymś. Nie podoba mi się znaleźć się w sytuacji, gdy zatwierdzam jakieś zmiany, a za kilka tygodni okazuje się, że jeden serwer nie ma ich wprowadzonych. (Później uzupełniłem nieco reguły cfengine o samonaprawianie się, więc ten check przestał być niezbędny.)
  • Doszło monitorowanie lokalnej repliki drzewa LDAP, używanego przy uwierzytelnianiu użytkowników. Na niektórych serwerach slapd przestawał działać; trzeba było usuwać stare pliki bazy i pozwalać zadziałać mechanizmowi replikacji. Sprawdzanie LDAP pozwoliło po pierwsze dowiedzieć się o awarii zawczasu, a po drugie namierzyć przyczynę.
  • Uzupełniłem usługi o sprawdzanie, które zadania Baculi zdefiniowane w konfigu nie miały udanego uruchomienia dawniej niż kilka dni temu. Od tej pory przestałem się martwić o codzienne sprawdzanie poczty z nieudanymi zadaniami backupu.

Nie sprawdzam zajętości swapa, nie monitoruję wielkości ruchu sieciowego ani obciążenia dysków. Nie wiem do czego mogłyby te wartości w mojej instalacji służyć i jaki miałbym mieć z nich pożytek, a skoro tak -- te parametry są dla mnie nieistotne.

Powolutku, pomalutku mój Nagios się rozrastał do obecnej postaci, a dzisiaj wyraźnie było widać, że do czegoś się przydaje: jednemu z serwerów nawalił sprzęt i serwer przestał odpowiadać. Udało nam się dowiedzieć o problemie zanim którykolwiek z użytkowników zgłosił awarię. Sprawa została zgłoszona dostawcy sprzętu, a dwie godziny później serwer już wstał z powrotem.

Tak sprawna reakcja miała miejsce nie tylko dlatego, że mamy jakiś monitoring. Była możliwa przede wszystkim dlatego, że na każde ostrzeżenie reagujemy.

Wysłany: 06 grudnia 2011, 14:54:10; 24 komentarze

kung-fu administratora: RPC

Zadziwiające, jak niektóre kapitalne wprost narzędzia są przez administratorów ignorowane, a w ich zastępstwie stosuje się kłopotliwe, nieeleganckie konstrukcje.

Takim właśnie narzędziem są protokoły RPC. Idea RPC to, z grubsza rzecz biorąc, uruchomienie na serwerze pewnej funkcji na rzecz klienta, przy czym argumenty tej funkcji dostarcza klient i klient odbiera zwracaną wartość. Dzięki temu, że funkcja wywoływana jest oddzielona od programu klienckiego, może działać z dowolnymi potrzebnymi jej uprawnieniami, niezależnie od tego, z jakimi prawami pracuje klient.

Niestety, nie widziałem u nikogo użycia protokołów RPC (z wyjątkiem NFS i SMB/CIFS, które na RPC są zbudowane; ale to użycie sieciowego filesystemu, a nie bezpośrednio RPC). Nie widziałem nawet nikogo chwalącego się tym na blogu, usenecie czy forum dyskusyjnym. Po prostu cisza. Zamiast tego widuje się co jakiś czas pytanie w stylu "jak zrobić żeby przez panel webowy dało się przekonfigurować firewall?" i sugestię nadania Apache'owi uprawnień do uruchomienia sudo. This is not the way to go!

Innym przykładem błędu w sztuce jest użycie SSH do wielokrotnego bezobsługowego uruchamiania zadań na zdalnej maszynie. Dlaczego uważam to za niedorzeczne?

  • Zarządzanie kluczami publicznymi zdalnych maszyn jest uciążliwe, bo trzeba każdy pojedynczy klucz jakoś uprzednio dostarczyć. Nie ma czegoś takiego jak hierarchia certyfikatów.
  • Logowanie się na zdalny serwer, które nie wymaga podawania hasła, wymusza utrzymanie na tamtym serwerze pełnoprawnego konta użytkownika (razem z katalogiem domowym) i umieszczenia publicznej części klucza klienckiego, na podstawie którego logowanie się będzie się odbywać.
  • Jeśli skrypt, który będzie uruchamiany, przyjmuje jakieś parametry od klienta, walidacja ich może stać się horrorem: ani jednolitego standardu przesyłania argumentów i wyników, ani gotowych narzędzi do tego.
  • Nie ma jednolitego sygnalizowania błędów, a o obsłudze błędów jeszcze trzeba dodatkowo pamiętać, jeśli uruchamiany skrypt jest skryptem shellowym.

Trochę za dużo problemów jak dla mnie. Miałbym za każdym razem się martwić o to wszystko? Dużo rozsądniej by było użyć jakiegoś daemona, który przyjmuje żądania RPC, najlepiej owinięte w HTTP (co automatycznie załatwi uwierzytelnianie klienta (HTTP authentication), serwera i zabezpieczenie transmisji (HTTPs)), i na tej podstawie uruchamia odpowiednie procedury.

Z protokołów, które pasują do tego opisu, bardzo dobrze by się sprawdził XML-RPC, zwłaszcza że jest całkiem rozpowszechniony (np. standardowa dystrybucja Pythona dostarcza biblioteki xmlrpclib, podczas gdy o obsługę takiego SOAP trzeba się specjalnie postarać), ale tak właściwie dowolny protokół (który ma, rzecz jasna, opublikowaną specyfikację) by się nadał. Podstawowym kłopotem okazał się brak gotowego daemona (brokera) RPC.

Właśnie to uważam za zadziwiające: od ponad dwunastu lat mamy na rynku dobry protokół RPC, a administratorzy jeszcze go nie zaadoptowali. Rozumiem że przy poszczególnych pytaniach łatwiej było poradzić "sudo dla www-data", ale ciężko mi uwierzyć, że nigdy wcześniej żaden administrator-programista nie nadział się na problem uruchamiania procedury na zdalnym hoście.

W każdym razie postanowiłem wypełnić tę lukę własnym daemonem (napisanym w Perlu), który otrzymał mało pomysłową nazwę xmlrpcd. Na celu miał dostarczyć przede wszystkim łatwego w konfiguracji, lekkiego serwera HTTP obsługującego SSL i uwierzytelnianie, który by rozdzielał żądania do poszczególnych metod i potrafił je uruchamiać z różnymi uprawnieniami (w tym: z uprawnieniami roota). Niejako przy okazji powstał command-line'owy klient xmlrpcaller, który -- rzecz jasna -- jest używalny nie tylko z xmlrpcd.

Prosty przykład użycia jest na stronie narzędzia.

Wysłany: 28 października 2011, 16:15:29; 46 komentarzy

sleep $RANDOM; push-logs

Dzisiaj dość prosta sprawa.

Mam pod opieką coś koło 350 maszyn. Potrzebuję żeby te maszyny raz na jakiś (nie za długi, tak ze 20 minut) czas wysłały logi z wykonania do centralnego serwera. Ot, proste zadanie cronowe:

*/20 *  * * *  root  /usr/local/sbin/push-logs

Problem, jakiego próbuję uniknąć, to skok obciążenia serwera odbierającego logi: 350 połączeń SSL naraz (o *:00, *:20 i *:40) może łatwo zeżreć zasoby serwera, zwłaszcza gdy ten jest jedynie wirtualką z jednym procesorem.

Podszedłem do sprawy podobnie jak to robi cfengine: zadanie najpierw śpi pewną liczbę sekund, a potem uruchamia skrypt wysyłający. Spokojnie mógłbym zapisać zadanie tak:

*/20 *  * * *  root  sleep $(( $RANDOM % (5 * 60) )); /usr/local/sbin/push-logs

Nie jest to jednak moim zdaniem ładne rozwiązanie: ten sam serwer będzie miał w wysyłaniu logów opóźnienie raz rzędu paru sekund, raz trzech minut. Fajnie by było mieć komendę, która zwróci liczbę różną na różnych hostach, ale dla danego hosta zawsze taką samą (albo przynajmniej rzadko się zmieniającą).

Pomysł: MD5 z nazwy hosta i ewentualnie reszty danych zwracanych przez uname(2). Rozwiązanie (skrypty nazwałem hosthash):

#!/usr/bin/perl -l

use warnings;
use strict;

use POSIX qw{uname};
use Digest::MD5 qw{md5};

if (@ARGV && $ARGV[0] eq '--help') {
  print "Usage: $0 [modulus]";
  print "";
  print "This script prints a 32-bit number (0 <= num < modulus).";
  print "The number is based on uname() (including hostname and kernel release).";
  exit;
}

my $hash = md5(join " ", uname());

my $int = unpack "N", $hash;

if (@ARGV) {
  print $int % $ARGV[0];
} else {
  print $int;
}

# vim:ft=perl
#!/usr/bin/python

from hashlib import md5
from os import uname
from sys import argv
from struct import unpack

if len(argv) > 1 and argv[1] == "--help":
  print "Usage: %s [modulus]" % argv[0]
  print ""
  print "This script prints a 32-bit number (0 <= num < modulus)."
  print "The number is based on uname() (including hostname and kernel release)."
  exit()

h = md5(" ".join(uname())).digest()
# XXX: to be compatible with Perl version
i = unpack(">L", h[0:4])[0]

if len(argv) > 1:
  print i % int(argv[1])
else:
  print i

# vim:ft=python:ts=2:sw=2:et

Teraz zadanie cronowe może wyglądać tak:

*/20 *  * * *  root  sleep $(hosthash $((5 * 60))); /usr/local/sbin/push-logs

Wysłany: 16 września 2011, 15:29:17; 34 komentarze

debhelper: po co mi to, tak właściwie?

Tak już któryś raz z rzędu wciskam sekwencję Enterów przy instalacji pakietu w Debianie. Tym razem był to libnss-ldap i pytał o parametry serwera LDAP, z którym ma się połączyć.

Nasunęło mi to myśl, że ja prawie w ogóle z pytań debhelpera nie korzystam. Tam, gdzie takie pytania przyspieszyłyby pracę administratorowi (na przykład przy instalacji Exima albo modułu NSS/LDAP), ja i tak używam systemu dystrubucji konfiguracji. Wychodzi mi, że w większych sieciach debhelper jest zwyczajnie zbędny.

Wysłany: 29 sierpnia 2011, 16:07:53; 8 komentarzy

kilka powodów przeciw SSH na roota

Jakoś tak wyszło, że od zawsze na swoje maszyny (desktopy, laptopy, serwery -- na wszystko) loguję się najpierw na konto zwykłego użytkownika, a dopiero potem na roota, nawet jeśli chcę tylko wykonać parę operacji administracyjnych. Ot, jakoś się przyzwyczaiłem do takiej metodlogii.

Czasem ktoś na Usenecie zapyta, co jest bezpieczniejsze (znaczy na co trudniej się włamać). Roztrząsanie tej sprawy doprowadziło mnie już jakiś czas temu do wniosku, że w przeciętnych warunkach nie ma większego znaczenia, czy administrator loguje się od razu na konto roota, czy nie.

Ostatnio jednak przyjrzałem się krytycznie swojej pracy na dwóch rodzajach maszyn: na takich, gdzie mam konto osobiste i na takich, gdzie loguję się od razu na roota. Udało mi się wylistować następujące przewagi (zauważalne dopiero gdy maszyny mają kilku administratorów pracujących jednocześnie) używania konta osobistego:

  1. Mam lepszą kontrolę kto właściwie ma prawa administratora (np. łatwiej zablokować dostęp odchodzącemu z firmy administratorowi blokując konto (wtedy i tak z reguły ufa się, że admin nie zostawił furtek)).
  2. Audit trail: kto robił tę zmianę? ano ten, kto był wtedy zalogowany. Wcale nie chodzi o kopanie w zadek za zepsucie czegoś. Częściej się zdarza, że chcę zapytać o co chodziło w zmianie (jakiś ticket? prośba użytkownika?).
  3. Własne środowisko (~/.*) -- ważna rzecz, zwłaszcza przy dłuższej pracy. Niby chcę tylko wykonać dwa polecenia, ale bashowe uzupełnianie składni i w ogóle edycja historii średnio mi pasują i wolę swoje ustawienia zsh. A jeśli będę chciał napisać jakiś skrypt? Tworzenie .vimrc albo wpisywanie go z palca w każdej sesji edytora jest uciążliwe; mam w końcu swoje przyzwyczajenia.
  4. Sporo zadań nie wymaga praw roota, np. czytanie logów, pisanie i wstępne testowanie skryptów. Ewentualne błędy mają mniejszy impact (nieśmiertelne rm -rf * psuje tylko moje pliki). Świadomość tego znacząco podnosi mi komfort pracy (podobnie jak ładowanie wszystkich zmienianych plików do systemu kontroli wersji).

Żadne z powyższych nie jest wprawdzie krytyczne, ale dla mnie w zupełności wystarczające, żeby zacząć ewangelizację. W końcu chodzi o moje środowisko pracy, a to powinno być dla mnie jak najwygodniejsze.

Wysłany: 26 listopada 2010, 00:10:01; 18 komentarzy

un*x admin's best practices

Wpis ten miał początkowo być wstępem do cfengine, ale poszczególne punkty za bardzo mi się rozrosły żebym jeszcze rozpychał tekst o oryginalny pomysł. cfengine opiszę następnym razem. A tymczasem okazuje się, że dobry unix-admin musi całkiem sporo umieć i powinien zbudować sobie pokaźną infrastrukturę.

Spis treści

Historia pewnego chaosu

Każdy administrator systemów w pewnym momencie rozbudowuje swoje środowisko o nowe maszyny. Tych maszyn robi się więcej i więcej, ani się obejrzysz i już masz pod opieką kilkanaście systemów i dokładasz kolejne. Serwery baz danych, serwery aplikacji, poczty, Jabbera, webaplikacji, backupu, logów, monitorowania...

Coraz więcej czasu poświęcasz na logowanie się na właściwy serwer i dokładanie cegiełki konfiguracyjnej albo sprawdzanie co było jak zrobione. O, tu mnie jeszcze nie było? Nie mam tu konta ani katalogu domowego? To root i tworzymy co trzeba... Nie ma mojej powłoki? To doinstalujmy... Nie ma tcpdumpa i iftopa? Nie ma lsof, strace, perlowego DBD::mysql? Damn, jak to się wolno ściąga, może ftp.pl.debian.org jednak nie był takim dobrym pomysłem i trzeba było postawić własny mirror repozytorium pakietów? A gdzie się podziały skrypty do sporządzania zrzutów bazy danych? O tutaj, ale która to wersja? Najnowsza aby? Czego one tam wymagały oprócz Pythona? Sprawdzę na innej maszynie, tylko znajdę hasło roota... Pamiętasz jeszcze w ogóle od czego się zaczął ten koszmarny ciąg zadań?

Jeśli twoja codzienna praca administratora przypomina takie bezładne szamotanie się między kolejnymi rzeczami do zrobienia, pora zacząć automatyzować i centralizować co się da. A właściwie to pora już dawno minęła, teraz zostało ci jedynie ratować się przed wypaleniem zawodowym. Bo taki chaos jest bardzo męczący. Zero czasu na rozwój, tylko koszmar debuggowania niedziałających usług.

Punkt pierwszy: użytkownicy

Punktem startowym do zrobienia jest centralizacja zarządzania użytkownikami i grupami. NIS, LDAP -- cokolwiek, byle nie było rozwiązaniem domowej roboty (nie sprawdza się na dłuższą metę). Dobrym pomysłem jest zapewnienie względnej niezależności serwerów (replikacja LDAP). Katalogi domowe w takim środowisku powinny się tworzyć same, w miarę potrzeb -- pam_mkhomedir twoim przyjacielem.

Wraz z tym powinna iść możliwość zalogowania się z twojego rozdystrybuowanego wszędzie użytkownika na roota, żeby wykonać odpowiednie zadania administracyjne. sudo z zezwoleniem dla wybranej grupy będzie dobrym pomysłem. W końcu może ktoś do ciebie dołączy do zespołu?

Punkt drugi: zdrowy sen

Kolejnym przystankiem jest system backupów. To jest konieczność. Kopie zapasowe służą do zapewniania administratorowi spokojnego snu i pracy: cokolwiek zepsujesz podczas prac porządkowych, będziesz mógł to przywrócić do stanu używalności we w miarę przewidywalnym czasie.

Jeśli nie stać cię na napęd taśmowy (czy wręcz zmieniarkę taśm), backup na dysk wystarczy. Na tym etapie nie martwisz się awarią maszyn, tylko omyłkami popełnionymi przez ciebie. Ważne żeby backup wykonywał się automatycznie i wymagał jak najmniej twojej ingerencji. Backup kłopotliwy dla administratora to backup, który jest często pomijany, to z kolei prosta droga do utraty danych.

Tu podpowiedź: backup bezwzględnie zbieraj i trzymaj na dedykowanej maszynie. To może być maszyna wirtualna, byle dobrze zabezpieczona przed przypadkowym jej skasowaniem. Tej maszyny nie ruszasz, z wyjątkiem dodawania zadań i odzyskiwania plików.

A jeśli nie masz pomysłu co backupować, zacznij od /etc/ i listy zainstalowanych pakietów (/var/lib/apt i /var/lib/dpkg pod Debianem). To jest administratorski no brainer. Nie kosztuje wiele zasobów, a bywa bardzo, bardzo przydatny. Zadania dla kolejnych usług będziesz dokładać w miarę postępów prac.

Punkt trzeci: nowe serwery

Do tej pory doszedł ci co najmniej jeden serwer (serwer kopii zapasowych). Przypomnij sobie teraz: co na nim trzeba było zainstalować? (tcpdump! iftop!) Co skonfigurować? (NIS/LDAP! sudo! adresy sieciowe! routing!) Jakie polecenia wydać? (dpkg-reconfigure locales!) W jakiej kolejności? (najpierw wybór mirrora repozytorium pakietów, potem instalacja iftopa)

Spisz to wszystko. To jest twoja procedura instalacji nowych serwerów. Najlepiej spisz to na wiki, bo pierwsza wersja tej procedury z pewnością nie będzie kompletna. Powiem więcej: nawet gdy będzie kompletna, będzie ulegać zmianom. Przecież co jakiś czas trzeba dodać nowe pakiety na wszystkich maszynach albo coś przekonfigurować (np. zmieni się adres serwera DNS albo dodasz software do inwentaryzacji sprzętu). Nie chcesz przecież co jakiś czas się budzić na nowym serwerze bez tego, co na innych maszynach już przecież jest, prawda?

Żebyśmy mieli jasność: procedura ma się dać wydrukować i odkreślać w miarę postępów instalacji. Więcej: drukuj ją i odkreślaj dla każdego serwera. Jeśli cokolwiek ci przerwie instalację, będziesz wiedzieć od którego momentu ją wznowić. Nie czarujmy się, jesteś administratorem i użytkownicy wzywają cię do każdej pierdoły; nie uda ci się za jednym zamachem zainstalować i skonfigurować całego serwera.

Punkt trzeci (a): odzyskiwanie danych

Masz już przygotowane miejsce do trzymania procedur. Pora na drugą procedurę: instrukcję odzyskiwania danych z backupu. Zgadza się, właśnie tak: instrukcję odzyskiwania danych. Po co? Wyobraź sobie że przez niedopatrzenie wyleciała ci w powietrze połowa /etc. Im mniej będziesz się zastanawiać i przypominać sobie, tym lepiej. Wydrukuj sobie tę procedurę i powieś w widocznym miejscu.

Wbrew pozorom instrukcja nie jest takim hop-siup. Musisz wiedzieć w pierwszym rzędzie jakim poleceniem wybrać pliki do odzyskania, jak nastawić miejsce, gdzie pliki wylądują (serwer i katalog na serwerze) i jak je skopiować z powrotem. Owszem, czasem wystarczy po prostu skopiować pliki. Ale czasem będzie trzeba je jakoś importować albo zarejestrować (np. skrzynki Cyrus IMAP-a tak mają; zwykle też trzeba to zrobić zrzutom baz danych, jak MySQL, PostgreSQL czy LDAP). No i prawie zawsze trzeba zrestartować serwis, którego operacja dotknęła. A czasem nawet kilka serwisów. W instrukcji masz mieć spisaną procedurę dla każdego zadania backupowego, choćby lakoniczne "skopiować pliki na miejsce; nic więcej nie trzeba robić".

Punkt czwarty: nazwy

W miarę możliwości postaw własny serwer DNS. Każdemu serwerowi przypisz rekord A odpowiadający jego nazwie.

Unikaj nadawania serwerom nazw wprost wskazujących przeznaczenie. Nie chodzi o względy bezpieczeństwa (zresztą to by było tylko urojone bezpieczeństwo). Serwery mają tendencję do gromadzenia większej niż początkowo zakładana ilości usług. Nagle się okazuje że serwer pocztowy pal sześć, ważniejszy jest LDAP który tam stoi. Co odpowiesz na pytanie "gdzie znajduje się LDAP"? Bo przecież nie "na mail.nabla.net". Lepiej: "na serwerze szafir".

Z drugiej strony usługi mają tendencję do wędrowania między serwerami i może się okazać, że ldap.nabla.net jest serwerem plików, główna instancja LDAP stoi na mail.nabla.net, a poczta jest serwowana przez samba.nabla.net. Lepiej oddziel nazwy serwerów od ich funkcji.

Nazwy serwerów to jedno, ale do usług trzeba się jakoś dostać.

  • Po pierwsze: nie stosuj adresów IP. Używaj nazw DNS.
  • Po drugie: nigdy, przenigdy nie stosuj adresów IP.
  • Po trzecie: nie używaj nazw serwerów. Nie po to były oddzielane od nazw serwowanych usług żeby je teraz mieszać.

No OK, ale co w zamian? Dla każdej usługi dodaj rekord DNS typu CNAME (albo przynajmniej A; dla poczty na przykład nie wolno używać CNAME) wskazujący na serwer, który tę usługę świadczy. Używaj CNAME kiedy się da. Gdy przyjdzie do lokalizowania usługi wystarczy że odpytasz DNS, on ci rozwiąże alias do nazwy serwera, a nazwę do adresu IP. Masz dwa za jednym zamachem: ustalenie który to serwer i poznanie adresu usługi.

Nawet awaryjne postawienie usługi na innym serwerze będzie łatwe. Wystarczy że zmienisz wpis w DNS i już można korzystać z tymczasowego serwera.

A pamiętasz o backupie stref DNS?

Punkt piąty: używaj lustra

Postaw lokalny mirror repozytorium pakietów, przynajmniej tego używanego do instalacji serwera. Przestrzeń dyskowa jest względnie tania. Łącze wprawdzie też, ale twój czas już nie. Zyskasz również możliwość zadbania o to żeby wszystkie maszyny miały tę samą wersję pakietów: to ty przecież kontrolujesz częstotliwość uaktualnień mirrora. A jeśli adres, z którego sporządzasz mirror, przestanie być aktywny -- zmieniasz wszystko tylko w jednym miejscu.

Przypominam, że tej usłudze przysługuje osobna nazwa, niezależnie od serwera na którym wyląduje.

Punkt szósty: ./configure && make burdel

Jednym z większych grzechów administratora jest pomijanie systemu pakietów. Instalacja programów przez własnoręczną kompilację to Zło[tm]. Poprawną metodą jest instalacja pakietu; w Debianie to będzie polecenie dpkg -i. Widzisz jaka to wersja jest, widzisz jakie toto ma zależności od innych pakietów, możesz pakiet usunąć nie obawiając się że zostawi jakieś śmieci po sobie. Podczas reinstalacji maszyny albo stawiania drugiej takiej samej szybciej będzie ci zainstalować pakiet niż kompilować wszystko od początku.

Że co proszę? W dystrybucyjnym repozytorium pakietu nie ma albo jest za stary? To zbuduj sobie pakiet z nowszą wersją -- ale zbuduj pakiet. Możesz się posłużyć źródłami pakietu dystrybucyjnego, to zwykle dobry punkt startowy. A myślisz że jak się pakiety dostały do repozytorium? Nie spadły z nieba, ktoś je zbudował.

To naprawdę procentuje. Aktualizacje w przyszłości będą prostsze, zwłaszcza jeśli masz zachowany pakiet źródłowy (*.src.rpm dla Red Hata, *.dsc i okolice dla Debiana).

Punkt szósty (a): własne repozytorium

Gdy już masz własne pakiety, zacznij się zastanawiać co zrobić, żeby te pakiety instalowało się tak samo jak dystrybucyjne. Czyli: jak zrobić własne repozytorium z pakietami. Przecież nie będziesz kopiować tej samej paczki na kilkanaście różnych serwerów, prawda? Mnie by się nie chciało. Poza tym yum i apt-get same pociągną ewentualne zależności pakietu. To też ważne. Uruchamianie apt-get install dla każdego z ośmiu pakietów wymaganych przez twój foo_1.0-1_all.deb jest uciążliwe.

Punkt siódmy: spójna konfiguracja

Masz już masę serwerów. Prawdopodobnie na każdym masz MTA (Exim, może Postfix albo ssmtp), wszędzie jest NIS/LDAP, serwer SSH, sudo, syslog, klient systemu backupów... Dużo tego, zwłaszcza przemnożone przez ilość instancji.

A teraz dodaj wszędzie opcje AcceptEnv ORIGIN_HOST do sshd_config i SendEnv ORIGIN_HOST do ssh_config.

Pewnie zaczniesz od pętli for, ale to rozwiązanie ma krótkie nogi. W pewnym momencie okaże się że pisanie skryptów seda jest mało wygodne (zwłaszcza przy nieznanym stanie konfigów), za którymś razem trzeba skopiować kilka plików na maszyny docelowe, a tobie niezbędny się okaże terminal interaktywny żeby uruchomić normalny ekranowy edytor do wprowadzenia zmian.

Prawdopodobnie znajdziesz ClusterSSH, ale to wystarczy na góra dziesięć maszyn (ileż można mieć terminali naraz otwartych?) i działa tylko dla usługi, która ma bardzo podobne konfigi na wszystkich maszynach (kursor w edytorze musi się znaleźć w odpowiednim miejscu -- i jeszcze musisz sprawdzić czy aby na pewno wszędzie jest tam gdzie trzeba).

Co ci jest teraz potrzebne, to centralizacja: system dystrybucji plików konfiguracyjnych i system szablonów do generowania tych plików.

Punkt siódmy (a): dystrybucja konfigów

Do wyboru masz dwa: cfengine i Puppet. Dla Debiana 5.0 oba znajdują się w oficjalnym repozytorium (cfengine jako cfengine2). Pakiety dla Red Hat Enterprise Linux znajdziesz w repozytorium RPMforge.

Tak naprawdę jest jeszcze bcfg2, ale konfiguracji oczekuje w XML-u, co go dyskwalifikuje. Ten format ledwo się nadaje do formatowania tekstu, a używa się go gdzie popadnie.

cfengine i Puppet służą w pierwszym rzędzie do kopiowania plików z serwera, do kopiowania plików na miejsce robocze (to nie to samo!), do przeładowywania usług, których konfigi się zmieniły i do instalacji potrzebnych pakietów. Potrafią wprawdzie nieco więcej, ale na razie tyle ci wystarczy. Chociaż jeśli o kopiowanie z centralnej kopii konfiguracji chodzi, to poradzimy sobie inaczej.

Z dwóch wymienionych osobiście wolę cfengine. Puppetowi musisz z góry powiedzieć czego się spodziewasz po maszynie ("tu i tu ma się znajdować Fcron; dodaj symlink /usr/bin/crontab wskazujący na fcrontab"), natomiast cfengine potrafi wykryć co trzeba i poradzić sobie ze stanem zastanym ("jeśli Fcron jest zainstalowany, to dodaj symlink /usr/bin/crontab wskazujący na fcrontab"). Oczywiście cfengine'owi można też nakazać instalację ("na tej maszynie ma być serwer LDAP"). Po prostu masz wybór.

Punkt siódmy (b): generowanie konfigów

Na potrzeby pracy sporządziłem jakiś czas temu system szablonów oparty o Perla, YAML i Template Toolkit. Zasada działania jest prosta: w plikach *.yaml trzymasz parametry specyficzne dla hostów, a szablony te parametry wykorzystują. Skrypt służy do wypełnienia szablonów parametrami.

Niestety nie znam żadnego gotowego narzędzia do tego (inaczej zresztą bym go użył zamiast pisać własne), a obecnej wersji cfgena brakuje przyjaźniejszej instrukcji obsługi.

Tak czy owak, generator konfiguracji jest ci potrzebny. Możesz poszukać czegoś lepszego od cfgena, możesz napisać własny, możesz też przegryźć się przez kod cfgena i przykładowe szablony. Bo chyba nie masz zamiaru edytować ręcznie konfiguracji dla kilku (nastu?) serwerów?

Punkt siódmy (c): system kontroli wersji

Weź przykład z zawodowych programistów: szablony i zmienne trzymaj w systemie kontroli wersji. Po co?

  • każda zmiana będzie opatrzona datą ("od dwóch dni nie dochodzi do mnie poczta" -- a potem zgaduj co było wtedy zmieniane...)
  • każda zmiana będzie opatrzona opisem co i po co robi (łatwo będzie ją wycofać, powtórzyć w innej instalacji albo pokazać komuś)
  • każda zmiana będzie opatrzona autorem (jeśli adminów jest więcej niż jedna sztuka, to pozwoli ustalić kogo kopać w d*** za zepsucie usługi)
  • pamiętane są wszystkie zmiany, więc dostajesz automatycznie backup, tylko o drobniejszej granulacji niż zwykle (repozytorium oczywiście też backupuj! to na pewno nie zaszkodzi)
  • przypadkowe usunięcie szablonu lub pliku ze zmiennymi dla hosta skutkuje utratą zmian najwyżej od ostatniego commita

System kontroli wersji w podstawowym użyciu nie robi wiele więcej niż kopia na boku, ale jest o niebo wygodniejszy, głównie przez zapisanie całkiem sporej ilości danych (czas, autor, zmiany od wersji poprzedniej), wszystko za jednym krótkim poleceniem.

Ja najbardziej lubię gita, ale Mercurial, Subversion czy nawet od biedy CVS też się nadadzą (choć ostatniego nie polecam, bo ma topornego klienta).

A teraz sztuczka: system kontroli wersji, o ile jest zabezpieczony przez protokół SSL/TLS (git, Mercurial i Subversion pozwalają na to bez problemu) i uwierzytelnianie klienta (np. kliencki certyfikat X.509 albo para login+hasło), może zostać użyty do dystrybucji plików konfiguracyjnych (to oznacza, że pliki wygenerowane z szablonów również powinny się znaleźć pod kontrolą wersji). Z crontaba może się uruchamiać zadanie aktualizujące kopię roboczą z konfigami (a sam wpis może przecież dodać cfengine!), a cfengine będzie się zajmować aplikowaniem nowych ustawień, jeśli któreś pliki się zmieniły.

Punkt siódmy (*): komentarze

Z mojego doświadczenia wynika, że wygodniej jest trzymać wszędzie jak najbardziej podobną konfigurację, w miarę możliwości taką samą. Jeśli usługa pozwala rozróżniać fragmenty konfiguracji przysługujące takiemu lub innemu hostowi -- skorzystaj z tego. Dobrym przykładem są sudo i sam cfengine. Jeżeli systemowi dystrybucji konfiguracji cokolwiek się stanie, możesz skopiować konfig z innej maszyny i będzie działać bez większych zmian. Może się też okazać, że będziesz potrzebować porównać konfigi. Jeśli będą identyczne albo będą występować dwie-trzy grupy identycznych konfigów, wystarczy ci jeden rzut okiem na md5sum.

Nie jest to jakoś specjalnie niezbędne, ale czasem się przydaje.

Punkt ósmy: systematyczne sprzątanie

Teraz, skoro już masz generator i dystrybutor konfigów, możesz obejmować systemem kolejne usługi, które masz pod opieką. Cokolwiek pojawia się na więcej niż jednej maszynie, powinno być konfigurowane z tego samego miejsca! Dzięki temu i dołożenie kolejnej instancji będzie łatwe, i odzyskanie instalacji po awarii będzie łatwiejsze.

Dobrze zrobisz od razu dokładając zadanie backupu dla takiej usługi. Przecież może ci coś nie wyjść, ręka się omsknie i skrypt restore.sh skasuje ważne dane.

Na koniec podpowiem ci kapitalną rzecz. Możesz praktycznie całą procedurę instalacji serwera opisać jako reguły systemu zarządzania konfiguracją! Genialne, prawda? Niech automat przygotuje wszystko co trzeba, ty później tylko sprawdzisz czy działa (checklista!).

Wysłany: 05 czerwca 2010, 01:50:38; 23 komentarze

puppet vs cfengine: moje drugie podejście

Dzisiaj miałem drugie podejście do Puppeta. Znowu szukałem najważniejszej dla mnie rzeczy w narzędziach do automatyzacji administracji: metody dynamicznego definiowania do jakiej klasy należy który host.

Zastrzeżenie: nadal nie skonfigurowałem i nie postawiłem Puppeta. Uważam to za pointless, skoro najwyraźniej nie potrafi tego czego od niego chcę.

W cfengine jest to rozwiązane przyjemnie.

  • Klasie przypisuje się listę hostów i klas, które się składają na nią.
  • Można zdefiniować klasy dynamicznie: jakaś akcja została podjęta, definiuje się dodatkową klasę dla tego hosta. Dalsze wykonanie już weźmie pod uwagę że dla tego hosta mamy zdefiniowaną nową klasę.
  • Klasy można zdefiniować (i często się to robi) na podstawie zastanego stanu systemu: jeśli istnieje plik /etc/nabla, to host należy do klasy qwerty.

Klasy właściwie nie mają jakiejś specjalnej hierarchii. Jedne mogą zawierać drugie, inne mogą się przecinać (na przykład przez włączenie całej klasy i wyłączenie z niej paru hostów).

Operacje są wykonywane tylko w obrębie jakiejś klasy (często jest to klasa any, która jest domyślna dla operacji jeśli nie podano inaczej).

W Puppecie trochę to wygląda inaczej. Klasa to zbiór operacji do wykonania (podobnie w cfengine, gdy przychodzi do definicji reguł). Klasa może dziedziczyć po innych klasach. Klas się nie definiuje przy wykonaniu pewnych operacji; do tego żeby pewne operacje przeprowadzić tylko gdy inne zostały wykonane (restart usługi po rekonfiguracji) służy mechanizm subscribe/notify.

Do tego mamy listę hostów (nodes), które należą do pewnych klas. Lista klas do których należy host jest zaszyta w definicji tego hosta.

Fajnie, pięknie, ale nagle się okazuje, że to całkiem silna regresja w stosunku do cfengine (Puppet miał być silnie wzorowany na cfengine). cfengine może brać pod uwagę stan zastany: jeśli zainstalowany jest pakiet LDAP, to znaczy że trzeba ten LDAP skonfigurować; i tu następuje kopiowanie plików konfiguracyjnych, ewentualne rejestrowanie CA X.509 (ldaps://), restartowanie LDAP i zestawianie replikacji jak trzeba. W Puppecie natomiast trzeba dla każdego hosta zdefiniować że należy się na nim spodziewać LDAP.

Aż tak tragicznie nie jest żeby się przedzierać przez wszystkie definicje hostów; od tego w końcu jest dziedziczenie. Ale że Puppet nie może sprawdzić już przy lokalnym uruchomieniu do jakich klas ma należeć? Że musi mieć wprowadzoną listę wszystkich hostów? Toż to horror!

W cfengine miałem to zupełnie inaczej zorganizowane: każdy host który był Red Hatem 4 należał do grupy up2date, każdy który był RHEL5 był w yum, a Debiany z kolei siedziały (well, mogły siedzieć; używałem wbudowanej grupy debian) w apt. Wygodniej było pozwolić cfengine'owi wykryć dystrybucję/wersję i ustawić co trzeba jak trzeba, niż ręcznie zawczasu podawać że tu a tu się spodziewamy Red Hata -- a tak to by się odbywało w Puppecie.

Poza tym wcale nie musiałem mieć przygotowanej zawczasu listy wszystkich hostów świadczących (mających świadczyć) usługi. Owszem, fajnie móc powiedzieć "na tej-i-tej maszynie ma być taka-a-taka usługa" i się wszystko co potrzebne poinstaluje. Ale coś wykrywającego usługi też jest potrzebne. Poza tym do sporządzania listy faktycznie świadczonych usług służy narzędzie do inwentaryzacji.

Inna sztuczka, którą w Puppecie prawdopodobnie trudno by było osiągnąć: pakiety mają być instalowane tylko gdy na danej maszynie (sposród laptopów, desktopów i prywatnych serwerów) mam w danej chwili dostęp do szybkiego łącza (think of: netbook z GPRS). W cfengine ustawiałem sobie że klasa fast_network jest definiowana gdy podniesiony jest interfejs eth0, wlan0, br0 albo venet0.

Wysłany: 09 maja 2010, 00:26:45; 2 komentarze

monitoring -- piece of shift

Tak sobie przeglądam najpopularniejsze narzędzia do monitorowania stanu sieci i serwerów i myślę, że ten rynek doczekał się od FLOSS jedynie mniej lub bardziej krapowatych rozwiązań. Z co popularniejszych programów:

  • Nagiosowi brakuje monitorowania i śledzenia wydajności serwerów. Nieźle się sprawdza do monitorowania stanu binarnego (działa/nie działa), ale sporządzić wykres obciążenia (ilość połączeń, procesów, wykresy I/O i zużycia procesora...) już trzeba samemu. Poza tym, konfiguracja tego jest dość skomplikowana i żmudna (albo kolega, który to konfigurował, przekombinował; ale szczerze w to wątpię, bo to za dobry admin).
  • collectd nadal uważam głównie za zbieracza statystyk, nawet mimo możliwości wysłania e-maila. Nie można oznaczyć jakiegoś zdarzenia jako maintenance break, nie ma zależności między zdarzeniami (niedostępność routera powoduje automatycznie niedostępność hostów za nim, o czym nie trzeba już powiadamiać).
  • Zenoss został napisany jak dla programistów, nie jak dla administratorów. Rozszerzanie go pluginami polega na budowaniu Python eggs, co jest moim zdaniem mało wygodne, zwłaszcza dla osoby, która programistą Pythona nie jest (nawet mimo tego że pisać w Pythonie potrafi). Brakuje też Zenossowi przyzwoitej procedury instalacji, autorzy zalecają śmiecenie w /usr/local zamiast dostarczyć skrypty budujące pakiety.
  • ZABBIX. Łatwy w instalacji, ładnie wygląda GUI, ale brakuje mu tego, co administratorowi już średniej wielkości systemów jest niezbędne: wiersza poleceń. Spróbuj dodać *naście maszyn, a potem jeszcze trzymać listę monitorowanych serwerów zsynchornizowaną z listą serwerów objętych generatorem konfiguracji. Jedyne co ZABBIX oferuje, to webinterfejs napisany w PHP. Do tego brak mu jakiejkolwiek dokumentacji o strukturze bazy, w samej bazie brak referencji między tabelami (tj. ''FOREIGN KEYs''), więc zależności bardzo trudno jest prześledzić i próba dodawania hostów SQL-owymi insertami to niemal samobójstwo. Żeby było śmieszniej, ta struktura wygląda na zrobioną głupio (jeśli monitorowana maszyna jest zlinkowana z jakimś szablonem, to w bazie z wartościami czy wykresami wpisy dla szablonu są kopiowane dla tej maszyny). Wyobrażam sobie że to mogłoby mieć sens ze względu na szybkość działania daemona, ale brak dokumentacji o strukturze bazy uniemożliwia mi bronienie ZABBIXa w ten sposób.
    Chociaż tyle dobrego, że interfejs w PHP został napisany w miarę rozsądnie, jako biblioteka funkcji obrabiających dane wołanych z GUI. Dzięki temu dość niskim kosztem można przerobić PHP, którego nie cierpię, na coś przyzwoitszego w rodzaju shella.

Cacti i Muninowi nie przyglądałem się na żywo -- nie miałem okazji -- ale za Wikipedią, wyglądają jak robiące to samo co collectd.

W sumie najbliżej mi będzie do ZABBIXa, raz, że jest w instalacji i rozszerzaniu dość prosty, dwa, że w projekcie, w którym biorę udział, klient podobno życzy sobie właśnie jego. Jak dobrze że przynajmniej webinterfejs nie został w nim spieprzony...

Wysłany: 29 września 2009, 17:22:54; 4 komentarze

jak dobrze mieć własne repozytorium z pakietami

Do zapamiętania: dla każdego site-developed narzędzia powinien być przygotowany skrypt budujący pakiet. RPM-owy spec, slackbuild, ebuild, debian/rules czy co tam jest w użyciu w dystrybucji, którą się opiekuję.

Do zapamiętania 2: każde site-developed narzędzie, które jest zainstalowane na więcej niż jednej maszynie, jest wystarczającym powodem do założenia własnego repozytorium z pakietami. Powtarzam: każde i na więcej niż jednej.

Pierwsze TOREMEMBER bierze się stąd, że narzędzia napisane przez administratora do wspomagania zarządzania systemu, o ile to nie są 50-wierszowe skrypty w shellu, mają tendencję do rozrastania się i obrastania w funkcje. Takie ciągłe zmiany powodują prędzej czy później

  • problemy z ustaleniem, czy na tym systemie jest już najnowsza wersja narzędzia, czy jeszcze nie
  • problemy z przypomnieniem sobie jak wyglądała procedura budowania narzędzia (nawet jeśli to były skrypty, to przecież konfigi w wersji developerskiej zwykle nie leżą w /etc/ i przy instalacji trzeba te ścieżki poprawić)
  • problemy z przypomnieniem sobie jak wyglądała procedura instalacji lub aktualizacji (czy trzeba było założyć jakiś lock żeby narzędzia nie próbowały się uruchamiać podczas upgrade'u? jakie katalogi jeszcze trzeba utworzyć? może dodać użytkownika? zrestartować usługę? a może narzędzie wymagało jakiegoś perlowego modułu, który trzeba doinstalować?)

Wszystko to załatwia przeniesienie odpowiedzialności na system pakietów i skrypt budujący. Gotowy pakiet jest dużo łatwiejszy w utrzymaniu niż stado skryptów i binarek w /usr/local/bin. A jeszcze lepsze, że możesz oddać zadanie instalacji/aktualizacji pakietu koledze, jeśli idziesz na urlop. Kolega spokojnie da sobie radę, skoro wszystko załatwi system pakietów.

Drugie TOREMEMBER: jeśli narzędzie jest instalowane już na drugiej maszynie, znaczy że jest wystarczająco użyteczne. To z kolei znaczy, że wkrótce może się pojawić trzecia, czwarta i fafnasta maszyna z tym ustrojstwem. Wtedy zaczyna się horror z ręcznie instalowanymi pakietami. Skąd wziąć najnowszy pakiet? Jak go przerzucić na maszynę? Czy wszystko wymagane przez pakiet jest już zainstalowane? To niby drobnostki, ale dość uciążliwe, jeśli się powtarzają regularnie przy każdej aktualizacji narzędzia razy paręnaście serwerów.

Gdy masz własne repozytorium z pakietami przestajesz się martwić o aktualizacje. Po prostu logujesz się na wszystkie maszyny i polecasz systemowi pakietów zainstalować najnowszą wersję twojego pakietu (yum install pakiet, aptitude install pakiet or compatible). Sam pakiet i wszystkie ewentualne nowe zależności zostaną ściągnięte automatycznie. Nie musisz pamiętać która to wersja. Jeśli któraś maszyna jest akurat niedostępna, to albo zostawiasz w jakimś atd polecenie zalogowania się na nią i instalacji pakietu, albo zostawiasz karteczkę z notatką na następny dzień.

Własne repozytorium przydaje się jeszcze w jednej sytuacji. Czasem trafisz na narzędzia, które chcesz mieć, a których nie ma w oficjalnym repozytorium. Ot, syslog-ng czy dvtm na Red Hatach albo hpacucli do zarządzania kontrolerami RAID SmartArray (HP). Pierwsze dwa mają pliki *.spec do budowania RPM-a, a trzecie jest dostarczane w pakiecie RPM, ale jakoś trzeba jeszcze rozdystrybuować takie zewnętrzne pakiety.

Własne repozytorium jest po prostu wygodne, a dość proste w przygotowaniu.

Wysłany: 12 września 2009, 19:22:41; Dodaj komentarz

centralizacja zarządzania serwerami linuksowymi

Niektórym administratorom przychodzi opiekować się więcej niż dwiema-trzema maszynami. Kiedy serwerów jest osiem, dziesięć, *naście -- uciążliwym zaczyna być ciągłe kopiowanie i edytowanie plików w tę i z powrotem. A to dodać użytkownika, a to wpisać hosta w /etc/dhcpd.conf, a to wydzielić logi nowej usługi do osobnego pliku, zmienić listę serwerów DNS używanych przez maszyny, poprawić konfigurację Exima/Postfiksa...

Zauważmy najpierw parę faktów:

  • Większość usług na większości serwerów jest skonfigurowana podobnie albo wręcz tak samo, a wyjątki są rzadkie.
  • Większość serwerów ma zainstalowany i uruchomiony podobny zestaw usług i programów (syslog, lokalny serwer pocztowy, OpenSSH, sudo i tak dalej).
  • Niektóre serwery są wydelegowane do specjalnych zadań, przez co mają uruchomione dodatkowe usługi (serwery DHCP w oddziałach firmy, bramki VPN, serwery slave DNS). Dla większości zadań serwery można uporządkować w grupy, żeby wewnątrz grupy zestaw usług do zadania był taki sam.

Widać z tego, że bez żadnego automatu administrator masę czasu by poświęcał na wprowadzanie tych samych poprawek na kilku różnych maszynach, a to prosta droga do wprowadzenia dużej ilości małych rozbieżności między serwerami (tu się zapomni coś poprawić, tam się czegoś nie zmieni, bo przecież się będzie pamiętać...). Im więcej takich rozbieżności, tym więcej administrator poświęca czasu na ich wyszukiwanie i poprawianie. Wniosek? Potrzebny jest system dystrybucji konfiguracji.

W poprzedniej pracy napisałem coś takiego samodzielnie. Składało się z generatora konfiguracji (skrypt uruchamiany z crontaba), stada agentów odbierających powiadomienia o aktualizacji, serwera dystrybuującego powiadomienia o aktualizacji i serwera Subversion trzymającego wygenerowaną konfigurację. Nawet sprawnie działało. Po aplikacji konfiguracji restartowało usługę bądź w inny sposób ją nakłaniało do przeładowania konfiguracji (kill -HUP). Ale trzeba było dość ostrożnie podchodzić do rozrzucania informacji o aktualizacjach i nie wiem czy chciałoby mi się po raz drugi takie coś pisać (pierwsza w miarę działająca wersja powstała w tydzień pracy, a dopracowywanie trwało chyba kolejny miesiąc).

Na szczęście jest jeszcze coś takiego jak cfengine. Dokumentację do niego czytałem przez jakiś tydzień, ale było warto. cfengine w elegancki sposób załatwia sprawę dystrybucji konfiguracji i aplikowania jej działającemu systemowi.

Cfengine posługuje się pojęciem grupy maszyn. Z jednej strony każdy z serwerów należy do kilku grup. Z drugiej, grupie można przypisać kilka operacji, jak na przykład skopiowanie plików konfiguracyjnych w stosowne miejsce, sprawdzenie czy działa odpowiednia usługa, wyczyszczenie katalogu tymczasowego innej usługi, dodanie wpisu do /etc/services, upewnienie się że jakiś plik/katalog istnieje i ma prawidłowe uprawnienia. Co więcej, różnice między dystrybucjami są załatwiane w podobnie elegancki sposób: każda maszyna należy do grupy o nazwie pochodzącej od nazwy dystrybucji: redhat, debian, gentoo czy slackware. Dla takiej grupy tworzonej od dystrybucji wystarczy zdefiniować zmienną wskazującą gdzie się znajduje plik konfiguracyjny (na przykład /etc/dhcpd.conf pod Slackware i /etc/dhcp/dhcpd.conf pod Gentoo), a w regule dla serwera DHCP podać jedynie tę ścieżkę. Można też zdefiniować dwie reguły kopiowania dhcpd.conf, tylko po co utrzymywać dwa prawie identyczne wpisy?

Gdy przychodzi do przeładowania konfiguracji, ujawnia się druga przyjemna cecha cfengine. Możesz zarządzić, że po operacji kopiowania pliku dhcpd.conf dla tej maszyny ma zostać tymczasowo zdefiniowana dodatkowa grupa, powiedzmy dhcpd_restart. Teraz wystarczy że utworzysz regułę restartującą serwer DHCP, która ma być wykonana tylko na maszynach w grupie dhcpd_restart, normalnie niezdefiniowanej. Proste, logiczne i wygodne.

Interesującą rzeczą w cfengine jest możliwość bezpiecznej samoaktualizacji konfiguracji. Konfiguracja dla maszyny składa się z dwóch plików: update.conf, który powinien zawierać reguły aktualizowania konfiguracji cfengine (i jako taki powinien się zmieniać jak najrzadziej, a najlepiej wcale) i cfagent.conf, który zawiera reguły dla wszystkich pozostałych usług. Jeśli przypadkiem zepsujesz cfagent.conf, to mógłbyś mieć problem z dalszym działaniem cfengine. Właśnie dlatego do samoaktualizacji przeznaczony jest osobny plik, wczytywany i uruchamiany przed plikiem głównym.

Z innych ciekawostek, cfengine działa w całkiem pomysłowy sposób. To agent sobie wybiera (na podstawie konfiguracji, rzecz jasna) z kim chce współpracować i co wykonać, a jeśli sąsiedzi nie są dostępni, to robi tyle ile umie. Oznacza to całkiem sprawną pracę nawet przy odcięciu od sieci. Oczywiście możemy zapomnieć w takiej sytuacji o aktualizacji plików konfiguracyjnych, ale pozostałe operacje, jak sprawdzanie czy usługa działa, sprawdzanie istnienia i nadawanie praw katalogom, będą wykonane, a aktualizacja nastąpi wkrótce po przywróceniu dostępu do sieci. Mówiąc krótko, system pod kontrolą cfengine dąży do stanu zapisanego w konfiguracji cfengine.

Jedno w cfengine mi się nie podoba. Autor nie używa protokołu SSL, a szyfrowanie nie dość że jest opcjonalne i trzeba je specjalnie włączyć, to jeszcze jest wykonywane sposobem bliżej nieznanym (choć w dokumentacji stoi 3DES, a z komunikatów cfengine wynika że używają Blowfisha; ale w którym trybie? jak wymieniane klucze sesyjne?). Autor tłumaczy nieużywanie SSL/TLS tym, że nie chce zmuszać administratora do wykupowania osobnego certyfikatu dla każdej maszyny. Średnio ufam umiejętnościom kryptograficznym kogoś, kto nie słyszał o X.509, PKI i własnych małych centrach certyfikacji. Może niedługo zastosuję do SSL-a w cfengine metodę dozziego (chodźmy weźmy napiszę).

I jeszcze jedna uwaga na koniec: cfengine jako taki nie zajmuje się generowaniem plików konfiguracyjnych, może je najwyżej dystrybuować. Do generowania przygotowałem skrypt w Perlu żeniący zmienne dla hostów zapisane w plikach YAML z szablonami pisanymi w Template::Toolkit. Skrypt ten jest dość krótki, bo zajmuje raptem dwieście linii kodu.


Chciałem początkowo napisać krótkie howto do cfengine, ale sam opis systemu zajął dość miejsca żeby odłożyć to na przyszłość. Przy czym raczej niezbyt odległą, bo mam do opisania jeszcze system dystrybucji konfiguracji osobistej (.zshrc, .vimrc i tym podobne śmieci) wspomagany przez cfengine (chociaż akurat tylko w ramach nanoszenia zmian, a nie dystrybucji plików).

Wysłany: 25 czerwca 2009, 23:32:21; 5 komentarzy