ADG Оставить заявку
Блог DevOps 4 мин чтения

Terraform 0.14: lock-файл для провайдеров и конец истории с расходящимися планами

Terraform 0.14 добавил .terraform.lock.hcl для фиксации версий провайдеров. Разбираем как это меняет совместную работу и что фиксировать в git.

Контекст момента

Terraform 0.14 вводит .terraform.lock.hcl - файл фиксации версий провайдеров, который решает проблему воспроизводимости планов в команде и CI/CD

Terraform 0.14 вышел в декабре, и одна фича там настолько точно бьёт в больное место, что мы решили написать отдельно. Речь про .terraform.lock.hcl - файл фиксации версий провайдеров. Звучит скромно. На практике это решает проблему, которую мы терпели последние полтора года.

В чём была боль

Сценарий, знакомый любому кто работает с Terraform в команде. Разработчик делает terraform plan на ноутбуке - всё чисто, изменений нет. CI/CD делает тот же план в пайплайне - получает несколько изменений в ресурсах, о которых никто не просил. Или наоборот: план проходит в CI, а на ноутбуке у другого человека - другой результат. Начинаешь разбираться и находишь: версии провайдеров разные.

До 0.14 ситуация выглядела так. В terraform.tf (или versions.tf) мы писали что-то вроде:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

~> 3.0 - это «любая 3.x, но не 4». При terraform init Terraform тянет последнюю подходящую версию. Что значит «последнюю»? Ту, которая есть в registry на момент инита. Если разработчик делал init в ноябре, а CI делает init в январе - версии расходятся. Особенно весело когда провайдер вышел с минорным обновлением, которое чуть-чуть меняет поведение plan по ресурсам, которых у вас много.

Классический обход - прибить версию до точной в version = "= 3.22.0". Работает, но хрупко: кто-то обновляет версию в одном файле, забывает сказать остальным, и пока все не переинитились - живём в расходящихся состояниях. Плюс точная версия в constraints - это ручная работа при каждом обновлении провайдера.

Нам это несколько раз создавало реальные проблемы. Один раз план из CI применился с версией провайдера, отличной от той, которую тестировал разработчик. Изменения были небольшими и прошли, но осадок остался.

Что делает .terraform.lock.hcl

При terraform init в 0.14 Terraform создаёт (или обновляет) .terraform.lock.hcl в корне модуля. Файл выглядит так:

provider "registry.terraform.io/hashicorp/aws" {
  version     = "3.26.0"
  constraints = "~> 3.0"
  hashes = [
    "h1:yNQMgS3Sl3g4MMg7A5i5J/bWgqzmqXvqpXbDfWbL5tE=",
    "zh:19b3f876e0fd69b0ebbf3ea4b6ebc8f31bdb40cde5a5....",
  ]
}

Три поля: конкретная версия которую выбрал Terraform, исходный constraint из конфига, и хэши для проверки целостности пакета. При следующем terraform init - неважно, на ноутбуке другого человека или в CI - если этот файл есть в репозитории, Terraform берёт ровно ту версию, которая там зафиксирована. Не «последнюю подходящую», а конкретную.

Это ровно то, что делает package-lock.json в npm или Gemfile.lock в Ruby. Идея не новая, но появилась в Terraform только сейчас.

Что делать с этим файлом

Коммитить в git. Это основная точка, где люди могут запутаться. Lock-файл - это не артефакт сборки, это часть конфигурации. Без него смысл теряется.

Обновлять явно, через terraform init -upgrade. Обычный terraform init без флага не обновляет зафиксированные версии - читает из lock-файла. Это то что нужно: случайных обновлений не будет. Когда хотите обновить провайдер - запускаете init -upgrade, он выбирает новую версию в рамках constraints, обновляет lock-файл, вы коммитите изменение и отправляете в PR. Изменение версии провайдера становится видимым в diff-е, а не скрытым в состоянии ~/.terraform.

Хэши и платформы. Есть нюанс: Terraform по умолчанию записывает в lock-файл хэши только для текущей платформы. Если в команде есть и Linux, и macOS - при terraform init на другой платформе Terraform может пожаловаться на несовпадение хэшей. Решается через terraform providers lock с явным перечислением платформ:

terraform providers lock \
  -platform=linux_amd64 \
  -platform=darwin_amd64

После этого в lock-файле будут хэши для обеих платформ, и CI на Linux не будет расходиться с разработчиками на Mac.

Как мы раскатываем это на клиентские проекты

В managed-сопровождении у нас больше десятка проектов с Terraform. Стратегия простая: при ближайшем касании каждого проекта делаем init на 0.14, коммитим полученный lock-файл, добавляем шаг terraform providers lock для нужных платформ если CI на Linux, а разработчики на Mac. Отдельной задачей это не ставим - будет сделано по ходу работы.

В .gitignore у большинства проектов был .terraform/ - это правильно, там кеш провайдеров и плагинов, его не надо коммитить. Но .terraform.lock.hcl лежит не внутри .terraform/, а рядом - так что gitignore его не задевает, всё нормально.

Где сейчас

Пара проектов уже на 0.14 с lock-файлом в репозитории. Ощущение от workflow правильное: когда кто-то обновляет версию провайдера, это видно в PR как изменение lock-файла. Можно смотреть с какой версии на какую, можно обсуждать. Раньше это было невидимым.

Расходящихся планов между ноутбуком и CI пока не видели - но и прошло немного времени. Паттерн с lock-файлом выглядит разумно, и мы его фиксируем как стандарт для новых проектов.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.