19 February

Ansible playbooks — это код: проверяем, тестируем, непрерывно интегрируем. Иван Пономарёв

System administrationPythonIT InfrastructureServer AdministrationDevOps

Предлагаю ознакомиться с расшифровкой доклада Ивана Пономарёва «Ansible playbooks — это код: проверяем, тестируем, непрерывно интегрируем».


Рефакторинг кода может быть увлекательным, особенно если это код вашей инфраструктуры. К тому же Ansible-роли почему-то имеют тенденцию к быстрому увеличению сложности. И это добавляет «изюминку» в вашу задачу. Иван расскажет, как можно преодолевать сложность Ansible-кода с помощью тестирования. В Docker-контейнерах.
По мере разрастания кодовой базы в Ansible приходят знакомые проблемы: сложность поддержки кода, ошибки и страх изменений. У знакомых проблем есть знакомое решение: автоматическое тестирование и CI. В докладе Иван покажет, как с использованием ряда инструментов решить проблемы «хрупкости» Ansible-кода, выполнить статический анализ, протестировать Ansible-скрипты и настроить CI-системы для публикации ролей в Ansible Galaxy.



Немного о себе. Я работаю в небольшой софтверной компании, мы делаем софт на заказ. Раз в неделю я преподаю на Физтехе, на той же кафедре, на которой я когда-то учился.



Компания у нас небольшая. Мы работаем с пулом заказчиков. Мы поставляем им различные системы. Расскажу о диапазоне наших работ. Самые простые — это классическая «трёхзвеночка», где один-два сервера и несколько десятков пользователей, но это всё должно стоять и работать. Самый сложный проект, который есть у нас в пуле, — это около сорока серверов в DigitalOcean. И мы используем вот такую связку: Terraform + Ansible. Мы ноды разворачиваем при помощи Terraform, а с помощью Ansible мы конфигурируем все наши виртуальные машины для того чтобы ставить там то что надо.



Когда мы начали использовать Ansible с его низким порогом входа и прекрасной экосистемой, у нас стали появляться роли в соответствии с best practices. И ролей стало накапливаться много-много — для фронта, для бэка, для мониторинга, для кэшей, для логов и т. д.



У нас есть первый проект для одного заказчика, второй проект для второго заказчика. Каждый делается по best practices, но некоторые роли пересекаются. Соответственно, ребята просят дать им роль для установки чего-нибудь, которую можно будет скопировать в репозиторий кода и чуть-чуть подпилить под свои нужды.



И код стал расти как снежный ком, и стали знакомые проблемы возникать. Это:


  1. Страх поломать.
    • Код не переиспользуется, а копируется в проекты.
    • Нет рефакторинга. Действительно: зачем я буду улучшать то, что работает уже и так? А вдруг оно сломается?
  2. Нет уверенности в том, что это вообще сработает, если это надо будет запустить.
  3. Отладка в процессе деплоя. Запустим playbook, а там посмотрим, что будет.


Нужно делать автоматизированное тестирование и CI. Но как? Ведь это не просто код, на котором unit-tests запускаем, как на Java или на Python. Это же configuration is code. Как это делать?



Прежде чем мы перейдём к тестам, давайте упростим проблему и посмотрим, что мы можем проверить, не запуская код, а просто глядя на этот код.


Прежде всего — его well-formedness: является ли он вообще синтаксически валидным. Для этой задачи у нас есть три инструмента, которые можно использовать совместно. Это YAMLLint, Ansible-lint и Syntax check в самом playbook. Сейчас для начала пробежимся по ним.



Проект YAMLLint.


  • Проверяет синтаксис YAML. YAML не так прост, как вы знаете. Он проверяет много чего, у него десятки всяких чеков.
  • Проверяет лишние пробелы.
  • Проверяет переносы строк UNIX-style.
  • Проверяет одинаковость отступов, потому что можно сделать валидный YAML, но с разными отступами, и это будет не очень красиво выглядеть.

В принципе он более строг, чем Ansible, к коду, но он позволяет некоторые проблемы убрать. Например, у нас разработка ведётся под Windows, и если попадают переносы строк в Windows-style в конфигурационные файлы, то это беда. YAMLLint это решает.



Так выглядит вывод YAMLLint. И обратите внимание: мы эти правила можем настроить, отключить какие-то проверки, например длину строки сделать побольше или ещё что-то. YAMLLint проверяет просто все YAML-файлы в вашей папке, поэтому если это не Ansible playbooks или роли, а просто какие-то конфигурационные файлы, то он тоже их проверяет.



