Тестирование серверов с InSpec и Test Kitchen

Illustration of a line of cartoon Maneki-neko cats with one tipping over and small papers flying out. Illustration of a line of cartoon Maneki-neko cats with one tipping over and small papers flying out.

У любого начинающего DevOps специалиста очень быстро возникает вопрос о том, как писать свои скрипты управления конфигурацией, проверяя их локально, а не на реальном сервере. В современных девопс практиках принято воспринимать инфраструктуру как код, а любой код нуждается в хорошем тестировании прежде чем быть отправленным в production. О том, как облегчить себе жизнь при разработке в этой сфере, мы и поговорим в данной статье.

Зачем нужно тестирование?

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

InSpec

Это и есть тот самый волшебный фреймворк, который мы будет использовать. На момент написания он еще сыроват, но работает шустро и имеет довольно богатый DSL – благодаря RSpec, на котором он основан. Как и в случае с RSpec, все ваши тесты представляют собой код на Ruby, так что можете смело использовать все возможности этого языка.

Весь его DSL представляет собой вызов каких-то ресурсов для работы с файлами, сервисами, конфигами и т.д.:

   describe file(arguments...) do
     ...
   end

Внутри каждого такого блока указываются различные проверки. Какие именно проверки поддерживает ресурс описано в доках к ресурсу.

   describe file("/file.txt") do
     it { should exist }
   end

Значение каждой такой проверки можно проверять разными способами.

be используется для сравнения значений с числом:

   describe file('/proc/cpuinfo') do
     its('size') { should be >= 120 }
   end

eq – сокращение от equivalent. Он просто проверяет на равенство. Обратите внимание, что он сравнивает без приведения типов, т.е. 2 = 2, но '2' != 2.

   describe sshd_config do
     its('Protocol') { should eq '2' }
   end

cmp - тоже самое, что и eq, но с приведением типов:

   describe sshd_config do
     its('Protocol') { should eq 2 }
   end

include проверяет, есть ли значение в списке:

   describe passwd do
     its('users') { should include 'my_user' }
   end

match проверяет, есть ли какое значение в строке. Его особенность заключается в том, что он принимает регулярные выражения:

   describe sshd_config do
     its('Ciphers') { should_not match '/cbc/' }
   end

Так же вы можете объединить множество таких проверок в control. Плюс этого синтаксиса в том, что вы можете явно указать что именно вы тестируете. Это что-то в духе shared_examples в RSpec:

   control "tmp" do
     title "Тестируем tmp папку"
     describe "Какое-то дополнительное описание"

     describe file('/tmp') do
       it { should exist }
       it { should be_mounted }
     end
   end

Запуск тестов

Inspec поддерживает 4 способа запуска.

   # 1. Локальный запуск, когда вы хотите протестировать собственную конфигурацию
   inspec exec test.rb

   # 2. Запустить тесты на сервере по SSH
   inspec exec test.rb -t ssh://user@host

   # 3. Запустить тесты на Windows сервере через WinRM
   inspec exec test.rb -t winrm://admin@windhost

   # 4. Запуск тестов в docker контейнере
   inspec exec test.rb -t docker://conainer_id


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

Kitchen Test

Kitchen Test представляет собой инструмент, который поднимет за вас виртуальную машину и установит туда ОС, в которой мы и будем проверять наше приложение. Он поддерживает различные платформы, инструменты и типы виртуализации, такие как Amazon EC2, Blue Box, CloudStack, OpenStack, Vagrant, Docker и т.п.

Что должно быть на нашей машине

В первую очередь Ruby, на котором и написаны все наши инструменты. Какая-то система виртуализации, я использую vagrant-libvirt, а так же Vagrant. Если вы хотите подробнее разобраться в различных способах виртуализации и выбрать что предпочтительнее использовать, вам сюда.

Test Kitchen устанавливается командой gem install test-kitchen. Как поставить и настроить остальные инструменты, вам придется найти самому, перейдя по ссылкам в предыдущем абзаце.

Давайте сразу заполним Gemfile минимально необходимым набором:

   source "https://rubygems.org"
   gem "test-kitchen"
   gem "kitchen-vagrant"

