Боль, которую мы лечим, простая. Создавать ВМ руками - это всегда "а какую зону я выбрал в прошлый раз", "а сколько ядер было", "а почему у соседней машины другой образ Ubuntu". Через terraform yandex cloud вся эта память переезжает в текстовый файл. Файл - источник правды. Это и есть наш Hello World, только вместо вывода строки в консоль мы поднимаем настоящий сервер.
Что нам нужно описать
Чтобы ВМ в Yandex Cloud вообще запустилась, ей нужна сеть. Машина без сетевого интерфейса никуда не подключится, поэтому минимальный набор - это три ресурса:
- yandex_vpc_network - сама виртуальная сеть, контейнер верхнего уровня.
- yandex_vpc_subnet - подсеть внутри неё, привязанная к зоне доступности, с диапазоном адресов.
- yandex_compute_instance - собственно виртуальная машина, которая воткнётся интерфейсом в эту подсеть.
Код: Выделить всё
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
}
}
required_version = ">= 1.5"
}
provider "yandex" {
zone = "ru-central1-a"
# cloud_id, folder_id и токен удобнее задавать
# через переменные окружения или yc init,
# чтобы не светить их в коде
}

Описываем сеть и машину
Теперь сами ресурсы. Обрати внимание, как они ссылаются друг на друга через выражения вида yandex_vpc_network.main.id - terraform сам поймёт порядок создания по этим связям, об этом был отдельный разговор про граф зависимостей.
Код: Выделить всё
resource "yandex_vpc_network" "main" {
name = "lesson5-net"
}
resource "yandex_vpc_subnet" "main" {
name = "lesson5-subnet"
zone = "ru-central1-a"
network_id = yandex_vpc_network.main.id
v4_cidr_blocks = ["10.5.0.0/24"]
}
resource "yandex_compute_instance" "vm" {
name = "lesson5-vm"
platform_id = "standard-v3"
zone = "ru-central1-a"
resources {
cores = 2
memory = 2
}
boot_disk {
initialize_params {
# ID публичного образа Ubuntu 22.04 LTS;
# актуальный смотри в каталоге образов YC
image_id = "fd8ccfn8achs7l1q73vh"
size = 10
}
}
network_interface {
subnet_id = yandex_vpc_subnet.main.id
nat = true
}
metadata = {
ssh-keys = "ubuntu:${file("~/.ssh/id_ed25519.pub")}"
}
}
- platform_id - поколение железа. standard-v3 это актуальные процессоры Intel Ice Lake, дефолт для большинства задач.
- resources - блок с cores и memory. Оба обязательные. memory задаётся в гигабайтах, поэтому 2 это 2 ГБ, а не 2 МБ.
- boot_disk с вложенным initialize_params - тут мы говорим "создай новый диск из образа". Альтернатива - disk_id, если диск уже существует. Атрибут image_id берёт публичный образ Ubuntu, size - размер диска в ГБ.
- network_interface - сетевая карта. subnet_id обязателен, подсеть должна жить в той же зоне, что и машина. nat = true выдаёт внешний IP, иначе достучаться по SSH снаружи не выйдет.
- metadata с ssh-keys - закидываем публичный ключ внутрь машины, чтобы потом зайти под пользователем ubuntu. Функция file читает файл с диска.
Полный цикл: init, plan, apply, destroy
Сохрани всё в файл main.tf и поехали по шагам. Это четыре команды, которые ты будешь повторять до конца жизни как DevOps.
Код: Выделить всё
terraform init
Код: Выделить всё
terraform plan
Читать plan надо обязательно, а не проматывать. Зелёный плюс перед именем ресурса - будет создан. Тильда - будет изменён на месте. Минус - будет удалён. А самое страшное сочетание это минус-плюс (-/+), оно означает "пересоздать", то есть старый ресурс снесут и поднимут новый. Для базы данных или диска с продакшен-данными такое читается как сигнал тревоги. Строки со значением (known after apply) - это атрибуты, которые облако присвоит само: IP-адреса, ID, fqdn. Их terraform узнает только после создания, и это нормально.Plan: 3 to add, 0 to change, 0 to destroy.
Когда plan устраивает - применяем.
Код: Выделить всё
terraform apply
Теперь проверь руками: зайди в веб-консоль Yandex Cloud, раздел Compute Cloud. Машина lesson5-vm должна быть там, с тем самым внешним IP, который apply вывел в конце. Можешь даже подключиться: ssh ubuntu@внешний_ip. Вот он, мост между кодом и реальностью.
Наигрался - убираем за собой, чтобы не капали деньги:
Код: Выделить всё
terraform destroy
Типичные грабли
- Зоны не совпадают. Машина в ru-central1-a, а подсеть случайно в ru-central1-b - apply упадёт. subnet_id обязан быть в той же зоне, что и инстанс.
- Забыл nat = true. ВМ создастся, но без внешнего IP. По SSH снаружи не зайдёшь и будешь думать, что сломал сеть.
- Устаревший image_id. ID образов в каталоге меняются. Если provider ругается на несуществующий образ - возьми свежий image_id Ubuntu из официального каталога образов YC.
- Не задан folder_id. Провайдер не знает, в какую папку складывать ресурсы. Проще всего прогнать yc init и экспортнуть переменные окружения, тогда токен и folder_id подхватятся сами.
- Применил без чтения plan. Классика. Привыкай смотреть на план как на чек перед оплатой - один раз сэкономит тебе боевую базу.
Повтори руками, не копируя бездумно:
- Собери main.tf из трёх ресурсов выше и блока provider.
- Прогони terraform init и найди в выводе, какую версию провайдера он скачал.
- Сделай terraform plan и вслух проговори, что именно создастся и почему "3 to add".
- Запусти apply, дождись RUNNING, найди машину в веб-консоли.
- Поменяй cores с 2 на 4, снова сделай plan - и посмотри, покажет он change или пересоздание.
- Обязательно заверши destroy, чтобы ничего не висело.
- Зачем для одной ВМ нужны ещё два ресурса - network и subnet?
- Чем отличается boot_disk с initialize_params от boot_disk с disk_id?
- Что означают в выводе plan символы +, ~ и -/+, и какой из них опаснее всего?
- Что физически произойдёт, если в network_interface убрать nat = true?
Минимальная ВМ в Yandex Cloud - это сеть плюс подсеть плюс compute_instance, связанные через .id. Рабочий цикл всегда один: init подтягивает провайдер, plan показывает diff (читаем его как договор), apply создаёт и пишет в terraform state, destroy всё убирает. Имена атрибутов бери из доки провайдера, а не из головы - resources с cores/memory, boot_disk с image_id, network_interface с subnet_id и nat. И главное правило на всю карьеру: сначала plan, потом думаешь, и только потом apply.