Instrukcja: jak przetestować role Ansible i dowiedzieć się o problemach przed rozpoczęciem produkcji

Witam wszystkich!

Pracuję jako inżynier DevOps w usłudze rezerwacji hoteli. Ostrovok.ru. W tym artykule chcę opowiedzieć o naszych doświadczeniach w testowaniu ról Ansible.

W Ostrovok.ru używamy ansible jako menedżera konfiguracji. Ostatnio doszliśmy do konieczności testowania ról, ale jak się okazało, narzędzi do tego nie ma zbyt wiele - najpopularniejszy chyba jest framework Molecule, więc postanowiliśmy go wykorzystać. Okazało się jednak, że jego dokumentacja milczy na temat wielu pułapek. Nie mogliśmy znaleźć wystarczająco szczegółowego przewodnika w języku rosyjskim, dlatego postanowiliśmy napisać ten artykuł.

Instrukcja: jak przetestować role Ansible i dowiedzieć się o problemach przed rozpoczęciem produkcji

Cząsteczka

Cząsteczka — framework pomagający testować role Ansible.

Uproszczony opis: Molecule tworzy instancję na wskazanej przez Ciebie platformie (chmura, maszyna wirtualna, kontener; więcej szczegółów znajdziesz w dziale Kierowca), uruchamia na nim Twoją rolę, następnie uruchamia testy i usuwa instancję. Jeśli na którymś etapie wystąpi awaria, Molecule powiadomi Cię o tym.

Teraz więcej.

Trochę teorii

Rozważmy dwie kluczowe jednostki cząsteczki: scenariusz i sterownik.

Scenariusz

Skrypt zawiera opis co, gdzie, jak i w jakiej kolejności będzie wykonywane. Jedna rola może mieć kilka skryptów, a każdy z nich jest katalogiem na ścieżce <role>/molecule/<scenario>, zawierający opisy działań wymaganych do wykonania testu. Musi być scenariusz default, który zostanie utworzony automatycznie, jeśli zainicjujesz rolę za pomocą Molecule. Nazwy poniższych skryptów zależą od Twojego uznania.

Nazywa się sekwencja działań testowych w skrypcie matrycai domyślnie wygląda to tak:

(Kroki zaznaczone ?, są domyślnie pomijane, jeśli nie zostały określone przez użytkownika)

  • lint - bieganie lintersów. Domyślnie yamllint и flake8,
  • destroy — usuwanie instancji z ostatniego uruchomienia Molecule (jeśli jakieś pozostały),
  • dependency? — zainstalowanie zależności anible testowanej roli,
  • syntax - sprawdzanie składni roli przy użyciu ansible-playbook --syntax-check,
  • create - utworzenie instancji,
  • prepare? — przygotowanie instancji; na przykład sprawdzanie/instalowanie Pythona2
  • converge — uruchomienie testowanego playbooka,
  • idempotence — uruchom ponownie playbook dla testu idempotencji,
  • side_effect? — czynności niezwiązane bezpośrednio z rolą, ale niezbędne do testów,
  • verify — przeprowadzenie testów powstałej konfiguracji przy użyciu testinfra(domyślny) /goss/inspec,
  • cleanup? - (w nowych wersjach) - z grubsza mówiąc, „czyszczenie” infrastruktury zewnętrznej dotkniętej Molekułą,
  • destroy — usunięcie instancji.

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

Każdy z powyższych kroków można uruchomić osobno za pomocą molecule <command>. Ale powinieneś zrozumieć, że dla każdego takiego polecenia cli może istnieć osobna sekwencja działań, którą możesz sprawdzić, uruchamiając molecule matrix <command>. Na przykład podczas uruchamiania polecenia converge (uruchamiając testowany podręcznik) zostaną wykonane następujące czynności:

$ molecule matrix converge
...
└── default         # название сценария
    ├── dependency  # установка зависимостей
    ├── create      # создание инстанса
    ├── prepare     # преднастройка инстанса
    └── converge    # прогон плейбука