Ansible-lint — это прекрасный проект, который содержит десятка два good practices.


Для примера:


  • Когда использовать command module, когда — shell module? Часто с этим путаются новички. Ansible-lint это находит.
  • Иногда используется command module, а в нём вызов команд, для которых есть стандартный модуль Ansible. Ansible-lint подскажет, что его возможно заменить на стандартный модуль Ansible.
  • Он проверяет idempotence (идемпотентность) вашего использования command и shell. Про idempotence мы чуть-чуть поговорим позже поподробней. Он строже, чем сам Ansible, и он проверяет best practices вашего кода.
  • И, кроме того, если вы хотите на Python что-нибудь написать, то там есть фреймворк, который даёт возможность расширять эти правила и добавлять новые.


Запускается это вот так вот. Здесь есть одни «грабли». Если ваш код использует какие-то стандартные роли, то он будет обходить в том числе и стандартные роли. Но в стандартных ролях есть критические ворнинги, и поэтому он завалит вашу CI-сборку. Поэтому стандартные роли исключите.



Вот так выглядит его вывод.



Третий инструмент — это Syntax check, встроенный в сам Ansible. Тут всё просто. Но единственные «грабли», которые также нужно учесть, — это если вы делаете на CI, то установите все свои стандартные роли. Потому что Syntax check хоть и называется Syntax check, но проверяет он не только синтаксис, он заходит внутрь всех ролей и тоже их обходит. И если там какая-то роль не установлена, то у вас завалится сборка.



Всё это вы можете объединить в скрипт вашей любимой CI-системы. У меня это Jenkins, а вы можете использовать что угодно. И получить уже вот такой pipeline. Получить вы его можете прямо сейчас, не вставая с места. Это ничего вам не стоит, вы просто получаете довольно подробный статический анализ всего вашего Ansible-хозяйства.



Но, как известно, возможности статического анализа ограниченны. Всё-таки, чтобы протестировать, программу нужно запускать. Как же тут быть?



История вопроса вот какая. Jeff Geerling — это человек, известный в Ansible-экосистеме. Он автор множества ролей, он автор книги «Ansible for DevOps». В четырнадцатом году он написал вот эту статью, предлагая в Travis разворачивать роли, которые он тестирует.



Два года спустя он в продолжении этой статьи предлагает делать то же самое, но в разных docker-контейнерах. Таким образом за один раз он прогоняет свои роли на семи разных операционных системах.



И в этой же статье он упоминает Molecule. Это штука, которая существует где-то с пятнадцатого года. Она сейчас очень активно развивается. И это очень удобный инструмент для тестирования Ansible-ролей.



Это типичный современный OpenSource-проект, который хорошо интегрирует другие проекты.



Устанавливается он в ваш Python environment. Нужно поставить ansible, molecule и, если вы будете тестировать в Docker, — docker-py.



Этот инструмент тестирует роли, то есть он работает внутри отдельной роли. Не проекта Ansible целиком, а внутри роли. Соответственно, внутри роли у вас могут быть разные сценарии. Внутри сценария вы настраиваете instances (сервера, виртуальные машины), на которые вы будете накатывать эту роль. Допустим, на разные операционные системы.


Кроме того, у каждого сценария есть свой playbook. Playbook — это то, что будет выполняться, накатываясь на эти instances.


И есть конфигурационные тесты.



Во-первых, инициализация. Если вы хотите создать новую роль уже с molecule, то вы пишете вот такую команду. И она создаёт вам папку со всеми файлами, даже с readme-файлом, то есть со всеми вещами, необходимыми для создания Ansible-роли. А внутри папки Molecule у них возникает единственный сценарий под названием Default. В принципе, одного сценария под названием Default мне лично всегда хватало, более одного сценария я не писал.



Если вы хотите добавить molecule в вашу существующую роль, то вам надо выполнить вот такую команду. Она тоже добавит вам Default-сценарий. Там никакой магии нет, она просто создаёт папки и прописывает туда файлы. Вы можете просто копировать уже из существующих ролей, которые под Molecule, папки в новые роли. Там всё это переиспользовать очень легко.



После того как мы проинициализировали, заходите в консоль и пишете: molecule test. Всё, у вас Molecule тестирует вашу роль. Как правило, это упадёт. Пришло сообщение об ошибке. Непонятно, почему она упала.



