Модуль 01 — Инфраструктура как код

Почему «создам сервер в консоли облака, это быстрее» перестаёт быть быстрее на втором окружении

Прогресс курса Модуль 1 из 9

Что вы освоите в этом модуле

01

Инфраструктура, созданная мышкой

Первый сервер в облаке создают в веб-консоли: выбрать зону, образ, размер диска, сеть, группу безопасности, добавить ключ SSH. Это занимает десять минут и кажется самым быстрым способом. Через месяц нужен тестовый стенд «как прод», ещё через месяц — второй регион. Выясняется, что никто не помнит, какие галочки стояли в группе безопасности, а сеть прода собрана в три приёма разными людьми. Такую работу называют ClickOps.

ПроблемаСледствие
Настройки живут только в консоли облакаТестовый стенд отличается от прода, ошибки «у нас не воспроизводится»
Ручные правки во время аварииДрейф: реальная инфраструктура расходится с тем, что все считают правдой
Нет истории и ревьюНепонятно, кто открыл порт 5432 в интернет и зачем; откатить нечего
Забытые ресурсыДиски и адреса от удалённых стендов годами списывают деньги со счёта

Инфраструктура как код (Infrastructure as Code, IaC) — описать серверы, сети, базы данных, DNS-записи и права доступа в текстовых файлах, хранить их в Git и создавать программой. Описание проходит ревью в merge request, второе окружение — это тот же код с другими переменными, а удалить стенд целиком — одна команда, после которой не остаётся забытых дисков.

02

Декларативный подход

Скрипт с командами облачной CLI описывает действия: «создай сеть», «создай сервер». Запустите его второй раз — получите вторую сеть и второй сервер или ошибку «имя занято». Чтобы скрипт можно было повторять, в нём пишут проверки «а не создано ли уже», и через полгода проверок больше, чем полезных команд.

Terraform описывает результат: «должна существовать сеть с такими параметрами и в ней два сервера». Перед изменением Terraform сравнивает описание с тем, что уже создано, и составляет план — список действий, которые приведут одно к другому. Если всё совпадает, план пуст и ничего не делается. Изменили в описании размер диска — в плане одно изменение, а не пересоздание всего.

ЗадачаСкрипт: действияTerraform: результат
Первый запускСоздаёт всё по порядкуПлан «создать N ресурсов», после подтверждения — создаёт
Повторный запускДубли или ошибки, если не писать проверки«No changes» — ничего не делает
Поменяли параметрПисать отдельный скрипт измененияПлан с одним изменением
Удалить стендВспомнить всё созданное и удалять в обратном порядкеterraform destroy — по записанному списку и в правильном порядке
03

Terraform и Ansible

Оба инструмента описывают нужное состояние кодом, но отвечают за разные слои. Terraform создаёт инфраструктуру: виртуальные машины, сети, балансировщики, управляемые базы данных, бакеты, записи DNS. Ansible настраивает то, что внутри машин: пакеты, файлы конфигурации, службы. Типичная связка: Terraform создаёт три сервера и выдаёт их адреса, Ansible ставит на них nginx и PostgreSQL.

TerraformAnsible
Что делаетСоздаёт, изменяет и удаляет ресурсы через API облаков и сервисовНастраивает существующие машины по SSH
Как знает, что уже естьХранит состояние — список созданных ресурсов и их идентификаторыКаждый раз проверяет машину заново
УдалениеУдаляет всё, что создал, одной командойУбрать задачу из плейбука ≠ удалить пакет с сервера
ЯзыкHCLYAML

Границы не жёсткие: Terraform умеет передать машине скрипт первого запуска (cloud-init), Ansible умеет создавать машины в облаке. Но каждый силён в своём, и в команде их используют вместе.

04

Как устроен Terraform

  ваш каталог с кодом *.tf
          │
  terraform (ядро): читает код, строит граф зависимостей,
          │         сравнивает с состоянием и составляет план
          ├── провайдер yandex-cloud/yandex ──▶ API Yandex Cloud
          ├── провайдер kreuzwerker/docker  ──▶ Docker на этой машине
          └── провайдер hashicorp/local     ──▶ локальные файлы
          │
  terraform.tfstate — что создано и с какими идентификаторами

