Перейти к содержанию

Terraform

Команды, управляющие облаком через Terraform, могут развёртывать Turbo EA тем же способом. Репозиторий содержит четыре корневых модуля в deploy/terraform/: по одному на каждый управляемый контейнерный сервис со страницы Управляемые контейнерные сервисы, плюс модуль, устанавливающий Helm-чарт со страницы Kubernetes и облако в уже существующий кластер. Эта страница — о модулях; две страницы по ссылкам остаются источником сведений о том, что каждая платформа умеет, а что нет.

Что создают модули

Модуль Платформа Создаёт Вы предоставляете
ecs-fargate AWS ECS Fargate Кластер, задачу и сервис, Application Load Balancer с HTTPS, EFS, секреты Secrets Manager, RDS for PostgreSQL (опционально) VPC с публичными и приватными подсетями (NAT), сертификат ACM
azure-container-apps Azure Container Apps Окружение, container app, Log Analytics, учётную запись хранилища и файловый ресурс, Flexible Server (опционально) Группу ресурсов; при желании делегированную подсеть и частную зону DNS
cloud-run Google Cloud Run Сервис, Filestore, секреты Secret Manager, удалённый репозиторий Artifact Registry, глобальный HTTPS-балансировщик с управляемым сертификатом, Cloud SQL (опционально) Проект, VPC с подсетью, доступ к частным сервисам в этой VPC
kubernetes Любой кластер Kubernetes Namespace, Secret с учётными данными, релиз Helm Кластер и kubeconfig, сервер PostgreSQL

Три облачных модуля создают ту же группу контейнеров, что и шаблоны со страницы управляемых контейнерных сервисов: пограничный nginx на порту 8920 перед фронтендом, бэкендом и опциональным сервером MCP, все на общем localhost; ровно один бэкенд, который никогда не масштабируется и не уменьшается до нуля; /app/data на постоянном ресурсе, принадлежащем пользователю 1000; TLS завершается на границе платформы. Та же форма, другой инструмент — всё, что страница платформы говорит о пробах, наложении при развёртывании и стартовой блокировке, действует без изменений.

Три соглашения действуют для всех четырёх:

  • Версия — явный входной параметр. У image_tag (или chart_version для модуля Kubernetes) нет значения по умолчанию, которое могло бы устареть; terraform.tfvars.example рядом с каждым модулем содержит текущий выпуск.
  • База данных создаётся по умолчанию, с переключателем на собственную. create_database = false вместе с db_host и db_password направляет бэкенд на уже работающий сервер. Откуда бы ни взялась база, объекты хранилища секретов (SECRET_KEY и пароль базы) принадлежат модулю, поэтому определение контейнера имеет единственную форму.
  • Сеть никогда не создаётся. Идентификаторы VPC, VNet и подсетей — входные параметры; README каждого модуля перечисляет, что они уже должны обеспечивать.

Быстрый старт

cd deploy/terraform/<модуль>
cp terraform.tfvars.example terraform.tfvars
# отредактируйте terraform.tfvars, а секрет держите вне любых файлов:
export TF_VAR_secret_key="$(openssl rand -base64 48)"
terraform init
terraform plan
terraform apply

Направьте DNS на выходное значение модуля — alb_dns_name в AWS, fqdn в Azure, load_balancer_ip в Google Cloud, — откройте public_url и зарегистрируйтесь: первый пользователь становится администратором.

Состояние содержит секреты

secret_key, сгенерированные пароли базы и значения секретов Container Apps попадают в состояние Terraform. Используйте зашифрованный удалённый бэкенд с контролем доступа (S3 с SSE и блокировкой, контейнер Azure Storage, бакет GCS, Terraform Cloud) — и никогда terraform.tfstate на ноутбуке для боевого экземпляра. Храните SECRET_KEY вместе с резервными копиями базы: его потеря делает недействительными все сессии и все зашифрованные настройки, и одна база их не вернёт.

AWS ECS Fargate

Перед началом: VPC как минимум с двумя публичными подсетями (балансировщик) и двумя приватными подсетями в разных зонах доступности (задача, точки монтирования EFS, база данных); NAT-шлюз, чтобы приватные подсети могли получать образы с ghcr.io; сертификат ACM для имени хоста в том же регионе.

Задаваемые параметры: region, vpc_id, public_subnet_ids, private_subnet_ids, certificate_arn, public_url, image_tag. При желании route53_zone_id — тогда модуль сам создаст alias-запись. Создаваемый экземпляр RDS приватный, зашифрованный, с включённой защитой от удаления и семью днями резервных копий; свою базу подключайте через create_database = false, db_host и db_security_group_id (модуль откроет порт со стороны задачи).

Развёртывание идёт по схеме «остановить, затем запустить» (deployment_minimum_healthy_percent = 0): обновление версии стоит одну-две минуты простоя и никогда не запускает два бэкенда. Для оболочки: aws ecs execute-command … --container backend --interactive --command sh.

Azure Container Apps

Перед началом: существующая группа ресурсов. Для интеграции с VNet — подсеть /27, делегированная Microsoft.App/environments. Для базы, недоступной из интернета, — вторая подсеть, делегированная Microsoft.DBforPostgreSQL/flexibleServers, и частная зона DNS с окончанием .postgres.database.azure.com, привязанная к VNet: задавайте postgresql_delegated_subnet_id и postgresql_private_dns_zone_id вместе. Без них создаваемый сервер сохраняет публичную конечную точку, ограниченную сервисами Azure.

