Infrastructure as Code (Terraform)
Уся AWS-інфраструктура Abitly/Studsearch кодифікується у Terraform-репозиторії abitly-org/cloud-infrastructure. Це джерело правди для естейту; таблиці в hosting та мапа інфраструктури — людиночитний дзеркальний зріз цього коду.
Метадані
Section titled “Метадані”| Поле | Значення |
|---|---|
| Репо | abitly-org/cloud-infrastructure (private, гілка master) |
| Мова | HCL (Terraform) + кілька Node-скриптів (Lambda, secrets) |
| Статус | 🟡 кодифікує prod-естейт, але state ще локальний (див. розрив нижче) |
| Власник | @Vladbandurin (bus factor 1) |
| AWS | account 952854879948, регіон eu-central-1, VPC 10.20.0.0/16 |
Призначення та скоуп
Section titled “Призначення та скоуп”Ціль — тримати всю AWS-інфру одним IaC-репо: мережу, обчислення, БД, кеш, search, CI/CD, секрети, IAM, observability. Усе, що раніше описувалось руками в хабі, тепер має відповідний Terraform-модуль.
Структура репозиторію
Section titled “Структура репозиторію”| Шлях | Що там |
|---|---|
terraform/envs/ | root-модулі (по одному state на env): abitly-dev, abitly-prod, abitly-shared, studsearch |
terraform/modules/ | перевикористовувані модулі (2 родини — див. каталог) |
scripts/manage-secrets.mjs | seed SSM Parameter Store з .env + sync frontend-слайсу в Amplify |
ACCESS.md | доступ до Strapi-адмінки через Cloudflare Access (Zero Trust) |
TUNNEL.md | SSM 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.md | baseline-знімок старого 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/studsearch | VPC 10.20.0.0/16 · subnets · IGW · shared RDS Postgres 17 · EB backend · Amplify frontend · SSM | — (фундамент) |
envs/abitly-shared | Typesense (EC2 + EBS) · private DNS zone · access IAM group | studsearch (VPC) |
envs/abitly-dev | Amplify frontend · EC2 backend (Docker + Caddy + EIP) · RDS t4g.micro · Valkey · GitHub OIDC provider · dev pipelines/secrets | studsearch · abitly-shared |
envs/abitly-prod | ECS Fargate frontend+backend (ARM64) · shared ALB · Strapi EC2 + S3 media + ECR · Valkey · prod pipelines/secrets · SSM-redeploy Lambda | studsearch (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: studsearch → abitly-shared → abitly-dev → abitly-prod. (prod читає OIDC-провайдер з dev-state, тож dev застосовується раніше за prod.)
Поза цим ланцюжком живуть самодостатні сателіт-стеки з remote state у S3 (бакет abitly-tfstate, use_lockfile), які знаходять спільну інфру через data-lookups замість remote_state, тому застосовуються в будь-який момент незалежно від ядра:
| Сателіт-стек | Що робить |
|---|---|
envs/abitly-email | email-мікросервіс (SES + Lambda + DynamoDB + CloudFront) |
envs/abitly-tutor | AI-тьютор: EC2 arm64 + Caddy, реюзає shared RDS |
envs/abitly-ads-audience-sync | щоденний CRM→Meta синк ремаркетинг-аудиторій: EventBridge Scheduler → Fargate-таска, алярми → Telegram — див. платна реклама |
Стан state
Section titled “Стан state”Усі чотири 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).
Каталог модулів
Section titled “Каталог модулів”Дві родини модулів — за стеком, який їх викликає:
Generic-композити (terraform/modules/) — реюзаються стеком studsearch:
| Модуль | Шар |
|---|---|
network | VPC, subnets, IGW, RDS SG |
data | RDS PostgreSQL 17 + parameter group |
secrets | SSM Parameter Store entries |
compute | Elastic Beanstalk (backend) + Amplify (frontend) + pipeline.tf |
Дрібнозернисті abitly-{env}-{resource} — стеки abitly-*:
| Шар | Модулі |
|---|---|
| Network / compute | abitly-prod-network · abitly-prod-alb · abitly-prod-ecs-{backend,frontend} · abitly-dev-ec2 · abitly-prod-ec2-strapi · abitly-shared-typesense |
| Data / cache | abitly-dev-rds · abitly-dev-elasticache · abitly-prod-elasticache (prod RDS реюзає studsearch — окремого модуля нема) |
| CI/CD | abitly-{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 / S3 | abitly-{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.
| Тег | Джерело | Значення |
|---|---|---|
Project | provider default_tags (авто) | abitly / studsearch |
Environment | provider default_tags (авто) | dev / prod / shared |
ManagedBy | provider default_tags (авто) | terraform |
Component | tags var у виклику модуля | frontend backend database cache search cms network cicd secrets observability |
Service | tags var у виклику модуля | abitly-web abitly-api strapi typesense studsearch abitly-platform (cross-cutting) |
Project / Environment / ManagedBy ставляться автоматично через default_tags у providers.tf кожного env — не дублюй їх по ресурсах.
Версії та провайдер
Section titled “Версії та провайдер”| Інструмент | Обмеження |
|---|---|
| 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.
Як застосовувати
Section titled “Як застосовувати”cd terraform/envs/<env> # abitly-dev | abitly-prod | abitly-shared | studsearchterraform initterraform planterraform apply# порядок між env: studsearch → abitly-shared → abitly-dev → abitly-prodСекрети
Section titled “Секрети”Source of truth для env-змінних — SSM Parameter Store (/abitly/{dev,prod}/*). Terraform створює connection-параметри (DB_HOST, REDIS_URL, …) і плейсхолдери для секретів; реальні значення заливаються out-of-band:
node scripts/manage-secrets.mjs seed ../abitly-api-v2/.env.dev frontend-secrets.envnode scripts/manage-secrets.mjs sync # пушить frontend-слайс у Amplify app configСкрипт пропускає TF-керовані ключі й відмовляється писати літерал PLACEHOLDER_REPLACE_OUT_OF_BAND. Жодних реальних значень — лише назви, повний індекс: environments.
Доступ до інфри
Section titled “Доступ до інфри”- Strapi-адмінка → Cloudflare Access (Zero Trust, OTP-email), origin замкнено на CF-діапазони + AOP mTLS.
ACCESS.md+ repodecision-log.mdADR-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 у робочому профілі нема — черезawsCLI (реєстр).
Зв’язок із рештою хабу
Section titled “Зв’язок із рештою хабу”| Тема | Source of truth | Дзеркало в хабі |
|---|---|---|
| AWS-ресурси per-сервіс | цей репо (terraform/) | hosting |
| Мапа платформ | цей репо | architecture/infrastructure |
| Pipelines / OIDC / SSM-redeploy | модулі *-pipeline, *-github-oidc, abitly-prod-ssm-redeploy | deploy-pipeline |
| Рішення по інфрі | repo decision-log.md | — |
Open questions / TODO
Section titled “Open questions / TODO”- 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 (де яка нумерація живе).