Instrukcja: jak testować role ansible i dowiadywać się o problemach przed wdrożeniem

Cześć wszystkim!

Pracuję jako inżynier DevOps w serwisie rezerwacji hoteli Ostrovok.ru. W tym artykule chciałbym opowiedzieć o naszym doświadczeniu w testowaniu ról ansible.

W Ostrovok.ru jako menedżer konfiguracji używamy ansible. Niedawno doszliśmy do konieczności testowania ról, ale jak się okazało, narzędzi do tego nie ma zbyt wiele — najpopularniejszym jest prawdopodobnie framework Molecule, dlatego postanowiliśmy go użyć. Okazało się jednak, że jego dokumentacja pomija wiele pułapek. Nie udało nam się znaleźć wystarczająco szczegółowego przewodnika w języku rosyjskim, więc postanowiliśmy napisać ten artykuł.

Instrukcja: jak testować role ansible i dowiadywać się o problemach przed wdrożeniem

Molecule

Molekuła — framework do pomocy w testowaniu ról ansible.

Uproszczony opis: Molekuła tworzy instancję na wybranej przez Ciebie platformie (chmura, wirtualka, kontener; więcej informacji w rozdziale Driver), uruchamia na niej Twoją rolę, następnie wykonuje testy i usuwa instancję. W przypadku wystąpienia błędu na jednym z etapów, Molekuła Cię o tym poinformuje.

Teraz bardziej szczegółowo.

Trochę teorii

Rozważmy dwie kluczowe encje Molekuły: Scenario i Driver.

Scenario

Scenariusz zawiera opis tego, co, gdzie, jak i w jakiej kolejności będzie wykonane. Jedna rola może mieć kilka scenariuszy, a każdy z nich to katalog w ścieżce /molecule/, zawierający opisy niezbędnych działań do testu. Musi istnieć scenariusz default, który zostanie automatycznie utworzony, jeśli zainicjujesz rolę przy pomocy Molekuły. Nazwy następnych scenariuszy są wybierane według Twojego uznania.

Sekwencja działań testowych w scenariuszu nazywa się macierz, a domyślnie wygląda następująco:

(Kroki oznaczone ?, domyślnie są pomijane, jeśli nie są opisane przez użytkownika)

  • lint — uruchomienie linterów. Domyślnie używane są yamllint i flake8,
  • destroy — usunięcie instancji z ostatniego uruchomienia Molekuły (jeśli pozostały),
  • dependency? — установка ansible-зависимости тестируемой роли,
  • syntax — sprawdzenie składni roli za pomocą ansible-playbook --syntax-check,
  • create — utworzenie instancji,
  • prepare? — подготовка инстанса; например, проверка / установка python2
  • converge — uruchomienie testowanego playbooka,
  • idempotence — ponowne uruchomienie playbooka w celu przetestowania idempotencji,
  • side_effect? — действия, не относящиеся непосредственно к роли, но нужные для тестов,
  • verify — uruchomienie testów otrzymanej konfiguracji za pomocą testinfra(domyślnie) /goss/inspec,
  • cleanup? — (в новых версиях) — грубо говоря, «очистка» внешней инфраструктуры, задетой Молекулой,
  • destroy — usunięcie instancji.

Ta sekwencja obejmuje większość przypadków, ale w razie potrzeby można ją zmienić.

Każdy z wymienionych kroków można uruchamiać osobno za pomocą molecule. Należy jednak pamiętać, że dla każdej takiej komendy CLI może istnieć własna sekwencja działań, której można się dowiedzieć, wykonując molecule matrix. Na przykład, przy uruchamianiu komendy converge (uruchamianie testowanego playbooka) wykonane zostaną następujące kroki:

$ molecule matrix converge
...
└── default         # nazwa scenariusza
    ├── dependency  # instalacja zależności
    ├── create      # tworzenie instancji
    ├── prepare     # wstępne przygotowanie instancji
    └── converge    # uruchomienie playbooka

Sekwencję tych działań można edytować. Jeśli coś z listy zostało już wykonane, zostanie pominięte. Obecny stan oraz konfiguracja instancji są przechowywane przez Molecule w katalogu $TMPDIR/molecule//.

