Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Це розшифровка виступи на DevOps-40 2020-03-18:

Починаючи з другого комміту, будь-який код стає legacy, т.к. Початкові задуми починають розходитися із суворою реальністю. Це не добре і не погано, це даність з якою складно сперечатися та необхідно уживатися. Частиною цього процесу є рефакторинг. Рефакторинг Infrastructure as Code. Так почнеться історія як відрефакторити Ansible за рік і не злетіти з котушок.

Зародження Legacy

День №1: Нульовий пацієнт

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Жив був умовний проект. На ньому Dev команда розробки та Ops інженери. Вони вирішували те саме завдання: як розвернути сервера і запустити додаток. Проблема була в тому, що кожна команда вирішувала це по-своєму. На проекті було вирішено використовувати Ansible для синхронізації знань між командами Dev та Ops.

День № 89: Зародження Legacy

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Самі того не помітивши, хотіли зробити якнайкраще, а вийшло legacy. Як так виходить?

  • У нас тут термінова тяга, зробимо брудний хак – потім виправимо.
  • Документацію можна не писати і так все зрозуміло, що тут відбувається.
  • Я знаю Ansible / Python / Bash / Terraform! Дивіться, як я можу викрутитися!
  • Я Full Stack Overflow Developer скопіював це зі stackoverflow, не знаю як це працює, але виглядає прикольно та вирішує завдання.

У результаті можна отримати код незрозумілого виду, на який немає документації, незрозуміло що він робить, чи потрібен він, але проблема в тому, що вам необхідно його розвивати, доопрацьовувати, додавати милиці з підпорами, роблячи ситуацію тільки гірше.

- hosts: localhost
  tasks:
    - shell: echo -n Z >> a.txt && cat a.txt
      register: output
      delay: 1
      retries: 5
      until: not output.stdout.find("ZZZ")

День №109: Усвідомлення проблеми

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Спочатку задумана та реалізована модель IaC перестає відповідати дійсності із запитами користувачів/бізнесу/інших команд, час внесення змін до інфраструктури перестає бути прийнятним. У цей момент приходить розуміння, що час вживати заходів.

Рефакторинг IaC

День № 139: А вам точно потрібний рефакторинг?

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Перш ніж кидатися рефакторити ви повинні відповісти на низку важливих питань:

  1. Навіщо вам це все?
  2. Чи маєте ви час?
  3. Чи достатньо знань?

Якщо ви не знаєте як відповісти на запитання, то рефакторинг закінчиться так і не розпочавшись або може вийти лише гірше. Т.к. був досвід( Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду), то від проекту надійшов запит допомоги виправити ролі та покрити їх тестами.

День № 149: Підготовка рефакторингу

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Першочергове це треба підготуватися. Визначитись що робитимемо. Для цього спілкуємося, знаходимо проблемні точки та прикидаємо шляхи їх вирішення. Отримані концепти якось фіксуємо, наприклад стаття в confluence, щоб при появі питання "як краще?" або "як правильніше?" ми не збилися з курсу. У нашому випадку ми дотримувалися ідеї розділяй і володарюй: дробимо інфраструктуру на маленькі шматочки / цеглини. Такий підхід дозволяє взяти ізольований шматок інфраструктури, зрозуміти що він робить, покрити його тестами та змінити не побоявшись щось зламати.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Виходить, що тестування інфраструктури стає наріжним каменем і варто згадати піраміду тестування інфраструктури. Рівно теж ідея, що в розробці, але для інфраструктури: йдемо від дешевих швидких тестів, які перевіряють прості речі, наприклад відступи, до дорогих повноцінних тестів, що розгортають цілісну інфраструктуру.

Спроби тестування Ansible

Перш ніж підемо описувати як покривали тестами Ansible на проекті, опишу спроби та підходи, які довелося використовувати раніше, щоб зрозуміти контекст прийнятих рішень.

День №997: SDS provision

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Вперше тестувати Ansible довелося на проекті з розробки SDS (Software Defined Storage). Є окрема стаття на цю тему
Як наламати велосипедів поверх милиць під час тестування свого дистрибутиваАле якщо коротко, то у нас вийшла перевернута піраміда тестування і тестування ми витрачали 60-90 хвилин на одну роль, що є довго. Основою була e2e тести, тобто. ми розгортали повноцінну інсталяцію і потім її тестували. Ще обтяжуючим був винахід свого велосипеда. Але це рішення працювало і дозволяло стабільно релізитися.

