Перейти до вмісту

Infrastructure as Code (Terraform)

Уся AWS-інфраструктура Abitly/Studsearch кодифікується у Terraform-репозиторії abitly-org/cloud-infrastructure. Це джерело правди для естейту; таблиці в hosting та мапа інфраструктури — людиночитний дзеркальний зріз цього коду.

ПолеЗначення
Репоabitly-org/cloud-infrastructure (private, гілка master)
МоваHCL (Terraform) + кілька Node-скриптів (Lambda, secrets)
Статус🟡 кодифікує prod-естейт, але state ще локальний (див. розрив нижче)
Власник@Vladbandurin (bus factor 1)
AWSaccount 952854879948, регіон eu-central-1, VPC 10.20.0.0/16

Ціль — тримати всю AWS-інфру одним IaC-репо: мережу, обчислення, БД, кеш, search, CI/CD, секрети, IAM, observability. Усе, що раніше описувалось руками в хабі, тепер має відповідний Terraform-модуль.

ШляхЩо там
terraform/envs/root-модулі (по одному state на env): abitly-dev, abitly-prod, abitly-shared, studsearch
terraform/modules/перевикористовувані модулі (2 родини — див. каталог)
scripts/manage-secrets.mjsseed SSM Parameter Store з .env + sync frontend-слайсу в Amplify
ACCESS.mdдоступ до Strapi-адмінки через Cloudflare Access (Zero Trust)
TUNNEL.mdSSM port-forward тунель до shared RDS (IAM-authed, без статичних IP)
TAGGING.mdсхема тегів (Component / Service тощо)
PROD-MIGRATION.md, *-migration-plan.md, dev-migration-handoff.mdплани/хендофи міграції DigitalOcean → AWS
current-architecture.mdbaseline-знімок старого DO-стека (2026-04-30)
decision-log.mdвласний журнал ADR інфри (ADR-034, ADR-035, …) — незалежна нумерація від хабних ADR
.claude/.skills/terraform-style-guide/bundled Claude-скіл зі стиль-гайдом + security-нотатками

Середовища та залежності

Section titled “Середовища та залежності”

Чотири незалежні root-стеки. Кожен має власний state; крос-зв’язок — через data "terraform_remote_state" (читання state інших env). Це задає жорсткий порядок apply.

Env (root-модуль)Що володіє (owns)Споживає (remote_state)
envs/studsearchVPC 10.20.0.0/16 · subnets · IGW · shared RDS Postgres 17 · EB backend · Amplify frontend · SSM— (фундамент)
envs/abitly-sharedTypesense (EC2 + EBS) · private DNS zone · access IAM groupstudsearch (VPC)
envs/abitly-devAmplify frontend · EC2 backend (Docker + Caddy + EIP) · RDS t4g.micro · Valkey · GitHub OIDC provider · dev pipelines/secretsstudsearch · abitly-shared
envs/abitly-prodECS Fargate frontend+backend (ARM64) · shared ALB · Strapi EC2 + S3 media + ECR · Valkey · prod pipelines/secrets · SSM-redeploy Lambdastudsearch (VPC + RDS) · abitly-shared (typesense) · abitly-dev (OIDC)
flowchart TB
    studsearch["envs/studsearch<br/>VPC 10.20.0.0/16 · shared RDS Postgres 17<br/>EB + Amplify"]
    shared["envs/abitly-shared<br/>Typesense · private DNS · access IAM"]
    dev["envs/abitly-dev<br/>Amplify · EC2 backend · RDS dev · Valkey<br/>GitHub OIDC provider"]
    prod["envs/abitly-prod<br/>ECS frontend+backend · ALB · Strapi EC2<br/>S3 · ECR · Valkey · SSM-redeploy"]

    studsearch -- "vpc_id, rds endpoint+SG" --> shared
    studsearch -- "vpc_id, subnets" --> dev
    studsearch -- "vpc_id, rds, subnets" --> prod
    shared -- "typesense fqdn+key" --> dev
    shared -- "typesense fqdn+key" --> prod
    dev -- "github_oidc provider arn" --> prod

Порядок apply: studsearchabitly-sharedabitly-devabitly-prod. (prod читає OIDC-провайдер з dev-state, тож dev застосовується раніше за prod.)

Поза цим ланцюжком живуть самодостатні сателіт-стеки з remote state у S3 (бакет abitly-tfstate, use_lockfile), які знаходять спільну інфру через data-lookups замість remote_state, тому застосовуються в будь-який момент незалежно від ядра:

Сателіт-стекЩо робить
envs/abitly-emailemail-мікросервіс (SES + Lambda + DynamoDB + CloudFront)
envs/abitly-tutorAI-тьютор: EC2 arm64 + Caddy, реюзає shared RDS
envs/abitly-ads-audience-syncщоденний CRM→Meta синк ремаркетинг-аудиторій: EventBridge Scheduler → Fargate-таска, алярми → Telegram — див. платна реклама

Усі чотири env зараз на backend "local" — state-файли лежать тільки на машині оператора (.gitignore виключає terraform.tfstate*). Це працює для bus-factor-1, але:

  • ❌ немає remote backend → немає колаборації / бекапу state поза локальним диском;
  • ❌ немає state locking → ризик паралельного apply;
  • ❌ крос-env remote_state читає локальні шляхи (var.*_state_path), а не S3.