Kolejność tych działań można edytować. Jeśli coś z listy zostało już ukończone, zostanie pominięte. Bieżący stan oraz konfiguracja instancji są przechowywane w katalogu Molecule $TMPDIR/molecule/<role>/<scenario>.

Dodaj kroki za pomocą ? Możesz opisać żądane działania w formacie podręcznika Ansible i nadać nazwę plikowi zgodnie z krokiem: prepare.yml/side_effect.yml. Oczekuj, że te pliki Molecule będą znajdować się w folderze skryptów.

Kierowca

Sterownik to obiekt, w którym tworzone są instancje do testów.
Lista standardowych sterowników dla których Molecule posiada gotowe szablony to: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

W większości przypadków szablony są plikami create.yml и destroy.yml w folderze skryptów, które opisują odpowiednio utworzenie i usunięcie instancji.
Wyjątkami są Docker i Vagrant, ponieważ interakcje z ich modułami mogą odbywać się bez powyższych plików.

Warto wyróżnić sterownik Delegowany, gdyż w przypadku jego wykorzystania w plikach tworzenia i usuwania instancji opisana jest jedynie praca z konfiguracją instancji, resztę powinien opisać inżynier.

Domyślnym sterownikiem jest Docker.

Przejdźmy teraz do praktyki i rozważmy tam dalsze funkcje.

Pierwsze kroki

W ramach „witaj świecie” przetestujemy prostą rolę instalacyjną Nginx. Wybierzmy dokera jako sterownik - myślę, że większość z Was go ma zainstalowaną (i pamiętajcie, że doker jest sterownikiem domyślnym).

Przygotujmy się virtualenv i zainstaluj w nim molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule установит ansible как зависимость; docker для драйвера

Następnym krokiem jest zainicjowanie nowej roli.
Inicjalizacja nowej roli, a także nowego skryptu odbywa się za pomocą polecenia molecule init <params>:

> molecule init role -r nginx
--> Initializing new role nginx...
Initialized role in <path>/nginx successfully.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 directories, 1 file

Rezultatem jest typowa rola ansible. Co więcej, wszystkie interakcje z interfejsem CLI Molecules są wykonywane z poziomu głównego roli.

Zobaczmy, co znajduje się w katalogu ról:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Jinja-шаблон для Dockerfile
├── INSTALL.rst.   # Немного информации об установке зависимостей сценария
├── molecule.yml   # Файл конфигурации
├── playbook.yml   # Плейбук запуска роли
└── tests          # Директория с тестами стадии verify
    └── test_default.py

1 directory, 6 files

Spójrzmy na konfigurację molecule/default/molecule.yml (zastąpimy tylko obraz okna dokowanego):

---
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

zależność

W tej sekcji opisano źródło zależności.

Możliwe opcje: galaktyka, dotyczy, powłoka.

Shell to po prostu powłoka poleceń, której można używać, jeśli galaktyka i pozłacanie nie zaspokajają twoich potrzeb.

Nie zabawię tu długo, jest to wystarczająco opisane w dokumentacja.

kierowca

Imię kierowcy. Dla nas jest to doker.

szarpie

Yamllint jest używany jako linter.

Przydatnymi opcjami w tej części konfiguracji jest możliwość określenia pliku konfiguracyjnego dla yamllint, przekazywania zmiennych środowiskowych lub wyłączania lintera:

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

Platformy

Opisuje konfigurację instancji.
W przypadku okna dokowanego jako sterownika Molecule wykonuje iterację po tej sekcji, a każdy element listy jest dostępny w Dockerfile.j2 jako zmienna item.

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

Ponieważ Molecule zapewnia zarządzanie instancjami modułów Ansible, powinieneś poszukać tam listy możliwych ustawień. Na przykład w przypadku Dockera używany jest moduł moduł_dokowany_kontenera. Jakie moduły są używane w innych sterownikach, można dowiedzieć się w dokumentacja.