Если мы заполним флажок --debug, то она скажет, что вы забыли установить docker-py. А я несколькими слайдами ранее говорил, что docker-py надо ставить отдельно. Этот --debug помогает разобраться: что же пошло не так.



Потом она покажет вам Test matrix. Это некий план или сценарий, по которому Molecule будет работать с вашей ролью. Как видите, она уже предлагает некую матрицу. На первых этапах она проверяет статический анализ, потом она проверяет syntax, converge, idempotence, и заканчивается всё сносом тестовых instances, на которые это всё ставилось.



Самое интересное, конечно, — это на что же, собственно, Molecule накатывает ваши роли, на какие instances, откуда она эти ресурсы берёт? Эти ресурсы настраиваются вот в таком файлике — molecule.yml. Вы можете прописать в разделе Platforms столько instances, сколько вам нужно. Если вы их пропишете три или пять, то их будет три или пять. И Ansible на этапе converge будет их раскатывать сразу на несколько нод, как Ansible обычно это делает.


Если это docker, то здесь вы прописываете базовые image. Заметьте, что здесь image хитрые. Сделано это для того, чтобы у нас работал systemd. Потому что если ваши роли раскатывают какие-то сервисы, которые работают в systemd, а, как известно, docker с этим не работает, то можно воспользоваться хитрыми базовыми контейнерами. Таким образом можно обойтись, и у вас будут тестироваться роли, которые сервисы ставят в systemd.



Кроме docker вы можете использовать другие драйверы.


Во-первых, вы можете создавать тестовые instances в облаках, используя соответствующие модули, встроенные в Ansible.


Во-вторых, вы можете сами написать драйвер. Там есть для этого заготовка.


И есть Vagrant, и есть многое другое.


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



Следующий важный момент: когда вы начинаете этим пользоваться, вам нужно подключить зависимости. Под зависимостями я имею в виду роли, от которых зависит тестируемая роль.


Это прописывается в файле requirements.yml, как и положено по best practices Ansible. И вы можете указать настройку: какие роли каких версий вам нужны для того чтобы протестировать вашу роль как зависимость. И на первых этапах, на этапе dependency, именно роли именно этих версий будут скачаны.



Этап статического анализа использует эту троицу, о которой я рассказывал в самом начале: YAMLLint, Ansible-lint, Syntax check.



Этап converge на ваши тестовые instances накатывает файл playbook.yml. Вот что вы там напишите, то он и сделает. Вы можете даже вашу тестируемую роль туда не подключать, а что-то другое. То есть этап converge просто выполняет файл playbook.yml. И здесь вы можете несколько ролей выполнить, как-то их сконфигурировать и посмотреть потом, как они будут работать совместно.



Если что-то пошло не так: converge вроде как бы прошёл, или он не прошёл, но непонятно, что происходит, — вы можете запуститься с ключиком destroy=never (это значит «никогда не удаляй тестовые instances») и если вы в docker, то обычным образом через interactive-терминал залогиниться в ваш контейнер и посмотреть, какие файлы не туда легли и что там вообще происходит.



Следующий этап после converge. Предположим, роль сработала, на все ваши тестовые instances раскатилась, всё здорово. Molecule ещё раз выполняет вашу роль. Раньше, когда не было diff в Ansible, Jeff Geerling предлагал так: проверять, что ничего не изменено при втором прогоне. Это проверка идемпотентности, то есть что ваша роль написана таким образом, что она не делает лишних операций. Если операции не нужно делать, она их не делает.



Сейчас она использует --diff. Если так получится, что роль ваша работает и действительно делает то, что нужно, но при втором прогоне хотя бы одна вещь поменяется, это будет failed на idempotence.



И самая интересная часть в Molecule — это инфраструктурные тесты. Предположим, мы прогнали, роль прошла и всё установилось. Но чтобы узнать, работает там всё или нет, для этого можно использовать инфраструктурные тесты. Molecule поддерживает разные инфраструктурные тесты.


Три вида:


  • Testinfra (Python, default).
  • Serverspec (Ruby).
  • Goss (written in Go, tests in YAML).

Если так случилось, что у вас в команде уже есть какие-то инфраструктурные тесты, например на Serverspec, то вы можете подключить готовые инфраструктурные тесты в Molecule.


Если инфраструктурных тестов ещё нет, я рекомендую Testinfra, потому что она на Python, так же как Molecule и Ansible, и всё будет сделать проще.