Задаваемые параметры: subscription_id, resource_group_name, location, public_url, image_tag, а также глобально уникальные storage_account_name и postgresql_server_name. Первое развёртывание отвечает на выходе fqdn; привяжите собственный домен через az containerapp hostname add / bind, как описано на странице управляемых контейнерных сервисов, затем задайте public_url и примените снова. У Flexible Server в Azure нет флага защиты от удаления, поэтому модуль ставит блокировки CanNotDelete на сервер и учётную запись хранилища (db_deletion_protection, storage_deletion_protection).

Google Cloud Run

Перед началом: проект, сеть VPC с подсетью в регионе и доступ к частным сервисам в этой VPC — он нужен частному IP Cloud SQL. Если в VPC его ещё нет, один раз задайте create_private_service_connection = true; второй пиринг в VPC, где он уже есть, завершится ошибкой.

Задаваемые параметры: project_id, region, network, subnetwork, public_url, image_tag. Модуль включает API, создаёт удалённый репозиторий Artifact Registry, проксирующий ghcr.io (Cloud Run не может тянуть образы с ghcr.io напрямую), экземпляр Filestore для /app/data (уровень по умолчанию BASIC_HDD начинается с 1 ТиБ и составляет основную часть стоимости), запускает разовое задание, передающее ресурс пользователю 1000, и ставит перед сервисом глобальный HTTPS-балансировщик с сертификатом, управляемым Google. Создайте A-запись DNS для хоста public_url, указывающую на load_balancer_ip; сертификат остаётся в состоянии PROVISIONING, пока запись не начнёт разрешаться. Загрузки больше 32 МиБ — крупный импорт рабочего пространства — не проходят по пути HTTP/1 в Cloud Run; это ограничение платформы, а не настройка модуля.

Kubernetes

Модуль kubernetes оборачивает опубликованный чарт в helm_release — для команд, чьи кластеры тоже управляются Terraform. Он создаёт namespace и Secret с SECRET_KEY и POSTGRES_PASSWORD (либо использует через existing_secret Secret от External Secrets или Sealed Secrets), формирует собственные ключи values чарта и передаёт Secret по имени — секреты никогда не проходят через values. Задаваемые параметры: chart_version, public_url, db_host и либо secret_key + db_password, либо existing_secret. Класс Ingress, аннотации и TLS передаются через объект ingress; всё, чего модуль не раскрывает (backend.resources, seed.demo…), — через extra_values, список документов values чарта, объединяемых после сгенерированного.

Два блока provider читают kubeconfig; замените их аутентификацией вашего кластера (токен EKS, учётные данные AKS, плагин аутентификации GKE), если Terraform создаёт и сам кластер.

Обновление и удаление

Обновление версии — это изменение image_tag (или chart_version) и terraform apply; бэкенд выполняет миграции при запуске, под стартовой блокировкой на платформах, где старый и новый экземпляры накладываются. Сначала прочитайте примечания к выпуску и сделайте резервную копию базы, как при любой другой установке — Эксплуатация и обновления применимы.

terraform destroy отклоняется, пока включена защита от удаления: отключите db_deletion_protection (AWS, Google Cloud и блокировки в Azure), deletion_protection у сервиса Cloud Run и в AWS решите вопрос с финальным снимком RDS (db_skip_final_snapshot), примените, затем удаляйте. Имя удалённого экземпляра Cloud SQL нельзя использовать повторно в течение недели.

Проверка без облака

Каждый модуль содержит тесты в tests/, работающие с имитациями провайдеров: настоящие схемы провайдеров, вымышленные значения, никаких учётных данных, ничего не создаётся. Они фиксируют связку, от которой зависят страницы платформ, — один бэкенд, граница на порту 8920, секреты по ссылке, том данных, переключатели на собственные ресурсы, — и CI запускает их вместе с terraform validate и tflint при каждом изменении. Чего они доказать не могут — что облако примет план; этой проверкой служит первый terraform plan в реальной учётной записи. OpenTofu не тестируется, но модули избегают всех возможностей, доступных только в Terraform.

Устранение неполадок

Симптом Причина и решение
Error creating Service Networking Connection … already exists (Google Cloud) В VPC уже есть доступ к частным сервисам. Задайте create_private_service_connection = false.
Сертификат, управляемый Google, остаётся в PROVISIONING A-запись DNS хоста public_url ещё не разрешается в load_balancer_ip. Исправьте DNS и подождите; применять ничего не нужно.
Permission denied в /app/data на Cloud Run Ресурс не принадлежит пользователю 1000 — например, после восстановления. Измените chown_job_token и примените, чтобы запустить задание снова.
postgresql_delegated_subnet_id and postgresql_private_dns_zone_id must be set together (Azure) Частный доступ требует обоих; зона должна быть привязана к VNet, а подсеть — отличаться от подсети окружения.
db_host is required when create_database is false Для собственной базы нужны db_host и db_password (TF_VAR_db_password).
terraform destroy отказывает Включена защита от удаления или блокировка CanNotDelete; см. Обновление и удаление.
Приложение отвечает по URL платформы, но не по public_url DNS указывает в другое место, или в public_url всё ещё FQDN платформы — задайте окончательный origin и примените; cookie сессии привязан к нему.