У меня есть pet project, которым я занимаюсь в свободное время. Этот проект полностью посвящён инфраструктурным экспериментам. Для управления конфигурацией я использую SaltStack. SaltStack — это централизованная система управления инфраструктурой. Это значит, есть мастер-сервер, который настраивает подчинённые серверы. За время жизни проекта я наступил на небольшой набор граблей, но в итоге пришёл к очень удобному подходу работы с ним. В общем про это и статья — как оно все начиналось и к чему пришло.  Весь проект был монолитным, в нем было всё: Весь проект лежал в одном git репозитории, который был подключён к мастер-серверу через gitfs. Это жутко удобно — не надо заботиться об актуализации данных на мастер-сервере. SaltStack сам собирает все из репозитория. Я мог бы поднять тестовую копию своей инфраструктуры и тестировать все через неё, используя отдельную ветку в git-репозитории. Но поднять копию инфраструктуры для тестов дорого: С другой стороны, "бой" и так один сплошной "тест" и ничего страшного, если поломаю (ну как "не страшно", обидно бывает). А раз не страшно, то каждое изменение, в том числе и промежуточное, я деплоил через push в репозиторий. Commit-лог стал выглядеть жутко, мягко говоря: На самом деле не все было так плохо, но в целом картинку пример передаёт правильную %) Дальше стало ещё хуже — со временем я начал забывать, какое состояние что именно делает и как оно это делает. Все-таки это личный проект и работаю над ним я не всегда, а время от времени. README файл уже плохо решал эту проблему. Также была ещё и связанность состояний через данные. Разные состояния использовали одни и те же данные, если структуру данных изменить для одного состояния — другие гарантировано ломались. Из-за этого в какой-то момент времени "боевая" конфигурация находится в состоянии "кишки наружу". Я могу чинить баг на протяжении нескольких дней, а значит, в это время какое-то из состояний могло оставаться нерабочим. В целом это говорило о плохой продуманности архитектуры проекта. Но все эти минусы меня мало волновали. В моем режиме я готов был с ними жить. Не мог я мириться только с одним — меняя что-то в структуре данных, я забывал проверить все состояния. Ловить потом такие "отложенные" ошибки долго и стремно. Я осознал, что если я напишу тесты, то у меня будет гарантия, что если я что-то изменил, то автотесты проверят результат работы всех состояний. Ура! Все вполне просто. Задача ясна: хочу проверять результат работы состояний в проекте. Итак, что у нас есть для тестирования? Результат работы SaltStack'a — это конфигурационные файлы, сервисы, Docker контейнеры, настройки фаервола, SElinux и так далее. Вот это все отлично тестируется с помощью Serverspec тестов. Я начал вспоминать конференции, где был, вспоминать статьи, какие встречал на эту тему. В общем, на русском из актуального и хорошего в голове крутился только один автор — Игорь Курочкин  IgorITL, кого я вживую слушал на DevConf'e 2015. Можно посмотреть его доклад "Тестируем инфраструктуру как код": Ещё я нашёл неплохую статью для понимания проблемы "Agile DevOps: Test-driven infrastructure". После прочтения всех материалов я понял, что для моей задачи подходит инструмент KitchenCI, так как он: Я посчитал, что, кажется, теперь я все знаю. Есть теория, в голове всё уложилось — теперь-то уж точно можно начать писать тесты, не так ли? Посмотрел я на проект и увидел, что как-то теория в моей голове ну вообще никак не укладывается на мою реальность. Как подойти к моему проекту? Куда класть тесты? Как их запускать? В поисках ответа я опять полез читать внимательно документацию KitchenCI. К сожалению, в ней сильно отвлекает специализация этого инструмента на Chef и его особенности. Примеры опять же все для него. Давайте посмотрим на KitchenCI чуть внимательнее. В этом инструменте мы оперируем следующими объектами: У меня же используется SaltStack. Гугл нам подсказывает, что есть сторонний проект 'kitchen-salt', который реализует provisioner salt_solo для SaltStack. Там же есть подробный урок и пример, как это использовать. Прочитав документацию по KitchenCI и kitchen-salt, я вынес главное — тестируются отдельные рецепты (в терминологии Chef'a), а не вся конфигурация целиком. В SaltStack'е аналогом Chef'овских рецептов являются формулы — независимые состояния, вынесенные в самостоятельный проект. Эти формулы используются для повторного использования кода в других проектах. Например, целая пачка таких формул доступна на GitHub. В этом и заключается основная причина, почему мой проект "не подходит" для KitchenCI — он монолитный. В голове закрутились слова "рефакторинг", "связанность кода", "модульный подход" и подобное. Я взгрустнул. Как не программист я и слов-то таких знать не должен. Challenge accepted! Насколько я помню первое правило рефакторинга, у него должна быть ясная, достижимая и измеримая цель. Обычно это развёрнутый ответ на вопрос "Для чего мы вносим изменения?". В моем случае это было сформулировано следующим образом: Составив список задач, я опять загрустил, попил кофе и пошёл делать. Состояние за состоянием превращались в отдельные формулы. Вынося состояние из основного проекта, я вносил в конфигурацию мастер-сервера ссылку на новую формулу. Таким образом, работоспособность проекта особо не страдала на протяжении всей переделки. Из-за связанности части состояний пришлось пересмотреть структуру хранения данных — я сделал её более независимой, разбил на не пересекающиеся части для разных формул и при этом старался избежать дублирования данных. Это было, пожалуй, самым сложным. Из-за этого часть логики некоторых состояний была перенесена в другие. Итогом работы стало почти два десятка отдельных формул, простых и понятных, с примерами используемых данных и минимальной документацией. Итоговая структура данных стала заметно проще. Даже на этом этапе я ощутил положительный результат — я теперь знал, что мои формулы независимы, и можно смелее вносить в них изменения. Как только я начал рассматривать отдельную формулу как объект тестирования, у меня сразу же сложилась картинка в голове о том, как применять KitchenCI. Давайте разберём процесс тестирования на примере простейшей формулы "Common packages". Данная формула устанавливает системные пакеты, которые я ожидаю встретить на любом из своих серверов. Это просто привычные для меня утилиты. NB! Дальше по тексту, все команды выполняются в корне проекта формулы. Вот так выглядит изначальная файловая структура формулы: Состояние init.sls: Пример данных, pillar.example: Для работы KitchenCI нам потребуется установленные Vagrant и ruby (и gem bundler, конечно). Создадим Gemfile cо списком требуемых ruby gems в корне проекта нашей формулы: Устанавливаем перечисленные зависимости:  Попросим KitchenCI создать нам структуру и файлы заглушки для тестов: У нас появились: файл .gitignore (если он не был вами создан ранее), куда добавились строки: Вносим в .kitchen.yml описание нашей формулы: В общем, все готово. Давайте создадим виртуальную машину, настроим её и прогоним в ней формулу:  Да, KitchenCI выполнил для нас следующие действия: Хо-хо! Я теперь могу разрабатывать формулы и фиксить в них баги без необходимости коммитить промежуточные изменения в мастер и выкладывать их на "бой". "Боевая" инфраструктура будет заметно стабильнее и, кажется, мой commit-лог теперь будет не стыдно показать, если вдруг придётся. Можно посмотреть руками результат работы формулы, зайдя внутрь машинки: Я научился с помощью KitchenCI запускать формулы и проверять их работоспособность. Проверять руками — это здорово. Но где же автотесты? Давайте все-таки проверять результат работы формулы автотестами.
Для этого выполним следующие шаги: Внимание! Суффикс _spec обязателен. Почитать об этом и других нюансах и в целом познакомиться со Serverspec можно на официальном сайте: http://serverspec.org/.  Чтобы сэкономить время и не ждать опять, пока машинка будет создана и настроится с нуля, давайте просто попросим KitchenCI прогнать тесты:  Вот и вся магия. KitchenCI позволяет сделать все вышеперечисленные шаги одной командой: kitchen test. Будет создана виртуальная машина, прогонятся формула и тесты, затем машинка будет уничтожена. kitchen-salt может тестировать не только отдельные формулы, но и их наборы. То есть, вы вполне можете тестировать итоговый результат работы нескольких формул. Такая проверка покажет, могут ли ваши формулы работать совместно и дают ли они ожидаемый результат. Все это возможно благодаря различным комбинациям опций provisioner’a: https://github.com/simonmcc/kitchen-salt/blob/master/provisioner_options.md. А это значит, что я вполне мог и к исходному виду моего проекта привязать KitchenCI и тесты, но как мне кажется в итоге получилось значительно лучше. Теперь я потихоньку покрываю тестами свои старые формулы и пишу новые, причём пишу намного быстрее, чем раньше. И я в любой момент уверен в работоспособности своих формул, как новых, так и старых. Да, несмотря на временные затраты на рефакторинг и написание тестов, я получил явный прирост в работе со своим карманным проектом. Теперь нет опасений, что отложив проект на продолжительный период времени, я не смогу продолжить его из-за сложности самого проекта или непонятно почему не работающих формул. Да, рефакторинг сожрал несколько дней моего личного времени. Да, тесты писать скучно, но они дают чувство уверенности в проекте. Классное такое чувство. Буду рад ответить на вопросы, выслушать замечания и советы на будущее :)