Testinfra положит дефолтовый сценарий в Molecule. И вы можете прописывать туда тесты.



Что, например, можно проверить? Ну, что мы обычно проверяем, когда сконфигурировали что-то на сервере и хотим посмотреть, шевелится оно или нет? Мы лезем в shell, выполняем какую-то команду и смотрим, что нам выдала эта команда.


Это можно сделать вот таким вот образом. Вы пишете питоновскую процедуру, получаете аргументом host, у этого host вызываете метод «run» и дальше можете получить return code и можете получить stdout и stderr и проверить этот вывод.



Мы в assert, допустим, написали, что rc=0. Если бы он был равен не нулю или если бы мы что-то там не нашли, то вывод в питоновском assert умный, он показывает вам контекст вашего питоновского кода: где что слетело, что именно чему не равно.



Когда вы гоняете molecule test, то самое длительное время — это накатка Ansible-скриптов на instances. Чтобы не тратить время на накатку тестовых скриптов на instances, вы можете воспользоваться снова флагом destroy=never и вызывать molecule verify. В этом случае он будет просто выполнять инфраструктурные тесты на ваших готовых instances.



Вот как это выглядит, когда инфраструктурные тесты зелёные. Когда в 2000 году появился JUnit, первая система Unit-тестирования, у них был такой девиз: «keep the bar green to keep your code clean». Так как у нас теперь everything is code, то мы можем то же самое сказать про инфраструктуру.



Что ещё можно проверять? Мы можем проверять всё, что мы можем проверять через командную строку. Установили curl — можем подёргать какие-то сервисы, можем попроверять вывод curl.



Но в Testinfra есть ещё хорошие абстракции для проверки других вещей. Например, процессы. Host.process даёт вам фильтруемую коллекцию процессов, запущенных сейчас на host. Вы можете фильтровать их по какому-то фильтру, по какому-то аргументу, например по названию запущенного файла, и проверить его свойства: например что от правильного пользователя запущен этот процесс, что мы не от root его запускаем, или что он запущен с какими-то определёнными аргументами.



С сервисами всё просто. Можете получить сервис по имени и проверить, запущен он или нет.



Также очень легко проверять файлы и их содержимое. Например, если наш запуск сервиса порождает какие-то логи, в которые мы хотим зайти и проверить, что в этих логах что-то появилось, то это очень легко сделать. Во-первых, по exists мы можем проверить, что файл вообще существует по данному пути, а при помощи contains очень легко можно проверить, что этот файл содержит какую-то подстроку, которая нам сигналит, что всё OK.



Если кто-то любит TDD, то, используя быструю итерацию перезапуска теста, мы можем сначала писать тесты под Ansible, а потом уже сам Ansible.


То есть Ansible-код становится таким же кодом, как код на Java или на Python, и разрабатывать его можно теми же самыми средствами.


Тут, может быть, кто-то вспомнит — пока мы говорили про Testinfra, — что в самом Ansible есть модуль assert. Действительно, хорошая мысль — включить такую проверку прямо в Ansible-роль. Jython — это интерпретатор Python в JVM. Установили, выполнили команду в jython version, проверили, что он даёт какой-то осмысленный вывод.



Хорошая идея. Правда, она не пройдёт, если вы пользуетесь Molecule, потому что Molecule скажет, что этот код не идемпотентный. Почему? Это не пропустит и Ansible-lint. И даже если вы загасите в Ansible-lint эту проверку, то потом Molecule на этапе проверки идемпотентности вас не пропустит.


Поэтому нельзя просто так взять и вставить assert в код Ansible.



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


Идея использовать проверки в хэндлерах мне кажется очень хорошей. Потому что если всё-таки на production что-то пошло не так, то вы узнаете об этом сразу, то есть ещё в процессе запуска Ansible playbooks у вас будет осмысленный вывод.



Ещё в хэндлерах можно проверить файлы и их содержимое, ещё в хэндлерах можно проверить веб-сервисы. В принципе этого достаточно, чтобы организовать такой аналог инфраструктурных тестов, попроще, чем Testinfra, но достаточно мощный. Это хорошая идея, и я призываю писать Ansible-код таким образом.