В курсе нет облачного аккаунта и денег не нужно: практика — на провайдере docker (контейнеры, сети и тома на вашем Docker из курса docker-basics), local и random. Приёмы те же, что в облаке; где отличие важно, показан код для Yandex Cloud.

05

Terraform и OpenTofu

Terraform выпускает компания HashiCorp. До августа 2023 года он распространялся под открытой лицензией MPL 2.0; с версии 1.6 HashiCorp перешла на BSL 1.1 — исходный код открыт, но нельзя строить на нём конкурирующий коммерческий продукт. В ответ сообщество сделало форк последней открытой версии — OpenTofu, он развивается в Linux Foundation под MPL 2.0, первая стабильная версия вышла в январе 2024 года. В 2025 году HashiCorp стала частью IBM.

TerraformOpenTofu
Командаterraformtofu
ЛицензияBSL 1.1MPL 2.0
Язык, провайдеры, состояниеОдин и тот же HCL, те же провайдеры, совместимый файл состояния. Код этого курса работает в обоих без правок
Только здесьStacks, actions, поиск ресурсов terraform queryШифрование состояния, переменные в настройках backend, for_each у провайдеров
Документация облаков РоссииYandex Cloud, Selectel, Cloud.ru, VK CloudВ основном статьи сообщества

Курс построен на Terraform: его требуют в вакансиях и под него написаны инструкции российских облаков. Лицензия BSL не мешает ни учиться, ни управлять инфраструктурой своей компании — ограничение касается тех, кто продаёт Terraform как сервис. Всё, что вы изучите, переносится на OpenTofu заменой terraform на tofu; в конце модуля это показано на деле, а возможности только одного из продуктов в курсе помечены.

06

Доступ из России

С 2022 года HashiCorp закрыла для российских адресов сайт загрузки releases.hashicorp.com, свой apt-репозиторий и реестр провайдеров registry.terraform.io. Реестр OpenTofu registry.opentofu.org с августа 2024 года тоже не отвечает российским адресам. В пакетах Debian 13 нет ни Terraform, ни OpenTofu.

Обходить блокировки через VPN не нужно: Yandex Cloud держит зеркала, и ими пользуются компании в России.

ЧтоЗеркало
Архивы Terraformhttps://hashicorp-releases.yandexcloud.net/terraform/
Провайдеры для Terraform и OpenTofuhttps://terraform-mirror.yandexcloud.net/
Архивы OpenTofuРелизы на GitHub: github.com/opentofu/opentofu/releases

Есть и зеркало провайдеров Selectel (tf-proxy.selectel.ru). Зеркало — это доверие третьей стороне: вы получаете тот файл, который отдал Яндекс. Поэтому архив Terraform проверяем по контрольной сумме, а провайдеры фиксируем в файле блокировки (ниже).

07

Установка в Debian 13

Terraform — один исполняемый файл в zip-архиве. Скачиваем архив и файл контрольных сумм той же версии, проверяем и распаковываем (unzip — sudo apt install unzip):

$ mkdir -p ~/dl && cd ~/dl
$ curl -sSO https://hashicorp-releases.yandexcloud.net/terraform/1.16.5/terraform_1.16.5_linux_amd64.zip
$ curl -sSO https://hashicorp-releases.yandexcloud.net/terraform/1.16.5/terraform_1.16.5_SHA256SUMS
$ sha256sum -c --ignore-missing terraform_1.16.5_SHA256SUMS
terraform_1.16.5_linux_amd64.zip: ЦЕЛ
$ unzip -o terraform_1.16.5_linux_amd64.zip terraform
Archive:  terraform_1.16.5_linux_amd64.zip
  inflating: terraform               
$ ./terraform version
Terraform v1.16.5
on linux_amd64