Można dodać kroki z ? opisując pożądane działania w formacie playbooka Ansible, a nazwę pliku dostosować do kroku: prepare.yml/side_effect.yml. Molecule oczekuje tych plików w folderze scenariusza.

Driver

Sterownik to byt, w którym tworzone są instancje do testów.
Lista standardowych sterowników, dla których Molecule przygotowało szablony, jest następująca: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

W większości przypadków szablony to pliki create.yml i destroy.yml w folderze scenariusza, które opisują tworzenie i usuwanie instancji odpowiednio.
Wyjątkami są Docker i Vagrant, ponieważ interakcje z ich modułami mogą odbywać się bez wyżej wymienionych plików.

Warto wyróżnić sterownik Delegated, ponieważ w przypadku jego użycia w plikach tworzenia i usuwania instancji opisana jest tylko praca z konfiguracją instancji, resztę powinien opisać inżynier.

Domyślnym sterownikiem jest Docker.

Teraz przejdźmy do praktyki i sprawdźmy dodatkowe szczegóły tam.

Rozpoczęcie pracy

Jako "hello world" przetestujemy prostą rolę instalacji nginx. Jako sterownik wybierzemy Docker – myślę, że jest zainstalowany u większości z was (pamiętajmy, że Docker to domyślny sterownik).

Przygotujemy virtualenv i zainstalujemy w nim molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule zainstaluje ansible jako zależność; docker dla sterownika

Kolejnym krokiem jest zainicjowanie nowej roli.
Inicjalizacja nowej roli, podobnie jak nowego scenariusza, odbywa się za pomocą polecenia molecule init:

> molecule init role -r nginx
--> Inicjalizowanie nowej roli nginx...
Zainicjowano rolę w /nginx pomyślnie.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 katalogów, 1 plik

Powstała typowa rola ansible. Wszystkie dalsze interakcje z CLI Molekuły odbywają się z katalogu roli.

Zobaczmy, co znajduje się w katalogu roli:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Szablon Jinja dla Dockerfile
├── INSTALL.rst.   # Trochę informacji o instalacji zależności scenariusza
├── molecule.yml   # Plik konfiguracyjny
├── playbook.yml   # Plebiscyt uruchamiający rolę
└── tests          # Katalog z testami etapu weryfikacji
    └── test_default.py

1 katalog, 6 plików

Rozważmy konfigurację molecule/default/molecule.yml (zmienimy tylko obraz docker):

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: instance
    image: centos:7
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8

dependency

Ta sekcja opisuje źródło zależności.

Możliwe opcje: galaxy, gilt, shell.

Shell – to po prostu powłoka, która jest używana, jeśli galaxy i gilt nie spełniają Twoich potrzeb.

Nie będę tu się rozwodzić, wystarczająco opisano w dokumentacji.

driver

Nazwa kierowcy. W naszym przypadku to docker.

lint

Jako linter używany jest yamllint.

Przydatne opcje w tej części konfiguracji to możliwość wskazania pliku konfiguracyjnego dla yamllint, przekazanie zmiennych środowiskowych lub wyłączenie lintera:

lint:
  name: yamllint
  options:
    config-file: foo/bar
  env:
    FOO: bar
  enabled: False

platforms

Opisuje konfigurację instancji.
W przypadku użycia dockera jako kierowcy, Molekuła iteruje po tej sekcji, a każdy element listy jest dostępny w Dockerfile.j2 jako zmienna item.

W przypadku kierowcy, w którym wymagane są create.yml i destroy.yml, sekcja jest dostępna w nich jako molecule_yml.platforms, a iteracje po niej opisane są już w tych plikach.

Ponieważ Molekuła zapewnia zarządzanie instancjami modułami ansible, lista możliwych ustawień również powinna być tam poszukiwana. Dla dockera, na przykład, używany jest moduł docker_container_module. Jakie moduły są używane w pozostałych kierowcach, można znaleźć w dokumentacji.

A także przykłady użycia różnych kierowców można znaleźć w testach samej Molekuły.

Zamienimy tutaj centos:7 na ubuntu.

provisioner

