Terraform, Azure, Иркутск и еще 1207 слов о переносе игры в облако

    У нас были балансировщики нагрузки, несколько серверов приложений, 5 баз данных, 24 ядра, 32 гигабайта оперативки, nginx, php, redis, memcached и еще куча других сетевых технологий всех форм и расцветок. Не то чтобы это был необходимый минимум для бэкенда, но когда начал делать отличные онлайн-игры, становится трудно остановиться. Мы знали, что рано или поздно перейдем и на облако.



    Это теперь мы делаем бэкенд для игр на основе микросервисов — раньше все было совсем по-другому. Был фиксированный аппаратный сетап, постоянные риски, что вот, еще чуть-чуть, и все сломается из-за наплыва игроков. Начинался 2013 год. Тогда мы и выпустили игру 2020: My Country.

    Прошло достаточно много времени, проект рос и развивался, нагрузки постепенно увеличивались, и в какой-то момент мы решили перенести бэкенд в облако Azure — we do what we must because we can. Облака дают хороший запас по вычислительной мощности, поэтому начать мы решили с меньшего количества гигабайт и гигагерц, чем было в нашем дата-центре. Основой нового бэкенда стали менее мощные машины с более новыми процессорами. Больше всего мы переживали по поводу нагрузки на новые БД-сервера и даже готовились к разбиению баз, но переживали, как оказалось, зря.


    Рис. 1 — Портал Microsoft Azure

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

    Но перед этим — внимание, уважаемые знатоки, — вопрос задает начинающий системный инженер из Иркутска: «Хорошо, это один проект. А что, если их будет много, а хостов будут десятки и сотни?» Отвечаем: в таких случаях на помощь приходит terraform от HashiCorp, который у нас уже успешно работает в AWS и также поддерживает Azure.

    В первом подходе к terraform нам помогает документация, где есть пример создания ресурсной группы, ее сети и файла с учетными данными для terraform. Мы вынесли эти данные в файл с расширением .tf в директории, откуда запускаются скрипты.

    provider "azurerm" {
      subscription_id = "xxxx-xxxx"
      client_id       = "xxxx-xxxx"    
      client_secret   = "xxxx-xxxx"
      tenant_id       = "xxxx-xxxx"
    }

    Команда terraform plan показывает превью будущих действий — планируется добавление ресурсов, которые уже созданы вручную и существуют.


    Рис. 2 — Результат команды terraform plan.

    Это не совсем то, что мы бы хотели увидеть, поэтому вспоминаем свой опыт с AWS — в terraform есть команда import, для, очевидно, импорта существующей инфраструктуры.

    При импорте нужно ввести идентификаторы существующих ресурсов из Azure. По умолчанию они достаточно длинные и сложные, поэтому (наверное) на портале есть кнопка Copy to clipboard, которая значительно упрощает весь процесс.


    Рис. 3 — Результат импорта

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

    Добавляем конфигурацию и смотрим на terraform plan.


    Рис. 4 — terraform plan после импорта ресурсов из Azure

    Тильда и желтый цвет означают изменение ресурса — terraform хочет пересоздать subnet mc2020 без привязки к security group. Вероятно, это произошло из-та того, что мы не указали security group для подсети. Чтобы узнать причину наверняка, посмотрим на то, как импортирована существующая виртуальная сеть. Это можно сделать с помощью файла terraform.tfstate (нечитабельная JSON-простыня) или командой state. Вполне очевидно, какой способ выбрали мы.


    Рис.5 — Снова terraform state

    Действительно, привязка к security_group есть, но в изначальной конфигурации мы про нее забыли. Для исправления проходим все шаги заново — находим группу на портале Azure, копируем id, снова импортируем и прописываем в конфигурацию.

    После все выглядит так:
    resource "azurerm_resource_group" "mc2020" {
        name     = "mc2020"
        location = "${var.location}"
    }
    resource "azurerm_virtual_network" "mc2020-vnet" {
      name                = "mc2020-vnet"
      address_space       = ["XX.XX.XX.XX/24"]
      location            = "${var.location}"
      resource_group_name = "${azurerm_resource_group.mc2020.name}"
    }
    resource "azurerm_subnet" "mc2020-snet" {
        name = "mc2020"
        resource_group_name = "${azurerm_resource_group.mc2020.name}"
        virtual_network_name = "${azurerm_virtual_network.mc2020-vnet.name}"
        address_prefix = "XX.XX.XX.XX/24"
        network_security_group_id = "${azurerm_network_security_group.mc2020-nsg.id}"
    }
    resource "azurerm_network_security_group" "mc2020-nsg" {
        name = "mc2020-nsg"
        location = "${var.location}"
        resource_group_name = "${azurerm_resource_group.mc2020.name}"
        security_rule {
          name = "default-allow-ssh"
          priority = 1000
          direction = "Inbound"
          access = "Allow"
          protocol = "TCP"
          source_port_range = "*"
          destination_port_range = "22"
          source_address_prefix = "XX.XX.XX.XX/24"
          destination_address_prefix = "*"
        }
        security_rule {
          name = "trusted_net"
          priority = 1050
          direction = "Inbound"
          access = "Allow"
          protocol = "*"
          source_port_range = "*"
          destination_port_range = "0-65535"
          source_address_prefix = "XX.XX.XX.XX/32"
          destination_address_prefix = "*"
        }
        security_rule {
          name = "http"
          priority = 1060
          direction = "Inbound"
          access = "Allow"
          protocol = "TCP"
          source_port_range = "*"
          destination_port_range = "80"
          source_address_prefix = "*"
          destination_address_prefix = "*"
        }
        security_rule {
          name = "https"
          priority = 1061
          direction = "Inbound"
          access = "Allow"
          protocol = "TCP"
          source_port_range = "*"
          destination_port_range = "443"
          source_address_prefix = "*"
          destination_address_prefix = "*"
        }
    }


    Замечание: при импорте ресурсов нужно внимательно смотреть их атрибуты — они case-sensitive, и важно не ошибаться при их наборе. Все это издержки того, что мы импортируем уже существующую инфраструктуру и кодируем ее.

    После всех правок команда terraform plan выдает нам то, что мы ожидали увидеть изначально.

    No changes. Infrastructure is up-to-date. This means that Terraform
    could not detect any differences between your configuration and
    the real physical resources that exist. As a result, Terraform
    doesn't need to do anything.

    (Здесь был скучный процесс импорта\записи инфраструктуры всего проекта, который мы пропустили по очевидным причинам)

    Мы рекомендуем создавать инфраструктуру сразу в виде кода. Магия делается следующим образом:

    1. Добавляем ресурс;
    2. Смотрим terraform plan;
    3. Выполняем terraform apply;
    4. Смотрим на чудеса автоматической переконфигурации ресурсов;

    Примерно так. Спасибо за вопрос, внимательный читатель! Вернемся к паре обещанных ранее вещей.

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


    Рис.6 — Итоговая схема инфрастуктуры в Azure

    На момент переноса в нескольких базах было около 500 гигабайт данных. Данные с двух самых объемных серверов мы перенесли между дата-центрами через xtrabackup на скорости в 1 Гбит/c, а для дампа необходимых баз с третьего сервера мы использовали myloader, который работает быстрее стандартного mysqldump. После этого мы допили оставшийся кофе и провели окончательные настройки всего-что-можно-было-настроить.

    Во-вторых, мы начали потихоньку переводить трафик на новые балансировщики нагрузки, следя за ошибками и нагрузкой. Для обработки всего трафика мы добавили еще один app-сервер и в итоге получили ~30% нагрузки app- и 8-15% db-серверов.

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

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

    Для полного переноса осталось:

    • Пропатчить клиенты, чтобы прописать новые хосты (“az-”);
    • Перенести файлы статики, которые заливаются скриптом на один из старых серверов и служат Origin для Akamai CDN;
    • Через некоторое время отключить старые сервера.

    Весь процесс переноса бэкенда на Azure (вместе с подготовкой и чтением документации по terraform) занял чуть меньше недели, а непосредственно перенос — около двух дней. Облако Azure из коробки позволяет почувствовать преимущества автоматического масштабирования, дает возможность мгновенно добавить ресурсы в случае повышения нагрузки и все остальное добро и позитив, которое приносят в жизнь разработчиков облачные сервисы.

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

    Ждем ваших вопросов в комментариях. Спасибо за внимание!
    • +21
    • 5.4k
    • 9
    Game Insight
    42.17
    Разработчик и издатель мобильных игр
    Support the author
    Share post

    Comments 9

      +2
      Зачем скрины консоли? Может есть другой способ вставить текст?
      Почему выбрали именно Azure а не amazon например? Что подтолкнуло к такому выбору?
        +3
        Спасибо за вопросы!
        1) Мы честно пытались минимизировать количество скринов. В дальнейшем будем просить у хранителей консоли логи сразу текстом)
        2) Мы работаем и с Amazon, вот здесь рассказ о том, как мы выбирали платформу для другой нашей игры.
        +2
        Azure хорош по возможностям, цены только хотелось бы поменьше. Если не секрет, намного дороже новое решение по сравнение с датацентром раньше.
          +2
          С точки зрения ресурсов, мы стали использовать меньше мощностей для тех же задач. По деньгам сложно сказать, прошло мало времени для подсчета статистики.
          +1
          Хорошое начало, как будто сам старина Рауль Дьюк рассказывает: )
            0
            Ходят слухи, что продолжение статьи тоже ничего так :)
            Тем не менее, спасибо на добром слове!
            +1

            Terraform штука отличная, поверх его state файлов сделал обвязку для автоматизации типовых задач с сервисами (AWS).


            Если используете docker и в принципе архитектура не критична к пропаданиям узлов, посмотрите Spotinst, для AWS при массовых деплоях позволяет хорошо экономить.

              0
              Спасибо! Передам ваш совет хранителям консоли :)
              0
              Перенести файлы статики, которые заливаются скриптом на один из старых серверов и служат Origin для Akamai CDN;


              Можно использовать не один старый сервер, а на прямую BLOB. CDN умеет получать информацию напрямую.

              Only users with full accounts can post comments. Log in, please.