Почему мы уверены в том, что развернули

image
Часто бывает, когда что-то не работает. И никто не хочет, чтобы что-то не работало по его вине. В контексте больших инфраструктур и распределенных приложений ошибка конфигурации может быть фатальной.
В статье я покажу как правильно тестировать окружение для приложения, какие инструменты использовать, приведу примеры удачного и целесообразного тестирования.
Статья будет интересна командам, которые практикуют DevOps или SRE, ответственным Dev, и прочим хорошим людям.

Следует сказать, что реализовывать концепцию Infrastructure as Code можно и без тестирования. И даже будет работать. Но разве есть предел совершенству?
Тестирование к нам пришло из мира разработки, тестирование — это целая огромная эпопея, которая включает в себя такие варианты реализаций:
  • Юнит тесты (тестируют код) — есть функция, которая принимает на вход один параметр, проводит вычисления и возвращает результат. Юниты — проверка того, что результат соответствует ожидаемому, грубо говоря, что 1 == 1.
  • Интеграционные тесты (тестируют взаимосвязь компонентов) — есть приложение, которому необходима база для корректной работы, и в ней табличка `users`. Этот тест проверит что есть база, что есть табличка, что все готово к работе.
  • Системные тесты (тестируют приложение в целом) — есть приложение, оно должно конвертировать файл. Тест проверяет, что приложение конвертирует, и результат нас устраивает.
  • Дымовые тесты (имитируют поведение пользователя) — тестирует, что пользователь может открыть в браузере сайт, сделать log in, загрузить изображение, и сконвертировать его.

И тут может прийти лживое осознание, что в нашем мире Ansible/Chef/Puppet/Salt все очень примитивно, все очень просто, и в экосистеме наших тулзовин нету реализации чего-то подобного.
А вот как-бы не так.
Все это есть, все это используют уже давно и успешно.

Чем тестировать


Уточню сразу, что мы не будем рассматривать высокоуровневое программирование для Configuration management. Например, если вы написали свой Ansible модуль, или Chef LWRP — ваших знаний достаточно, чтобы понять как правильно тестировать ваше творение.

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

Везде будем решать одну задачу для примера: мы должны убедиться в том, что пакет 'apache2' у нас установлен, и сервис 'apache2' запущен и работает. В конце проверяем, что порт 80 кто-то слушает.

ServerSpec


image
Первый фреймворк для тестирования, который мы рассмотрим — Serverspec.
Пример использования:
require 'spec_helper'

describe package('apache2') do
  it { should be_installed }
end

describe service('apache2') do
  it { should be_enabled   }
  it { should be_running   }
end

describe port(80) do
  it { should be_listening }
end

InSpec


image
Второй фреймворк — InSpec.
Синтаксис идентичный Serverspec.

Testinfra


image
Третий фреймворк — Testinfra.
def test_apache_is_installed(Package):
    apache = Package("apache2")
    assert apache.is_installed

def test_apache_running_and_enabled(Service):
    apache = Service("apache2")
    assert apache.is_running
    assert apache.is_enabled

def test_apache_port(Socket):
    sock = Socket("tcp://80")
    assert sock.is_listening  

Goss


image
Четвертый фреймворк — Goss.
package:
  apache2:
    installed: true
service:
  apache2:
    enabled: true
    running: true
port:
  tcp:80:
    listening: true

Serverspec/InSpec/Testinfra/Goss?


Как видно по синтаксису — есть из чего выбрать. Кому что больше нравится, то лучше всего и использовать. Например, если у вас Chef — отлично ляжет InSpec или Serverspec. Если Ansible или Salt — круто подойдет Testinfra или Goss.

Все отличия — это наличие или отсутствие готовых модулей, первым делом необходимо подготовить список необходимых требований для фреймворка и искать тот, в котором есть все что нужно (или почти все).

Окей, выбрали. Что же именно тестировать?

Что тестировать


Несколько недель назад мы общались в нашем DevOps Community по поводу того, что именно нужно тестировать, нужно ли вообще что-то тестировать, сколько лет лететь до Trappist-1 и так далее.
Например, ctrlok считает что тестировать декларативные штуки не правильно. И я с ним согласен.

Оговорюсь, что точно не нужно тестировать:

  • Разные декларативные штуки (наличие пакета, версию пакета, присутствие файла) — обо всем этом позаботится ваш Configuration management.

