Краткая история
- Авг 2023, HashiCorp меняет лицензию Terraform с MPL-2.0 на BSL (Business Source License), запрещающую коммерческие альтернативы.
- Сен 2023, Linux Foundation объявляет OpenTofu, fork последнего MPL-релиза (1.5.x).
- 2024, OpenTofu 1.6, 1.7, фичи добавляются параллельно с Terraform, иногда раньше.
- 2025, обе утилиты живут; community-провайдеры поддерживают обе.
Технически на 1.5 они идентичны. С 1.6+, расхождение растёт.
Что одинаковое
- HCL-синтаксис.
- Все провайдеры (single source, Registry).
- Команды (init, plan, apply, destroy, fmt, validate).
- State-format и обратная совместимость state'а, миграция
terraform state→tofu stateбез конверсии. - Backends (S3, GCS, local, Terraform Cloud в read-only mode для OpenTofu).
- Сценарии 95% использования.
Что разное
| Фича | Terraform | OpenTofu |
|---|---|---|
*.tftest.hcl фреймворк | 1.6+ | 1.6+ |
| mock_provider | 1.7+ | 1.7+ |
| state encryption at rest | Нет (только backend SSE) | Да (с 1.7, native) |
exclude в for_each | Нет | Да (с 1.8) |
| Stacks | Да (HCP-only) | Нет |
| Terraform Cloud integration | Нативный | Read-only (через OAuth) |
| License | BSL | MPL-2.0 |
| Provider registry | registry.terraform.io | registry.opentofu.org (зеркало) |
Migration
Сценарий: hcp-free workflow, хочешь уйти от BSL.
# Терраформ устанавливал так:
brew install hashicorp/tap/terraform
# OpenTofu:
brew install opentofu
tofu --version
Дальше, tofu вместо terraform:
tofu init
tofu plan
tofu apply
State совместим, tofu plan на сырой terraform.tfstate от Terraform
работает. Никакой конверсии. Lockfile (.terraform.lock.hcl) общий.
Если стeк использует фичи 1.6+ (например mock_provider), сделай
тест-прогон. Парность хорошая, но изредка нюансы.
State encryption
OpenTofu 1.7+ умеет шифровать state на стороне клиента до записи в backend:
terraform { encryption { key_provider "aws_kms" "key" {kms_key_id = "arn:aws:kms:..."
region = "us-east-1"
key_spec = "AES_256"
}
method "aes_gcm" "method" {keys = key_provider.aws_kms.key
}
state {method = method.aes_gcm.method
}
plan {method = method.aes_gcm.method
}
}
}
Это плюс: даже если backend (S3) compromised, state в нём зашифрован. Terraform полагается на SSE backend'а, что иногда недостаточно. См. tf-secrets-in-state.
exclude в for_each (OpenTofu 1.8)
variable "envs" {type = set(string)
default = ["dev", "stage", "prod", "test"]
}
resource "aws_s3_bucket" "logs" {for_each = var.envs
bucket = "logs-${each.key}" exclude {condition = each.key == "test"
}
}
Удаляет элемент из for_each без переиндексирования. Terraform такого
не имеет, нужно setsubtract(var.envs, ["test"]).
Matrix-CI с обеими
Самое практичное в неопределённости, гонять оба:
jobs:
plan-terraform:
runs-on: ubuntu-latest
steps:
- uses: hashicorp/setup-terraform@v3
with: { terraform_version: 1.9.8 }- run: terraform init
- run: terraform plan
plan-opentofu:
runs-on: ubuntu-latest
steps:
- uses: opentofu/setup-opentofu@v1
with: { tofu_version: 1.8.0 }- run: tofu init
- run: tofu plan
Если что-то расходится, оба job'а покажут. На production-apply выбираешь один (тот, на котором state).
Это страховка: если HashiCorp ещё раз поменяет лицензию, у тебя готовый switch.
Подводные камни
-
registry.terraform.iovsregistry.opentofu.org. Провайдеры те же, но регистры разные. OpenTofu по умолчанию ходит в свой;~/.terraformrcили env-var переопределяет. Для air-gapped, оба через mirror. -
Terraform Cloud features. Sentinel-policy, workspaces, dynamic credentials, все эти TC-фичи в OpenTofu не работают; OpenTofu может только читать state из TC.
-
Расхождение версионирования. Major-version числа не совпадают: OpenTofu 1.7 ≠ Terraform 1.7. Сравнивай feature-by-feature, не по номеру.
-
hcp-terraformблок в config'е. OpenTofu понимает синтаксис, но не подключается. На миграционный период оставь как есть, не мешает. -
Tooling-расхождение. Atlantis, Spacelift, Env0, что-то умеет
tofu, что-то нет. Перед миграцией проверь. -
Подписи и checksum'ы провайдеров. OpenTofu использует свои public-keys. Если в
terraform_required_providersуказан explicit hash, он может не совпасть. Решение: убрать lockfile, переинициализировать. -
Community уже разделено. Issue в одном репозитории может не отражаться в другом. При репорте бага, оба.
-
State encryption, opt-in. Включил, все участники команды должны иметь KMS-доступ. Без KMS-decrypt никто не может работать с state'ом. Заведомо сложнее рекавери после потери ключа.