ЦЕЛ — сумма архива совпала с опубликованной (в английской локали — OK). --ignore-missing нужен, потому что в файле сумм перечислены архивы для всех систем, а скачан один. Для полной уверенности проверяют ещё подпись самого файла сумм ключом HashiCorp (SHA256SUMS.sig, gpg --verify) — сумма защищает от битого архива, подпись — от подменённого вместе с суммами.

Остаётся положить программу в каталог из PATH:

$ sudo install -m 755 terraform /usr/local/bin/terraform
$ terraform version
$ terraform version
Terraform v1.16.5
on linux_amd64
$ terraform -help | head -20
Usage: terraform [global options] <subcommand> [args]

The available commands for execution are listed below.
The primary workflow commands are given first, followed by
less common or more advanced commands.

Main commands:
  init          Prepare your working directory for other commands
  validate      Check whether the configuration is valid
  plan          Show changes required by the current configuration
  apply         Create or update infrastructure
  destroy       Destroy previously-created infrastructure

All other commands:
  console       Try Terraform expressions at an interactive command prompt
  fmt           Reformat your configuration in the standard style
  force-unlock  Release a stuck lock on the current workspace
  get           Install or upgrade remote Terraform modules
  graph         Generate a Graphviz graph of the steps in an operation
  import        Associate existing infrastructure with a Terraform resource

Версию в команде фиксируют, а не берут «последнюю»: в курсе 1.16.5. Обновление Terraform — такое же изменение, как любое другое: сначала на тестовом стенде, потом у всей команды. Список версий — в каталоге зеркала hashicorp-releases.yandexcloud.net/terraform/.

В Windows архив terraform_…_windows_amd64.zip с того же зеркала распаковывают в каталог из PATH; сумму проверяют командой PowerShell Get-FileHash. Но удобнее работать в WSL с Debian — команды курса будут совпадать.
08

Зеркало провайдеров: ~/.terraformrc

Провайдеры Terraform по умолчанию берёт из registry.terraform.io. Чтобы он ходил в зеркало, в домашнем каталоге создают файл настроек командной строки ~/.terraformrc (в Windows — %APPDATA%\terraform.rc):

$ cat ~/.terraformrc
provider_installation {
  network_mirror {
    url     = "https://terraform-mirror.yandexcloud.net/"
    include = ["registry.terraform.io/*/*"]
  }
  direct {
    exclude = ["registry.terraform.io/*/*"]
  }
}

Этот файл — настройка вашей машины, а не проекта: в репозиторий его не кладут, в CI он создаётся в задании (модуль 08).

09

Первый цикл: init, plan, apply, destroy

Опишем самый простой ресурс — файл на диске. Каталог проекта hello/, в нём один файл main.tf:

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.9"
    }
  }
}

# Нужное состояние: файл с таким содержимым существует
resource "local_file" "motd" {
  filename = "${path.module}/motd.txt"
  content  = "Сервер учебного сервиса. Настроен Terraform.\n"
}

Блок terraform говорит, какой провайдер нужен, resource описывает сам файл. Подробно о синтаксисе — в модуле 02, сейчас важен цикл. Сначала terraform init — подготовить каталог и скачать провайдер:

$ terraform init
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.9"...
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

╷
│ Warning: Incomplete lock file information for providers
│ 
│ Due to your customized provider installation methods, Terraform was forced
│ to calculate lock file checksums locally for the following providers:
│   - hashicorp/local
│ 
│ The current .terraform.lock.hcl file only includes checksums for
│ linux_amd64, so Terraform running on another platform will fail to install
│ these providers.
│ 
│ To calculate additional checksums for another platform, run:
│   terraform providers lock -platform=linux_amd64
│ (where linux_amd64 is the platform to generate)
╵
Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Провайдер скачан с зеркала. Пометка unauthenticated и предупреждение означают, что зеркало не отдаёт подписи HashiCorp, поэтому Terraform сам посчитал контрольную сумму скачанного файла — только для текущей системы, linux_amd64. В каталоге появились:

$ ls -a
.
..
main.tf
.terraform
.terraform.lock.hcl
$ cat .terraform.lock.hcl
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/local" {
  version     = "2.9.1"
  constraints = "~> 2.9"
  hashes = [
    "h1:qGLHCuYSus+uHNnoEL4SuJqOs5yrNOcB7gnuHoVqizo=",
  ]
}

Чтобы файл блокировки подошёл коллегам на Windows и macOS, добавляем суммы для их систем. Команде providers lock зеркало указывают явно: настройки из ~/.terraformrc она не читает и без -net-mirror пошла бы в закрытый реестр:

$ terraform providers lock -net-mirror=https://terraform-mirror.yandexcloud.net/ -platform=linux_amd64 -platform=windows_amd64 -platform=darwin_arm64
- Fetching hashicorp/local 2.9.1 for linux_amd64...
- Retrieved hashicorp/local 2.9.1 for linux_amd64 (unauthenticated)
- Fetching hashicorp/local 2.9.1 for windows_amd64...
- Retrieved hashicorp/local 2.9.1 for windows_amd64 (unauthenticated)
- Fetching hashicorp/local 2.9.1 for darwin_arm64...
- Retrieved hashicorp/local 2.9.1 for darwin_arm64 (unauthenticated)
- Obtained hashicorp/local checksums for darwin_arm64; Additional checksums for this platform are now tracked in the lock file
- Obtained hashicorp/local checksums for linux_amd64; All checksums for this platform were already tracked in the lock file
- Obtained hashicorp/local checksums for windows_amd64; Additional checksums for this platform are now tracked in the lock file

Success! Terraform has updated the lock file.

Review the changes in .terraform.lock.hcl and then commit to your
version control system to retain the new checksums.

$ grep -c "h1:" .terraform.lock.hcl
3

Теперь terraform plan — что Terraform собирается сделать:

$ terraform plan

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.motd will be created
  + resource "local_file" "motd" {
      + content              = <<-EOT
            Сервер учебного сервиса. Настроен Terraform.
        EOT
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./motd.txt"
      + id                   = (known after apply)
    }

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

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.

Знак + — ресурс будет создан. (known after apply) — значения, которые станут известны только после создания. Итог плана — 1 to add, 0 to change, 0 to destroy. terraform apply снова строит план и после подтверждения выполняет его; ключ -auto-approve отвечает «yes» сам — в учебных примерах удобно, в работе с настоящей инфраструктурой план читают глазами (модуль 03):