Теперь немножко поговорим про процесс разработки ролей. Он стандартный. Репозиторий для ролей — это Galaxy. И он жёстко привязан к GitHub, то есть если вы разрабатываете роли в OpenSource, то это только лишь GitHub и, соответственно, GitHub-процесс с проверкой на CI на валидность вашей роли. И то, что попадает в Master, то мы считаем релизом. Если мы хотим ссылаться на какую-то версию, то мы можем поставить какой-то тэг на Master, и это будет ссылка на версию вашей роли.



Как это можно сделать? Запуск Molecule легко организовать в вашей CI-системе. Если это Jenkins, то тут есть некоторые «грабли». Если мы используем Jenkins Multibranch, то он делает checkout с контрольной версии вашего проекта не в папку с названием этого проекта MyRole, а в уникальную папку с большим набором цифр и букв, и в результате у Ansible сносит крышу, потому что он ничего не понимает: он ищет роль под названием MyRole, а там другое название. Но это можно исправить небольшим костылём: если ему подсунуть symlink внутри в подпапочку, то это всё решается.



Вы можете заморочиться и разделить выполнение Molecule на стадии.



Если вы так сделаете, вы получите вот такую красивую матрицу в Jenkins. И если у вас что-то слетело на каком-то этапе, то вы можете увидеть, на каком этапе это произошло. Но честно скажу, что я так не делал. Я видел статью, где было сказано, что нужно делать именно так, но я ограничиваюсь только molecule test.



Другая опция, которая будет для вас более актуальной, если вы разрабатываете роли в OpenSource, даже единственная, — это Travis. Тут вообще всё очень просто. Мы указываем в services docker, мы указываем в инсталляцию, что нам нужно поставить Ansible Molecule docker-py. Кстати, это хорошая идея — указывать явные версии того, что вы хотите поставить, иначе у вас сборка сегодня может выполниться, а завтра нет.


И очень простой скрипт — molecule test, если у вас роль, которую вы разрабатываете, находится в GitHub-репозитории.



И, используя вот такой webhook, вы нотифицируете Ansible Galaxy тем, что роль ваша хорошая: build passing или build failing.


И в результате вы имеете в окне поиска ролей в Galaxy вот такие бейджики. Это не те бейджики, которые в GitHub, это бейджики именно в Ansible Galaxy. Чтобы их получить, надо пользоваться Travis и тем webhook. Из другого CI это, может быть, и можно сделать, но я не понял пока — как. Задокументированный вариант только через Travis.



После того как всё внедрилось, что получается? У нас было вот что — то есть какие-то проекты, какие-то роли, между ними copy-paste-modify и непонятно что.



Что стало. Общие роли, которые мы используем между проектами, мы вынесли в Galaxy, они расшариваются. Соответственно, каждая роль тестируется в Molecule, допиливает роль каждый под свой проект сам, и это становится общим достоянием.


На остальную часть, которая уникальна для каждого проекта, навешен linting. Вот эти три инструмента — YAMLLint, Ansible-lint и Syntax Check. И это уже гораздо лучше, чем было.



Но возникает вопрос: можно ли здесь пойти дальше и улучшить что-нибудь ещё? У нас есть часть, которая в Ansible Galaxy и которая проверяется Molecule, у нас имеются роли, которые мы в Molecule тоже можем проверить внутри нашей подроли. Но есть большая часть конфигурационного кода, который содержит переменные, всякие конфигурационные файлы, и это достаточно большие куски кода, которые не проверяются никак. Вернее, мы проверили, что YAML в well format, и больше ничего мы не проверили здесь.



Можем ли мы ещё что-то проверить? Как быть с конфигурацией? Плохая новость в том, что Molecule — только для ролей. Но вы можете, правда, проверить в Molecule комбинацию ролей.


Но проверить развёртывание на прод можно только развернув на прод, потому что у вас там прописано, на какие машины вы устанавливаете, как вы связываете это всё, и только в процессе развёртывания на прод это запустится.



Но хорошая новость в том, что вы можете проверить проект, не запуская его.


Это докладчики с конференции по тестированию ПО — Heisenbug. Они рассказывали про такую вещь, как конфигурационное тестирование.



На самом деле идея очень-очень простая. Если у вас имеется некий конфигурационный файл, то что мы можем проверить?


  • Мы можем проверить формат значений переменных. Если у нас в значении порт, то это именно порт, а не что-нибудь ещё, если хост, то это хост, если URL, то URL.
  • Мы можем проверить, что у нас не утекают в явном виде пароли.
  • Мы можем проверить уникальность портов. Потому что если вы назвали две переменные одинаковым портом, то, как правило, это ошибка, в Ansible особенно.
  • И другие более специфические вещи. Например, Андрей Сатарин рассказывал в своём докладе о том, что ему необходимо было проверять, что разные instances какого-то сервиса ставятся в машины, расположенные в разных стойках, потому что иначе не будет достаточного failsafe. Потому что если их запихнуть все в одну стойку и стойка отключится, то тогда какая разница, что у нас было три instances?

