Тестирование серверов с InSpec и Test Kitchen
У любого начинающего 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. Удачи вам с вашим приложением :)