$ terraform apply -auto-approve

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.motd will be created
  + resource "local_file" "motd" {
      + content              = <<-EOT
            Сервер учебного сервиса. Настроен Terraform.
        EOT
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./motd.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
local_file.motd: Creating...
local_file.motd: Creation complete after 0s [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ cat motd.txt
Сервер учебного сервиса. Настроен Terraform.

Повторный apply ничего не делает — реальность уже совпадает с описанием:

$ terraform apply -auto-approve
local_file.motd: Refreshing state... [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

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

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

$ echo "Поправил руками" > motd.txt
$ terraform plan
local_file.motd: Refreshing state... [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.motd will be created
  + resource "local_file" "motd" {
      + content              = <<-EOT
            Сервер учебного сервиса. Настроен Terraform.
        EOT
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./motd.txt"
      + id                   = (known after apply)
    }

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

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
$ terraform apply -auto-approve
local_file.motd: Refreshing state... [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.motd will be created
  + resource "local_file" "motd" {
      + content              = <<-EOT
            Сервер учебного сервиса. Настроен Terraform.
        EOT
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./motd.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
local_file.motd: Creating...
local_file.motd: Creation complete after 0s [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ cat motd.txt
Сервер учебного сервиса. Настроен Terraform.

Так Terraform борется с дрейфом: ручная правка не переживёт следующего apply. Хотите другое содержимое — меняйте код, а не файл. Последний шаг — terraform destroy удаляет всё, что создал Terraform в этом каталоге:

$ terraform destroy -auto-approve
local_file.motd: Refreshing state... [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # local_file.motd will be destroyed
  - resource "local_file" "motd" {
      - content              = <<-EOT
            Сервер учебного сервиса. Настроен Terraform.
        EOT -> null
      - content_base64sha256 = "gYEVgaamqUvZuFMTpylExST8wD7F+Oyr+1PkUNtrKZo=" -> null
      - content_base64sha512 = "jhgnunVeHeEjlIurM8Ke4m7+HFyv2PXywNXvjzgpziSgjJOZSenu+5PegQU0VyvmDSvolPB6y5dOE4GUHy0WRg==" -> null
      - content_md5          = "9e540ceb1a687f76a10b6454b329562d" -> null
      - content_sha1         = "63d5b2a71fb2d70a2bf757506a1e7de4217ab733" -> null
      - content_sha256       = "81811581a6a6a94bd9b85313a72944c524fcc03ec5f8ecabfb53e450db6b299a" -> null
      - content_sha512       = "8e1827ba755e1de123948bab33c29ee26efe1c5cafd8f5f2c0d5ef8f3829ce24a08c939949e9eefb93de810534572be60d2be894f07acb974e1381941f2d1646" -> null
      - directory_permission = "0777" -> null
      - file_permission      = "0777" -> null
      - filename             = "./motd.txt" -> null
      - id                   = "63d5b2a71fb2d70a2bf757506a1e7de4217ab733" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.
local_file.motd: Destroying... [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]
local_file.motd: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.
$ ls
main.tf
terraform.tfstate
terraform.tfstate.backup

Файла motd.txt больше нет. Остались terraform.tfstate — состояние (теперь в нём пусто) и его предыдущая копия .backup. Что в них и почему их не кладут в Git — модуль 05.

10

Тот же код в OpenTofu

OpenTofu устанавливается так же: архив tofu_…_linux_amd64.zip и tofu_…_SHA256SUMS с GitHub, проверка, распаковка. Свой реестр по умолчанию у него registry.opentofu.org, поэтому в ~/.tofurc на зеркало отправляются оба адреса — зеркало Яндекса отдаёт и тот, и другой:

$ cp hello/main.tf tofu/ && cd tofu
$ tofu version
OpenTofu v1.13.2
on linux_amd64
$ cat ~/.tofurc
provider_installation {
  network_mirror {
    url     = "https://terraform-mirror.yandexcloud.net/"
    include = ["registry.opentofu.org/*/*", "registry.terraform.io/*/*"]
  }
  direct {
    exclude = ["registry.opentofu.org/*/*", "registry.terraform.io/*/*"]
  }
}
$ tofu init | grep -E "Installing|Installed|initialized"
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (unauthenticated)
OpenTofu has been successfully initialized!
$ tofu apply -auto-approve | grep -E "Creation complete|Apply complete"
local_file.motd: Creation complete after 0s [id=63d5b2a71fb2d70a2bf757506a1e7de4217ab733]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ tofu destroy -auto-approve | grep "Destroy complete"
Destroy complete! Resources: 1 destroyed.

Тот же main.tf, та же последовательность команд, тот же результат. Дальше в курсе команды показаны для terraform; в OpenTofu они называются так же.

Ключевые выводы модуля

Запомните главное

  • Инфраструктура как код: серверы, сети и сервисы описаны в файлах в Git и создаются программой — с ревью, историей и без забытых ресурсов
  • Terraform декларативен: сравнивает описание с созданным и строит план; повторный запуск без изменений — «No changes»
  • Terraform создаёт инфраструктуру через API, Ansible настраивает машины внутри; их используют вместе
  • Ядро + провайдеры + состояние; провайдеры скачивает terraform init
  • OpenTofu — открытый форк с тем же языком и состоянием; код курса работает в обоих
  • Из России: Terraform — с hashicorp-releases.yandexcloud.net с проверкой SHA256SUMS, провайдеры — network_mirror в ~/.terraformrc
  • .terraform.lock.hcl — в Git, .terraform/ — нет; суммы для других систем — terraform providers lock -net-mirror=… -platform=…
  • Цикл: init → plan → apply → destroy; ручная правка исчезает при следующем apply
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Все модули Модуль 02: HCL и первый ресурс