terraform

Terraform: создай сервер кодом

До этого Ansible настраивал серверы, которые уже существуют. Остался последний ручной кусок: сами серверы все еще появлялись кликами в панели облака. Terraform закрывает и его. Из первого шага модуля вы помните разделение ролей: Terraform отвечает на вопрос «что должно существовать», и сейчас вы увидите, как сервер, сеть и диск появляются из текстового файла.

Как устроен Terraform

Когда вы кликаете в панели облака «создать виртуальную машину», панель на самом деле отправляет запрос в API облака, программный интерфейс, через который облаком можно управлять без панели вообще. Terraform работает напрямую с этим API: вы описываете ресурсы в файле, а он отправляет облаку нужные запросы.

С каждым облаком Terraform общается через провайдера, плагин, который знает API конкретной платформы. Провайдеры есть под сотни сервисов: Yandex Cloud, AWS, Selectel, VK Cloud и дальше по списку. Сменилось облако, меняется провайдер и детали описания, а подход и команды остаются теми же.

Факт. Terraform сделала компания HashiCorp, первый релиз вышел в 2014 году. В 2023 году у проекта появился свободный форк OpenTofu, который работает с теми же .tf файлами и теми же командами.

И еще одна деталь, без которой Terraform не понять: он ведет состояние, то есть запоминает в специальном файле все, что создал. Благодаря этому он всегда может сравнить три вещи: что написано в ваших файлах, что он создавал раньше и что реально существует в облаке. На этом сравнении построена вся его работа.

Файл, те самые 15 строк

Файлы Terraform пишутся на языке HCL и имеют расширение .tf. Сначала служебная часть: какой провайдер нужен и где создавать ресурсы:

terraform {
  required_providers {
    yandex = {
      source = "yandex-cloud/yandex"
    }
  }
}
 
provider "yandex" {
  zone = "ru-central1-a"     # доступ к облаку настроен заранее через токен
}

Эта часть пишется один раз на проект. Дальше главное, описание сервера, тот фрагмент, который вы видели в первом шаге модуля, теперь в полном виде:

resource "yandex_compute_instance" "web" {
  name        = "web-server"
  platform_id = "standard-v3"
 
  resources {
    cores  = 2
    memory = 4
  }
 
  boot_disk {
    initialize_params {
      image_id = "fd8kdq6d0p8sij7h5qe3"   # Ubuntu 22.04
      size     = 20
    }
  }
 
  network_interface {
    subnet_id = "e9b0xxxxxxxxxxxxx"
    nat       = true                      # выдать внешний IP
  }
}
 
output "external_ip" {
  value = yandex_compute_instance.web.network_interface.0.nat_ip_address
}

Блок resource описывает один ресурс. В кавычках две вещи: тип ресурса из провайдера (yandex_compute_instance, виртуальная машина в Yandex Cloud) и ваше внутреннее имя (web), по которому на ресурс можно ссылаться из других мест файла. Внутри анкета сервера, которая читается без перевода: 2 ядра, 4 ГБ памяти, диск 20 ГБ с Ubuntu, сетевой интерфейс с внешним адресом.

Блок output просит Terraform после работы напечатать значение, в данном случае внешний IP созданного сервера. Обратите внимание, как он записан: это ссылка на атрибут ресурса web, то самое внутреннее имя в деле.

Три команды, init, plan, apply

Работа с Terraform держится на трех командах.

Команда terraform init выполняется один раз в начале проекта и скачивает указанных провайдеров. Дальше про нее можно забыть.

Команда terraform plan это репетиция. Terraform сравнивает описанное в файлах с тем, что существует, и показывает, что собирается сделать, не делая ничего:

Terraform will perform the following actions:

  # yandex_compute_instance.web will be created
  + resource "yandex_compute_instance" "web" {
      + name        = "web-server"
      + platform_id = "standard-v3"
      ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Плюс перед строкой означает «будет создано», минус «будет удалено», тильда «будет изменено». По духу это git diff из модуля про Git, только разница показывается не между версиями файла, а между описанной и реальной инфраструктурой. Привычка читать plan перед применением это то же самое, что привычка смотреть diff перед коммитом: вы всегда знаете, что именно сейчас произойдет.

Команда terraform apply это применение. Она еще раз показывает план, спрашивает подтверждение, и после ввода yes идет создавать:

yandex_compute_instance.web: Creating...
yandex_compute_instance.web: Still creating... [20s elapsed]
yandex_compute_instance.web: Creation complete after 42s

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:
external_ip = "51.250.x.x"

Меньше минуты назад этого сервера не существовало. Теперь он есть в панели облака, у него внешний адрес из output, и на него можно зайти по SSH. Вы не нажали в панели ни одной кнопки.

Изменения, правим файл, а не сервер

С этого момента панель облака становится местом, куда смотрят, но где не кликают. Нужно сделать сервер мощнее, меняете в файле cores = 2 на cores = 4, запускаете plan и видите, что изменится ровно этот параметр, затем apply. Нужен второй сервер, копируете блок resource с другим именем, и plan покажет 1 to add.

А если кто-то все-таки поменял настройки мышкой в обход файла? Terraform это заметит: при следующем plan он сравнит реальность с описанием и покажет расхождение, а apply вернет все как в коде. Configuration drift из первого шага модуля здесь не просто лечится, его видно в явном виде, строчкой в плане.

terraform destroy, убрать за собой

Четвертая команда нужна реже, но для обучения она едва ли не главная. Команда terraform destroy удаляет все ресурсы, которые создал этот проект, со списком и подтверждением, как у apply.

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

Terraform и Ansible вместе

Теперь оба шага модуля складываются в целое. Terraform создает машину и отдает ее IP, Ansible по этому IP приходит и настраивает:

От пустого облака до работающего веб-сервера это два файла и две команды, ноль кликов. Оба файла лежат в Git со всей историей и ревью, и вся инфраструктура проекта читается как код, потому что она и есть код. Именно это имелось в виду в первом шаге модуля, когда подход назывался Infrastructure as Code.