Установим все с помощью комнды bundle install и подправим конфигурационный файл .kitchen.yml:

  ---
   driver:
     name: vagrant

   platforms:              # список ОС, в которых мы будем проверять
     - name: centos-7.2    # наши рецепты.
       driver:
         customize:
           memory: 2048    # настройки для данной ОС

   suites:                 # список окружений
     - name: default       # имя окружения

Список созданных вам окружений можно посмотреть, набрав kitchen list.

Создадим описанную нами виртуалку:

   kitchen create default-centos-72

Теперь вы можете зайти на нее с помощью kitchen create default-centos-72 и делать все там. Больше можете не бояться того, что вы угробите что-то на реальной железке :) Сейчас вы можете выполнять вашу настройку в созданной виртуальной машине и либо переносить ваши тесты на неё и запускать, либо сказать InSpec зайти по ssh и проверить. Доступ по ssh такой же, как и для всего, что поднял vagrant: ssh vagrant@localhost -p 2222.

Все это, конечно, здорово, но не очень удобно.

Test Kitchen + InSpec + Chef

Да-да, все это можно объединить вместе и получить очень даже удобную систему разработки. Что такое Chef и с чем его едят вы можете прочитать тут. Если для управления конфигурацией вы предпочитаете Puppet, Ansible или что-нибудь еще – используйте, это не принципиально. В рамках этой статьи мы напишем небольшой рецептик для Chef, устанавливающий Nginx, который и протестируем в нашей виртуальной машине.

Первичная настройка

В нашем Chef репозитории создадим тестовый кукбук:

   chef generate cookbook cookbooks/nginx_test

Перейдем в папочку с кукбуком и заведет там Gemfile со следующим содержимым:

   source "https://rubygems.org"
   gem "inspec"
   gem "berkshelf"
   gem "test-kitchen"
   gem "kitchen-vagrant"
   gem "kitchen-inspec"

После чего установим гемы и инициализирует Kitchen:

   bundle install
   kitchen init

Test Kitchen создаст конфиг .kitchen.yml со своими настройками. Давайте сразу подправим его.

   ---
   driver:
     name: vagrant # виртуализация

   provisioner:
     name: chef_zero # кто будет выполнять команды. Тут может быть еще
                     # Puppet, Ansible и т.д.

   verifier:
     name: inspec  # чем прогонять тесты

   platforms:              # список ОС, в которых мы будем проверять
     - name: centos-7.2    # наши рецепты.
       driver:
         customize:
           memory: 2048    # настройки для данной ОС

   suites:                 # список окружений
     - name: default       # имя окружения должно совпадать с папкой тестов
       run_list:           # run_list Chef
         - recipe[nginx_test::default]
       attributes:

Создадим описанную нами виртуалку:

   kitchen create default-centos-72

Можете убедится, что она поднялась и работает, зайдя на нее при помощи kitchen login.

Пишем рецепт

Особо заморачиваться не будем, поэтому просто подключим уже существующий рецепт установки nginx и вызовем его:

# metadata.rb

   ...
   depends 'nginx'
   depends 'yum'
# recipes/default.rb

include_recipe 'nginx'

Теперь попросим berks подтянуть зависимости и выполним рецепт в виртуалке:

   berks install
   kitchen converge default-centos-72

Пишем тест

Тесты должны лежать в test/integration/имя виртуалки/. Если вы планируете использовать несколько тестовых фремворков, то нужно создавать еще подпапки с их названием.

# test/integration/default/test_spec.rb

   describe package("nginx") do
     it { should be_installed } # пакет должен быть установлен
   end

   describe file("/etc/nginx/sites-available/default") do
     it { should exist }  # файл настройки должен существовать
   end

   describe command("curl localhost") do
     its("stdout") { should match "404 Not Found" }
     # при запросе nginx должен отдать нам 404
   end

Время запустить наш тест!

   kitchen verify default-centos-72

Удалить созданную машину можно так: kitchen destroy default-centos-72.

Хочу заметить, что, если у вас уже проделан весь этот фронт работ, вам не обязательно каждый раз сначала создавать машину, потом выполнять в ней рецепт и затем запускать проверку. Все эти 3 действия вы можете реализовать одной коммандой: kitchen test default-centos-72

Ну вот, собственно, и все. Мы научились создавать виртуалки для наших Chef рецептов и тестировать их с помощью InSpec. Удачи вам с вашим приложением :)