В этом уроке разберём блок required_providers, семантику версий, что делает terraform init, зачем нужен файл .terraform.lock.hcl (и почему его коммитят в git), и как жить с провайдером в РФ через зеркало. Тема скучная на первый взгляд, но именно тут чаще всего ломаются сборки в CI, когда "у меня локально всё работало".
Что такое провайдер и зачем он нужен
Провайдер - это плагин. Отдельный бинарник, который Terraform скачивает и запускает рядом с собой. Провайдер знает, как разговаривать с конкретным API: какие у ресурсов поля, как создать виртуалку, как удалить сеть, как прочитать состояние. Terraform общается с плагином по своему внутреннему протоколу, а плагин уже переводит это в HTTP/gRPC-запросы к облаку.
Грубо говоря, Terraform - это коробка передач, а провайдер - это мотор под конкретное топливо. Хочешь Yandex Cloud - ставишь провайдер yandex. Нужен ещё S3-бакет в AWS параллельно - подключаешь второй провайдер. В одном проекте их может быть сколько угодно: yandex, kubernetes, helm, random, tls. Каждый тянет свой набор ресурсов и data-источников.
Откуда они берутся? Из реестра. По умолчанию это публичный Terraform Registry на registry.terraform.io. У провайдера есть адрес-источник в формате namespace/type, например yandex-cloud/yandex. Это полное имя - registry.terraform.io/yandex-cloud/yandex. Terraform по этому адресу ходит, узнаёт список версий, выбирает подходящую и качает бинарник под твою ОС и архитектуру.

