Ansible – Dobre praktyki

Ten artykuł, jest moją próbą wskazania wszystkich, znanych mi, dobrych lub bardzo dobrych praktyk związanych z narzędziem Ansible. Część z nich jest bardzo autorska, część ogólna i uznana przez społeczność.Postaram się pokazać jak pracować z Ansible, żeby się nie narobić i nie skaleczyć; w co warto zainwenstować swój czas i jak wykonaną pracę potem wykorzystać. Ostrzegam. Będzie długo. Bardzo. 😉

Inwentarz

Plik inwentarza (ang. inventory) jest miejscem, w którym Administrator definiuje maszyny, na których zostaną wykonane akcje oraz (być może) zmienne, których one dotyczą. W Ansible, co do ogółu, mamy dwa rodzaje inwentarza. Dynamiczny oraz statyczny. Statyczny jest plikiem formatu INI:


[atlanta]
host1
host2 [raleigh] host2 host3 [southeast:children] atlanta raleigh [southeast:o,ars] some_server=foo.southeast.example.com halon_system_timeout=30 self_destruct_countdown=60 escape_pods=2 [usa:children] southeast northeast southwest northwest

Listing 1: Plik statycznego inwentarza. Zródło: dokumentacja Ansible.

Widzimy, istotnie, że jest to plik, który:

  • listuje dostępne maszyny,
  • grupuje je,
  • konfiguruje zmienne dla maszyn (nie polecam korzystać z inwentarza w ten sposób),
  • jest w stanie agregować grupy poprzez dyrektywę :children.

Dynamiczne zaś, jest to plik wykonywalny, najczęściej skrypt napisany w Pythonie, który na wyjściu zwraca JSON o formie podobnej do tej:

{
 "databases":{
 "hosts":[
 "host1.example.com",
 "host2.example.com"
 ],
 "vars":{
 "a":true
 }
 },
 "webservers":[
 "host2.example.com",
 "host3.example.com"
 ],
 "atlanta":{
 "hosts":[
 "host1.example.com",
 "host4.example.com"
 ],
 "vars":{
 "b":false
 },
 "children":[
 "marietta",
 "5points"
 ]
 },
 "marietta":[
 "host6.example.com"
 ],
 "5points":[
 "host7.example.com"
 ]
}

Listing 2: Poprawne dane zwracane przez dobrze napisany skrypt dynamicznego inwentarza. Zródło: dokumentacja Ansible .

W sieci jest wiele gotowych skryptów do generowania inwentarza. Oto kilka przykładów widocznych w repozytorium Ansible:

W skrócie. Zaleca się, aby wykorzystywać dynamiczny inwentarz. Powodów jest kilka.

  • Dynamiczny inwentarz to oszczędność czasu. Statyczny skutkuje tym, że za każdym razem, gdy do Administratora wpłynie wniosek o nowe środowisko, Sysadmin musi ręcznie uzupełnić plik o nowe maszyny, co kosztuje czas.
  • Wykorzystywanie dynamicznego inwentarza pomaga uniknąć błędu człowieka. Zauważmy, że posiadając bardziej rozbudowaną infrastrukturę oraz statyczny inwentarz jesteśmy skazani na manualne wprowadzanie maszyn do pliku, co prędzej czy później, musi skończyć się pomyłką ze strony wprowadzającego i nowopowstała maszyna nie znajdzie się w jakiejś grupie, co spowoduje, że nie zostanie wykonana na niej akcja bądź rola wdrożona w playbook. Na nic Administratowi automatyzacja, która się nie wykona.

Składnia

Kolejną bolączką, którą jeszcze relatywnie często widuję w sieci są źle (tak, nazwijmy to po imieniu: źle) sformatowane role i playbooki, które nie posiadają składni YAML-a, XML-a lub JSON-a.

Czytając dokumentację Ansible oraz standardu YAML możemy przeczytać.