На самом деле идея очень простая: мы берём и пишем эти проверки, используя тестовые framework, например pytest. Просто это неожиданно, потому что это не исполняемый код, а статический. Сейчас я покажу, как это может выглядеть.



Например, как мы можем проверить валидность портов? Мы можем написать вот такой вот параметризованный тест, являющийся частью pytest, который проверяет для всех пар ключ значений, где ключ — это переменная, которая держит порт, а значение — это значение этой переменной. Мы можем обычные asserts написать: что это интовое поле, что порт находится в разрешённом вами диапазоне. То есть идея очень простая. Магия находится вот где: вам надо написать генератор на Python, который вам будет выдавать все values-значения для переменных, в которых содержатся порты.



Как же мы отличим переменные, которые содержат порты, от других переменных? Мы это можем сделать вот таким вот образом. Мы можем договориться, что наши переменные, в которых прописываются порты, имеют постфикс ‘port$’. И если у вас есть другая функция «var_values», которая выдаёт вам все переменные, все пары ключ-значений, все переменные в вашем Ansible playbooks, то вот таким вот образом вы можете отфильтровать из этого потока var_values только те, которые оканчиваются на «port», и, как было показано слайдом ранее, параметризовать тот тест, и тогда этот тест будет выполняться на портах, будет проверять их.


А как написать var_values? Взять на Python и написать. Это же рекурсивный обход YAML-дерева. Примерно мы знаем, как устроены YAML у Ansible, и надо просто вытащить оттуда все куски, которые содержат переменные.



Это не так сложно, но, так как мы рассчитываем уже на некоторые соглашения, которые есть в вашем коде, то здесь не может быть универсального решения, это решение придётся самим разрабатывать.


Как можно проверить уникальность портов? Очень просто. Если у нас уже есть port_var_values, который возвращает нам все переменные с портами, то мы можем просто собрать их в один сет и на каждом шаге проверять, что они задают уникальный порт.



Таким образом мы можем ещё один слоган сделать, теперь уже для тестов конфигурации: «keep the bar green to keep the configuration clean». Вот как это выглядит, когда всё сработало.


Обратите внимание: там внизу написано, что у нас выполнился сто один тест. Это не значит, что мы написали сто один тест. Это значит, что параметризованные тесты выполнились сто один раз для сто одной пары переменной и значения. Он всё просуммировал, естественно. То есть большие количества проверок не означают, что для этих проверок надо много кода писать. На самом деле кода писать надо довольно мало, так же, как и для инфраструктурных текстов.



И напоследок — пример, как можно поймать «утекающий» пароль в таком случае. Во-первых, мы из всего потока ключ-значений переменных выделим те, которые содержать в себе пароль.


Выделить их можно так: они заканчиваются либо на слово «password», либо на слово «pass», либо на «pwd». Во-вторых, мы просто проверяем, что значением должен быть placeholder, который ссылается на что-то, например на какую-то переменную, которая находится в Vault. И всё, этого достаточно.



Вот это реальный случай. Был такой pull request. Запустился тест. И этот тест возвращает мне: k=’myskq_root_password’, v=’12345’. То есть не то плохо, что password «12345», а то, что он в GitHub теперь. И понятно, что такой pull request мерджить нельзя.


Давайте к выводам переходить.



Первый вывод — тестируйте ваш Ansible.


  • Во-первых, эту троицу: YAMLLint + Ansible-lint + Syntax check — вы можете подсоединить прямо сегодня. Если у вас есть какие-то портянки с Ansible-кодом, есть какие-то репозитории, где всё это лежит, просто возьмите и подключите, посмотрите, какие ворнинги там возникнут. Потом у вас будет увлекательное время, чтобы пофиксить. Ansible-lint может вам многое рассказать, многому вас научить, потому что его best practices многие не знают.
  • Проверяйте роли на Molecule.
  • Вставляйте проверки в хэндлеры. Делайте ваши playbook failed fast. Так, чтобы, если что-то пошло не так, мы бы об этом узнали как можно раньше.
  • И тестируйте конфигурацию.


  • Если у вас где-то есть роли — попробуйте Molecule. Это просто: pip install molecule, molecule init, molecule test.
  • Лень разбираться с тестами? Попробуйте converge и idempotence.
  • Лень разбираться с converge и idempotence? Пусть он хотя бы синтаксис проверит.


