Это расшифровка выступления на DevOps-40 2020-03-18: Начиная со второго коммита любой код становится legacy, т.к. изначальные задумки начинают расходиться с суровой реальностью. Это не хорошо и не плохо, это данность с которой сложно спорить и необходимо уживаться. Частью этого процесса является рефакторинг. Рефакторинг Infrastructure as Code. Да начнется история как отрефакторить Ansible за год и не слететь с катушек.  Жил был условный проект. На нем была Dev команда разработки и Ops инженеры. Они решали одну и ту же задачу: как развернуть сервера и запустить приложение. Проблема была в том, что каждая команда решала эту задачу по своему. На проекте было принято решение использовать Ansible для синхронизации знаний между командами Dev и Ops.  Сами того не заметив, хотели сделать как можно лучше, а получилось legacy. Как так получается? В итоге можно получить код непонятного вида, на который нет документации, непонятно что он делает, нужен ли он, но проблема в том, что вам необходимо его развивать, дорабатывать, добавлять костыли с подпорками, делая ситуацию только еше хуже.  Изначально задуманная и реализованная модель IaC перестаёт отвечать действительности с запросами пользователей / бизнеса / других команд, время внесение изменений в инфраструктуру перестаёт быть приемлемым. В этот момент приходит понимание, что пора принимать меры.  Прежде чем кидаться рефакторить вы должны ответить на ряд важных вопросов: Если вы не знаете как ответить на вопросы, то рефакторинг закончится так и не начавшись или может получиться только хуже. Т.к. был опыт( Что я узнал, протестировав 200 000 строк инфраструктурного кода), то от проекта пришел запрос помощи исправить роли и покрыть их тестами.  Первоочерeдное это надо подготовиться. Определиться что будем делать. Для этого общаемся, находим проблемные точки и прикидываем пути их решения. Полученные концепты как-то фиксируем, например статья в confluence, что бы при появление вопроса "как лучше?" или "как правильнее?" мы не сбились с курса. В нашем случае мы придерживались идеи разделяй и властвуй: дробим инфраструктуру на маленькие кусочки / кирпичики. Такой подход позволяет взять изолированный кусок инфраструктуры, понять что он делает, покрыть его тестами и изменить не побоявшись что-нибудь сломать.  Получается, что тестирование инфраструктуры становится краеугольным камнем и тут стоит упомянуть пирамиду тестирования инфраструктуры. Ровно таже идея, что в разработке, но для инфраструктуры: идем от дешевых быстрых тестов которые проверяют простые вещи, например отступы, к дорогим полноценным тестами разворачивающих цельную инфраструктуру. Прежде чем пойдем описывать как покрывали тестами Ansible на проекте, опишу попытки и подходы которые довелось использовать ранее, что бы понять контекст принимаемых решений.  Первый раз тестировать Ansible довелось на проекте по разработке SDS (Software Defined Storage). Есть отдельная статья на эту тему
Как наломать велосипедов поверх костылей при тестировании своего дистрибутива, но если кратко, то у нас получилась перевернутая пирамида тестирования и тестирование мы тратили 60-90 минут на одну роль, что есть долго. Основа была e2e тесты, т.е. мы разворачивали полноценную инсталляцию, и потом ее тестировали. Еще отягчающим было изобретение своего велосипеда. Но надо признаться это решение работало и позволяло стабильно релизиться.  Развитием идеи тестирования Ansible стало использование готовых инструментов, а именно test kitchen / kitchen-ci и inspec. Выбор был обусловен знанием Ruby ( подробней в статье на хабре: Мечтают ли YML программисты о тестировании ansible?) работало быстрее порядка 40 минут на 10 ролей. Мы создавали пачку виртуальных машин и внутри гоняли тесты.  В целом решение работало, но был осадочек из-за неоднородности. Когда же увеличили количество тестируемых до 13 базовых ролей и 2 мета ролей комбинирующие более мелкие роли, то вдруг тесты стали бежать 70 минут, что почти в 2 раза дольше. Об XP (extreme programming) практиках было сложно говорить т.к. никто не захочет ждать 70 минут. Это стало подоводом для изменения подхода  Концептуально это похоже на testkitchen, только мы перевели тестирование ролей в docker и сменили стэк. Итогом, время сократилось до стабильных 20-25 минут для 7 ролей.  Увеличив количество тестирумых ролей до 17 и линтовку 45 ролей мы прогоняли это за 28 минут на 2 jenkins slave.  С наскоку задачу рефакторинга, скорей всего сделать не получится. Задача должна быть измеримой, что бы вы могли ее разбить на мелкие кусочки и съесть слона по частям чайной ложкой. Должно быть понимание в правильном ли вы направлении идет движение, долго ли еще идти.  В целом не суть важно как это будет сделано, можно писать на бумажку, можно клеить стикеры на шкаф, можно создавать таски в jira, а можно завести google docs И туда записывать текущий статус. Ноги растут из того, что процесс не сиюминутный, он будет долгий и нудный. Маловероятно, что кто-то хочет, что бы за время рефакторинга вы перегорели идей, устали и забили. Рефакторинг прост: и так повторяем пока не достигнем намеченной цели.  Все сразу начать тестировать может не получится, поэтому у нас первой задачей было начать с линтовки и проверки синтаксиса.   Линтовка это небольшой первый шаг к Green Build Master. Это почти ничего не сломает, но позволит отладить процессы и сделать зелененькие билды в jenkins. Идея в том, что бы выработать привычки у команды:  Выстроив процесс попадания кода в мастер можно начинать процесс поэтапного улучшения — заменяя линтовку на запуск ролей, можно даже без идемпотентности. Необходимо понять как применять роли, как они работают.  Когда unit тестами покрыто большинство ролей и всё линтуется, можно переходит к добавлению интеграционных тестов. Т.е. тестированию не отдельного кирпичика в инфраструктуре, а их комбинации, например полноценную конфигурацию инстанса.  На jenkins мы генерировали множество стадий, которые в параллель линтовали роли / плэйбуки, потом юнит тесты в контейнерах и в конце интеграционные тесты.   Первое время рефакторингом занималась небольшая группа людей в пару-тройку человек. Они делали ревью кода в мастерe. Со временем в команде вырабатолось знание как писать код и code review способствовало распространению знаний об инфраструктуре и том как она устроена. Изюминкой здесь было, то что ревьюверы выбирались по очереди, по графику, т.е. с некоторой долей вероятности ты залезешь в новый участок инфраструктуры.  И здесь должно быть удобно. Удобно делать ревью, видеть в рамках какой задачи оно сделано, историю обсуждений. Мы интегрировали jenkins + bitbucket + jira. Но как таковое ревью не панацея, как-то, у нас пролез в мастер код, который сделал нам флапающие тесты: Потом это исправили, но осадочек остался.  Со вренем тестов становилось больше, билды бежали медленнее до часа в плохом случае. На одном из ретро была фраза по типу "хорошо что есть тесты, но они медленные". В итоге мы отказались от интеграционных тестов на виртуальных машинах и адаптировали под docker, дабы было быстрее. Так же заменили testinfra на ansible verifier что бы уменьшить кол-во используемых инструментов.  Строго говоря тут был комплекс мер:  В итоге Pipeline на jenkins тоже унифицировался Ansible использует глобальные переменные, есть частичный workaround в виде private_role_vars, но это не панацея.  Приведу пример. Пусть у нас есть role_a и role_b  Забавная вещь, что результат работы плэйбуков будет зависеть от не всегда очевидных вещей, например очередности перечисления ролей. К сожалению это в натуре Ansible и лучшее что можно сделать, то использовать какие-то договоренности, например внутри роли использовать только переменную описанные в этой роли. BAD: использовать глобальную переменную. GOOD: В defaults определять необходимые переменные и позже использовать только их. BAD: использовать глобальную переменную. GOOD: В роли для переменных использовать переменные с префиксом имени роли, это посмотрев на inventory позволит проще понять что происходит. BAD: Использовать в циклах стандартную переменную item, если этот таск/плэйбук будет где-то заинклюдан то это может привести к непредвиденному поведению GOOD: Переопределять переменную в цикле через loop_var. Мы договорлись использовать префиксы переменных, не будет лишним проверить что они определены как мы ожидаем и, например, не были перекрыты пустым значением GOOD: Проверять переменные. Если роль ожидает hash/dictionary в одному из параметров, то если мы захотим поправить один из дочерних параметров, нам надо будет переопределять весь hash/dictionary, что повысит сложность конфигурирования. BAD: Использовать hash/dictionary. GOOD: Использовать плоскую структуру переменных. Роли и плэйбуки должны быть идемпотентными, т.к. уменьшает configuration drift и страх сломать что-то. Но если вы пользуете molecule, то это поведение по умолчанию. Использование shell модуля приводит к императивной парадигме описания, вместо декларативной, которая является основной Ansible. Molecule позволяет весьма гибка штука, давай посмотрим несколько сценариев. В molecule.yml в секции platforms можно описать множество хостов которые разворачивать. Соответственно эти хосты, можно потом в converge.yml использовать: В molecule есть возможность использовать ansible Для проверки того, что инстанс был настроен правильно, более того это по умолчанию с 3 релиза. Это не так гибко как testinfra/inspec, но можно проверять, что содержимое файла соответствует нашим ожиданиям: Или развернуть сервис, дождаться его доступности и сделать smoke test: Ansible проповедует декларативный подход, поэтому когда вы делаете ветвление кода, трансформацию данных, shell модули, то код становится сложно читаемым. Что бы побороться с этим и оставить его простым для понимания, не будет лишним, бороться с этой сложностью путём создания своих модулей.  Нельзя просто так взять и отрефакторить инфраструктуру на проекте, даже если у вас IaC. Это долгий процесс требующий терпения, времени и знаний. UPD1 2020.05.01 20:30 — Для первичного профилирования плэйбуков можно использовать callback_whitelist = profile_tasks что бы понять что именно долго работает. После чего проходимся по классике ускорения ansible. Можно попробовать mitogen
UPD2 2020.05.03 16:34 — English version Software Architect