YAML can therefore be viewed as a natural superset of JSON, offering improved human readability and a more complete information model. This is also the case in practice; every JSON file is also a valid YAML file. This makes it easy to migrate from JSON to YAML if/when the additional features are required.

Cytat 1: Dokumentacja YAML.

We use YAML because it is easier for humans to read and write than other common data formats like XML or JSON. Further, there are libraries available in most programming languages for working with YAML.

Cytat 2: Dokumentacja Ansible.

Stąd, nie ma znaczenia, czy swoją rolę utworzymy przy pomocy pliku JSON czy pliku YAML.

[
 {
 "gather_facts":"false",
 "connection":"local",
 "tasks":[
 {
 "debug":{
 "msg":"blah"
 }
 }
 ],
 "hosts":"localhost",
 "name":"all"
 }
]

Listing 3: Plik uruchomienia zadania w formacie JSON. Źródło: opracowanie własne.

gather_facts: "false"
connection: "local"
tasks:
 - debug:
   msg: "blah"
   hosts: "localhost"
   name: "all"

Listing 4: Plik uruchomienia zadania w formacie YAML. Zródło: opracowanie własne.

Obie formy będą poprawne i zostaną odczytane przez narzędzie. Jednak składnia, która jeszcze bywa widoczna w sieci Internet, taka jak ta:

# Example vmware_cluster command from Ansible Playbooks
 - name: Create Cluster
  local_action: >
    vmware_cluster hostname="{{ ansible_ssh_host }}" username=root password=vmware datacenter_name="datacenter" cluster_name="cluster" enable_ha=True enable_drs=True enable_vsan=True

Listing 5: Nieczytelna forma uruchomienia zadania. Zródło: dokumentacja Ansible.

Jest, po prostu, nieczytelna i w moim odczuciu powinna być zamieniona na poprawną, to znaczy:

- name: 'create cluster'
  vmware_cluster:
    hostname: "{{ vcenter_host }}"
    username: '{{ vcenter_username }} '
    password: '{{ vcenter_password }}'
    datacenter_name: 'datacenter'
    cluster_name: 'cluster'
    enable_ha: 'true'
    enable_drs: 'true'
    enable_vsan: 'true'

Listing 6: Poprawna forma zadania. Zródło: opracowanie własne.

Warto tutaj zaznaczyć, że przez bardzo długi czas taka składania była widoczna w dokumentacji samego narzędzia od Czerwonych kapeluszy, stąd trudno winić użytkowników za jej stosowanie.

Fakt jest jeden; to jak zmienia się czytelność kodu po zastosowaniu poprawnej składni jest nie do przecenienia. Mówimy tutaj o automatyzacji, zatem warto zarekomendować automatyzację walidacji kodu. Przychodzi tutaj z pomocą następujące narzędzie: yamllint, z którym zachęcam się zaprzyjaźnić. Pozwala sprawdzić i skonfigurować, takie parametry jak:

  • liczba spacji w liście,
  • liczba pustych linii,

Szkice

Dyrektywa wstępu

Zawsze, zawsze i jeszcze raz zawsze, we wszystkich plikach, które są zarządzane przez Ansible, o ile to możliwe, powinno się to wyraźnie zasygnalizować dyrektywą:

{{ ansible_managed | comment }}

Listing 7: Dyrektywa wstępu. Źródło: opracowanie własne.

Jeśli trudno jest Wam to zaakceptować to postawcie się w sytuacji Administratora, który w musiał znaleźć błąd w konfiguracji serwera SSH. Znalazł go. Poprawił. Zweryfikował działanie. Zamknął zadanie. Trzydzieści minut później zadanie wraca do niego wraca, bo zostało nadpisane przez moduł ansible-pull; następnie Administrator spędził kolejne godziny na sprawdzeniu jaki proces modyfikuje ten plik, dlaczego to robi, co go wywołuje, w jakich odstępach czasowych… Nie polecam.

Tryb tylko do odczytu

Bardzo często stosuję praktykę trybu 0400. Powód jest banalny. Zmusza do zastanowienia się przy mechanicznym wpisaniu ’:wq’ w vim.

Niestety, nie we wszystkich konfiguracjach da się zastosować taki model. Przykładem jest aplikacja Redis, której mechanizm przełączenia klastra wymusza modyfikację pliku konfiguracyjnego. Stąd, brak drugiego bitu w uprawnieniach, uniemożliwią start usługi.

Walidacja szkicu

Ansible umożliwia walidację szkicu przed jego nadpisaniem, stąd jeśli Czytelnik zarządza serwerem SSH, warto by zwalidował wygenerowany plik konfiguracyjny tegoż serwera przed wdrożeniem go na serwer przy pomocy takiego zadania:

- name: 'deploy configuration file'
  template:
    dest: '{{ centsible_sshd_config_file_dest }}'
    backup: 'true'
    group: 'root'
    owner: 'root'
    mode: '0400'
    src: 'sshd-config.j2'
    validate: 'sshd -t -f %s'
 notify: 'restart sshd'

Listing 8: Przykład wdrozenia pliku SSH z wykorzystaniem Ansible i modułu walidacji . Źródło: opracowanie własne.

Z pewnością sprawnemu oku Czytelnika nie umknął argument: ’validate’. Dzięki niemu przed nadpisaniem pliku konfiguracyjnego SSH, zostanie wykonana komenda:

sshd -t -f %s

W powyższym, argument %s jest tymczasową ścieżką szkicu. Inny przykład komendy walidującej:

aide -c %s -D

Warto podkreślić, że taka walidacja nie ogranicza się do pliku konfiguracyjnego. Równie dobrze możemy taką walidację przeprowadzać innego rodzaju danych (XML, JSON) przy pomocy odpowiednich narzędzi.

Kopia zapasowa

Czytelnik z pewnością zauważył szalenie istotny argument: backup w ostatnim przykładzie. Tak. Kopie zapasowe plików konfiguracyjnych niejednokrotnie uratowały majtki Autora przed brązową plamą podczas wdrożenia. Kopie zapasowe są ważne. Kopie zapasowe należy robić. Zawsze.

Moduły powłoki

Moduły powłoki i manualnego wywoływania komend, takie jak:

Powinny one być używane możliwie najrzadziej. Oczywiście, samodzielne implementowanie API nie należy do najciekawszych zadań; z drugiej strony, zyskujemy lepszą obsługę błędów. Dużo lepszym rozwiązaniem jest, również, napisanie własnego modułu. Tutaj zachęcam do lektury poradnika z dokumentacji Ansible.

Operatory | oraz >

Zmienne z zawartością wielolinijkową powinne być poprzedzone odpowiednio: | bądź >, w zależności od tego, czy powinna zostać przekazana w jednej linii czy w z zachowaniem znaku kolejnego wiersza.

System kontroli wersji

Do śledzenia zmian w rolach oraz playbookach zaleca się korzystanie z systemu kontroli wersji; niezależnie czy jest to git, czy SVN, czy dowolna inna technologia. Korzystanie z niego jest wymagane w zakresie pracy z Ansible Galaxy, jednak do współpracy w zespole jest to nieocenione. Zespół jest w stanie weryfikować, kto wykonał jaką zmianę, integrując to z systemem zadaniowym – łatwo znaleźć background danego commita.

Smoke-test

Trust nothing. Paranoia is your friend.

Tak kończy się dokumentacja oprogramowania sprawdzającego integralność plików w systemach Linuksowych zwanego AIDE. (Notabene: bardzo polecam to narzędzie.) Pisanie testów jednostkowych i testów funkcjonalnych jest bardzo naturalne w procesie tworzenia oprogramowania. Podobnie, nawykiem powinne być testy dymne w pisaniu ról w Ansible. Oto kilka przykładów.

Przykład 1: Podczas wdrażania konfiguracji serwera SSH, warto wykonać test sprawdzający połączenie między maszyna Ansible a hostem, na którym przeprowadzana była konfiguracja.

Przykład 2: Korzystając z Ansible do działań Continuous Integration / Continuous Deployment, warto po wykonaniu wdrożenia, wykonać proste testy sprawdzające, takie jak:

  • odpowiedź od strony serwera HTTP,
  • kontent strony aplikacji,
  • zweryfikować podłączenie do bazy danych z poziomu maszyny aplikacyjnej dla
  • użytkownika aplikacyjnego.
Szyfrowanie

W jednej z poprzednich sekcji rekomendowałem wykorzystywanie SCM. Warto nie ograniczać SCM do sekretów i wykorzystać moduł Vault, a lokalnie umieścić w pliku odpowiednią wzmiankę o haśle do rozszyfrowania.

Fakty

Moduł faktów (ang. facts) jest nieoceniony w dynamicznym powoływaniu infrastruktury. Nie mniej; zbieranie tych informacji kosztuje czas, co bezsprzecznie koliduje z szybkością i komfortem pracy Administratora. Żeby unikać dyskomfortu podczas pracy, proponuję skorzystanie z pamięci podręcznej dla faktów. Dostępne mamy 2 formy pamięci podręcznej: – plik JSON, – baza Redis.

Obie formy są konfigurowalne z poziomu pliku ansible.cfg.

Idempotentność

Matematycznie, idempotentność to własność pewnych funkcji (jedno, dwu- lub wieloargumentowych). Mając daną funkcję f określoną na zbiorze X, funkcję f nazywamy idempotentną, jeśli spełnia następującą własność.

f(f(x)) = f(x)

Role powinny być spełnione tak, aby za każdym razem gdy zostanie uruchomiona dwukrotnie (bądź więcej, w mocy indukcji) rola, zachowywała stan OK. Zawsze. Pozwala to na dostrzeganie różnic w konfiguracjach maszyn, audyt, zwiększenie stanu bezpieczeństwa oraz upewnia Administratora o aktualnym stanie maszyny. Kolejne wykonania to wykonania puste.

Ansible: Zastosowania

Configuration Management

Bardzo standardowe zastosowanie. Tak standardowe, że na wzmiance o nim zakończę podrozdział. 😉

Bezpieczeństwo

Ansible z powodzeniem pracuje w charakterze narzędzia zwiększania bezpieczeństwa maszyn. Czytelnik może zapoznać się z gotowymi rolami do hardeningu hostów.

W ubiegłym roku podczas DevOps Days 2016 miałem przyjemność wygłosić Ignite Talk, w którym przedstawiłem ideę implementacji Security Technical Implementation Guidelines wydawanych przez departament bezpieczeństwa Stanów Zjednoczonych (DISA) i konfiguracji maszyn zgodnie z tymi dyrektywami. W tym miejscu, bardzo serdecznie zapraszam do zapoznania się z moją prelekcją.

Przykłady gotowych ról służących do podwyższania bezpieczeństwa:

Oczywiście, nic nie stoi na przeszkodzie, aby wewnątrz organizacji utworzyć własne role implementujące wewnętrzne polityki bezpieczeństwa. Ansible, z powodzeniem wdroży:

  • narzędzia do weryfikacji integralności plików,
  • skonfiguruje zaporę sieciową,
  • ztuninguje parametry jądra,
  • zmodyfikuje punkty montowania,
  • zadba o bezpieczeństwo autoryzacji.

 

Continuous Integration / Continuous Deployment

Ansible bardzo dobrze sprawdza się jako narzędzie do wspomagania procesu ciągłego wdrożenia i integracji procesów wdrażania oprogramowania. O tym rozwiązaniu, w połączeniu ze standardowym Configuration management mówiłem podczas AWS User Group #18, gdzie postawiłem klaster z Universal Control Plane na chmurze Amazon Web Services i przeprowadziłem proces pełnej integracji na Gitlabie.