Я верю, что всё должно быть как код: инфраструктура как код, база данных как код, документация как код. Что это значит — быть как код? Это не только лежать в репозитории, не только лежать в Git, это значит, что должен быть pipeline, должны быть quality gates, должна быть процедура изменения этого кода.



Штатный набор инструментов, который они сами предлагают, — это GitHub + Travis + Galaxy. Это если вы разрабатываете роли в OpenSource. Правда, я вижу мало поводов разрабатывать роли не в OpenSource. Jenkins Multibranch тоже отлично для нас работает.



Ссылки, про которые я упоминал, будут на слайдах, которые будут расшарены.



Вопросы


Вопрос: Спасибо за доклад! Меня зовут Роман. Я хотел бы уточнить: делали ли вы написание тестов обязательным, чтобы, например, выложить роль в общее пользование? И если да, то насколько это увеличило порог вхождения в Ansible? Потому что Ansible всем подаётся как простая система управления конфигурациями, а в итоге человеку нужно знать Python, Testinfra, разобраться с Molecule. И количество боли увеличивается, потому что каждое изменение нужно покрыть тестами на Python, который может ему не нравиться, или он его не знает.


Ответ: Как инфорсить написание тестов? Оно точно так же форсится, когда вы пишите на Java. Сделали pull request — и решаем: тесты нужны или не нужны. Касательно порога вхождения, роли — это какой-то расшаренный код, то есть это как библиотечный код. Как правило, если человеку нужно что-то быстро подправить, то он работает с кодом, который уже существует, поэтому он просто зайдёт и подправит. Тесты для этого не нужны. Тесты на playbooks не нужны. Есть тесты на ролях, которые именно расшарены между собой. И даже если он что-то подправит, то надо ли менять тесты? Потому что это же не тесты как код. Инфраструктурные тесты очень просты, там два-три стейтмента, которые просто проверяешь. Мы запустили что-то в shell, а этот shell нам что-то вывел. Поэтому мы можем достаточно многое поменять в роли, а тесты не менять.


Вопрос: Тестировали ли Ansible в Windows? Мы запускаем Ansible playbook на Windows. И как это тестировать? Может быть, есть у вас опыт в этом?


Ответ: К сожалению, нет у меня опыта разворачивания Ansible чего-то на Windows и тестирования этого в Testinfra. Мы разрабатываем под Windows, то есть Ansible в cygwin запускается и работает. Но все роли мы выкладываем на Linux-сервера и тестируем там, поэтому не могу ответить. Может быть, возможно. Но у меня такого опыта не было.


Вопрос: Меня зовут Михаил, у меня такой вопрос. Выносить проверки в хэндлер — это хорошая идея. Но как мне поступить, если от результата проверки зависит роль? То есть если логика роли будет меняться. Поможет ли мне в этом changed_when, например?


Ответ: Вы имеете в виду, что вы хотите запустить что-то, получить какой-то вывод в консоли, захватить его в переменную и в зависимости от результата этого вывода выполнить либо одно, либо другое?


Вопрос: Допустим, проверить, есть ли пользователь и могу ли я залогиниться с паролем, который у меня есть. И если пользователя нет, то создать пользователя.


Ответ: Это не про проверки, это не про asserts, это про идемпотентность вообще. Тут надо писать идемпотентные скрипты. Выход тут один. Если вы пользуетесь command или shell, то у вас есть возможность указать ситуации, при которых он не будет выполняться. И если вы уже создали нужного вам пользователя, то у вас должно быть такое условие, при котором эта ветка даже отрабатываться не будет. То есть идемпотентность — это значит, что мы отработали один раз — создали то, что нам надо. Потом мы отработали второй раз и проверили, что нет этого. И оно никакие модификации Ansible выполнять не будет. В принципе, если подумать, то это всегда можно сделать, так или иначе. Это частности.


Вопрос: Хотел бы в продолжение заданного вопроса уточнить следующий момент. Насколько мне известно, в Ansible можно писать задачи для gathering facts study, когда он собирает информацию перед тем, как катить…


Ответ: Ну да.


