Гостевой пост: управляем «Виртуальным приватным облаком» с помощью Terraform
Сегодня мы публикуем гостевой пост, написанный нашим клиентом Алексеем Ваховым. Алексей — технический директор компании Учи.Ру. Компания занимается разработкой одноимённой образовательной платформы, а также проводит интерактивные олимпиады для школьников. Вся инфраструктура Учи.Ру построена на базе нашего сервиса “Виртуальное приватное облако”. Алексей Вахов подробно рассказывает о том, как он использует утилиту Terraform для автоматизации настройки и поддержки виртуальной инфраструктуры. Надеемся, что его опыт будет интересен и другим пользователям нашего облака.
Сегодня мы публикуем гостевой пост, написанный нашим клиентом Алексеем Ваховым. Алексей — технический директор компании Учи.Ру. Компания занимается разработкой одноимённой образовательной платформы, а также проводит интерактивные олимпиады для школьников. Вся инфраструктура Учи.Ру построена на базе нашего сервиса «Виртуальное приватное облако».
Алексей Вахов подробно рассказывает о том, как он и его коллеги используют утилиту Terraform для автоматизации настройки и поддержки виртуальной инфраструктуры. Надеемся, что его опыт будет интересен и другим пользователям нашего облака.
Вот уже более четырех лет мы создаем онлайн-платформу для школьного образования. Сотни тысяч учеников со всей России решают интерактивные задания, участвуют в регулярных олимпиадах, и все это происходит в рамках нашей платформы. Сейчас система состоит из более десяти продакшенов, часть которых обслуживает внешних посетителей с нагрузками 500-800 запросов в секунду, на других работают приложения для внутреннего использования.
Наш флагманский продукт — это сайт https://uchi.ru . Помимо работы над сайтом мы проводим онлайн-олимпиады по математике, предпринимательству и русскому языку. Продакшены олимпиад, как и любых мероприятий с четкими датами проведения, отличаются сильно неравномерным трафиком. Во время основного тура сайт нагружен сильно, и на нас лежит высокая ответственность и важно, чтобы технически произошло все гладко. А после награждения и подведения итогов продакшен олимпиады работает в спокойном режиме.
Около года назад мы перевели почти всю нашу инфраструктуру с выделенных серверов в облако и очень довольны. Возможность быстро докупить любое количество серверов особенно актуальна для олимпиад, так как там пиковая нагрузка держится всего пару недель, и на это время мы поднимаем ферму из пары десятков серверов, а потом ее расформировываем. Однако и для всех остальных продакшенов возможность быстро подкрутить ресурсы очень ценна.
В облаке поменялись даже принципы организации инфраструктуры. На железных серверах приходилось комбинировать по несколько ролей на бокс, так как они большие и довольно дорогие, к тому же подключение нового сервера всегда занимает несколько дней. Система со временем становилась хрупкой и не гибкой. Сейчас мы выделяем каждый продакшен в отдельный изолированный проект со своей подсетью и под каждую роль заводим отдельный сервер. Например, под VPN доступ берем самые маленькие, на одно ядро и 512 мегабайт памяти, а под серверы приложений и базы данных чаще всего покупаем сервер максимального размера.
Когда количество серверов выросло до нескольких десятков, мы столкнулись с новой проблемой: управлять конфигурацией через графический интерфейс стало некомфортно.
Когда с графической панелью работают несколько человек, начинает проявляться человеческий фактор: представим себе, например, ситуацию, когда кто-то что-то поменял и не сообщил об этом коллегам (да и как об этом сообщить?).
Скажем из 8 серверов, которые должны быть одинаковые, один может иметь другую версию операционной системы или чуть-чуть другое количество оперативной памяти. Задача клонирования или поднятия нового продакшена с нуля также подразумевала много ручной работы. Поэтому мы решили автоматизировать работу по настройке и поддержке нашей инфраструктуры с помощью утилиты Terraform..
В этой статье я расскажу для чего нужен Terraform, покажу, как настроить окружение и создать тестовый сервер в любом OpenStack-облаке на примере облака Селектела. Также мы обсудим особенности использования утилиты на живом продакшене.
Что такое Terraform
Terraform — это замечательная утилита от компании HashiCorp (создатели Vagrant, Consul и некоторых других широко известных в узких кругах инструментов). С её помощью можно моделировать, хранить и изменять облачную инфраструктуру в виде простых шаблонов на языке HCL (HashiCorp Configuration Language). Хотя создание своего языка — обычно очень плохая идея, HCL мне нравится. Он представляет собой надстройку над JSON, которую легко и приятно читать.
Приведу упрощенный пример (в реальности нужно указать чуть больше атрибутов) для создания сервера в облаке «Селектела», практически полностью объясняющий все тонкости синтаксиса, которые нам пригодятся:
resource "openstack_blockstorage_volume_v1" "disk" {
name = "disk"
region = "ru-1"
size = 10
}
resource "openstack_compute_instance_v2" "server" {
name = "server"
flavor_name = "flavor-1024-1"
region = "ru-1"
block_device {
uuid = "${openstack_blockstorage_volume_v1.disk.id}"
}
}
Если сохранить приведённый выше код в файл с расширением .tf, настроить необходимые токены для доступа и вызвать в консоли команду `terraform apply`, то произойдет ровно то, что ожидается: Terraform создаст диск и только после этого создаст сервер, на основе этого свежего диска.
Если запустить `terraform apply` еще раз, то утилита увидит, что такой диск и сервер уже есть, и ничего делать не будет. Также легко добавить памяти на сервер, поменяв `flavor_name` на `flavor-2048-1` и снова вызвав `terraform apply`. Terraform предсказуемо обновит только память, а диск трогать не будет. Команда `terraform destroy` удалит все созданные ресурсы (и можно забыть про висящие в пустоту DNS-записи тестовых серверов, забытые диски и другие артефакты, которые раздражают любого перфекциониста).
Прежде чем приступать к работе с Terraform, внимательно изучите официальную документацию (она очень хорошая и понятная, читается на одном дыхании) и несколько дней поэкспериментируйте с тестовыми инфраструктурами, желательно на отдельных аккаунтах. Это очень важно: непонимание тонкостей работы с Terraform может привести к непоправимым последствиям. Эксперименты как раз и помогут вам разобраться, как Terraform работает состоянием.
Рассмотрим сценарии работы с Terraform более подробно.
Подготовка
Для начала необходимо настроить окружение. Я расскажу, как это делаем мы, но, думаю, если вы уже скриптуете свою инфраструктуру, то наверняка знаете какие моменты требуют автоматизации. Рекомендую изучить полный список поддерживаемых провайдеров, там есть очень неожиданные представители — Grafana, PostgreSQL, Heroku и многие другие. Может быть, что-то пригодится при моделировании вашей инфраструктуры.
Мы пока управляем только серверами и DNS-записями.
Для работы нам понадобятся:
- проект с необходимым количеством квот;
- пользователь с доступом к данному проекту;
- консольные утилиты OpenStack;
- идентификатор образа операционной системы;
- подготовленный набор переменных окружения.
Новый проект, пользователя и квоты настройте через GUI (хотя это тоже можно скриптовать, но уже не через Terraform). Сохраните ID проекта: он будет использоваться при настройке доступа OpenStack-провайдера. Зайдите в проект и создайте локальную сеть, если она еще не создана (в облаке Selectel локальная сеть создается автоматически, если вы закажете плавающий IP-адрес).
Далее установите консольные утилиты для получения некоторых внутренних идентификаторов, которые недоступны через GUI. В статье «Работа с API виртуального приватного облака: консольные клиенты» подробно написано, как их установить и использовать. Для удобства мы завернули их в контейнер и используем следующим образом:
docker run --rm \
-e OS_AUTH_URL=https://api.selvpc.ru/identity/v3 \
-e OS_PROJECT_ID=#{...} \
-e OS_USER_DOMAIN_NAME=#{...} \
-e OS_USERNAME=#{...} \
-e OS_PASSWORD=#{...} \
-e OS_REGION_NAME=#{...} \
uchiru/ostack:v2 команда, которую необходимо запустить
Где OS_USER_DOMAIN_NAME — логин в панели «Селектела» (номер договора), PROJECT_ID — идентификатор проекта, OS_REGION_NAME — регион (ru-1 для Санкт-Петербурга, ru-2 — Москва), OS_USERNAME/OS_PASSWORD — логин/пароль пользователя (не забывайте, что у него должен быть доступ в проект).
Чтобы получить идентификатор образа, запустите команду glance image-list и найдите нужный вам образ:
root@2118c4e58238:/# glance image-list |grep 16.04
eecd3d0f-6968-40ea-bed6-4c2949bbac3d | Ubuntu-16.04 LTS 32-bit
ce532860-acef-40cd-b3c7-699c22b4dfd6 | Ubuntu-16.04 LTS 64-bit
Теперь осталось создать нужные флаворы. Обычно OpenStack-провайдеры предоставляют фиксированный набор конфигураций, однако «Селектел» в этом плане более гибок, так как позволяет собрать произвольную конфигурацию для каждого сервера (при работе в UI флавор создается автоматически на каждый новый сервер). Мы же будем создавать флаворы с помощью команды nova. Сразу же примите схему именования, которой будете следовать. Разумно выбрать что-то типа flavor-1024-2 —двухядерный сервер с гигабайтом памяти.
Формат команды:
nova --is-public False flavor-create <flavor-name> auto <mem-in-mb> 0 <cpu-n>.
Наконец, для корректной работы Terraform понадобится экспортировать несколько переменных окружения:
export TF_VAR_SELECTEL_ACCOUNT=112233 # ваш логин от аккаунта Selectel
export TF_VAR_PROJ_ID=5b1b496.. # идентификатор проекта
export TF_VAR_USER=... # логин пользователя
export TF_VAR_PASSWORD=... # пароль этого пользователя
Вот и всё! Теперь у вас есть все компоненты для того, чтобы создать свой первый сервер.
Приступаем к работе
Создайте файл provider.tf (имя файла, естественно, может быть любым) с доступом к облаку:
provider "openstack" {
domain_name = "${var.SELECTEL_ACCOUNT}"
auth_url = "https://api.selvpc.ru/identity/v3"
tenant_name = "${var.PROJ_ID}"
tenant_id = "${var.PROJ_ID}"
user_name = "${var.USER}"
password = "${var.PASSWORD}"
}
Добавьте файл variables.tf:
variable "image_list" {
type = "map"
default = {
"ubuntu-x64-1604" = "2de94623-a2a2-49e3-984d-3e6ca85e2b84"
}
}
# переменные необходимо объявить
variable "PROJ_ID" {}
variable "SELECTEL_ACCOUNT" {}
variable "USER" {}
variable "PASSWORD" {}
# плавающий IP и идентификатор сети можно скопировать из UI
variable "box01-floating-ip" { default = "..." }
variable "network-id" { default = "..." }
variable "box01-ip" { default = "192.168.0.4" }
И, наконец, файл box01.tf с описанием сервера:
resource "openstack_blockstorage_volume_v1" "disk-for-box01" {
name = "disk-for-box01"
region = "ru-1"
size = 10
image_id = "#${var.image_list["ubuntu-x64-1604"]}"
volume_type = "basic.ru-1a"
}
resource "openstack_compute_instance_v2" "box01" {
name = "box01"
flavor_name = "flavor-1024-1"
region = "ru-1"
network {
uuid = "${var.network-id}"
fixed_ip_v4 = "${var.box01-ip}"
floating_ip = "${var.box01-floating-ip}"
}
metadata = {
"x_sel_server_default_addr" = "{\"ipv4\":\"\"}"
}
block_device {
uuid = "${openstack_blockstorage_volume_v1.disk-for-box01.id}"
source_type = "volume"
boot_index = 0
destination_type = "volume"
}
}
Затем выполните команду `terraform apply` — и через несколько секунд ваш первый сервер готов! Мы раздаем IP-адреса вручную для большей контролируемости, плюс при изменении ресурсов OpenStack выдаст новый адрес, если он не указан явно, что не всегда удобно.
Синтаксис Terraform достаточно самодокументированный. Вы из кирпичиков-ресурсов складываете те конструкции, которые вам нужны. Утилита сама просчитывает зависимости и, к примеру, создаст сервер только после того, как создан диск. Однако то, что можно сделать параллельно — будет делать параллельно.
Конечно, один-единственный сервер гораздо быстрее и удобнее создать через GUI. Я думаю, что реальная польза от Terraform начинается от пары десятков серверов, особенно если также скриптовать DNS-записи, бакеты S3 и другие сервисы.
Изменение конфигурации
Добавить или убрать память (или диски) очень просто: измените флавор и вызовите команду `terraform apply`. Обратите внимание: в результате выполнения этой команды сервер перезагрузится!
Размер диска следует менять аккуратно, так как Terraform пересоздаст его с нуля, если делать через скрипты. Но выход есть: увеличьте размер через UI и в соответствующем tf-скрипте. Terraform вас поймет правильно и трогать диск не будет.
Добавить сервер проще всего, скопировав файл box01.tf и обновив соответсnвующие переменные. Для наших проектов в 10-20 серверов мы храним tf-файлы плоско (не формируя иерархию из файлов по какому-либо признаку): сколько серверов — столько файлов.
Сценарии использования Terraform
Повторюсь ещё раз: по уровню бед, которые можно натворить на живом продакшене (причем, очень, очень быстро) Terraform оставит далеко позади даже `truncate table`. Пожалуй, это самый опасный инструмент, с которым я когда-либо работал. Поэтому очень важно хорошо понимать принципы его работы.
С технической точки зрения Terraform представляет собой один-единственный бинарный файл на Go, его можно скомпилировать, разобраться, отладить. Вся магия реализована через файл состояния и многообразие провайдеров для работы с разными сервисами.
Типичный сценарий выглядит так. Вы задаете желаемую конфигурацию в tf-скриптах. Файл состояния в этот момент пустой. Набираете команду `terraform plan`.Это самая приятная и безопасная команда: она ничего не меняет, просто пишет, какие изменения утилита внесет, если запустить команду `terraform apply`.
Далее вы запускаете `terraform apply` и только тогда будут созданы реальные серверы, диски, записи в DNS и так далее. Информация об этих ресурсах будет сохранена в файл состояния (JSON-файл с довольно простым синтаксисом). При дальнейшей работе утилита будет использовать файл состояния, также собирать информацию из реального мира и сравнивать ее с вашими скриптами.
Причиной всех недопониманий является то, что Terraform работает с тремя объектами одновременно:
- tf-скрипты — это то, что вы хотите видеть;
- файл состояния — это то, как выглядит инфраструктура по мнению Terraform;
- реальное состояние дел — как на самом деле выглядит инфраструктура.
В момент планирования запроса (а apply команда отличается от plan только тем, что изменения реально применяются), Terraform считывает файл состояния, обновляет свойства каждого объекта по его ID (в любом сервисе у каждого ресурса есть уникальный идентификатор, именно его использует Terraform для идентификации) и сравнивает полученные атрибуты.
Грубо говоря, можно считать, что состояние — это перечень идентификаторов ресурсов, которыми управляет данный проект. Если вы добавите сервер или DNS-запись руками, то Terraform про нее не узнает. Необходимо будет поместить ее в состояние явно. Если удалите сервер через GUI, то Terraformу также нужно будет обновить состояние с помощью команды `terraform state rm <удаленные ресурсы>`.
В предыдущем разделе я рекомендовал увеличивать диск через UI и в tf-скрипте. Как это работает? Допустим, изначально в tf-скрипте указан диск 10ГБ. Terraform создал его и записал id свежего диска, а также его размер в файл состояния. Вы поменяли размер через UI на 20ГБ. Если запустить `terraform plan` сейчас, то Terraform узнает у OpenStack, что размер диска 20ГБ, а в tf-файле — 10ГБ, и предложит его пересоздать. Если вы поменяете размер диска на 20ГБ, то Terraform убедится, что диск реально 20ГБ и в скриптах — 20ГБ и ничего делать не будет. Может показаться, что хранить в состоянии факт, что когда-то диск был 10ГБ и не нужно, так как Terraform не использует этот атрибут. На самом деле, это так и есть, но особенность реализации некоторых провайдеров требует сохранение некоторых дополнительных атрибутов (например, регион для всех OpenStack-ресурсов), подозреваю, что именно поэтому авторы решили хранить в состоянии последние значения всех свойств каждого ресурса.
Чтобы Terraform работал корректно, необходимо хранить файл состояния централизованно. Мы храним прямо в репозитории, также можно хранить на S3, в коммерческом сервисе Atlas, в Consul и некоторых других хранилищах, полный список которых также есть в официальной документации.
Заключение
В этой статье мы рассмотрели особенности работы с Terraform очень кратко. К сожалению, за рамками нашего рассмотрения осталась интереснейшая тема по импорту уже существующей инфраструктуры. Скажу кратко. Можно воспользоваться командой `terraform import`, которая, правда, появилась недавно, реализована далеко не для всех провайдеров и пока откровенно сыровата. Рабочий вариант на сегодня — добавить ресурс с правильным типом и идентификатором прямо в файл состояния и вызвать `terraform refresh`.
Итак, если вы активно пользуетесь облачными сервисами, часто создаете и удаляете разные сущности через GUI, рекомендую попробовать. Здесь вы найдете полный комплект скриптов из статьи, достаточный для заведения своего сервера с помощью Terraform. Хотя утилита молодая, с достаточно высоким порогом входа, после внедрения ощущение порядка и контроля сильно возрастет. Буду рад ответить на любые вопросы и замечания в комментариях и по почте vakhov@gmail.com.
Благодарю команду «Селектелa» за предложение написать гостевой пост. Также приглашаю всех читателей посетить мой личный блог. Каждый рабочий день публикую маленькую историю про разработку, мысли, инструменты и все такое.
Спасибо за внимание!