company_banner

Как мы победили сумрак между тестированием и эксплуатацией

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

    Выйти из сумрака

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

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

    Эта статья продолжает мой доклад на SQA Days-18.

    Файлы конфигурации


    Первый момент, который мы увидели, это разные файлы конфигурации на тестовых стендах и в бою. В нашем случае это были даже разные по расположению файлы. Если говорить об их содержимом, то их писали разные люди и разными способами. Что это значит? Это значит, что каждый пакет приносил с собой на боевые серверы файл настроек по умолчанию, который лежал в /var/lib/… или /usr/shared/… Это те самые настройки, которые удобны в тестовом окружении. И те из них, которые нужно было заменить, находились уже в файле конфигурации в /etc/имя_пакета. Настройки туда записывали сисадмины, когда получали соответствующую задачу в Jira.

    Давала ли такая схема взаимодействия гарантию того, что конфиг-файлы в бою настроены корректно? Абсолютно нет. Файлы с настройками, которые использовались в боевом окружении, не подвергались тестированию! И поэтому тестировщики и разработчики были частыми гостями в эксплуатации с вопросом: «Мы просили на прошлой неделе изменить вот эту настройку. Вы ее прописали? Покажите, что получилось.»

    Почему возникла такая ситуация? Потому, что на тестовых стендах, как мы привыкли их видеть, сервисы живут на одном сервере. В одной и той же файловой системе. И если настройки одного из них нужно поменять в файле /etc/default/jetty, то настройки другого придется менять как-то по-другому. Для этого на тестовом стенде были написаны особые init-скрипты для управления разными сервисами. И там же, в самописных init-скриптах, были указаны конфиг-файлы, которые лежали в нестандартных местах.

    Разрешить конфликт


    Как же разрешить этот конфликт? Мы решили, что нужна изоляция сервисов друг от друга на уровне файловой системы. Ведь в бою каждый сервис крутится на отдельном сервере или виртуалке.

    Возможно, chroot сможет решить задачу изоляции сервисов на тестовом стенде на уровне файловой системы. Тогда каждый сервис будет иметь свою папку /etc и свои файлы конфигурации, которые расположены там же, где они находятся на боевых серверах. И лог-файлы будут находиться в тех же самых папках, что и в бою. И такое решение приближает к решению задачи, как использовать те же конфиг-файлы на тестовом стенде, что и в production environment.

    Достаточно ли изоляции на уровне файловой системы?


    На старых тестовых стендах все сервисы слушали localhost, каждый на своем порту. И общались между собой через localhost. Однако на настоящем сайте каждый сервис живет на своем сервере. И, более того, каждый сервис запущен в 2-х и более экземплярах. Такое решение нужно, во-первых, для распределения нагрузки и горизонтальной масштабируемости сервиса. И, во-вторых, для обеспечения надежности работы сервиса. В тех случаях, когда один из серверов должен быть остановлен на обслуживание, другие такие же берут на себя его часть работы.

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

    Сисадминам выгодно?!


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

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

    Балансировщик


    И на новых стендах будет балансировщик нагрузки. Тогда нам потребуется в конфигурации nginx прописать IP и порты серверов в upstream. Возможно, будет удобнее действительно выделить каждому сервису по серверу, на котором он сможет работать.
    Итак, в дополнение к изоляции серверов по файловой системе, мы добавили изоляцию по IP. Кроме того, вспомогательные сервисы, такие как rsyslog, смогут работать по тому же принципу, как и на настоящем сайте. Пожалуй, только в том случае, если конфиг-файлы каждого сервиса будут те же, что и в бою.

    И это – третий пункт нашего списка задач. Как обеспечить использование тех же самых файлов конфигурации и с тем же содержимым, как на тестовых стендах, так и на боевых серверах?

    Как добиться одинаковых конфигов?
    Если мы уже решили, что каждый сервис будет запущен на отдельном сервере, то, может быть, мы сможем использовать и скрипты deploy, которые есть у сисадминов? И с их помощью раскладывать на тестовый стенд те же файлы конфигурации, что и на большом сайте? Да, мы можем так сделать. С учетом того момента, что пароли от платежных систем или СМС-рассылок должны остаться в секрете.

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

    Конфиги на GitHub


    Чтобы спрятать пароли в файлах конфигурации, мы используем переменные. Потому что сами файлы конфигов стали шаблонами Jinja2 для Ansible. Мы написали свою систему выкладки с его использованием. И Ansible позволяет нам иметь два набора переменных, т.е. две пары папок group_vars и host_vars, где определены значения переменных. Один набор находится в папке playbooks, а другой – в папке с файлом inventory. И один из этих наборов всегда имеет приоритет над другим.

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

    Храним секреты


    Секретные же значения, такие как пароли к платежным системам и сервисам СМС-рассылок, а также пароли к базе данных, находятся в приватном репозитории сисадминов и недоступны тестировщикам.

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

    Отдельный сервер для каждого стенда – расточительно
    Из написанного выше мы видим, каким требованиям должны отвечать те отдельные серверы для сервисов, которые мы будем запускать на тестовом стенде:

    • изоляция файловой системы;
    • отдельный IP-адрес;
    • init для запуска наших и сопутствующих пакетов;
    • opensshd для работы Ansible.

    Эти требования могут удовлетворить контейнеры Linux, LXC. Дополнительным плюсом при их использовании будет то, что они используют общую память, выделяя ее по мере того, как приложения внутри контейнеров ее запрашивают. И, в отличии от виртуальных машин, не откусывают сразу большой кусок RAM. Благодаря этому мы экономим память.

    Каковы выгоды нашего решения


    В результате того, что мы

    • выделили псевдосервер для каждого сервиса на тестовом стенде;
    • используем те же скрипты выкладки и те же файлы настроек, что и в бою;
    • повторили на Linux-контейнере внутри стенда внутренний балансировщик;
    • вынуждены сначала собирать пакет из ветки Git, чтобы выкатить его на свой псевдосервер с помощью deploy скриптов,

    мы добились некоторых результатов.

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

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

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

    И, в-четвертых. Теперь мы можем запустить два или больше экземпляра каждого сервиса. Мы не только можем отладить работу retries в nginx, но еще и протестировать, как ведет себя сайт во время выпуска новой версии.

    Представляете, что теперь можно запустить автотесты в тот момент, когда мы симулируем выход новой версии сайта! И добиться стабильной работы стенда в то время, когда один сервер уже работает на новой версии ПО, второй остановлен для обновления, а третий еще отвечает со старой версией. Вот достойная задача!

    Результат


    Подведем итог. Реинжиниринг процесса тестирования и рефакторинг тестовых стендов дали великолепный результат. За 2 года uptime сайта превысил 99,9%. Это отличный показатель, если пересчитать его в минуты. За один месяц простой сайта составляет меньше 43 минут. При этом мы в 3 раза ужесточили определение downtime, с 60 500-х ошибок в секунду до 20.

    И для интернет-бизнеса, которым и является HeadHunter, улучшение uptime означает реальную экономию денег. Прибавьте к этому клиентов, которых привлек hh.ru благодаря более стабильной работе сайта, чем раньше. Как вы думаете, является ли uptime сайта ключевым фактором успеха для вашего бизнеса?

    Итак, 4 простых шага:

    • выделите каждому сервису отдельный сервер на стенде;
    • тестируйте пакет, а не ветку с кодом;
    • используйте скрипты выкладки от эксплуатации и тестируйте конфиг-файлы;
    • повторите балансировщик и тестируйте процесс релиза (о том, как это сделать, irreality уже рассказывал).

    Победа света неизбежна!

    HeadHunter
    111,54
    HR Digital
    Поделиться публикацией

    Комментарии 3

      +1
      Отлично! Нам тоже понравился ansible, но встал вопрос GUI к этому делу. Как вы у себя решили задачу фронтэнда для ansible? Ведь иногда очень легко накосячить и с ansible… — Последствия будут фатальны. Мы используем rundeck для таких задач.

      Я думаю, что компании Вашего уровня, ansible tower, может быть не очень дорогим, но цены у него во истину не демократичные. От 5тыс. долларов за год на 100 узлов.
        0
        Благодарю за отзыв о компании. Также принимаю его как личный комплимент. Спасибо.

        Что касается аудита запущенных плейбуков и результатов их работы, то в ansible.cfg мы прописали логгирование в определенный файл. И дописали callback plugin по логгированию действий: pastebin.com/3Kni2U1r

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

        Что касается самих плейбуков Ansible, то чтобы там не сделать что-то неправильно, надо использовать тот самый тестовый стенд, на котором плейбук можно отладить. Кроме того, мы создали общий сценарий выкладки любого пакета. Таким образом, описание выкладки приложения выглядит следющим образом:

        - include: common-head.yml hosts='some_group' role='null' rsyslog_role='some_group' okagent_role='some_group' actions='[ "deploy", "check", "restart" ]'
        
        - hosts: some_group
          sudo: yes
          gather_facts: no
          serial: 1
          vars_files:
            - app_versions
          vars:
            role: some_role
            service_name:
              service: 'hh-service-name' # инит-скрипт
              on_config: restart
              configs:
                - { file: 'etc/hh-service-name/config_file.cfg', mode: '644' }
                - { file: 'etc/default/hh-service_name', mode: '644' }
              port: 999
              uri: '/status'
              list:
                - { pkg: 'libcurl3', ver: '{{ ver_libcurl }}' }
                - { pkg: 'python-tornado', ver: '{{ ver_tornado }}' }
                - { pkg: 'hh-service_name', ver: '{{ ver_hh_service_name }}' }
        
          tasks:
            - { include: '{{ inventory_dir }}/roles/common/tasks/dpkg-app.yml', pkg: '{{ service_name }}' }
        
          handlers:
            - { include: '{{ inventory_dir }}/roles/common/handlers/dpkg-app.yml', pkg: '{{ service_name }}' }
        


        Общие для хостов задачи решаются в common-head. Когда дело доходит до выкладки приложения, то вот что делает dpkg-app, tasks + handlers:
        • проверка, что версия пакета будет меняться
        • выключение сервера из балансировщика
        • обновление версии
        • обновление конфигов
        • если конфиги менялись, то выключение сервера из балансировщика; не дублируем, если уже отключали
        • запуск (перезапуск) сервиса, если меняли версию или конфиги
        • ожидание открытого порта
        • ожидание удачного ответа на указанный uri
        • включение в балансировщике

        Поскольку плейбук такой простой, а «библиотека» dpkg-app хорошо отлажена, то проблем с косяками у нас не бывает.

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

        Вы совершенно верно указали на задачу, которую решает наш способ: проверить, что даже доставка приложения в бой произойдет успешно!
        0
        Здесь был ошибочно опубликованный комментарий

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

        Самое читаемое