Інструкцыя: як тэставаць ansible-ролі і даведвацца аб праблемах да прадакшэну

Усім прывітанне!

Я працую DevOps-інжынерам у сэрвісе браніравання гатэляў Ostrovok.ru. У гэтым артыкуле я хачу расказаць аб нашым вопыце тэсціравання ansible-роляў.

У Ostrovok.ru у якасці мэнэджара канфігурацый мы выкарыстоўваем ansible. Нядаўна мы дашлі да неабходнасці тэставання роляў, але, як апынулася, прылад для гэтага існуе не так шмат - самым папулярным, мабыць, з'яўляецца фрэймворк Molecule, таму мы вырашылі выкарыстаць яго. Але аказалася, што яго дакументацыя замоўчвае аб многіх падводных камянях. Дастаткова падрабязнага кіраўніцтва на рускай мове нам не ўдалося знайсці, таму мы вырашылі напісаць гэты артыкул.

Інструкцыя: як тэставаць ansible-ролі і даведвацца аб праблемах да прадакшэну

малекула

Малекула - Фрэймворк для дапамогі ў тэставанні ansible-роляў.

Спрошчанае апісанне: Малекула стварае інстанс на паказанай вамі платформе (воблака, віртуалка, кантэйнер; падрабязней гл. раздзел Кіроўца), праганяе на ім вашу ролю, затым запускае тэсты і выдаляе інстанс. У выпадку ўзнікнення няўдачы на ​​адным з крокаў, Малекула паведаміць вам пра гэта.

Цяпер падрабязней.

крыху тэорыі

Разгледзім дзве ключавыя сутнасці Малекулы: Scenario і Driver.

сцэнар

Сцэнар змяшчае апісанне таго, што, дзе, як і ў якой паслядоўнасці будзе выканана. У адной ролі можа быць некалькі сцэнараў, і кожны - гэта дырэкторыя па шляху <role>/molecule/<scenario>, якая змяшчае ў сабе апісанні неабходных для тэсту дзеянняў. Абавязкова павінен прысутнічаць сцэнар default, які будзе аўтаматычна створаны, калі вы ініцыялізуеце ролю з дапамогай Малекулы. Імёны наступных сцэнарыяў выбіраюцца на ваша меркаванне.

Паслядоўнасць дзеянняў тэсціравання ў сцэнары называецца матрыца, і па змаўчанні яна такая:

(Крокі, пазначаныя ?, па змаўчанні прапускаюцца, калі не апісаны карыстальнікам)

  • lint - Прагон лінтэраў. Па змаўчанні выкарыстоўваюцца yamllint и flake8,
  • destroy - выдаленне інстансаў з мінулага запуску Малекулы (калі засталіся),
  • dependency? - усталёўка ansible-залежнасці тэстоўванай ролі,
  • syntax - Праверка сінтаксісу ролі з дапамогай ansible-playbook --syntax-check,
  • create - стварэнне інстансу,
  • prepare? - падрыхтоўка інстансу; напрыклад, праверка / усталёўка python2
  • converge - запуск тэстоўванага плэйбука,
  • idempotence - паўторны запуск плэйбука для тэсту на ідэмпатэнтнасць,
  • side_effect? - дзеянні, якія не адносяцца непасрэдна да ролі, але патрэбныя для тэстаў,
  • verify - запуск тэстаў атрыманай канфігурацыі з дапамогай testinfra(па змаўчанні) /goss/inspec,
  • cleanup? - (У новых версіях) - груба кажучы, «ачыстка» знешняй інфраструктуры, закранутай Малекулай,
  • destroy - Выдаленне інстансу.

Гэтая паслядоўнасць пакрывае большасць выпадкаў, але, пры неабходнасці, яе можна змяніць.

Кожны з вышэйпералічаных крокаў можна запускаць асобна з дапамогай molecule <command>. Але варта разумець, што для кожнай такой cli-каманды можа існаваць свая паслядоўнасць дзеянняў, пазнаць якую можна, выканаўшы molecule matrix <command>. Напрыклад, пры запуску каманды converge (прагон тэстоўванага плэйбука) будуць выкананы наступныя дзеянні:

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

Паслядоўнасць гэтых дзеянняў можна рэдагаваць. Калі нешта са спісу ўжо выканана, то яно будзе прапушчана. Бягучы стан, а таксама канфіг інстансаў, Малекула захоўвае ў дырэкторыі $TMPDIR/molecule/<role>/<scenario>.

Дадаць крокі з ? можна, апісаўшы жаданыя дзеянні ў фармаце ansible-плэйбука, а імя файла зрабіць адпаведна кроку: prepare.yml/side_effect.yml. Чакаць гэтыя файлы Малекула будзе ў тэчцы сцэнара.

Кіроўца

Драйвер - гэта сутнасць, дзе ствараюцца інстансы для тэстаў.
Спіс стандартных драйвераў, для якіх у Малекулы гатовыя шаблоны, такі: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

У большасці выпадкаў шаблоны - гэта файлы create.yml и destroy.yml у тэчцы сцэнара, якія апісваюць стварэнне і выдаленне інстанса адпаведна.
Выключэнні складаюць Docker і Vagrant, бо ўзаемадзеянні з іх модулямі можа адбывацца без вышэйзгаданых файлаў.

Варта вылучыць драйвер Delegated, бо ў выпадку яго выкарыстання ў файлах стварэння і выдаленні інстанса апісана толькі праца з канфігурацыяй інстансаў, астатняе павінен апісаць інжынер.