➡️ TODO: мігрувати на S3 remote state + locking (S3-native lock або DynamoDB), узгодити з наявним бакетом abitly-terraform-state (його зараз використовує analytics).

Дві родини модулів — за стеком, який їх викликає:

Generic-композити (terraform/modules/) — реюзаються стеком studsearch:

МодульШар
networkVPC, subnets, IGW, RDS SG
dataRDS PostgreSQL 17 + parameter group
secretsSSM Parameter Store entries
computeElastic Beanstalk (backend) + Amplify (frontend) + pipeline.tf

Дрібнозернисті abitly-{env}-{resource} — стеки abitly-*:

ШарМодулі
Network / computeabitly-prod-network · abitly-prod-alb · abitly-prod-ecs-{backend,frontend} · abitly-dev-ec2 · abitly-prod-ec2-strapi · abitly-shared-typesense
Data / cacheabitly-dev-rds · abitly-dev-elasticache · abitly-prod-elasticache (prod RDS реюзає studsearch — окремого модуля нема)
CI/CDabitly-{dev,prod}-ecr · abitly-{dev,prod}-github-oidc · abitly-dev-backend-pipeline · abitly-prod-{backend,frontend,strapi}-pipeline
Config-plane (Lambda)abitly-prod-ssm-redeploy · abitly-dev-amplify-sync (sync + rebuild) · abitly-dev-backend-restart · abitly-prod-ecs-restart · abitly-dev-amplify
Secrets / observability / DNS / S3abitly-{dev,prod}-secrets · abitly-{dev,prod}-observability · abitly-dev-dns · abitly-prod-s3-strapi
IAM-доступabitly-provisioner-access · abitly-backend-access · abitly-deny-access · abitly-{dev,prod}-db-access · abitly-shared-typesense-access

Кожен ресурс тегується по двох осях — функціональний шар (Component) і деплойний застосунок (Service) — щоб різати costs/inventory. Деталі — TAGGING.md.

ТегДжерелоЗначення
Projectprovider default_tags (авто)abitly / studsearch
Environmentprovider default_tags (авто)dev / prod / shared
ManagedByprovider default_tags (авто)terraform
Componenttags var у виклику модуляfrontend backend database cache search cms network cicd secrets observability
Servicetags var у виклику модуляabitly-web abitly-api strapi typesense studsearch abitly-platform (cross-cutting)

Project / Environment / ManagedBy ставляться автоматично через default_tags у providers.tf кожного env — не дублюй їх по ресурсах.

ІнструментОбмеження
Terraform>= 1.5
hashicorp/aws~> 5.50
іншіrandom ~> 3.6 · tls ~> 4.0 · archive ~> 2.4 · http ~> 3.4

Провайдер AWS у кожному env: profile = var.aws_profile (SSO-профіль abitly / abitly-admin), guard allowed_account_ids = [952854879948], регіон eu-central-1.

Terminal window
cd terraform/envs/<env> # abitly-dev | abitly-prod | abitly-shared | studsearch
terraform init
terraform plan
terraform apply
# порядок між env: studsearch → abitly-shared → abitly-dev → abitly-prod

Source of truth для env-змінних — SSM Parameter Store (/abitly/{dev,prod}/*). Terraform створює connection-параметри (DB_HOST, REDIS_URL, …) і плейсхолдери для секретів; реальні значення заливаються out-of-band:

Terminal window
node scripts/manage-secrets.mjs seed ../abitly-api-v2/.env.dev frontend-secrets.env
node scripts/manage-secrets.mjs sync # пушить frontend-слайс у Amplify app config

Скрипт пропускає TF-керовані ключі й відмовляється писати літерал PLACEHOLDER_REPLACE_OUT_OF_BAND. Жодних реальних значень — лише назви, повний індекс: environments.

  • Strapi-адмінка → Cloudflare Access (Zero Trust, OTP-email), origin замкнено на CF-діапазони + AOP mTLS. ACCESS.md + repo decision-log.md ADR-034 + хабний runbook.
  • Shared RDS → SSM port-forward через Strapi EC2 (TUNNEL.md), IAM-authed; публічний 0.0.0.0/0 на 5432 знято (ADR-035, SG-to-SG).
  • AWS загалом — SSO-роль, profile abitly у ~/.aws/config. MCP для AWS у робочому профілі нема — через aws CLI (реєстр).

Зв’язок із рештою хабу

Section titled “Зв’язок із рештою хабу”
ТемаSource of truthДзеркало в хабі
AWS-ресурси per-сервісцей репо (terraform/)hosting
Мапа платформцей репоarchitecture/infrastructure
Pipelines / OIDC / SSM-redeployмодулі *-pipeline, *-github-oidc, abitly-prod-ssm-redeploydeploy-pipeline
Рішення по інфріrepo decision-log.md
  • Remote state — мігрувати 4 env з local на S3 + locking (узгодити з abitly-terraform-state).
  • Консолідація — згорнути сюди abitly-analytics/infra/ та abitly-tg-bot-v3/infra/terraform/.
  • Уніфікувати дві родини модулів (generic-композити vs abitly-{env}-*) — або задокументувати, що різниця навмисна.
  • Узгодити repo decision-log.md ↔ хабні ADR (де яка нумерація живе).