OpenTofu 1.8: провайдерские функции и миграция ещё одного клиента с Terraform
OpenTofu 1.8 принёс провайдерские функции и обновлённый тестовый фреймворк. Разбираем типовые проблемы миграции состояния и отличия в поведении провайдера Yandex Cloud.
OpenTofu 1.8 с поддержкой провайдерских функций и улучшенным тестовым фреймворком
OpenTofu 1.8 вышел в августе 2024-го, и к моменту нашей миграции уже несколько месяцев был в проде у других команд. У нас как раз совпало: мы заканчивали миграцию ещё одного клиента с HashiCorp Terraform на OpenTofu. Так что разбирали новый релиз не в теории, а прямо в процессе - что удобно, потому что некоторые вещи в 1.8 прилетели очень вовремя.
Клиент - средний производственный объект, инфраструктура на Yandex Cloud, IaC-кодовая база примерно на две с половиной сотни ресурсов. До нас с этим кодом работала подрядная команда, которая ушла, и вся кодовая база досталась нам как есть - в разной степени актуальности, с несколькими workspace-ами и state-ами в YC Object Storage.
Что появилось в OpenTofu 1.8
Два главных изменения релиза:
Провайдерские функции (provider-defined functions). Провайдеры теперь могут экспортировать свои функции, доступные прямо в конфигурации через provider::<имя>::<функция>(). До этого приходилось либо тащить внешние data source только ради вычисления чего-то простого, либо городить local-only ресурсы как костыли. Yandex Cloud провайдер пока таких функций не экспортирует, но сам механизм интересен: ждём когда более распространённые провайдеры (AWS, GCP) его подтянут - для работы с ARN-ами или именами ресурсов это будет удобно.
Обновлённый тестовый фреймворк. В OpenTofu развивают встроенный tofu test - в 1.8 добавили поддержку mock-провайдеров в тестах. Это принципиально: можно писать тесты на конфигурацию без реального поднятия инфраструктуры и без билла за тест-ран. Для нас это пока факт к сведению, но на этом клиенте мы планируем написать unit-тесты на несколько критических модулей - как раз хороший повод попробовать.
Типовые проблемы миграции state
Миграция state с Terraform на OpenTofu в целом описана в документации и действительно несложная - state-файл совместим, tofu умеет его читать напрямую. Но несколько мест где мы потратили лишнее время:
State в YC Object Storage с Terraform-бэкендом. Клиент хранил state в бакете через backend "s3" - это работает и в OpenTofu, backend совместим. Проблема была в том, что у нас не было чистого доступа к ключам: SA-ключи были прошиты в CI-переменных GitLab, часть ротирована, часть - нет. Пришлось восстанавливать доступ через YC Console, создавать новый SA с минимальными правами на бакет и переконфигурировать backend. Это не проблема OpenTofu - это проблема отсутствия документированного доступа к инфраструктуре при смене подрядчика. Но в процессе миграции оно накладывается и добавляет хаоса.
Версии провайдеров в lock-файле. .terraform.lock.hcl содержал хэши, специфичные для Terraform-провайдеров с registry.terraform.io. OpenTofu использует registry.opentofu.org. При tofu init провайдеры скачиваются заново и lock-файл перегенерируется - но если в коде явно прописан source hashicorp/yandex, то tofu сам его перенаправляет на opentofu-реестр. Звучит прозрачно, и в большинстве случаев так и есть. Но у клиента был один кастомный провайдер с закрытого зеркала, прописанный через required_providers с кастомным адресом - его пришлось явно добавить в provider_installation блок в .terraformrc (для OpenTofu - .tofurc).
workspace-ы и state-дрейф. У клиента было три workspace-а: prod, staging, и загадочный old-staging, в котором ресурсов не было, но state не был пуст. Прогнали tofu plan по всем трём - в old-staging обнаружилось несколько ресурсов, которые в YC не существовали. Кто-то удалил их руками, не почистив state. tofu state rm решил вопрос, но это хорошее напоминание: перед миграцией сделать plan по всем workspace-ам и разобраться с расхождениями.
Отличия в поведении провайдера Yandex Cloud
YC провайдер под OpenTofu - тот же yandex-cloud/yandex, и поведение в основном идентично. Но несколько наблюдений:
Аутентификация через IAM-токен vs SA-ключ. В Terraform-конфигурации клиента аутентификация была через token (IAM-токен), который протухает через 12 часов. Для CI это была боль: джобы, которые запускались редко, падали с 401. В OpenTofu мы параллельно перевели аутентификацию на SA JSON-ключ через service_account_key_file - это не изменение провайдера, а просто давно откладываемый порядок.
Атрибут allow_stopping_for_update у compute_instance. В нескольких ресурсах compute_instance этот атрибут не был выставлен. При tofu apply с изменением параметров, требующих перезапуска VM, провайдер выдаёт ошибку вместо молчаливого перезапуска. Terraform вёл себя так же, но на старых версиях провайдера этот атрибут игнорировался при некоторых изменениях. После обновления провайдера до актуальной версии поведение стало строже. Нашли это на staging до прода - повезло.
Удаление дисков при destroy. YC провайдер по умолчанию не удаляет загрузочный диск при destroy compute_instance, если диск был создан отдельным ресурсом. Это старое поведение, но клиент не знал - и при первом тестовом destroy в staging несколько дисков остались висеть. Добавили depends_on и явный yandex_compute_disk с lifecycle { prevent_destroy = false } там, где это нужно.
Итог на сейчас
Миграция завершена: прод переведён на OpenTofu 1.8, state в YC Object Storage, CI переписан с terraform на tofu. Пайплайн работает, plan/apply проходят чисто.
Из нового в 1.8 в эксплуатации пока задействовали только обновлённый tofu init с перегенерацией lock-файла - провайдерские функции ждут пока их добавят в YC-провайдер, тестовый фреймворк с mock-провайдерами возьмём в следующую итерацию.
Если у вас инфраструктура на managed-сопровождении и Terraform, который не обновлялся с 2023 года, - разговор про переход на OpenTofu имеет смысл начать сейчас, пока расхождение в поведении ещё небольшое.