День № -701: Ansible та test kitchen

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Розвитком ідеї тестування Ansible стало використання готових інструментів, а саме test kitchen/kitchen-ci та inspec. Вибір був зумовлений знанням Ruby (докладніше у статті на хабрі: Чи мріють YML програмісти про ansible тестування?) працювало швидше близько 40 хвилин на 10 ролей. Ми створювали пачку віртуальних машин та всередині ганяли тести.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Загалом рішення працювало, але був осад через неоднорідність. Коли ж збільшили кількість тестованих до 13 базових ролей і 2 мета ролей, що комбінують дрібніші ролі, то раптом тести почали бігти 70 хвилин, що майже в 2 рази довше. Про XP (extreme programming) практики було важко говорити т.к. ніхто не захоче чекати на 70 хвилин. Це стало приводом для зміни підходу

День № -601: Ansible та molecule

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Концептуально це схоже на testkitchen, тільки ми перевели тестування ролей у docker та змінили стек. Підсумком час скоротився до стабільних 20-25 хвилин для 7 ролей.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Збільшивши кількість тестирумих ролей до 17 та лінтовку 45 ролей ми проганяли це за 28 хвилин на 2 jenkins slave.

День №167: Додаємо на проект тести Ansible

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

З наскоку завдання рефакторингу, швидше за все зробити не вийде. Завдання має бути вимірним, щоб ви могли її розбити на дрібні шматочки і з'їсти слона частинами чайною ложкою. Повинне бути розуміння в правильному напрямі йде рух, чи довго ще йти.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Загалом не має значення як це буде зроблено, можна писати на папірець, можна клеїти стікери на шафу, можна створювати таски в jira, а можна завести google docs І туди записувати поточний статус. Ноги ростуть із того, що процес не миттєвий, він буде довгим і нудним. Малоймовірно, що хтось хоче, щоб за час рефакторингу ви перегоріли ідей, втомилися та забили.

Рефакторинг простий:

  • Їсти.
  • Sleep.
  • Код.
  • IaC test.
  • Повторювати

і так повторюємо доки не досягнемо наміченої мети.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Все відразу почати тестувати може не вийде, тому у нас першим завданням було розпочати з лінтівки та перевірки синтаксису.

День №181: Green Build Master

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Лінтівка це невеликий перший крок до Green Build Master. Це майже нічого не зламає, але дозволить налагодити процеси і зробити зелені білди в jenkins. Ідея в тому, щоб виробити звички у команди:

  • Червоні тести погано.
  • Прийшов виправити щось заразом, зроби код трохи краще, ніж він був до тебе.

День № 193: Від лінтівки до unit тестів

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Вибудувавши процес попадання коду в майстер, можна починати процес поетапного поліпшення — замінюючи лінтівку на запуск ролей, можна навіть без ідемпотентності. Необхідно зрозуміти, як застосовувати ролі, як вони працюють.

День № 211: Від unit до integration тестів

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Коли unit тестами покрита більшість ролей і все лінтується, можна перейти до додавання інтеграційних тестів. Тобто. тестуванню не окремої цеглини в інфраструктурі, а їх комбінації, наприклад, повноцінну конфігурацію інстансу.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

На jenkins ми генерували безліч стадій, які в паралель лінтували ролі/плейбуки, потім юніт тести у контейнерах та наприкінці інтеграційні тести.

Jenkins + Docker + Ansible = Tests

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

  1. Checkout repo and generate build stages.
  2. Run lint playbook stages in parallel.
  3. Run lint role stages in parallel.
  4. Run syntax check role stages in parallel.
  5. Run test role stages in parallel.
    1. Lint role.
    2. Check dependency on other roles.
    3. Check syntax.
    4. Create docker instance
    5. Run molecule/default/playbook.yml.
    6. Check idempotency.
  6. Run integration tests
  7. обробка

День №271: Bus Factor

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Спочатку рефакторингом займалася невелика група людей у ​​пару-трійку людей. Вони робили рев'ю коду в майстрі. Згодом у команді виробилося знання як писати код та code review сприяло поширенню знань про інфраструктуру і те, як вона влаштована. Родзинкою тут було, що ревьювери вибиралися по черзі, за графіком, тобто. з деякою часткою ймовірності ти залізеш у нову ділянку інфраструктури.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

І тут має бути зручно. Зручно робити реву, бачити в рамках якого завдання воно зроблено, історію обговорень. Ми інтегрували jenkins + bitbucket + jira.

Але як таке рев'ю не панацея, якось, у нас проліз у майстер код, який зробив нам флапаючі тести:

- get_url:
    url: "{{ actk_certs }}/{{ item.1 }}"
    dest: "{{ actk_src_tmp }}/"
    username: "{{ actk_mvn_user }}"
    password: "{{ actk_mvn_pass }}"
  with_subelements:
    - "{{ actk_cert_list }}"
    - "{{ actk_certs }}"
  delegate_to: localhost