Вопрос: И, допустим, проверка JUnit на совпадение версий, — что, если её вынести не в хэндлер, а в gathering tasks, чтобы сначала проверить, та ли это версия, а потом уже накатывать?


Ответ: Интересная мысль. Но надо посмотреть, будет ли он воспринимать этот код как идемпотентный. Это пред-проверка, а там пост-проверка. Во-первых, чем хороша проверка в хэндлере? Тем, что это пост-проверка. Мы считаем, что мы его установили, но ещё мы дёрнули хэндлер, чтобы он зашёл, выполнил и проверил, что он даёт какой-то осмысленный вывод. Пред-проверка не будет проверять результат действия. Но мысль интересная, надо посмотреть: таски, выполняющиеся на этом этапе, будут ли считаться идемпотентными или нет? Это надо посмотреть.


Вопрос: Например, у меня есть роль, которая из GitHub качает архив, распаковывает его. И у меня в роли в начале стоит проверка, которая просто проверяет вывод команды на version. Но у меня это является условием, по которому ставить или не ставить.


Ответ: Правильно. Это нормальный валидный подход. Если вы будете это на Molecule тестировать, она вам ничего плохого не скажет.


Вопрос: Меня зовут Владимир. Приходилось ли вам писать или тестировать более сложные вещи, как корректность выполнения ролей? Например, написать какую-то роль, которая ставит какой-то сервис, и проверить, что она действительно сделала то, что от неё требовалось.


Ответ: Это инфраструктурные тесты. Можно в тесте прописать, что service is running. Имеется в виду то, что у меня на слайде «Сервисы»?


Вопрос: Имеется в виду, что сервис — это довольно просто, а бывают более сложные случаи. Бывает так, что он вроде бы как работает, но на самом деле…


Ответ: Инфраструктурный тест не проверяет функциональность, он проверяет, что там дым не идёт из щелей, что вместо запроса не вывалился стек-трейс, а вывалился какой-то ответ. Ничего сложнее, чем у нас сервер ответ «200» вернул, когда мы что-то запросили. И ничего сложнее на этом этапе не надо проверять. Мы пишем selenium-тесты в наш интерфейс. И если этот артефакт готов к установке, то мы в Ansible говорим: «ставь эту версию». И если она уставилась, то мы проверяем, что он ответ «200» возвращает, но мы уже не проверяем, что она делает то что надо, потому что мы это уже проверили на более ранних этапах pipeline.


Вопрос: Используете ли вы в production роли из Ansible Galaxy и тестируете ли вы их? Имеются в виду чужие.


Ответ: Используем чужие, конечно. Например, nginx, PostgreSQL. Кучу всего мы разворачиваем. Ту же Oracle Java разворачиваем при помощи ролей из Ansible Galaxy. Ролей там очень много. Выбирать её следует обычно по количеству скачиваний. И, как правило, они тестируются. Как правило, роли в Ansible имеют build pipeline. И если не на Molecule, то хотя бы в Travis они запускаются. Можно посмотреть хорошие, признанные роли, например того же Jeff Geerling.


Вопрос: Не страшно выбирать playbook из Ansible Galaxy?


Ответ: В тех случаях, когда я их использовал, это были нормальные роли. Их достаточно много. Конечно, есть и мусор, но если отфильтровать по количеству скачиваний, то сразу же видно. Есть роль, которую скачали 150000 раз, а есть роль, которую скачали 150 раз. И если 150000 раз её скачали, то, может быть, кто-то ею даже пользуется в production.


Вопрос: В моих проектах иногда бывают достаточно сложные навороты с Jinja. Допустим, Jinja может генерировать список хостов, которые я использую как переменные в hosts. Как это будет работать?


Ответ: Если говорить о хостах, то это не про Molecule точно, потому что Molecule тестирует роль, выполняя её на тестовых хостах. У неё там возникает некий динамический inventory. Molecule сама знает, на какие хосты нагонять эти роли, и она их будет выполнять. Если какие-то другие случаи… Что делает Molecule? Molecule всего лишь в какой-то момент выполняет файл, который называет playbook.yml. И что у вас в playbook.yml, ей не интересно, главное, чтобы там был валидный playbook. Если там какая-то сложная Jinja, то — прекрасно. Она либо будет работать, либо свалится. И тогда у вас Molecule свалит это всё.

Tags:ansiblepythondevopsci/cd
Hubs: System administration Python IT Infrastructure Server Administration DevOps
+14
5.5k 113
Comments 18
Ads