Zaczynamy przygodę z Puppetem i Vagrantem

Python posiada virtualenvy ułatwiające pracę i hostowanie wielu aplikacji Pythona posiadających różne zależności. Czasami zachodzi jednak potrzeba posiadania podobnego rozwiązania w bardziej globalnym zakresie - np. tworzenie środowiska deweloperskiego zbliżonego do konfiguracji środowiska produkcyjnego. Rozwiązania takie jak Puppet do zarządzania konfiguracją (np. serwerów produkcyjnych) zbliżają się coraz bardziej do programistów dzięki rozwiązaniom takim jak Vagrant. Dzięki Vagrantowi jedno polecenie wygeneruje nam środowisko deweloperskie oparte o oddzielny system operacyjny działający na Virtualboksie. Konfiguracją systemu zajmuje się Puppet (albo inne podobne rozwiązanie), dzięki czemu możemy od razu uzyskać np. rozwijaną aplikację działającą z nginxem i innymi potrzebnymi usługami. Patrząc na to z drugiej strony - nie trzeba będzie np. tłumaczyć frontendowcowi jak skonfigurować sobie środowisko testowe - automat zrobi to za niego.

W tym artykule poruszę raczej tylko podstawy Puppeta i Vagranta. Jakieś fajne rozwiązania zostawiam na drugi artykuł, który powinien powstać za jakiś czas.

Puppet

Puppet to narzędzie do zarządzania konfiguracją. Za jego pomocą możemy konfigurować kilka jak nie więcej dystrybucji Linuksa, Mac OSX, MS Windows, BSD i inne systemy operacyjnej. Za pomocą deklaratywnej składni określamy stan w jakim dana maszyna ma się znaleźć - np. ma na niej być serwer Nginx o zadanej konfiguracji, a nie przepis jak to zainstalować. Puppet sam przetworzy receptę na odpowiednie polecenia specyficzne dla danego systemu operacyjnego i dystrybucji. Używany m.in. przez dużych graczy zyskuje coraz większą popularność.

Pod Debianem i pochodnymi takimi jak Ubuntu puppeta znajdziemy w repozytoriach (zarówno klienta jak i serwer). Na stronie puppetlabs.com znajdziemy paczki w najnowszych wersjach dla wszystkich oficjalnie wspieranych systemów. W sieci można znaleźć liczne wprowadzenia do puppeta, te dla administratorów serwerów, czy orientowane bardziej pod programistów danego języka i platformy. Ja spróbuję zacząć od podstaw przedstawionych na stronie harker.com (ale z użyciem Ubuntu jako bazy). Dokumentacja dla najnowszych wersji dostępna jest na docs.puppetlabs.com. Dostępna jest też poręczna ściągawka.

Konfiguracja zawarta jest w receptach (recipes). Pliki recept mają rozszerzenie .pp. Recepty to definicja tego jak system powinien wyglądać. System działa w oparciu o serwer (puppet master) i klientów, którzy się autoryzują i pobierają przynależne im recepty i wykonują je.

Komponenty konfiguracyjne zorganizowane są w zasobach, a te zgrupowane są w kolekcjach. Zasób (resource) składa się z typu, tytułu i serii atrybutów, np:

file { "/tmp/foo.txt":
  owner => "root";
  group => "root:
}

Typem jest file, tytułem jest /tmp/foo.txt. Atrybuty definiują root jako grupę i użytkownika.

Żeby klienci mogli pobierać recepty trzeba uruchomić puppet mastera. Po instalacji pakietu na Debianie/Ubuntu serwer od razu wstanie. Ręczne startowanie to:

sudo service puppetmaster start

Logi zapisywane są w /var/log/puppet/masterhttp.log. Serwer konfigurowany jest przez dwa pliki - /etc/puppet/puppet.conf i /etc/puppet/manifests/site.pp. Tego pierwszego zazwyczaj nie trzeba edytować. Ten drugi ładuje wszystkie dostępne recepty. Testowy site.pp mógłby wyglądać tak:

file { "/tmp/foo.txt":
  owner => "root",
  group => "root",
  mode => "644",
}

Żeby odpalić klienta po raz pierwszy trzeba go będzie włączyć poleceniem (w zależności od systemu i wersji może już być włączony):