- copy:
    src: "{{ actk_src_tmp }}/{{ item.1 }}"
    dest: "{{ actk_dst_tmp }}"
  with_subelements:
    - "{{ actk_cert_list }}"
    - "{{ actk_certs }}"

Потім це виправили, але осад залишився.

get_url:
    url: "{{ actk_certs }}/{{ actk_item }}"
    dest: "{{ actk_src_tmp }}/{{ actk_item }}"
    username: "{{ actk_mvn_user }}"
    password: "{{ actk_mvn_pass }}"
  loop_control:
    loop_var: actk_item
  with_items: "{{ actk_cert_list }}"
  delegate_to: localhost

- copy:
    src: "{{ actk_src_tmp }}/{{ actk_item }}"
    dest: "{{ actk_dst_tmp }}"
  loop_control:
    loop_var: actk_item
  with_items: "{{ actk_cert_list }}"

День №311: Прискорюємо тести

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

З брехнею тестів ставало більше, білди бігли повільніше до години в поганому випадку. На одному з ретро була фраза на кшталт "добре що є тести, але вони повільні". У результаті ми відмовилися від інтеграційних тестів на віртуальних машинах та адаптували під docker, щоб було швидше. Так само замінили testinfra на ansible verifier щоб зменшити кількість використовуваних інструментів.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Суворо кажучи тут був комплекс заходів:

  1. Перехід до docker.
  2. Забрати тестування ролей, яке дублюється за рахунок залежностей.
  3. Збільшити кількість слейвів.
  4. Порядок запуску тестів.
  5. Можливість лінтувати ВСІ локально однією командою.

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

У результаті Pipeline на jenkins також уніфікувався

  1. Generate build stages.
  2. Lint all in parallel.
  3. Run test role stages in parallel.
  4. Готово.

Уроки, витягнуті

Уникайте глобальних змінних

Неможливо використовує глобальні змінні, є частковий workaround у вигляді private_role_varsале це не панацея.

Наведу приклад. Нехай у нас є role_a и role_b

# cat role_a/defaults/main.yml
---
msg: a

# cat role_a/tasks/main.yml
---
- debug:
    msg: role_a={{ msg }}

# cat role_b/defaults/main.yml
---
msg: b

# cat role_b/tasks/main.yml
---
- set_fact:
    msg: b
- debug:
    msg: role_b={{ msg }}

- hosts: localhost
  vars:
    msg: hello
  roles:
    - role: role_a
    - role: role_b
  tasks:
    - debug:
        msg: play={{msg}}

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Забавна річ, що результат роботи плейбуків залежатиме від не завжди очевидних речей, наприклад, черговості перерахування ролей. На жаль це в натурі Ansible і краще що можна зробити, то використовувати якісь домовленості, наприклад, усередині ролі використовувати тільки змінну описані в цій ролі.

BAD: використовувати глобальну змінну

# cat roles/some_role/tasks/main.yml
---
debug:
  var: java_home

ХОРОШИЙ: В defaults визначати необхідні змінні та пізніше використовувати тільки їх.

# cat roles/some_role/defaults/main.yml
---
r__java_home:
 "{{ java_home | default('/path') }}"

# cat roles/some_role/tasks/main.yml
---
debug:
  var: r__java_home

Prefix role variables

BAD: використовувати глобальну змінну

# cat roles/some_role/defaults/main.yml
---
db_port: 5432

ХОРОШИЙ: У ролі для змінних використовувати змінні з префіксом імені ролі, це подивившись на inventory дозволить простіше зрозуміти що відбувається.

# cat roles/some_role/defaults/main.yml
---
some_role__db_port: 5432

Use loop control variable

BAD: Використовувати в циклах стандартну змінну item, якщо цей таск/плейбук буде десь інклюдан то це може призвести до непередбачуваної поведінки

---
- hosts: localhost
  tasks:
    - debug:
        msg: "{{ item }}"
      loop:
        - item1
        - item2

ХОРОШИЙ: Перевизначати змінну в циклі через loop_var.

---
- hosts: localhost
  tasks:
    - debug:
        msg: "{{ item_name }}"
      loop:
        - item1
        - item2
      loop_control:
        loop_var: item_name

Check input variables

Ми домовилися використовувати префікси змінних, не буде зайвим перевірити, що вони визначені як ми очікуємо і, наприклад, не були перекриті порожнім значенням.

ХОРОШИЙ: Перевіряти змінні