Драйверам па змаўчанні з'яўляецца Docker.

Цяпер пяройдзем да практыкі і далейшыя асаблівасці разгледзім там.

Пачатак працы

У якасці "hello world" пратэстуем простую ролю ўсталёўкі nginx. У якасці драйвера абярэм докер - думаю, ён усталяваны ў большасці з вас (і які памятаецца, што докер - драйвер па змаўчанні).

Падрыхтуем virtualenv і ўсталюем у яго molecule:

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

Наступным крокам ініцыялізуем новую ролю.
Ініцыялізацыя новай ролі, як і новага сцэнара, робяцца з дапамогай каманды 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

Атрымалася тыповая ansible-роля. Далей усе ўзаемадзеянні з CLI Малекулы вырабляюцца з кораня ролі.

Паглядзім, што знаходзіцца ў дырэкторыі ролі:

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

1 directory, 6 files

Разбяром канфіг molecule/default/molecule.yml (заменім толькі docker image):

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

залежнасць

Гэтая секцыя апісвае крыніцу залежнасцяў.

Магчымыя варыянты: галактыка, Giltar, shell.

Shell - гэта проста камандная абалонка, якая выкарыстоўваецца ў выпадку, калі galaxy і gilt не пакрываюць вашых запатрабаванняў.

Не буду тут доўга спыняцца, дастаткова апісана ў дакументацыі.

кіроўца

Назва драйвера. У нас гэта docker.

Ворс

У якасці лінтара выкарыстоўваецца yamllint.

Карысныя опцыі ў дадзенай частцы канфіга - гэта магчымасць паказаць файл канфігурацыі для yamllint, пракінуць зменныя асяроддзі або адключыць лінтэр:

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

Платформы

Апісвае канфігурацыю інстансаў.
У выпадку з докерам у ролі драйвера, Малекула ітэруецца па гэтай секцыі, і кожны элемент спісу даступны ў Dockerfile.j2 як пераменная item.

У выпадку з драйверам, у якім абавязковыя create.yml и destroy.yml, секцыя даступная ў іх як molecule_yml.platforms, а ітэрацыі па ёй апісаны ўжо ў гэтых файлах.

Паколькі Малекула падае кіраванне інстансамі ansible-модулям, то і спіс магчымых налад трэба шукаць тамака. Для докера, напрыклад, выкарыстоўваецца модуль docker_container_module. Якія модулі выкарыстоўваюцца ў астатніх драйверах, можна знайсці ў дакументацыі.

А таксама прыклады выкарыстання розных драйвераў можна знайсці у тэстах самой Малекулы.

Заменім тут centos:7 на Ubuntu.

забеспячэнне

"Пастаўшчык" - сутнасць, якая кіруе інстансамі. У выпадку Малекулы гэта ansible, падтрымка іншых не плануецца, таму гэтую секцыю можна з агаворкай назваць пашыранай канфігурацыяй ansible.
Тут можна паказаць шмат усяго, вылучу асноўныя, на мой погляд, моманты:

  • playbooks: можна ўказваць, якія плэйбукі павінны выкарыстоўвацца на пэўных стадыях.

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

  • опцыі: параметры Ansible і зменныя асяроддзі

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

сцэнар

Назва і апісанне паслядоўнасцей сцэнарыя.
Змяніць матрыцу дзеянняў па змаўчанні якой-небудзь каманды можна, дадаўшы ключ <command>_sequence і як значэнне для яго вызначыўшы патрэбны нам спіс крокаў.
Дапушчальны, мы жадаем змяніць паслядоўнасць дзеянняў пры запуску каманды прагону плэйбука: molecule converge

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

праверка

Настройка фрэймворка для тэстаў і лінтара да яго. Па змаўчанні ў якасці лінтара выкарыстоўваецца testinfra и flake8. Магчымыя опцыі падобныя з вышэйпададзенымі:

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

Вернемся да нашай ролі. Адрэдагуем файл tasks/main.yml да такога віду:

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

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

І дадамо тэсты ў 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")

Гатова, засталося толькі запусціць (з кораня ролі, нагадаю):

> molecule test

Доўгі выхлап пад спойлерам:

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

Нашая простая роля пратэставалася без праблем.
Варта памятаць, што калі ўзніклі праблемы пры працы molecule test, то, калі вы не змянялі стандартную паслядоўнасць, Малекула выдаліць інстанс.

Для дэбага карысныя наступныя каманды:

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

Існуючая роля

Даданне новага сцэнара да існай ролі адбываецца з дырэкторыі ролі наступнымі камандамі:

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

У выпадку, калі гэта першы сцэнар у ролі, то параметр -s можна апусціць, бо будзе створаны сцэнар default.

Заключэнне

Як бачыце, Малекула не вельмі складаная, а пры выкарыстанні ўласных шаблонаў можна звесці разгортванне новага сцэнара да праўкі зменных у плэйбуках стварэння і выдаленні інстансаў. Малекула без праблем інтэгруецца з сістэмамі CI, што дазваляе павялічыць хуткасць распрацоўкі за кошт скарачэння часу на ручное тэсціраванне плэйбукаў.

Дзякуй за вашу ўвагу. Калі ў вас ёсць вопыт тэсціравання ansible-роляў, і ён не звязаны з Малекулай - раскажыце пра яго ў каментарах!

Крыніца: habr.com

Дадаць каментар