sudo puppet agent --enable

By pobrać recepty jako klient i je wykonać należy wykonać:

sudo puppet agent --test

Jeżeli zobaczymy błędy typu Error: Failed to apply catalog: getaddrinfo: Name or service not known to oznacza iż puppet nie wie na jakim serwerze/maszynie działa. Można dodać wpis do /etc/hosts, albo dodać flagę --server z nazwą hosta naszego systemu (polecenie hostname ją zdradzi):

sudo puppet agent --test --server NAZWA_HOST_SYSTEMU

Jako że recepta nie tworzy pliku jeżeli on nie istnieje (bo mu nie każemy) to stworzyłem /tmp/foo.txt i odpaliłem puppeta. Udane odpalenie puppeta da wynik tego typu:

sudo puppet agent --test --server leonidas
Info: Retrieving plugin
Info: Caching catalog for leonidas
Info: Applying configuration version '1389545865'
Notice: /Stage[main]//File[/tmp/foo.txt]/owner: owner changed 'piotr' to 'root'
Notice: /Stage[main]//File[/tmp/foo.txt]/group: group changed 'piotr' to 'root'
Notice: Finished catalog run in 0.02 seconds

Recept może być wiele. Mogą to być węzły (nodes) do konfiguracji hosta lub danego węzła maszyn. Znajdziemy też klasy mówiące co trzeba zrobić oraz moduły zawierające klasy wielokrotnego użytku.

Klasy definiują jak zainstalować i skonfigurować aplikacje, usługi, pliki i temu podobne. Klasę definiujemy tak:

class Title {
}
Oto przykład klasy z dwoma zasobami (o pliku tej klas za chwilę):
class spamfiles {
    file { "/tmp/foo.txt":
    owner => "root",
    group => "root",
    mode => "644",
    }

    file { "/tmp/bar.txt":
    owner => "root",
    group => "root",
    mode => "644",
    }
}

Mając klasy możemy użyć ich w węzłach (/etc/puppet/nodes.pp). Węzeł definiuje, które klasy i moduły są przeznaczone dla danego hosta (klasy konfigurujące bazę danych dla serwera bazodanowego itd.). Jeżeli nie zostanie dopasowany żaden konkretny węzeł to użyty zostanie ten o nazwie "default".

W puppecie mamy też moduły ulokowane domyślnie w /etc/puppet/modules. Są to przenośne zbiory klas, konfiguracji, szablonów i plików. Moduł pozwoli nam dostarczyć klasę do węzła default. Tworzę moduł o nazwie demomodule a w nim katalog manifests. W tym katalogu tworzę plik klasy nazywając go spamfiles.pp. W pliku ląduje powyższy kod z jedną zmianą - klasa nazywa się demomodule::spamfiles.

W /etc/puppet/manifests/nodes.pp tworzę węzeł default:

node default {
    include demomodule::spamfiles
}

Polecenie include ładuje podaną klasę - nazwamodułu::nazwaklasy co przekłada się na /etc/puppet/modules/nazwamodułu/manifests/nazwaklasy.pp (plus w tym pliku musi być klasa o takiej nazwie. Ścieżki taki można robić dłuższe, np. nazwamodułu::nazwakatalogu::nazwaklasy to /etc/puppet/modules/nazwamodułu/manifests/nazwakatalogu/nazwaklasy.pp. Po stworzeniu węzła puppet nadal powinien poprawnie działać (puppet agent --test).

Mając serwer z kilkoma aplikacjami Django chcielibyśmy dla każdej mieć plik konfiguracyjny. Pomiędzy aplikacjami pewnie różniłyby się np. nazwą projektu, czy jakimiś ścieżkami. Przydatne będą szablony. Dla naszego testowego modułu tworzę szablon /etc/puppet/modules/demomodule/templates/bar.txt zawierający w swojej treści jedną zmienną:

<%= a_variable %>

spamfiles.pp modyfikuję do postaci:

class demomodule::spamfiles {
    file { "/tmp/bar.txt":
         content => template("demomodule/bar.txt"),
         ensure => "present",
    }
}
Poprzez content wskazuję szablon, z którego /tmp/bar.txt ma zostać stworzone. ensure ustawione na present spowoduje że Puppet stworzy pliki jeżeli nie istnieje (jest to m.in. w ściągawce). Węzeł modyfikuję do postaci:
node default {
    $a_variable = 'default'
    include demomodule::spamfiles
}
Po wykonaniu puppeta plik /tmp/bar.txt powinien zyskać w treści słowo "default".

Teraz konfiguracja mastera i klienta na dwóch różnych maszynach. Dla przykładu w virtualboksie odpaliłem LiveCD z Linuxmint (hostname mint) i na nim zainstalowałem klienta. Klient i serwer działają wewnątrz tej samej sieci. Na kliencie instaluję puppeta. Do /etc/hosts dodaję nazwę hosta mastera dla jego IP (192.168... leonidas) i wykonuję sudo puppet agent --test --server leonidas. Za pierwszym razem klient zgłosi swój klucz do mastera. Klucz nie będzie jeszcze zaakceptowany więc na tym działanie agenta się skończy. Na maszynie mastera trzeba klienta zaakceptować. Możemy wyświetlić certyfikaty jakie zostały zgłoszone i podpisać odpowiednie, najprościej:

sudo puppet cert list
sudo puppet cert sign --all
Dla minta dodałem oddzielny węzeł:
node mint {
    $a_variable = 'mint rulez!'
    include demomodule::spamfiles
}

Przez co przy ponownym wywołaniu agenta na kliencie spowoduje iż system dostanie konfigurację z tego węzła.

Jak na razie Puppet nie robił niczego ciekawego w naszych przykładach. Wiemy już jak to mniej więcej działa i gdzie/co można konfigurować. Można teraz wykorzystać Puppeta do konfiguracji np. komputerów w firmie, czy serwerów pod tworzone aplikacje. Razem z Vagrantem może posłużyć do generowania środowiska deweloperskiego identycznego praktycznie co do konfiguracji z produkcją.

Vagrant

vagrantup.com to coś jak virtualenv tyle że dla całego systemu operacyjnego. Dzięki wirtualizacji dostajemy system operacyjny (np. wybrana dystrybucja Linuksa, zobacz vagrantbox.es) z określoną konfiguracją i współdzielonymi plikami, nad którymi pracujemy. Pakiet samego Vagranta powinien być w repozytoriach większych dystrybucji.

Jako przykład wykorzystam vagrantpress, czyli Wordpress na Vagrancie. Wykorzystuje on Puppeta, więc możemy przejrzeć jego pliki konfiguracyjne. Klonujemy repozytorium i uruchamiamy Vagranta poleceniem vagrant up. Pobrany (i skeszowany) zostanie obraz ISO Ubuntu po czym odpalony zostanie puppet, który zainstaluje i skonfiguruje cały stack LAMP z gotową instalacją Wordpressa. Gdy skończy wystarczy otworzyć adres http://localhost:8080 by zobaczyć naszego Wordpressa. Jedna komenda i gotowe.

vagrant ssh przeniesie nas na wirtualizowaną maszynę, provision odpali ponownie zarządcę konfiguracji (w tym przypadku puppet), a reload zatrzyma i odpali ponownie cały proces. halt ją zamknie. Na wirtualizowanej maszynie będzie istniał katalog /vagrant/ ze współdzielonymi plikami. Współdzielony jest katalog zawierający plik Vagrantfile za pomocą którego odpalono maszynę.

Plik Vagrantfile to plik konfiguracyjny. Podajemy tutaj system, a także np. sposób konfiguracji (provision). W przypadku Puppeta możemy podać namiary na zewnętrzny master, albo rozprowadzać konfigurację puppeta razem z konfiguracją Vagranta (co robi vagrantpress).

Dla Django istnieje kilka przykładów dla Vagranta (m.in. na Githubie). Używają różnych zarządców konfiguracji i każdy szkielet może mieć swoje plusy i minusy. Na chwilę obecną nie jestem w stanie wskazać jakiś konkretny szablon. Można napisać coś własnego na bazie np. nginxa, czy także supervisora i Gunicorna.

RkBlog

Django web framework tutorials, 12 January 2014

Comment article
Comment article RkBlog main page Search RSS Contact