Można też znaleźć przykłady użycia różnych sterowników w testach samej cząsteczki.

Zamieńmy tutaj centos:7 na ubuntu.

zaopatrzeniowiec

„Dostawca” to podmiot zarządzający instancjami. W przypadku Molecule jest to ansible, nie jest planowana obsługa innych, więc tę sekcję można z zastrzeżeniem nazwać rozszerzoną konfiguracją ansibla.
Można tu wiele wymienić, ale moim zdaniem skupię się na najważniejszych kwestiach:

  • playbooks: Możesz określić, które podręczniki mają 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'"

  • Opcje: Parametry Ansible i zmienne środowiskowe

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

scenariusz

Tytuł i opis sekwencji skryptów.
Możesz zmienić domyślną macierz akcji polecenia, dodając klucz <command>_sequence i jako wartość, określając listę kroków, których potrzebujemy.
Załóżmy, że chcemy zmienić kolejność działań podczas uruchamiania polecenia uruchamiania podręcznika: molecule converge

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

weryfikator

Stworzenie frameworka do testów i lintera do niego. Domyślnie używany jest linter testinfra и flake8. Możliwe opcje są podobne do powyższych:

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 do tego wyglądu:

---
- name: Install nginx
  apt:
    name: nginx
    state: present

- name: Start nginx
  service:
    name: nginx
    state: started

I dodaj 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, pozostaje tylko uruchomić (od źródła roli, przypomnę):

> molecule test

Długi wydech pod spojlerem:

--> Validating schema <path>/nginx/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

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

--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in <path>/nginx/...
Lint completed successfully.
--> Executing Flake8 on files found in <path>/nginx/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on <path>/nginx/molecule/default/playbook.yml...
Lint completed successfully.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'syntax'

    playbook: <path>/nginx/molecule/default/playbook.yml

--> Scenario: 'default'
--> Action: 'create'

    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)

    TASK [Create Dockerfiles from image names] *************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image] ***************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Create docker network(s)] ************************************************

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) creation to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=4    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'

    PLAY [Converge] ****************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [nginx : Install nginx] ***************************************************
    changed: [instance]

    TASK [nginx : Start nginx] *****************************************************
    changed: [instance]

    PLAY RECAP *********************************************************************
    instance                   : ok=3    changed=2    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in <path>/nginx/molecule/default/tests/...
    ============================= test session starts ==============================
    platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: <path>/nginx/molecule/default, inifile:
    plugins: testinfra-1.16.0
collected 4 items

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

    ========================== 4 passed in 27.23 seconds ===========================
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0

Nasza prosta rola została przetestowana bez problemów.
Warto o tym pamiętać, jeśli podczas pracy pojawią się problemy molecule test, to jeśli nie zmieniłeś standardowej sekwencji, Molecule usunie instancję.

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

> molecule --debug <command> # debug info. При обычном запуске Молекула скрывает логи.
> molecule converge          # Оставляет инстанс после прогона тестируемой роли.
> molecule login             # Зайти в созданный инстанс.
> molecule --help            # Полный список команд.

Istniejąca rola

Następuje dodanie nowego skryptu do istniejącej roli z katalogu ról za pomocą następujących poleceń:

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

Jeżeli jest to pierwszy skrypt w roli to parametr -s można pominąć, ponieważ zostanie utworzony skrypt default.

wniosek

Jak widać Molecule nie jest bardzo skomplikowane, a korzystając z własnych szablonów, można zredukować wdrażanie nowego skryptu do edycji zmiennych w playbookach w celu tworzenia i usuwania instancji. Cząsteczka płynnie integruje się z systemami CI, co pozwala zwiększyć szybkość rozwoju poprzez skrócenie czasu ręcznego testowania playbooków.

Dziękuję za uwagę. Jeśli masz doświadczenie w testowaniu ról ansible i nie jest ono związane z Molecule, powiedz nam o tym w komentarzach!

Źródło: www.habr.com

Dodaj komentarz