„Dostawca” to podmiot zarządzający instancjami. W przypadku Molekuły jest to ansible, wsparcie dla innych nie jest planowane, więc tę sekcję można z pewnymi zastrzeżeniami nazwać rozszerzoną konfiguracją ansible.
Tutaj można podać wiele rzeczy, wyróżnię najważniejsze, moim zdaniem, punkty:

  • playbooks: można określić, które playbooki powinny być używane na określonych etapach.

provisioner:
  name: ansible
  playbooks:
    create: create.yml
    destroy: ../default/destroy.yml
    converge: playbook.yml
    side_effect: side_effect.yml
    cleanup: cleanup.yml

provisioner:
  name: ansible
  config_options:
    defaults:
      fact_caching: jsonfile
    ssh_connection:
      scp_if_ssh: True

provisioner:
  name: ansible  
  connection_options:
    ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"

  • options: parametry Ansible oraz zmienne środowiskowe

provisioner:
  name: ansible  
  options:
    vvv: true
    diff: true
  env:
    FOO: BAR

scenario

Nazwa i opis sekwencji scenariuszy.
Można zmienić domyślną macierz działań dowolnej komendy, dodając klucz _sequence i jako wartość dla niego określając potrzebną nam listę kroków.
Załóżmy, że chcemy zmienić kolejność działań podczas uruchamiania polecenia wykonania playbooka: molecule converge

# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
  name: default
  converge_sequence:
    - create
    - converge

verifier

Konfiguracja frameworka do testów oraz lintera do niego. Domyślnie jako linter używany jest testinfra i flake8. Możliwe opcje są podobne do wcześniej opisanych:

verifier:
  name: testinfra
  additional_files_or_dirs:
    - ../path/to/test_1.py
    - ../path/to/test_2.py
    - ../path/to/directory/*
  options:
    n: 1
  enabled: False
  env:
    FOO: bar
  lint:
    name: flake8
    options:
      benchmark: True
    enabled: False
    env:
      FOO: bar

Wróćmy do naszej roli. Edytujmy plik tasks/main.yml aż do takiej formy:

---
- name: Zainstaluj nginx
  apt:
    name: nginx
    state: present

- name: Uruchom nginx
  service:
    name: nginx
    state: started

I dodamy testy do molecule/default/tests/test_default.py

def test_nginx_is_installed(host):
    nginx = host.package("nginx")
    assert nginx.is_installed

def test_nginx_running_and_enabled(host):
    nginx = host.service("nginx")
    assert nginx.is_running
    assert nginx.is_enabled

def test_nginx_config(host):
    host.run("nginx -t")

Gotowe, wystarczy tylko uruchomić (z katalogu roli, przypominam):

> molecule test

Długi wyjście pod spoilerem:

--> Walidacja schematu /nginx/molecule/default/molecule.yml.
Walidacja zakończona pomyślnie.
--> Macierz testowa

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

--> Scenariusz: 'default'
--> Akcja: 'lint'
--> Wykonywanie Yamllint na plikach znalezionych w /nginx/...
Lint zakończony pomyślnie.
--> Wykonywanie Flake8 na plikach znalezionych w /nginx/molecule/default/tests/...
Lint zakończony pomyślnie.
--> Wykonywanie Ansible Lint na /nginx/molecule/default/playbook.yml...
Lint zakończony pomyślnie.
--> Scenariusz: 'default'
--> Akcja: 'destroy'

    PLAY [Zniszcz] *****************************************************************

    TASK [Zniszczenie instancji molekularnych] ************************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    TASK [Czekanie na zakończenie usuwania instancji] ******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Usunięcie sieci dockerowych] *********************************************

    PODSUMOWANIE GRY ****************************************************************
    localhost                  : ok=2    zmieniono=1    unreachable=0    failed=0

--> Scenariusz: 'default'
--> Akcja: 'dependency'
Pominięto, brak pliku wymagań.
--> Scenariusz: 'default'
--> Akcja: 'syntax'

    playbook: /nginx/molecule/default/playbook.yml

--> Scenariusz: 'default'
--> Akcja: 'create'

    PLAY [Utwórz] ******************************************************************

    TASK [Zaloguj się do rejestru Docker] ******************************************
    pominięto: [localhost] => (item=None)

    TASK [Utwórz Dockerfile z nazw obrazów] ****************************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    TASK [Odnalezienie lokalnych obrazów Docker] ***********************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Zbudowanie obrazu kompatybilnego z Ansible] ******************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    TASK [Utworzenie sieci dockerowych] *********************************************

    TASK [Utwórz instancje molekularne] ********************************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    TASK [Czekanie na zakończenie tworzenia instancji] *****************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    PODSUMOWANIE GRY ****************************************************************
    localhost                  : ok=5    zmieniono=4    unreachable=0    failed=0

--> Scenariusz: 'default'
--> Akcja: 'prepare'
Pominięto, nie skonfigurowano playbooka przygotowawczego.
--> Scenariusz: 'default'
--> Akcja: 'converge'

    PLAY [Zjednoczenie] *************************************************************

    TASK [Zbieranie faktów] *********************************************************
    ok: [instancja]

    TASK [nginx : Zainstaluj nginx] ************************************************
    zmieniono: [instancja]

    TASK [nginx : Uruchom nginx] ****************************************************
    zmieniono: [instancja]

    PODSUMOWANIE GRY ****************************************************************
    instancja                   : ok=3    zmieniono=2    unreachable=0    failed=0

--> Scenariusz: 'default'
--> Akcja: 'idempotence'
Idempotencja zakończona pomyślnie.
--> Scenariusz: 'default'
--> Akcja: 'side_effect'
Pominięto, nie skonfigurowano playbooka efektu ubocznego.
--> Scenariusz: 'default'
--> Akcja: 'verify'
--> Wykonywanie testów Testinfra znalezionych w /nginx/molecule/default/tests/...
    ============================= sesja testowa zaczyna się =====================
    platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: /nginx/molecule/default, inifile:
    wtyczki: testinfra-1.16.0
zebrano 4 elementy

    tests/test_default.py ....                                               [100%]

    ========================== 4 zaliczone w 27.23 sekundy ======================
Weryfikacja zakończona pomyślnie.
--> Scenariusz: 'default'
--> Akcja: 'destroy'

    PLAY [Zniszcz] *****************************************************************

    TASK [Zniszczenie instancji molekularnych] ************************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    TASK [Czekanie na zakończenie usuwania instancji] ******************************
    zmieniono: [localhost] => (item=None)
    zmieniono: [localhost]

    TASK [Usunięcie sieci dockerowych] *********************************************

    PODSUMOWANIE GRY ****************************************************************
    localhost                  : ok=2    zmieniono=2    unreachable=0    failed=0

Nasza prosta rola przeszła test bez problemów.
Należy pamiętać, że jeśli wystąpią problemy podczas pracy test molekuł, to jeśli nie zmieniałeś standardowej sekwencji, Molekuła usunie instancję.

Do debugowania przydatne są następujące komendy:

> molecule --debug  # informacje debugowe. Przy normalnym uruchomieniu Molekuła ukrywa logi.
> molecule converge          # Zostawia instancję po wykonaniu testowanej roli.
> molecule login             # Zaloguj się do utworzonej instancji.
> molecule --help            # Pełna lista komend.

Istniejąca rola

Dodanie nowego scenariusza do istniejącej roli odbywa się z katalogu roli następującymi komendami:

# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>

W przypadku, gdy jest to pierwszy scenariusz w roli, parametr -s można pominąć, ponieważ zostanie utworzony scenariusz. default.

Podsumowanie

Jak widzisz, Molekuła nie jest zbyt skomplikowana, a przy użyciu własnych szablonów można sprowadzić wdrożenie nowego scenariusza do edycji zmiennych w playbookach tworzenia i usuwania instancji. Molekuła bez problemu integruje się z systemami CI, co pozwala zwiększyć szybkość rozwoju poprzez skrócenie czasu potrzebnego na ręczne testowanie playbooków.

Dziękuję za uwagę. Jeśli masz doświadczenie w testowaniu ról ansible i nie jest ono związane z Molekułą — opowiedz o tym w komentarzach!

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster