infra: add Terraform infrastructure as code for AWS deployment
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled

- Create main.tf with VPC, security groups, ECS, ALB, RDS, Redis, S3
- Add provider.tf with AWS provider configuration
- Create variables.tf for infrastructure configuration
- Add outputs.tf for infrastructure outputs
- Implement ecs.tf with ECS task definitions and services
- Add ecs_variables.tf for ECS-specific variables
- Create comprehensive README.md with usage instructions
- Implement state management with S3 backend and DynamoDB locking
- Add security best practices (private subnets, encryption, secrets manager)
This commit is contained in:
aitbc
2026-05-09 12:36:08 +02:00
parent 1ccfee1cbb
commit b3293527b8
7 changed files with 1017 additions and 0 deletions

144
infra/terraform/README.md Normal file
View File

@@ -0,0 +1,144 @@
# AITBC Terraform Infrastructure
This directory contains Terraform configurations for deploying AITBC infrastructure on AWS.
## Prerequisites
- Terraform >= 1.0
- AWS CLI configured with appropriate credentials
- S3 bucket for Terraform state (configured in backend)
- DynamoDB table for state locking (configured in backend)
## Directory Structure
```
terraform/
├── main.tf # Main Terraform configuration
├── provider.tf # Provider configuration
├── variables.tf # Infrastructure variables
├── outputs.tf # Infrastructure outputs
├── ecs.tf # ECS task definitions and services
├── ecs_variables.tf # ECS-specific variables
└── README.md # This file
```
## Usage
### Initialize Terraform
```bash
terraform init
```
### Plan Infrastructure
```bash
terraform plan -var-file=dev.tfvars
```
### Apply Infrastructure
```bash
terraform apply -var-file=dev.tfvars
```
### Destroy Infrastructure
```bash
terraform destroy -var-file=dev.tfvars
```
## Variables
Create a `dev.tfvars`, `staging.tfvars`, or `prod.tfvars` file with environment-specific variables:
```hcl
environment = "dev"
aws_region = "us-east-1"
db_username = "aitbc"
db_password = "your-secure-password"
database_url = "postgresql://..."
redis_url = "redis://..."
jwt_secret = "your-jwt-secret"
acm_certificate_arn = "arn:aws:acm:..."
```
## Infrastructure Components
### Networking
- VPC with public and private subnets
- NAT Gateway for private subnet internet access
- Security groups for different services
### Compute
- ECS Fargate cluster
- ECS task definitions for API services
- Application Load Balancer
- Auto-scaling capabilities
### Databases
- RDS PostgreSQL for application data
- ElastiCache Redis for caching
### Storage
- S3 bucket for data storage
- Versioning and encryption enabled
### Monitoring
- CloudWatch Log Groups
- ECS CloudWatch Container Insights
## State Management
Terraform state is stored in S3 with DynamoDB locking:
- State bucket: `aitbc-terraform-state`
- Lock table: `aitbc-terraform-locks`
## Security
- All resources are tagged with project and environment
- Security groups restrict access by CIDR blocks
- RDS and Redis are in private subnets
- Secrets stored in AWS Secrets Manager
- S3 encryption enabled
- RDS encryption enabled
## Cost Optimization
- Use appropriate instance sizes for environment
- Enable auto-scaling for production
- Monitor costs with AWS Cost Explorer
- Use reserved instances for predictable workloads
## Outputs
After applying the configuration, Terraform outputs:
- VPC and subnet IDs
- ECS cluster ID and ARN
- ALB DNS name
- Database and Redis endpoints
- S3 bucket name
## Troubleshooting
### State Lock Issues
If you encounter state lock issues:
```bash
terraform force-unlock <LOCK_ID>
```
### Resource Already Exists
If resources already exist outside Terraform, import them:
```bash
terraform import aws_vpc.this vpc-xxxxx
```
### Permission Errors
Ensure your AWS credentials have the necessary permissions:
- EC2 (VPC, subnets, security groups)
- ECS (clusters, task definitions, services)
- ELB (load balancers, target groups)
- RDS (database instances)
- ElastiCache (Redis clusters)
- S3 (buckets)
- Secrets Manager (secrets)

209
infra/terraform/ecs.tf Normal file
View File

@@ -0,0 +1,209 @@
# ECS task definition for AITBC API service
resource "aws_ecs_task_definition" "api" {
family = "${var.project_name}-${var.environment}-api"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.api_cpu
memory = var.api_memory
execution_role_arn = aws_iam_role.ecs_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
container_definitions = jsonencode([
{
name = "api"
image = "${var.api_image}:${var.api_image_tag}"
cpu = var.api_cpu
memory = var.api_memory
essential = true
portMappings = [
{
containerPort = 8000
protocol = "tcp"
}
]
environment = [
{
name = "ENVIRONMENT"
value = var.environment
},
{
name = "LOG_LEVEL"
value = var.log_level
}
]
secrets = [
{
name = "DATABASE_URL"
valueFrom = aws_secretsmanager_secret.database_url.arn
},
{
name = "REDIS_URL"
valueFrom = aws_secretsmanager_secret.redis_url.arn
},
{
name = "JWT_SECRET"
valueFrom = aws_secretsmanager_secret.jwt_secret.arn
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.api.name
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "api"
}
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}
])
tags = {
Name = "${var.project_name}-${var.environment}-api"
}
}
# ECS service for API
resource "aws_ecs_service" "api" {
name = "${var.project_name}-${var.environment}-api"
cluster = aws_ecs_cluster.this.id
task_definition = aws_ecs_task_definition.api.arn
desired_count = var.api_desired_count
launch_type = "FARGATE"
network_configuration {
subnets = module.vpc.private_subnets
security_groups = [aws_security_group.api_gateway.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.api.arn
container_name = "api"
container_port = 8000
}
depends_on = [aws_lb_listener.https]
tags = {
Name = "${var.project_name}-${var.environment}-api"
}
}
# IAM roles
resource "aws_iam_role" "ecs_execution_role" {
name = "${var.project_name}-${var.environment}-ecs-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-${var.environment}-ecs-execution-role"
}
}
resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" {
role = aws_iam_role.ecs_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_iam_role" "ecs_task_role" {
name = "${var.project_name}-${var.environment}-ecs-task-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-${var.environment}-ecs-task-role"
}
}
resource "aws_iam_role_policy" "ecs_task_policy" {
name = "${var.project_name}-${var.environment}-ecs-task-policy"
role = aws_iam_role.ecs_task_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
Resource = "${aws_s3_bucket.data.arn}/*"
},
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = [
aws_secretsmanager_secret.database_url.arn,
aws_secretsmanager_secret.redis_url.arn,
aws_secretsmanager_secret.jwt_secret.arn
]
}
]
})
}
# Secrets Manager
resource "aws_secretsmanager_secret" "database_url" {
name = "${var.project_name}/${var.environment}/database/url"
}
resource "aws_secretsmanager_secret_version" "database_url" {
secret_id = aws_secretsmanager_secret.database_url.id
secret_string = var.database_url
}
resource "aws_secretsmanager_secret" "redis_url" {
name = "${var.project_name}/${var.environment}/redis/url"
}
resource "aws_secretsmanager_secret_version" "redis_url" {
secret_id = aws_secretsmanager_secret.redis.id
secret_string = var.redis_url
}
resource "aws_secretsmanager_secret" "jwt_secret" {
name = "${var.project_name}/${var.environment}/jwt/secret"
}
resource "aws_secretsmanager_secret_version" "jwt_secret" {
secret_id = aws_secretsmanager_secret.jwt_secret.id
secret_string = var.jwt_secret
}

View File

@@ -0,0 +1,55 @@
# ECS API variables
variable "api_cpu" {
description = "API task CPU units"
type = number
default = 512
}
variable "api_memory" {
description = "API task memory in MB"
type = number
default = 1024
}
variable "api_desired_count" {
description = "API desired task count"
type = number
default = 2
}
variable "api_image" {
description = "API Docker image"
type = string
default = "aitbc/api"
}
variable "api_image_tag" {
description = "API Docker image tag"
type = string
default = "latest"
}
variable "log_level" {
description = "Application log level"
type = string
default = "INFO"
}
variable "database_url" {
description = "Database connection URL"
type = string
sensitive = true
}
variable "redis_url" {
description = "Redis connection URL"
type = string
sensitive = true
}
variable "jwt_secret" {
description = "JWT secret key"
type = string
sensitive = true
}

356
infra/terraform/main.tf Normal file
View File

@@ -0,0 +1,356 @@
# Terraform main configuration for AITBC infrastructure
# This file defines the infrastructure resources for AITBC deployment
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "aitbc-terraform-state"
key = "aitbc/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "aitbc-terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "AITBC"
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
# Random resources for unique naming
resource "random_pet" "this" {
length = 2
}
# VPC
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.project_name}-${var.environment}-vpc"
cidr = var.vpc_cidr
azs = var.availability_zones
private_subnets = var.private_subnet_cidrs
public_subnets = var.public_subnet_cidrs
enable_nat_gateway = true
single_nat_gateway = true
one_nat_gateway_per_az = false
enable_vpn_gateway = false
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-${var.environment}-vpc"
}
}
# Security groups
resource "aws_security_group" "blockchain_rpc" {
name = "${var.project_name}-${var.environment}-blockchain-rpc"
description = "Security group for blockchain RPC service"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 8006
to_port = 8006
protocol = "tcp"
cidr_blocks = var.allowed_cidr_blocks
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-blockchain-rpc"
}
}
resource "aws_security_group" "api_gateway" {
name = "${var.project_name}-${var.environment}-api-gateway"
description = "Security group for API gateway"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-api-gateway"
}
}
# ECS Cluster
resource "aws_ecs_cluster" "this" {
name = "${var.project_name}-${var.environment}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
# Application Load Balancer
resource "aws_lb" "this" {
name = "${var.project_name}-${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.api_gateway.id]
subnets = module.vpc.public_subnets
enable_deletion_protection = false
tags = {
Name = "${var.project_name}-${var.environment}-alb"
}
}
resource "aws_lb_target_group" "api" {
name = "${var.project_name}-${var.environment}-api-tg"
port = 8000
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
tags = {
Name = "${var.project_name}-${var.environment}-api-tg"
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.this.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.this.arn
port = 443
protocol = "HTTPS"
certificate_arn = var.acm_certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
}
# RDS PostgreSQL
resource "aws_db_subnet_group" "this" {
name = "${var.project_name}-${var.environment}-db-subnet-group"
subnet_ids = module.vpc.private_subnets
tags = {
Name = "${var.project_name}-${var.environment}-db-subnet-group"
}
}
resource "aws_db_instance" "this" {
identifier = "${var.project_name}-${var.environment}-db"
engine = "postgres"
engine_version = "15.4"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
max_allocated_storage = var.db_max_allocated_storage
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.this.name
vpc_security_group_ids = [aws_security_group.blockchain_rpc.id]
multi_az = var.db_multi_az
backup_retention_period = var.db_backup_retention_period
backup_window = var.db_backup_window
maintenance_window = var.db_maintenance_window
performance_insights_enabled = true
skip_final_snapshot = false
final_snapshot_identifier = "${var.project_name}-${var.environment}-db-final-snapshot"
tags = {
Name = "${var.project_name}-${var.environment}-db"
}
}
# ElastiCache Redis
resource "aws_elasticache_subnet_group" "this" {
name = "${var.project_name}-${var.environment}-redis-subnet-group"
subnet_ids = module.vpc.private_subnets
}
resource "aws_elasticache_replication_group" "this" {
replication_group_id = "${var.project_name}-${var.environment}-redis"
replication_group_description = "${var.project_name} Redis cluster for ${var.environment}"
node_type = var.redis_node_type
num_cache_clusters = var.redis_num_nodes
port = 6379
engine = "redis"
engine_version = "7.0"
parameter_group_name = "default.redis7"
subnet_group_name = aws_elasticache_subnet_group.this.name
security_group_ids = [aws_security_group.blockchain_rpc.id]
automatic_failover_enabled = true
multi_az_enabled = true
snapshot_retention_limit = var.redis_snapshot_retention_limit
snapshot_window = var.redis_snapshot_window
tags = {
Name = "${var.project_name}-${var.environment}-redis"
}
}
# CloudWatch Log Groups
resource "aws_cloudwatch_log_group" "api" {
name = "/aws/ecs/${var.project_name}-${var.environment}/api"
retention_in_days = var.log_retention_days
}
resource "aws_cloudwatch_log_group" "blockchain" {
name = "/aws/ecs/${var.project_name}-${var.environment}/blockchain"
retention_in_days = var.log_retention_days
}
# S3 Bucket for data storage
resource "aws_s3_bucket" "data" {
bucket = "${var.project_name}-${var.environment}-data-${random_pet.this.id}"
tags = {
Name = "${var.project_name}-${var.environment}-data"
}
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Outputs
output "vpc_id" {
description = "VPC ID"
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
description = "Private subnet IDs"
value = module.vpc.private_subnets
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = module.vpc.public_subnets
}
output "ecs_cluster_id" {
description = "ECS Cluster ID"
value = aws_ecs_cluster.this.id
}
output "alb_dns_name" {
description = "ALB DNS name"
value = aws_lb.this.dns_name
}
output "db_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.this.endpoint
sensitive = true
}
output "redis_endpoint" {
description = "Redis endpoint"
value = aws_elasticache_replication_this.primary_endpoint_address
}
output "s3_bucket_name" {
description = "S3 bucket name"
value = aws_s3_bucket.data.id
}

View File

@@ -0,0 +1,77 @@
# Terraform outputs for AITBC infrastructure
output "vpc_id" {
description = "VPC ID"
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
description = "Private subnet IDs"
value = module.vpc.private_subnets
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = module.vpc.public_subnets
}
output "ecs_cluster_id" {
description = "ECS Cluster ID"
value = aws_ecs_cluster.this.id
}
output "ecs_cluster_arn" {
description = "ECS Cluster ARN"
value = aws_ecs_cluster.this.arn
}
output "alb_dns_name" {
description = "ALB DNS name"
value = aws_lb.this.dns_name
}
output "alb_zone_id" {
description = "ALB zone ID"
value = aws_lb.this.zone_id
}
output "db_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.this.endpoint
sensitive = true
}
output "db_instance_id" {
description = "RDS instance ID"
value = aws_db_instance.this.id
}
output "redis_endpoint" {
description = "Redis primary endpoint"
value = aws_elasticache_replication_this.primary_endpoint_address
}
output "redis_port" {
description = "Redis port"
value = aws_elasticache_replication_this.port
}
output "s3_bucket_name" {
description = "S3 bucket name"
value = aws_s3_bucket.data.id
}
output "s3_bucket_arn" {
description = "S3 bucket ARN"
value = aws_s3_bucket.data.arn
}
output "security_group_blockchain_rpc_id" {
description = "Blockchain RPC security group ID"
value = aws_security_group.blockchain_rpc.id
}
output "security_group_api_gateway_id" {
description = "API gateway security group ID"
value = aws_security_group.api_gateway.id
}

View File

@@ -0,0 +1,28 @@
# Terraform provider configuration
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "AITBC"
Environment = var.environment
ManagedBy = "Terraform"
}
}
}