Блок terraform: required_providers и required_version
Зависимости проекта объявляются не где попало, а в специальном блоке terraform. Это служебный блок настроек самого движка. Минимальный честный пример для Yandex Cloud:
Код: Выделить всё
terraform {
required_version = ">= 1.6"
required_providers {
yandex = {
source = "yandex-cloud/yandex"
version = "~> 0.140"
}
}
}
provider "yandex" {
zone = "ru-central1-d"
# token, cloud_id, folder_id обычно берутся из переменных окружения
# YC_TOKEN, YC_CLOUD_ID, YC_FOLDER_ID
}
Обрати внимание: source писать обязательно. Раньше Terraform умел угадывать namespace hashicorp по короткому имени, но для сторонних провайдеров вроде yandex это не работает - нужен полный адрес yandex-cloud/yandex, иначе init пойдёт искать hashicorp/yandex и ничего не найдёт.
Семантика версий: что значат стрелочки
Версии провайдеров живут по semver: major.minor.patch, например 0.140.3. Ограничения в поле version - это не просто "хочу вот эту", а правила, какой диапазон допустим.
- = 0.140.3 - ровно эта версия, ни шагом влево. Жёстко, но иногда нужно.
- >= 0.140 - эта и любая новее. Опасно: завтра выйдет ломающее обновление, и init его подтянет.
- ~> 0.140 - так называемый pessimistic constraint. Разрешает обновлять последнюю указанную цифру. Для 0.140 это значит >= 0.140.0 и < 0.141.0, то есть только патчи внутри минорной версии.
- ~> 1.4 (для двузначной записи) - разрешает любой минор внутри мажора: >= 1.4.0 и < 2.0.0.
Главное правило: ограничение версии в HCL задаёт коридор допустимого, а конкретную версию из этого коридора выбирает init и записывает в lock-файл. Это две разные вещи, и путать их - классическая ошибка новичка.
terraform init и lock-файл .terraform.lock.hcl
Команда terraform init - это первое, что ты запускаешь в новом проекте. Она читает required_providers, ходит в реестр, скачивает подходящие бинарники в скрытую папку .terraform/ и - внимание - создаёт или обновляет файл .terraform.lock.hcl.
Lock-файл - это слепок ровно тех версий, которые реально установились, плюс их криптографические хэши. По смыслу это полный аналог package-lock.json из npm или composer.lock из PHP. Зачем он:
- Повторяемость. У тебя version = "~> 0.140" - коридор широкий. Без lock-файла на твоей машине встанет 0.140.1, а в CI через неделю - уже 0.140.5, и поведение может разойтись. Lock прибивает гвоздями конкретную версию для всех.
- Безопасность. В файле лежат хэши (h1: и zh:) скачанных бинарников. Если кто-то подменит пакет в зеркале - хэш не сойдётся, init заорёт.
К 2026 году привычка коммитить lock-файл стала стандартом де-факто и в Terraform, и в OpenTofu (открытый форк, который многие в РФ выбрали как замену из-за лицензии и санкционных вопросов; команды у него те же, файл состояния и lock-файл совместимы). Свежие версии OpenTofu, кстати, починили давнюю боль с хэшами при работе через локальное зеркало - реестр теперь отдаёт оба формата хэшей сразу, и не приходится отдельно гонять providers lock.
Реестр, зеркало и реалии РФ
Тут начинается специфика. Публичный Terraform Registry от HashiCorp из России работает нестабильно: то отвалится, то отдаст ошибку Failed to query available provider packages. Решение - официальное зеркало Yandex Cloud. Создаёшь в домашней папке файл конфигурации CLI (обычно ~/.terraformrc, на Windows - %APPDATA%/terraform.rc) и прописываешь установку через зеркало:
Код: Выделить всё
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
Обновление провайдера: init -upgrade
Раз lock-файл прибивает версию, обычный terraform init её больше не двигает - он уважает то, что записано. Чтобы подтянуть свежие версии в рамках твоих ограничений, есть отдельный флаг:
Код: Выделить всё
terraform init -upgrade
Типичные грабли
- Забыл source. Пишешь только version - init ищет hashicorp/yandex и падает. Для сторонних провайдеров source обязателен.
- .terraform/ в репозитории. Закоммитил папку с бинарниками - распух репозиторий, конфликты при смене ОС. Коммить только lock-файл, папку - в .gitignore.
- >= вместо ~>. Поставил version = ">= 0.140" без верхней границы - однажды прилетит мажор с ломающими изменениями и снесёт прод. Используй ~>.
- Lock-файл из другой ОС. Хэши h1: привязаны к платформам. Если в lock только хэши под macOS, а CI на Linux - init может ругаться. Лечится командой terraform providers lock с указанием нужных платформ или генерацией хэшей под все целевые ОС.
- Удалил lock после конфликта. Снёс файл, чтобы "не мешал" - потерял фиксацию версий и весь смысл повторяемости. Решай конфликт через init -upgrade, а не удалением.
Повтори руками, 15 минут:
- Создай пустую папку, положи main.tf с блоком terraform и required_providers для yandex (source yandex-cloud/yandex, version ~> 0.140) и блоком provider "yandex" с zone = "ru-central1-d".
- Если ты в РФ - пропиши зеркало в ~/.terraformrc как выше.
- Запусти terraform init. Посмотри, что появилась папка .terraform/ и файл .terraform.lock.hcl. Открой lock-файл и найди строки version, constraints и hashes.
- Поменяй version на ~> 0.139 и снова сделай init. Посмотри, как лок попробует откатиться (или потребует -upgrade).
- Верни ~> 0.140, выполни terraform init -upgrade и сравни git diff lock-файла.
- Чем отличается ограничение version в required_providers от версии, записанной в .terraform.lock.hcl?
- Что означает ~> 0.140 и чем это безопаснее, чем >= 0.140?
- Почему .terraform.lock.hcl коммитят в git, а папку .terraform/ - нет?
- Какой командой обновить провайдер до свежей версии в рамках ограничений и что при этом меняется в проекте?
Terraform сам по себе пустой движок, всю работу с API делают провайдеры-плагины. Зависимости объявляй в блоке terraform: required_version под движок, required_providers под плагины, и для yandex обязательно указывай source = "yandex-cloud/yandex". Версии фиксируй через ~>, а не голым >=. terraform init скачивает провайдеры и создаёт .terraform.lock.hcl - этот файл коммить в git, он гарантирует, что у тебя, у коллеги и в CI стоят одинаковые версии с проверенными хэшами. В РФ ставь провайдеры через зеркало terraform-mirror.yandexcloud.net. Обновляйся осознанно командой init -upgrade, проверяя план и diff лока. Это и есть инфраструктура как код по-взрослому: предсказуемо, повторяемо, под контролем версий.