Провайдеры: настройка и версии

Провайдер — это плагин-мост между Terraform и реальной системой (AWS, GitHub, Cloudflare). Его настраивают блоком provider, а версии фиксируют в required_providers и lock-файле.

Версия провайдера так же важна, как версия зависимости в коде. Незакреплённый провайдер однажды обновится сам, изменит поведение ресурса — и ваш безупречный plan вдруг захочет пересоздать прод.

Каждый провайдер нужно объявить (где взять и какой версии) и настроить (регион, аутентификация). Объявление живёт в блоке terraform { required_providers { ... } }, а настройка — в блоке provider "имя" { ... }. При init Terraform скачивает плагин и записывает точную версию в lock-файл.

Объявление и настройка

terraform {
  required_version = ">= 1.6"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }
}

provider "aws" {
  region = var.region
}

# алиас: два экземпляра одного провайдера (мульти-регион)
provider "aws" {
  alias  = "eu"
  region = "eu-west-1"
}

Аутентификацию (ключи доступа) почти никогда не пишут в коде — её берут из переменных окружения, профилей или ролей. Алиасы позволяют держать несколько настроек одного провайдера: например, ресурсы в двух регионах через provider = aws.eu.

Как работает под капотом

Lock-файл .terraform.lock.hcl фиксирует точную версию и контрольную сумму каждого провайдера, чтобы у всей команды и в CI был одинаковый плагин. Terraform проверяет: подходит ли залоченная версия под ограничение в коде. Смоделируем это:

constraint = (5, 40)          # ~> 5.40  => >=5.40, <6.0
locked = "5.42.0"
available = ["5.40.1", "5.42.0", "5.99.0", "6.0.0"]

def parse(v): return tuple(int(x) for x in v.split("."))

def satisfies(v, base):
    p = parse(v)
    return p >= base and p[0] < base[0] + 1

print("Залочена:", locked, "-> подходит?", satisfies(locked, constraint))
print("Совместимые версии:",
      [v for v in available if satisfies(v, constraint)])
print("6.0.0 запрещён мажором:", not satisfies("6.0.0", constraint))

«Попробуй сам ▶» — пока залоченная версия укладывается в ограничение, все на одной странице. init -upgrade поднимет lock до свежей совместимой.

Частые ошибки

  • Ключи доступа в коде. Хардкод credentials в provider — утечка в git. Используйте окружение/роли.
  • Не коммитить lock-файл. Без .terraform.lock.hcl в git у всех могут оказаться разные версии провайдера.
  • Незакреплённая версия провайдера. Без version Terraform возьмёт свежайшую — и поведение «уедет».

Best practices

  • Всегда указывайте version для провайдеров и коммитьте lock-файл в git.
  • Аутентификация — через переменные окружения, профили или IAM-роли, не через код.
  • Фиксируйте и required_version ядра, чтобы команда не разошлась по версиям Terraform.

Разбор глубже

Стоит разобраться, как провайдер вообще находит учётные данные. У большинства провайдеров есть цепочка источников аутентификации, которую он проверяет по порядку: явные аргументы в блоке provider (так делать не надо), затем переменные окружения (AWS_ACCESS_KEY_ID и подобные), затем общие конфиг-файлы (~/.aws/credentials), и наконец — роли среды выполнения (IAM-роль инстанса или OIDC-федерация в CI). Идеал — самый последний вариант: пайплайн получает временные права через роль, и в системе нет ни одного долгоживущего ключа, который можно украсть. Поэтому хардкод ключей в коде — это не просто плохой стиль, а прямая дыра в безопасности.

Версии провайдеров пинят по тем же причинам, что и модули, но с дополнительным нюансом: провайдер напрямую общается с API облака, и его новая мажорная версия может изменить поведение ресурсов или вовсе переименовать аргументы. Поэтому ограничение вида ~> 5.40 в продакшене — норма, а не перестраховка. Lock-файл .terraform.lock.hcl при этом фиксирует не просто номер версии, а ещё и контрольные суммы для каждой платформы (Linux, macOS, разные архитектуры), чтобы у разработчика на ноутбуке и в CI-контейнере оказался байт-в-байт один и тот же плагин. Обновляют lock осознанно командой terraform init -upgrade, после чего изменения в нём проходят через ревью.

Стоит также знать про механизм наследования провайдеров в модули. По умолчанию дочерний модуль получает конфигурацию провайдера от родителя автоматически, и явно прокидывать её не нужно. Но если модуль работает с несколькими экземплярами провайдера (например, два региона через алиасы), их передают явно через блок providers = { aws = aws, aws.replica = aws.eu } в вызове модуля. Это разделение «по умолчанию наследуется, при необходимости передаётся явно» держит простые модули простыми, а сложные мульти-региональные сценарии — управляемыми, не размывая при этом границу инкапсуляции между родителем и дочерним модулем.

Итог: провайдеры объявляют в required_providers, настраивают в provider, версии пинят через version и lock-файл. Секреты — мимо кода. Дальше — как организовать проект под несколько окружений.

Проверьте себя
1. Зачем коммитить .terraform.lock.hcl в git?
AДля ускорения init
BЧтобы у всей команды и CI был одинаковый набор версий провайдеров
CЭто требование лицензии
DОн хранит секреты
2. Зачем нужны алиасы провайдера (alias)?
AДля шифрования
BЧтобы держать несколько настроек одного провайдера, например ресурсы в разных регионах
CДля ускорения apply
DЧтобы скрыть секреты