Почему «создам сервер в консоли облака, это быстрее» перестаёт быть быстрее на втором окружении
Первый сервер в облаке создают в веб-консоли: выбрать зону, образ, размер диска, сеть, группу безопасности, добавить ключ SSH. Это занимает десять минут и кажется самым быстрым способом. Через месяц нужен тестовый стенд «как прод», ещё через месяц — второй регион. Выясняется, что никто не помнит, какие галочки стояли в группе безопасности, а сеть прода собрана в три приёма разными людьми. Такую работу называют ClickOps.
| Проблема | Следствие |
|---|---|
| Настройки живут только в консоли облака | Тестовый стенд отличается от прода, ошибки «у нас не воспроизводится» |
| Ручные правки во время аварии | Дрейф: реальная инфраструктура расходится с тем, что все считают правдой |
| Нет истории и ревью | Непонятно, кто открыл порт 5432 в интернет и зачем; откатить нечего |
| Забытые ресурсы | Диски и адреса от удалённых стендов годами списывают деньги со счёта |
Инфраструктура как код (Infrastructure as Code, IaC) — описать серверы, сети, базы данных, DNS-записи и права доступа в текстовых файлах, хранить их в Git и создавать программой. Описание проходит ревью в merge request, второе окружение — это тот же код с другими переменными, а удалить стенд целиком — одна команда, после которой не остаётся забытых дисков.
Скрипт с командами облачной CLI описывает действия: «создай сеть», «создай сервер». Запустите его второй раз — получите вторую сеть и второй сервер или ошибку «имя занято». Чтобы скрипт можно было повторять, в нём пишут проверки «а не создано ли уже», и через полгода проверок больше, чем полезных команд.
Terraform описывает результат: «должна существовать сеть с такими параметрами и в ней два сервера». Перед изменением Terraform сравнивает описание с тем, что уже создано, и составляет план — список действий, которые приведут одно к другому. Если всё совпадает, план пуст и ничего не делается. Изменили в описании размер диска — в плане одно изменение, а не пересоздание всего.
| Задача | Скрипт: действия | Terraform: результат |
|---|---|---|
| Первый запуск | Создаёт всё по порядку | План «создать N ресурсов», после подтверждения — создаёт |
| Повторный запуск | Дубли или ошибки, если не писать проверки | «No changes» — ничего не делает |
| Поменяли параметр | Писать отдельный скрипт изменения | План с одним изменением |
| Удалить стенд | Вспомнить всё созданное и удалять в обратном порядке | terraform destroy — по записанному списку и в правильном порядке |
Оба инструмента описывают нужное состояние кодом, но отвечают за разные слои. Terraform создаёт инфраструктуру: виртуальные машины, сети, балансировщики, управляемые базы данных, бакеты, записи DNS. Ansible настраивает то, что внутри машин: пакеты, файлы конфигурации, службы. Типичная связка: Terraform создаёт три сервера и выдаёт их адреса, Ansible ставит на них nginx и PostgreSQL.
| Terraform | Ansible | |
|---|---|---|
| Что делает | Создаёт, изменяет и удаляет ресурсы через API облаков и сервисов | Настраивает существующие машины по SSH |
| Как знает, что уже есть | Хранит состояние — список созданных ресурсов и их идентификаторы | Каждый раз проверяет машину заново |
| Удаление | Удаляет всё, что создал, одной командой | Убрать задачу из плейбука ≠ удалить пакет с сервера |
| Язык | HCL | YAML |
Границы не жёсткие: Terraform умеет передать машине скрипт первого запуска (cloud-init), Ansible умеет создавать машины в облаке. Но каждый силён в своём, и в команде их используют вместе.
ваш каталог с кодом *.tf
│
terraform (ядро): читает код, строит граф зависимостей,
│ сравнивает с состоянием и составляет план
├── провайдер yandex-cloud/yandex ──▶ API Yandex Cloud
├── провайдер kreuzwerker/docker ──▶ Docker на этой машине
└── провайдер hashicorp/local ──▶ локальные файлы
│
terraform.tfstate — что создано и с какими идентификаторами
terraform, один исполняемый файл. Само по себе оно не умеет создавать ни одного ресурса.yandex-cloud/yandex, selectel/selectel и другие. Ядро скачивает нужные провайдеры командой terraform init.В курсе нет облачного аккаунта и денег не нужно: практика — на провайдере docker (контейнеры, сети и тома на вашем Docker из курса docker-basics), local и random. Приёмы те же, что в облаке; где отличие важно, показан код для Yandex Cloud.
Terraform выпускает компания HashiCorp. До августа 2023 года он распространялся под открытой лицензией MPL 2.0; с версии 1.6 HashiCorp перешла на BSL 1.1 — исходный код открыт, но нельзя строить на нём конкурирующий коммерческий продукт. В ответ сообщество сделало форк последней открытой версии — OpenTofu, он развивается в Linux Foundation под MPL 2.0, первая стабильная версия вышла в январе 2024 года. В 2025 году HashiCorp стала частью IBM.
| Terraform | OpenTofu | |
|---|---|---|
| Команда | terraform | tofu |
| Лицензия | BSL 1.1 | MPL 2.0 |
| Язык, провайдеры, состояние | Один и тот же HCL, те же провайдеры, совместимый файл состояния. Код этого курса работает в обоих без правок | |
| Только здесь | Stacks, actions, поиск ресурсов terraform query | Шифрование состояния, переменные в настройках backend, for_each у провайдеров |
| Документация облаков России | Yandex Cloud, Selectel, Cloud.ru, VK Cloud | В основном статьи сообщества |
Курс построен на Terraform: его требуют в вакансиях и под него написаны инструкции российских облаков. Лицензия BSL не мешает ни учиться, ни управлять инфраструктурой своей компании — ограничение касается тех, кто продаёт Terraform как сервис. Всё, что вы изучите, переносится на OpenTofu заменой terraform на tofu; в конце модуля это показано на деле, а возможности только одного из продуктов в курсе помечены.
С 2022 года HashiCorp закрыла для российских адресов сайт загрузки releases.hashicorp.com, свой apt-репозиторий и реестр провайдеров registry.terraform.io. Реестр OpenTofu registry.opentofu.org с августа 2024 года тоже не отвечает российским адресам. В пакетах Debian 13 нет ни Terraform, ни OpenTofu.
Обходить блокировки через VPN не нужно: Yandex Cloud держит зеркала, и ими пользуются компании в России.
| Что | Зеркало |
|---|---|
| Архивы Terraform | https://hashicorp-releases.yandexcloud.net/terraform/ |
| Провайдеры для Terraform и OpenTofu | https://terraform-mirror.yandexcloud.net/ |
| Архивы OpenTofu | Релизы на GitHub: github.com/opentofu/opentofu/releases |
Есть и зеркало провайдеров Selectel (tf-proxy.selectel.ru). Зеркало — это доверие третьей стороне: вы получаете тот файл, который отдал Яндекс. Поэтому архив Terraform проверяем по контрольной сумме, а провайдеры фиксируем в файле блокировки (ниже).
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/.
terraform_…_windows_amd64.zip с того же зеркала распаковывают в каталог из PATH; сумму проверяют командой PowerShell Get-FileHash. Но удобнее работать в WSL с Debian — команды курса будут совпадать.Провайдеры 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/*/*"]
}
}
network_mirror — брать провайдеры, адрес которых подходит под include, с зеркала;direct с exclude — для остальных ходить в их реестры напрямую, а для registry.terraform.io — никогда, чтобы при сбое зеркала Terraform не пытался тихо уйти в закрытый реестр.Этот файл — настройка вашей машины, а не проекта: в репозиторий его не кладут, в CI он создаётся в задании (модуль 08).
Опишем самый простой ресурс — файл на диске. Каталог проекта 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=",
]
}
.terraform/ — скачанные провайдеры. В Git не кладут, init скачает их снова..terraform.lock.hcl — файл блокировки: какая версия провайдера выбрана и её контрольные суммы. Его кладут в Git: у коллег и в CI init поставит ту же версию и откажется ставить файл с другой суммой.Чтобы файл блокировки подошёл коллегам на 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.
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 они называются так же.
terraform inithashicorp-releases.yandexcloud.net с проверкой SHA256SUMS, провайдеры — network_mirror в ~/.terraformrc.terraform.lock.hcl — в Git, .terraform/ — нет; суммы для других систем — terraform providers lock -net-mirror=… -platform=…init → plan → apply → destroy; ручная правка исчезает при следующем apply