View File

@@ -0,0 +1,148 @@
# Terraform variables for AITBC infrastructure
variable "project_name" {
description = "Project name"
type = string
default = "aitbc"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "Availability zones"
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
variable "private_subnet_cidrs" {
description = "Private subnet CIDR blocks"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
variable "public_subnet_cidrs" {
description = "Public subnet CIDR blocks"
type = list(string)
default = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}
variable "allowed_cidr_blocks" {
description = "Allowed CIDR blocks for security groups"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "acm_certificate_arn" {
description = "ACM certificate ARN for HTTPS"
type = string
default = ""
}
variable "db_instance_class" {
description = "RDS instance class"
type = string
default = "db.t3.medium"
}
variable "db_allocated_storage" {
description = "RDS allocated storage in GB"
type = number
default = 20
}
variable "db_max_allocated_storage" {
description = "RDS max allocated storage in GB"
type = number
default = 100
}
variable "db_name" {
description = "Database name"
type = string
default = "aitbc"
}
variable "db_username" {
description = "Database username"
type = string
sensitive = true
}
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
variable "db_multi_az" {
description = "Enable multi-AZ for RDS"
type = bool
default = false
}
variable "db_backup_retention_period" {
description = "RDS backup retention period in days"
type = number
default = 7
}
variable "db_backup_window" {
description = "RDS backup window"
type = string
default = "03:00-04:00"
}
variable "db_maintenance_window" {
description = "RDS maintenance window"
type = string
default = "sun:04:00-sun:05:00"
}
variable "redis_node_type" {
description = "Redis node type"
type = string
default = "cache.t3.medium"
}
variable "redis_num_nodes" {
description = "Number of Redis nodes"
type = number
default = 2
}
variable "redis_snapshot_retention_limit" {
description = "Redis snapshot retention limit"
type = number
default = 5
}
variable "redis_snapshot_window" {
description = "Redis snapshot window"
type = string
default = "05:00-06:00"
}
variable "log_retention_days" {
description = "CloudWatch log retention in days"
type = number
default = 30
}