- name: "Verify that required string variables are defined"
  assert:
    that: ahs_var is defined and ahs_var | length > 0 and ahs_var != None
    fail_msg: "{{ ahs_var }} needs to be set for the role to work "
    success_msg: "Required variables {{ ahs_var }} is defined"
  loop_control:
    loop_var: ahs_var
  with_items:
    - ahs_item1
    - ahs_item2
    - ahs_item3

Avoid hashes dictionaries, use flat structure

Якщо роль очікує hash/dictionary в одному з параметрів, то якщо ми захочемо виправити один із дочірніх параметрів, нам треба буде перевизначати весь hash/dictionary, що підвищить складність конфігурування.

BAD: Використовувати hash/dictionary

---
user:
  name: admin
  group: admin

ХОРОШИЙ: Використовувати плоску структуру змінних

---
user_name: admin
user_group: "{{ user_name }}"

Create idempotent playbooks & roles

Ролі та плейбуки мають бути ідемпотентними, т.к. зменшує configuration drift та страх зламати щось. Але якщо ви користуєтеся molecule, то це поведінка за умовчанням.

Avoid using command shell modules

Використання модуля shell призводить до імперативної парадигми опису, замість декларативної, яка є основною Ansible.

Test your roles via molecule

Molecule дозволяє дуже гнучка штука, давай подивимося кілька сценаріїв.

Molecule Multiple instances

В molecule.yml у секції platforms можна описати безліч хостів, які розгортати.

---
    driver:
      name: docker
    platforms:
      - name: postgresql-instance
        hostname: postgresql-instance
        image: registry.example.com/postgres10:latest
        pre_build_image: true
        override_command: false
        network_mode: host
      - name: app-instance
        hostname: app-instance
        pre_build_image: true
        image: registry.example.com/docker_centos_ansible_tests
        network_mode: host

Відповідно ці хости, можна потім у converge.yml використовувати:

---
- name: Converge all
  hosts: all
  vars:
    ansible_user: root
  roles:
    - role: some_role

- name: Converge db
  hosts: db-instance
  roles:
    - role: some_db_role

- name: Converge app
  hosts: app-instance
  roles:
    - role: some_app_role

Ansible verifier

У molecule є можливість використовувати ansible Для перевірки того, що інстанс був налаштований правильно, навіть це за замовчуванням з 3 релізу. Це не так гнучко як testinfra/inspec, але можна перевіряти, що вміст файлу відповідає нашим очікуванням:

---
- name: Verify
  hosts: all
  tasks:
    - name: copy config
      copy:
        src: expected_standalone.conf
        dest: /root/wildfly/bin/standalone.conf
        mode: "0644"
        owner: root
        group: root
      register: config_copy_result

    - name: Certify that standalone.conf changed
      assert:
        that: not config_copy_result.changed

Або розгорнути сервіс, дочекатися його доступності та зробити smoke test:

---
  - name: Verify
    hosts: solr
    tasks:
      - command: /blah/solr/bin/solr start -s /solr_home -p 8983 -force
      - uri:
          url: http://127.0.0.1:8983/solr
          method: GET
          status_code: 200
        register: uri_result
        until: uri_result is not failed
        retries: 12
        delay: 10
      - name: Post documents to solr
        command: /blah/solr/bin/post -c master /exampledocs/books.csv

Put complex logic into modules & plugins

Ansible проповідує декларативний підхід, тому коли ви робите розгалуження коду, трансформацію даних, shell модулі, код стає складно читаним. Щоб поборотися з цим і залишити його простим для розуміння, не буде зайвим, боротися з цією складністю шляхом створення своїх модулів.

Summarize Tips & Tricks

  1. Avoid global variables.
  2. Prefix role variables.
  3. Use loop control variable.
  4. Check input variables.
  5. Avoid hashes dictionaries, use flat structure.
  6. Create idempotent playbooks & roles.
  7. Avoid using command shell modules.
  8. Test your roles via molecule.
  9. Put complex logic в modules & plugins.

Висновок

Як почати тестувати Ansible, відрефакторити проект за рік і не злетіти з котушок

Не можна просто так взяти та відрефакторити інфраструктуру на проекті, навіть якщо у вас IaC. Це довгий процес, що вимагає терпіння, часу та знань.

UPD1 2020.05.01 20:30 — Для первинного профілювання плейбуків можна використовувати callback_whitelist = profile_tasks щоб зрозуміти що саме довго працює. Після чого проходимося класиці прискорення ansible. Можна спробувати mitogen
UPD2 2020.05.03 16:34 - Англійська версія

Джерело: habr.com

Додати коментар або відгук