Гостевой пост: управляем «Виртуальным приватным облаком» с помощью 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» за предложение написать гостевой пост. Также приглашаю всех читателей посетить мой личный блог. Каждый рабочий день публикую маленькую историю про разработку, мысли, инструменты и все такое.

Спасибо за внимание!

Что еще почитать по теме

T-Rex 30 марта 2021

Что такое SMTP-протокол и как он устроен?

SMTP (Simple Mail Transfer Protocol) — протокол передачи почты. Он был представлен еще в 1982 году, но не теряет актуальности до сих пор. В статье разбираемся, какие задачи решает протокол и как он ра…
T-Rex 30 марта 2021
Владимир Туров 1 сентября 2020

Дело совершенно секретного iPod

Это был обычный серый день в конце 2005 года. Я сидел на рабочем месте и писал код для следующей версии iPod. Вдруг без стука ворвался директор ПО для iPod, начальник моего начальника, и закрыл дверь.
Владимир Туров 1 сентября 2020
T-Rex 21 августа 2020

TrendForce: цены на SSD упадут

Эксперты DRAMeXchange предсказывают значительное падение цен на оперативную память и твердотельные накопители в ближайшее время. Причина — сокращение спроса на чипы для NAND и DRAM.
T-Rex 21 августа 2020

Новое в блоге

Михаил Фомин 24 июня 2022

Docker Swarm VS Kubernetes — как бизнес выбирает оркестраторы

Рассказываем, для каких задач бизнесу больше подойдет Docker Swarm, а когда следует выбрать Kubernetes.
Михаил Фомин 24 июня 2022
Ульяна Малышева 30 сентября 2022

«Нулевой» локальный диск. Как мы запустили облако только с сетевыми дисками и приручили Ceph

Чем хороши сетевые диски и почему именно Ceph, рассказал директор по развитию ядра облачной платформы Иван Романько.
Ульяна Малышева 30 сентября 2022
Валентин Тимофеев 30 сентября 2022

Как проходит онбординг сотрудников ИТО? Что нужно, чтобы выйти на смену в дата-центр

Рассказываем, как обучаем новых сотрудников, какие задачи и испытания проходят инженеры прежде, чем выйти на свою первую смену.
Валентин Тимофеев 30 сентября 2022
T-Rex 28 сентября 2022

Книги по SQL: что почитать новичкам и специалистам

Собрали 6 книг, которые помогут на старте изучения SQL и при углублении в тему.
T-Rex 28 сентября 2022