Что нужно тестировать:

  1. Вещи, которые когда-то/недавно уже ломались
  2. Вещи, которые потенциально могут поломаться
  3. Вещи, которые кто-то (коллега) может поломать из-за незнания
  4. Вещи, без которых ничего точно работать не будет

У нас есть очень много приложений в контексте одного продукта, и все они требуют тонкой настройки окружения для нормальной работы. Одним из самых тревожных и узких мест было приложение, которое занимается конвертацией различных форматов документов. Не трудно догадаться, что в нем используются десятки пакетов и сотни библиотек, и это все может внезапно перестать работать, если обновляется какой-нибудь пакет, от которого оно зависит.
Для того, чтобы правильно оценить приложение и подготовить его для тестирования — нужно думать как тестировщик. Или, например, проконсультироваться у команды QA.
Во время общения необходимо продумать основные тест-кейсы и попытаться покрыть все из них. Практический пример (на нашем приложении):

  • Конвертация из pdf в html
  • Конвертация из html в pdf
  • Конвертация из doc/docx в pdf

В нашем случае отдельные пункты — это отдельные модули приложения, именно так мы их и будем тестировать.

Давайте тестировать!


Покажу небольшой пример интеграционно-системных тестов первого модуля приложения. Уточню, что у нас есть роль, которая должна правильно установить консольную утилиту pdf2htmlex, где четко описаны все зависимости и необходимые штуки, т.е. мы имеем ввиду что процесс развертывания окружения прошел успешно, и наступил этап тестирования инфраструктуры.
Для начала, предлагаю убедиться что бинарник доступен, его можно запустить, и что-то получить в результат:
# Validate binary exists and working
describe bash('pdf2htmlEX --version') do
  its('stderr') { should match /pdf2htmlEX version 0.14.6/ }
  its('exit_status') { should eq 0 }
end 

Теперь давайте протестируем, что этот бинарник реально умеет конвертировать тестовый документ, и результат нас устраивает:

# Try to convert and validate result html
describe bash('pdf2htmlEX /opt/test/pdf-sample.pdf /tmp/pdf-sample.html') do
  its('stderr') { should match /Working/ }
  its('exit_status') { should eq 0 }
end

describe file('/tmp/pdf-sample.html') do
  it { should exist }
  its('content') { should match //}
  its('size') { should eq 267189 }
end

В данном случае, если тест пройдет успешно — мы можем четко декларировать, что модуль приложения, который отвечает за конвертацию из PDF в HTML работает. Мало того, он работает корректно, нас устраивает результат его работы.

Сложно и долго делать такой тест? Не думаю.
Сколько это сэкономит денег бизнесу? У каждого своя цена ошибки.

Benefits


Стоит сказать, что тестировать окружения мы начали не сразу. Зачем тратить время на такие вещи — это же очевидно, что все мы супермены, и пишем код для Configuration Management сразу без ошибок?
И такой подход работает, когда DevOps команда состоит из 1–2 человек. В таком случае каждый инженер знает как должно работать приложение, знает как готовить для него окружение, и как его «потыкать» — посмотреть, что оно будет работать на площадке, которую он только что подготовил.
Все заканчивается тогда, когда в production среде все должно работать, но не работает. Начинается debug процесс руками на проде или откаты-роллбеки, а каждая минута очень дорого стоит для бизнеса.
Именно поэтому тестирование инфраструктуры — это инвестиция в будущее.
Теперь, когда что-то ломается — мы абсолютно уверены, что наша часть работы была сделана на 100%, и с нашей стороны все вопросы закрыты.
Поэтому мы и уверены в том, что только что развернули.

Выводы


Все мы работаем с приложениями, которые очень часто меняются в очень динамичном мире благодаря нашим же правильно настроенным процессам — CI  и CD. Мы все очень серьезно относимся к работе самих приложений, тестируя со всех сторон одно и тоже. И в это же время халатно относиться к инфраструктуре, на которой это приложение работает — не очень разумно. Тем более, это чревато большими последствиями.
Для себя мы решили, что будем тестировать в таких случаях:
  • если в модуле системы есть сложная логика (комплексность)
  • если модуль уже сейчас часто ломается (доступность)
  • если нужно передать специфичные знания (коммуникация)
  • если ошибка будет стоить нам больше Х денег (бизнес-ценность)

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

А сколько процентов вашей инфраструктуры покрыто тестами?

Комментарии (0)

© Habrahabr.ru