From 350f120d7c9bc51d456a76527ec33aa604b85202 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 01/17] feat: Add Terraform modules for AWS deployment --- terraform/README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 terraform/README.md diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..b8a3732 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,92 @@ +# Terraform deployment modules + +Infrastructure as Code modules for deploying postgres_ai monitoring to cloud providers. + +## Available modules + +### AWS (EC2) +Single EC2 instance deployment with Docker Compose. + +- **Path**: `aws/` +- **Architecture**: Single EC2 instance with Docker Compose +- **Best for**: Small to medium deployments (1-10 databases) +- **Documentation**: [aws/README.md](aws/README.md) + +### GCP (Coming soon) +Deploy to Google Cloud Platform using Compute Engine or Cloud Run. + +### Azure (Coming soon) +Deploy to Microsoft Azure using Virtual Machines or Container Instances. + +## Quick start + +### AWS deployment + +```bash +cd terraform/aws + +# Copy example variables +cp terraform.tfvars.example terraform.tfvars + +# Edit variables with your settings +vim terraform.tfvars + +# Initialize Terraform +terraform init + +# Review the plan +terraform plan + +# Deploy infrastructure (takes 5-10 minutes) +terraform apply +``` + +## Architecture overview + +The AWS deployment creates: + +1. **Compute** + - Single EC2 instance (t3.large recommended) + - Ubuntu 22.04 LTS (Jammy) with Docker and Docker Compose + - Systemd service for automatic startup + +2. **Storage** + - EBS volume for persistent data + - Automated snapshots available via AWS Backup + +3. **Networking** + - VPC with public subnet + - Security Group with restricted access + - Optional Elastic IP for stable addressing + +4. **Monitoring stack** + - Runs docker-compose from cloned repository + - Grafana accessible on port 3000 + +## Security considerations + +- EC2 instance in public subnet (can be changed to private with bastion) +- Security groups restrict access to SSH and Grafana only +- All data encrypted at rest (EBS encryption) +- Recommended: Use AWS Systems Manager Session Manager instead of SSH +- Recommended: Restrict `allowed_cidr_blocks` to your office/VPN IP + +## Instance types + +Recommended instance types based on workload: + +- **t3.medium**: 2 vCPU, 4 GiB RAM - suitable for 1-3 databases +- **t3.large**: 2 vCPU, 8 GiB RAM - suitable for 3-10 databases (recommended) +- **t3.xlarge**: 4 vCPU, 16 GiB RAM - suitable for 10+ databases + +Additional options: +- Use Spot Instances for non-critical workloads (subject to interruption) +- Disable Elastic IP if stable address not required + +## Support + +For issues or questions: +- Open an issue on GitLab +- Contact PostgresAI support +- Check documentation at https://postgres.ai + -- GitLab From 506480c4109302b3428049b461ae75d6733df91d Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 02/17] chore: Add .gitignore for AWS Terraform module --- terraform/aws/.gitignore | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 terraform/aws/.gitignore diff --git a/terraform/aws/.gitignore b/terraform/aws/.gitignore new file mode 100644 index 0000000..359b416 --- /dev/null +++ b/terraform/aws/.gitignore @@ -0,0 +1,33 @@ +# Terraform files +*.tfstate +*.tfstate.* +*.tfvars +!terraform.tfvars.example +.terraform/ +.terraform.lock.hcl +crash.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +tfplan +plan.log + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Backup files +*.bak +*.backup + +# SSH keys +*.pem +*.key -- GitLab From 5e252f3fab7e2b55391ca8015733ed684ac1eec0 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 03/17] docs: Create initial AWS deployment README --- terraform/aws/README.md | 300 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 terraform/aws/README.md diff --git a/terraform/aws/README.md b/terraform/aws/README.md new file mode 100644 index 0000000..7bcc6ce --- /dev/null +++ b/terraform/aws/README.md @@ -0,0 +1,300 @@ +# AWS deployment + +Single EC2 instance deployment with Docker Compose. + +## Architecture + +Single EC2 instance with Docker Compose. + +Terraform creates: +- VPC with public subnet +- EC2 instance (t3.large, Ubuntu 22.04 LTS) +- EBS volume (100GB gp3, encrypted) +- Security Group (SSH + Grafana ports) +- Elastic IP (optional) + +On first boot, EC2 instance clones this repository and runs `docker-compose up` to start all monitoring services. + +## Quick start + +See [QUICKSTART.md](QUICKSTART.md) for step-by-step guide. + +## Configuration + +### Minimal setup + +```hcl +# terraform.tfvars +ssh_key_name = "postgres-ai-key" +grafana_password = "SecurePassword123!" +``` + +### Minimal production setup + +```hcl +aws_region = "us-east-1" +environment = "production" +grafana_password = "SecurePassword123!" + +# Optional: API key for uploading reports to PostgresAI +# Get it from: https://console.postgres.ai → Your Org → Manage → Access Tokens +postgres_ai_api_key = "your-api-key-here" + +monitoring_instances = [ + { + name = "main-db" + conn_str = "postgresql://monitor:pass@db.example.com:5432/postgres" + environment = "production" + cluster = "main" + node_name = "primary" + } +] +``` + +### Full configuration + +```hcl +# AWS +aws_region = "us-east-1" +environment = "production" +instance_type = "t3.large" + +# Storage +data_volume_size = 100 + +# Security (restrict access in production) +allowed_ssh_cidr = ["203.0.113.0/24"] +allowed_cidr_blocks = ["203.0.113.0/24"] + +# Required +ssh_key_name = "postgres-ai-key" +grafana_password = "SecurePassword123!" + +# Optional +postgres_ai_api_key = "your-api-key" +enable_demo_db = false +use_elastic_ip = true + +# Monitoring instances +monitoring_instances = [ + { + name = "prod-db" + conn_str = "postgresql://monitor:pass@db.example.com:5432/postgres" + environment = "production" + cluster = "main" + node_name = "primary" + } +] +``` + +## Management + +### SSH access + +```bash +terraform output ssh_command +# Or directly: +ssh -i ~/.ssh/postgres-ai-key.pem ubuntu@$(terraform output -raw public_ip) +``` + +### Service management + +```bash +# On EC2 instance +cd /home/postgres_ai/postgres_ai + +# Status +sudo docker-compose ps + +# Logs +sudo docker-compose logs -f + +# Restart +sudo systemctl restart postgres-ai +``` + +### Add monitoring instance + +Method 1: Update terraform.tfvars and run `terraform apply` + +Method 2: Manual configuration on server: +```bash +ssh ubuntu@your-ip +cd /home/postgres_ai/postgres_ai +sudo -u postgres_ai vim instances.yml +sudo docker-compose restart +``` + +### Backup + +```bash +# Create snapshot +aws ec2 create-snapshot \ + --volume-id $(terraform output -raw data_volume_id) \ + --description "postgres-ai backup $(date +%Y-%m-%d)" +``` + +### System updates + +```bash +ssh ubuntu@your-ip + +# Update OS +sudo apt-get update && sudo apt-get upgrade -y + +# Update Docker images +cd /home/postgres_ai/postgres_ai +sudo docker-compose pull +sudo docker-compose up -d +``` + +## Security + +### Recommendations + +1. Restrict SSH access: +```hcl +allowed_ssh_cidr = ["your.ip.address/32"] +``` + +2. Restrict Grafana access: +```hcl +allowed_cidr_blocks = ["your.office.ip/24"] +``` + +3. Use AWS Systems Manager instead of SSH: +```bash +aws ssm start-session --target $(terraform output -raw instance_id) +``` + +4. Automate backups with AWS Backup or cron. + +## Monitoring + +### CloudWatch metrics + +```bash +# CPU utilization +aws cloudwatch get-metric-statistics \ + --namespace AWS/EC2 \ + --metric-name CPUUtilization \ + --dimensions Name=InstanceId,Value=$(terraform output -raw instance_id) \ + --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \ + --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \ + --period 300 \ + --statistics Average +``` + +### Disk space + +```bash +ssh ubuntu@your-ip "df -h /data" +``` + +## Troubleshooting + +### Services not starting + +```bash +# Check user-data log +ssh ubuntu@your-ip "sudo cat /var/log/user-data.log" + +# Check Docker +ssh ubuntu@your-ip "sudo systemctl status docker" +ssh ubuntu@your-ip "sudo docker ps -a" +``` + +### No access to Grafana + +```bash +# Check Security Group +aws ec2 describe-security-groups \ + --group-ids $(terraform output -raw security_group_id) + +# Check services +ssh ubuntu@your-ip "sudo docker-compose ps" +``` + +### Disk full + +```bash +# Increase EBS volume size +aws ec2 modify-volume --volume-id VOLUME_ID --size 200 + +# Expand filesystem +ssh ubuntu@your-ip "sudo resize2fs /dev/nvme1n1" +``` + +## Instance sizing + +Choose instance type based on monitoring workload: + +```hcl +instance_type = "t3.medium" # 2 vCPU, 4 GiB RAM +``` + +Suitable for: +- Monitoring 1-3 small databases +- Dev/test environments +- Proof of concept + +```hcl +instance_type = "t3.large" # 2 vCPU, 8 GiB RAM (default) +``` + +Suitable for: +- Monitoring 3-10 databases +- Production environments + +```hcl +instance_type = "t3.xlarge" # 4 vCPU, 16 GiB RAM +``` + +Suitable for: +- Monitoring 10+ databases +- High-frequency metric collection + +## Custom domain + +```bash +# Create A record in Route53 +aws route53 change-resource-record-sets \ + --hosted-zone-id YOUR_ZONE_ID \ + --change-batch '{ + "Changes": [{ + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "monitoring.example.com", + "Type": "A", + "TTL": 300, + "ResourceRecords": [{"Value": "'"$(terraform output -raw public_ip)"'"}] + } + }] + }' + +# Configure HTTPS with Let's Encrypt +ssh ubuntu@your-ip +sudo snap install certbot +sudo certbot certonly --standalone -d monitoring.example.com +``` + +## Limitations + +Single AZ deployment: +- No automatic failover +- Manual backups required +- Vertical scaling only +- Suitable for 1-10 databases + +Recovery time: 15-30 minutes (restore from snapshot) + +## Use cases + +This deployment is appropriate for: +- Development and testing environments +- Small to medium workloads (1-10 databases) +- Non-critical monitoring systems +- Budget-constrained projects +- Teams with Linux administration skills + +For production-critical systems requiring high availability, consider managed services (RDS, ECS Fargate) instead. -- GitLab From 4e2abd5b65b208acad41776c05c6a60682523ef5 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 04/17] docs: Create quick start guide for AWS deployment --- terraform/aws/QUICKSTART.md | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 terraform/aws/QUICKSTART.md diff --git a/terraform/aws/QUICKSTART.md b/terraform/aws/QUICKSTART.md new file mode 100644 index 0000000..5bad64c --- /dev/null +++ b/terraform/aws/QUICKSTART.md @@ -0,0 +1,97 @@ +# Quick start + +## Prerequisites + +```bash +# Create SSH key +aws ec2 create-key-pair --key-name postgres-ai-key \ + --query 'KeyMaterial' --output text > ~/.ssh/postgres-ai-key.pem +chmod 400 ~/.ssh/postgres-ai-key.pem + +# Configure AWS credentials +aws configure +``` + +## Deploy + +```bash +cd terraform/aws + +# Configure +cp terraform.tfvars.example terraform.tfvars +vim terraform.tfvars # Set ssh_key_name and grafana_password + +# Validate +./validate.sh + +# Deploy +terraform init +terraform plan +terraform apply + +# Get access info +terraform output grafana_url +terraform output ssh_command +``` + +## Access + +```bash +# Grafana dashboard +open $(terraform output -raw grafana_url) +# Login: monitor / + +# SSH +ssh -i ~/.ssh/postgres-ai-key.pem ubuntu@$(terraform output -raw public_ip) +``` + +## Add monitoring instances + +Edit `terraform.tfvars`: + +```hcl +monitoring_instances = [ + { + name = "prod-db" + conn_str = "postgresql://monitor:pass@db.example.com:5432/postgres" + environment = "production" + cluster = "main" + node_name = "primary" + } +] +``` + +Apply changes: +```bash +terraform apply +``` + +## Operations + +```bash +# View logs +ssh ubuntu@IP "sudo cat /var/log/user-data.log" + +# Restart services +ssh ubuntu@IP "sudo systemctl restart postgres-ai" + +# Create backup +aws ec2 create-snapshot --volume-id $(terraform output -raw data_volume_id) + +# Destroy +terraform destroy +``` + +## Troubleshooting + +```bash +# Check installation log +ssh ubuntu@IP "sudo cat /var/log/user-data.log" + +# Check service status +ssh ubuntu@IP "sudo systemctl status postgres-ai" + +# Check containers +ssh ubuntu@IP "sudo docker ps" +``` + -- GitLab From 4024f3c1c5c75f1070a8b34f6c1bb764312276d0 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 05/17] feat: Define core AWS infrastructure with Terraform --- terraform/aws/main.tf | 195 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 terraform/aws/main.tf diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf new file mode 100644 index 0000000..0c58a91 --- /dev/null +++ b/terraform/aws/main.tf @@ -0,0 +1,195 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Project = "postgres-ai-monitoring" + Environment = var.environment + ManagedBy = "terraform" + } + } +} + +# Data sources +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] # Canonical + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } +} + +# VPC (simplified - use default or create minimal) +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "${var.environment}-postgres-ai-vpc" + } +} + +resource "aws_subnet" "main" { + vpc_id = aws_vpc.main.id + cidr_block = "10.0.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = "${var.environment}-postgres-ai-subnet" + } +} + +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${var.environment}-postgres-ai-igw" + } +} + +resource "aws_route_table" "main" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id + } + + tags = { + Name = "${var.environment}-postgres-ai-rt" + } +} + +resource "aws_route_table_association" "main" { + subnet_id = aws_subnet.main.id + route_table_id = aws_route_table.main.id +} + +# Security Group +resource "aws_security_group" "main" { + name = "${var.environment}-postgres-ai-sg" + description = "Security group for postgres_ai monitoring EC2" + vpc_id = aws_vpc.main.id + + # SSH access + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = var.allowed_ssh_cidr + } + + # Grafana + ingress { + description = "Grafana" + from_port = 3000 + to_port = 3000 + protocol = "tcp" + cidr_blocks = var.allowed_cidr_blocks + } + + # Allow all outbound + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.environment}-postgres-ai-sg" + } +} + +# EBS Volume for data persistence +resource "aws_ebs_volume" "data" { + availability_zone = aws_subnet.main.availability_zone + size = var.data_volume_size + type = "gp3" + encrypted = true + + tags = { + Name = "${var.environment}-postgres-ai-data" + } +} + +# EC2 Instance +resource "aws_instance" "main" { + ami = data.aws_ami.ubuntu.id + instance_type = var.instance_type + subnet_id = aws_subnet.main.id + + vpc_security_group_ids = [aws_security_group.main.id] + + key_name = var.ssh_key_name + + root_block_device { + volume_size = 30 + volume_type = "gp3" + encrypted = true + } + + user_data = templatefile("${path.module}/user_data.sh", { + grafana_password = var.grafana_password + postgres_ai_api_key = var.postgres_ai_api_key + monitoring_instances = var.monitoring_instances + enable_demo_db = var.enable_demo_db + }) + + tags = { + Name = "${var.environment}-postgres-ai-monitoring" + } + + lifecycle { + ignore_changes = [user_data] + } +} + +# Attach EBS volume +resource "aws_volume_attachment" "data" { + device_name = "/dev/sdf" + volume_id = aws_ebs_volume.data.id + instance_id = aws_instance.main.id +} + +# Elastic IP (optional, for stable IP) +resource "aws_eip" "main" { + count = var.use_elastic_ip ? 1 : 0 + instance = aws_instance.main.id + domain = "vpc" + + tags = { + Name = "${var.environment}-postgres-ai-eip" + } +} + -- GitLab From 0e9968e4597415f932eef32d9e3febceedd71f50 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 06/17] feat: Expose key deployment outputs --- terraform/aws/outputs.tf | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 terraform/aws/outputs.tf diff --git a/terraform/aws/outputs.tf b/terraform/aws/outputs.tf new file mode 100644 index 0000000..182eaf9 --- /dev/null +++ b/terraform/aws/outputs.tf @@ -0,0 +1,67 @@ +output "instance_id" { + description = "EC2 instance ID" + value = aws_instance.main.id +} + +output "data_volume_id" { + description = "EBS data volume ID (for snapshots)" + value = aws_ebs_volume.data.id +} + +output "public_ip" { + description = "Public IP address" + value = var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip +} + +output "grafana_url" { + description = "Grafana dashboard URL" + value = "http://${var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip}:3000" +} + +output "ssh_command" { + description = "SSH command to connect" + value = "ssh -i ~/.ssh/${var.ssh_key_name}.pem ubuntu@${var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip}" +} + +output "grafana_credentials" { + description = "Grafana credentials" + value = { + username = "monitor" + password = var.grafana_password + } + sensitive = true +} + +output "deployment_info" { + description = "Deployment information" + value = { + instance_type = var.instance_type + region = var.aws_region + public_ip = var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip + data_volume = "${var.data_volume_size} GiB" + api_key_configured = var.postgres_ai_api_key != "" + monitoring_instances = length(var.monitoring_instances) + demo_mode = var.enable_demo_db + } + sensitive = true +} + +output "next_steps" { + description = "Next steps after deployment" + value = <<-EOT + +Deployment complete + +Grafana URL: http://${var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip}:3000 +Username: monitor +Password: (from terraform.tfvars) + +Monitoring: ${length(var.monitoring_instances)} instance(s) +API key: ${var.postgres_ai_api_key != "" ? "configured" : "not configured"} + +SSH: ssh -i ~/.ssh/${var.ssh_key_name}.pem ubuntu@${var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip} +Backup: aws ec2 create-snapshot --volume-id ${aws_ebs_volume.data.id} + +EOT +} + -- GitLab From 9a2e25874386ddc5e090060ae720409e64d418be Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 07/17] docs: Provide example Terraform variables --- terraform/aws/terraform.tfvars.example | 113 +++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 terraform/aws/terraform.tfvars.example diff --git a/terraform/aws/terraform.tfvars.example b/terraform/aws/terraform.tfvars.example new file mode 100644 index 0000000..1feda9a --- /dev/null +++ b/terraform/aws/terraform.tfvars.example @@ -0,0 +1,113 @@ +# AWS Deployment Configuration +# Copy to terraform.tfvars and customize + +# REQUIRED PARAMETERS +# ------------------------- + +# SSH key for EC2 access (create in AWS Console or CLI) +ssh_key_name = "your-key-name" + +# Grafana password (minimum 8 characters) +grafana_password = "YourSecurePassword123!" + + +# AWS SETTINGS +# ------------------------- + +# AWS region +aws_region = "us-east-1" + +# Environment +environment = "production" + +# EC2 instance type +# t3.medium - 2 vCPU, 4 GiB - for 1-3 databases +# t3.large - 2 vCPU, 8 GiB - for 3-10 databases (recommended) +# t3.xlarge - 4 vCPU, 16 GiB - for 10+ databases +instance_type = "t3.large" + +# EBS volume size for data (GiB) +data_volume_size = 100 + + +# SECURITY +# ------------------------- + +# CIDR blocks for SSH access (restrict to your IP in production) +allowed_ssh_cidr = [ + "0.0.0.0/0" # WARNING: Allows access from anywhere + # "203.0.113.0/24" # Replace with your office/VPN IP +] + +# CIDR blocks for Grafana access (restrict to your IP in production) +allowed_cidr_blocks = [ + "0.0.0.0/0" # WARNING: Allows access from anywhere + # "203.0.113.0/24" # Replace with your office/VPN IP +] + +# Allocate Elastic IP for stable address +use_elastic_ip = true + + +# POSTGRESQL MONITORING +# ------------------------- + +# PostgreSQL instances to monitor +monitoring_instances = [ + { + name = "production-db" + conn_str = "postgresql://monitor:password@db.example.com:5432/postgres" + environment = "production" + cluster = "main" + node_name = "primary" + }, + # { + # name = "production-replica" + # conn_str = "postgresql://monitor:password@replica.example.com:5432/postgres" + # environment = "production" + # cluster = "main" + # node_name = "replica-1" + # } +] + + +# OPTIONAL PARAMETERS +# ------------------------- + +# PostgresAI API key (for uploading reports to cloud) +# +# How to get API key: +# 1. Register at https://console.postgres.ai +# 2. Go to: Your Organization → Manage → Access Tokens +# 3. Create new token and copy it here +# +# If not set, reports will be generated locally without upload +# postgres_ai_api_key = "your-api-key-here" + +# Enable demo database (for testing) +# enable_demo_db = false + + +# CONFIGURATION EXAMPLES +# ------------------------- + +# Minimal configuration (for testing): +# ------------------------------------ +# instance_type = "t3.medium" +# data_volume_size = 50 +# enable_demo_db = true +# monitoring_instances = [] + +# Production configuration: +# ------------------------- +# instance_type = "t3.xlarge" +# data_volume_size = 200 +# allowed_ssh_cidr = ["10.0.0.0/8"] +# allowed_cidr_blocks = ["10.0.0.0/8"] +# use_elastic_ip = true + +# Minimal configuration (dev/test): +# ---------------------------------- +# instance_type = "t3.small" # WARNING: May be slow for production use +# data_volume_size = 30 +# use_elastic_ip = false -- GitLab From 896f248dabd31c5ac63e808c9c06451a9358ef92 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 08/17] feat: Implement user data script for initial setup --- terraform/aws/user_data.sh | 142 +++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 terraform/aws/user_data.sh diff --git a/terraform/aws/user_data.sh b/terraform/aws/user_data.sh new file mode 100644 index 0000000..3e63a52 --- /dev/null +++ b/terraform/aws/user_data.sh @@ -0,0 +1,142 @@ +#!/bin/bash +set -e + +# Log everything +exec > >(tee /var/log/user-data.log) +exec 2>&1 + +echo "Starting postgres_ai monitoring installation..." + +# Update system +apt-get update +apt-get upgrade -y + +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh +systemctl enable docker +systemctl start docker + +# Install Docker Compose +curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose + +# Create postgres_ai user +useradd -m -s /bin/bash postgres_ai +usermod -aG docker postgres_ai + +# Mount and prepare data volume +if [ ! -d /data ]; then + mkdir -p /data + + # Wait for volume to be attached + sleep 10 + + # Check if volume exists and format if needed + if [ -e /dev/nvme1n1 ]; then + DEVICE=/dev/nvme1n1 + elif [ -e /dev/xvdf ]; then + DEVICE=/dev/xvdf + else + echo "Data volume not found, using root volume" + DEVICE="" + fi + + if [ -n "$DEVICE" ]; then + # Check if filesystem exists + if ! blkid $DEVICE; then + mkfs.ext4 $DEVICE + fi + + # Mount volume + mount $DEVICE /data + + # Add to fstab for persistence + UUID=$(blkid -s UUID -o value $DEVICE) + echo "UUID=$UUID /data ext4 defaults,nofail 0 2" >> /etc/fstab + fi +fi + +# Set permissions +chown -R postgres_ai:postgres_ai /data + +# Clone postgres_ai repository +cd /home/postgres_ai +sudo -u postgres_ai git clone https://gitlab.com/postgres-ai/postgres_ai.git + +# Configure postgres_ai +cd postgres_ai + +# Create configuration +cat > .pgwatch-config < 0 } +cat > instances.yml <<'INSTANCES_EOF' +%{ for instance in monitoring_instances ~} +- name: ${instance.name} + conn_str: ${instance.conn_str} + preset_metrics: full + custom_metrics: + is_enabled: true + group: default + custom_tags: + env: ${instance.environment} + cluster: ${instance.cluster} + node_name: ${instance.node_name} + sink_type: ~sink_type~ +%{ endfor ~} +INSTANCES_EOF +%{ else } +# No monitoring instances configured - will use empty or default config +cat > instances.yml <<'INSTANCES_EOF' +# PostgreSQL instances to monitor +# Add your instances using: ./postgres_ai add-instance + +INSTANCES_EOF +%{ endif } + +# Set ownership +chown -R postgres_ai:postgres_ai /home/postgres_ai/postgres_ai + +# Create systemd service +cat > /etc/systemd/system/postgres-ai.service <<'SERVICE_EOF' +[Unit] +Description=Postgres AI Monitoring +After=docker.service +Requires=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/home/postgres_ai/postgres_ai +User=postgres_ai +Group=postgres_ai + +# Start services +ExecStart=/usr/local/bin/docker-compose up -d + +# Stop services +ExecStop=/usr/local/bin/docker-compose down + +[Install] +WantedBy=multi-user.target +SERVICE_EOF + +# Enable and start service +systemctl daemon-reload +systemctl enable postgres-ai +systemctl start postgres-ai + +# Wait for services to be healthy +sleep 30 + +echo "Installation complete!" +echo "Access Grafana at: http://$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4):3000" +echo "Username: monitor" +echo "Password: ${grafana_password}" + -- GitLab From 9e43cce0a9b3ed778c85ea06859a767add6d0ac4 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:24:14 +0700 Subject: [PATCH 09/17] feat: Define Terraform input variables --- terraform/aws/variables.tf | 78 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 terraform/aws/variables.tf diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf new file mode 100644 index 0000000..982bc25 --- /dev/null +++ b/terraform/aws/variables.tf @@ -0,0 +1,78 @@ +variable "aws_region" { + description = "AWS region" + type = string + default = "us-east-1" +} + +variable "environment" { + description = "Environment name" + type = string + default = "production" +} + +variable "instance_type" { + description = "EC2 instance type" + type = string + default = "t3.medium" +} + +variable "data_volume_size" { + description = "Size of EBS data volume in GiB" + type = number + default = 100 +} + +variable "ssh_key_name" { + description = "Name of SSH key pair for EC2 access" + type = string +} + +variable "allowed_ssh_cidr" { + description = "CIDR blocks allowed for SSH access" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "allowed_cidr_blocks" { + description = "CIDR blocks allowed for Grafana access" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "use_elastic_ip" { + description = "Allocate Elastic IP for stable address" + type = bool + default = true +} + +variable "grafana_password" { + description = "Grafana admin password" + type = string + sensitive = true +} + +variable "postgres_ai_api_key" { + description = "PostgresAI API key (optional)" + type = string + default = "" + sensitive = true +} + +variable "monitoring_instances" { + description = "PostgreSQL instances to monitor" + type = list(object({ + name = string + conn_str = string + environment = string + cluster = string + node_name = string + })) + default = [] +} + +variable "enable_demo_db" { + description = "Enable demo database" + type = bool + default = false +} + -- GitLab From 178296bd7ee21d483bfaf3566811f28a1b17cd36 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 17:52:11 +0700 Subject: [PATCH 10/17] chore: remove EC2 instance type comments from terraform.tfvars.example --- terraform/aws/terraform.tfvars.example | 4 ---- 1 file changed, 4 deletions(-) diff --git a/terraform/aws/terraform.tfvars.example b/terraform/aws/terraform.tfvars.example index 1feda9a..8b001ba 100644 --- a/terraform/aws/terraform.tfvars.example +++ b/terraform/aws/terraform.tfvars.example @@ -20,10 +20,6 @@ aws_region = "us-east-1" # Environment environment = "production" -# EC2 instance type -# t3.medium - 2 vCPU, 4 GiB - for 1-3 databases -# t3.large - 2 vCPU, 8 GiB - for 3-10 databases (recommended) -# t3.xlarge - 4 vCPU, 16 GiB - for 10+ databases instance_type = "t3.large" # EBS volume size for data (GiB) -- GitLab From 8312119267400a30f0d71c9973d4c1e047329b07 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 19:23:41 +0700 Subject: [PATCH 11/17] feat: Allow Grafana password to be configurable via env var --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4b31a95..1558298 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -99,7 +99,7 @@ services: container_name: grafana-with-datasources environment: GF_SECURITY_ADMIN_USER: monitor - GF_SECURITY_ADMIN_PASSWORD: demo + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-demo} GF_INSTALL_PLUGINS: yesoreyeram-infinity-datasource ports: - "3000:3000" -- GitLab From 3ff0d3446b8d37d7e6f0fe8f40029a102617be1a Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 19:23:41 +0700 Subject: [PATCH 12/17] chore: Reduce default instance size and EBS volume --- terraform/README.md | 6 +++--- terraform/aws/README.md | 6 +++--- terraform/aws/terraform.tfvars.example | 4 ++-- terraform/aws/variables.tf | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index b8a3732..a700f88 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -46,7 +46,7 @@ terraform apply The AWS deployment creates: 1. **Compute** - - Single EC2 instance (t3.large recommended) + - Single EC2 instance (t3.medium default) - Ubuntu 22.04 LTS (Jammy) with Docker and Docker Compose - Systemd service for automatic startup @@ -75,8 +75,8 @@ The AWS deployment creates: Recommended instance types based on workload: -- **t3.medium**: 2 vCPU, 4 GiB RAM - suitable for 1-3 databases -- **t3.large**: 2 vCPU, 8 GiB RAM - suitable for 3-10 databases (recommended) +- **t3.medium**: 2 vCPU, 4 GiB RAM - suitable for 1-3 databases (default) +- **t3.large**: 2 vCPU, 8 GiB RAM - suitable for 3-10 databases - **t3.xlarge**: 4 vCPU, 16 GiB RAM - suitable for 10+ databases Additional options: diff --git a/terraform/aws/README.md b/terraform/aws/README.md index 7bcc6ce..1d246b4 100644 --- a/terraform/aws/README.md +++ b/terraform/aws/README.md @@ -8,8 +8,8 @@ Single EC2 instance with Docker Compose. Terraform creates: - VPC with public subnet -- EC2 instance (t3.large, Ubuntu 22.04 LTS) -- EBS volume (100GB gp3, encrypted) +- EC2 instance (t3.medium, Ubuntu 22.04 LTS) +- EBS volume (50 GiB gp3, encrypted) - Security Group (SSH + Grafana ports) - Elastic IP (optional) @@ -239,7 +239,7 @@ Suitable for: - Proof of concept ```hcl -instance_type = "t3.large" # 2 vCPU, 8 GiB RAM (default) +instance_type = "t3.medium" # 2 vCPU, 8 GiB RAM (default) ``` Suitable for: diff --git a/terraform/aws/terraform.tfvars.example b/terraform/aws/terraform.tfvars.example index 8b001ba..e005873 100644 --- a/terraform/aws/terraform.tfvars.example +++ b/terraform/aws/terraform.tfvars.example @@ -20,10 +20,10 @@ aws_region = "us-east-1" # Environment environment = "production" -instance_type = "t3.large" +instance_type = "t3.medium" # EBS volume size for data (GiB) -data_volume_size = 100 +data_volume_size = 50 # SECURITY diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf index 982bc25..95cea22 100644 --- a/terraform/aws/variables.tf +++ b/terraform/aws/variables.tf @@ -19,7 +19,7 @@ variable "instance_type" { variable "data_volume_size" { description = "Size of EBS data volume in GiB" type = number - default = 100 + default = 50 } variable "ssh_key_name" { -- GitLab From b3319472e2f75674c706294a458bdc3337c67b03 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Tue, 7 Oct 2025 19:23:41 +0700 Subject: [PATCH 13/17] docs: Simplify terraform configuration, remove grafana_password --- terraform/aws/QUICKSTART.md | 4 +-- terraform/aws/README.md | 37 +++++++++++++++++++------- terraform/aws/terraform.tfvars.example | 6 ++--- terraform/aws/user_data.sh | 10 +++++++ terraform/aws/variables.tf | 3 ++- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/terraform/aws/QUICKSTART.md b/terraform/aws/QUICKSTART.md index 5bad64c..6f1bac9 100644 --- a/terraform/aws/QUICKSTART.md +++ b/terraform/aws/QUICKSTART.md @@ -19,7 +19,7 @@ cd terraform/aws # Configure cp terraform.tfvars.example terraform.tfvars -vim terraform.tfvars # Set ssh_key_name and grafana_password +vim terraform.tfvars # Set ssh_key_name # Validate ./validate.sh @@ -39,7 +39,7 @@ terraform output ssh_command ```bash # Grafana dashboard open $(terraform output -raw grafana_url) -# Login: monitor / +# Login: monitor / demo (or your custom password) # SSH ssh -i ~/.ssh/postgres-ai-key.pem ubuntu@$(terraform output -raw public_ip) diff --git a/terraform/aws/README.md b/terraform/aws/README.md index 1d246b4..ba7ad25 100644 --- a/terraform/aws/README.md +++ b/terraform/aws/README.md @@ -25,20 +25,37 @@ See [QUICKSTART.md](QUICKSTART.md) for step-by-step guide. ```hcl # terraform.tfvars -ssh_key_name = "postgres-ai-key" -grafana_password = "SecurePassword123!" +ssh_key_name = "postgres-ai-key" + +# Optional: Set custom Grafana password (defaults to 'demo') +# grafana_password = "YourSecurePassword123!" ``` ### Minimal production setup ```hcl +# terraform.tfvars + +# REQUIRED PARAMETERS +ssh_key_name = "your-key-name" + +# AWS SETTINGS aws_region = "us-east-1" environment = "production" -grafana_password = "SecurePassword123!" +instance_type = "t3.medium" + +# STORAGE +data_volume_size = 50 # GiB -# Optional: API key for uploading reports to PostgresAI -# Get it from: https://console.postgres.ai → Your Org → Manage → Access Tokens -postgres_ai_api_key = "your-api-key-here" +# SECURITY (restrict access!) +allowed_ssh_cidr = ["0.0.0.0/0"] # WARNING: Allows access from anywhere +allowed_cidr_blocks = ["0.0.0.0/0"] # WARNING: Allows access from anywhere + +# OPTIONAL PARAMETERS +# grafana_password = "YourSecurePassword123!" # Defaults to 'demo' +# postgres_ai_api_key = "your-api-key" # For uploading reports +# enable_demo_db = false # true for testing +# use_elastic_ip = true # Stable IP address monitoring_instances = [ { @@ -57,20 +74,20 @@ monitoring_instances = [ # AWS aws_region = "us-east-1" environment = "production" -instance_type = "t3.large" +instance_type = "t3.medium" # Storage -data_volume_size = 100 +data_volume_size = 50 # Security (restrict access in production) allowed_ssh_cidr = ["203.0.113.0/24"] allowed_cidr_blocks = ["203.0.113.0/24"] # Required -ssh_key_name = "postgres-ai-key" -grafana_password = "SecurePassword123!" +ssh_key_name = "ssh-key" # Optional +grafana_password = "SecurePassword123!" # Defaults to 'demo' postgres_ai_api_key = "your-api-key" enable_demo_db = false use_elastic_ip = true diff --git a/terraform/aws/terraform.tfvars.example b/terraform/aws/terraform.tfvars.example index e005873..f777855 100644 --- a/terraform/aws/terraform.tfvars.example +++ b/terraform/aws/terraform.tfvars.example @@ -7,9 +7,6 @@ # SSH key for EC2 access (create in AWS Console or CLI) ssh_key_name = "your-key-name" -# Grafana password (minimum 8 characters) -grafana_password = "YourSecurePassword123!" - # AWS SETTINGS # ------------------------- @@ -80,6 +77,9 @@ monitoring_instances = [ # If not set, reports will be generated locally without upload # postgres_ai_api_key = "your-api-key-here" +# Grafana admin password (defaults to 'demo') +# grafana_password = "YourSecurePassword123!" + # Enable demo database (for testing) # enable_demo_db = false diff --git a/terraform/aws/user_data.sh b/terraform/aws/user_data.sh index 3e63a52..bd32573 100644 --- a/terraform/aws/user_data.sh +++ b/terraform/aws/user_data.sh @@ -74,6 +74,11 @@ grafana_password=${grafana_password} %{ if enable_demo_db }demo_mode=true%{ else }demo_mode=false%{ endif } EOF +# Create .env file for docker-compose +cat > .env < 0 } cat > instances.yml <<'INSTANCES_EOF' @@ -135,6 +140,11 @@ systemctl start postgres-ai # Wait for services to be healthy sleep 30 +# Reset Grafana admin password to match terraform config +echo "Setting Grafana admin password..." +cd /home/postgres_ai/postgres_ai +docker exec grafana-with-datasources grafana-cli admin reset-admin-password "${grafana_password}" 2>/dev/null || true + echo "Installation complete!" echo "Access Grafana at: http://$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4):3000" echo "Username: monitor" diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf index 95cea22..c021b14 100644 --- a/terraform/aws/variables.tf +++ b/terraform/aws/variables.tf @@ -46,8 +46,9 @@ variable "use_elastic_ip" { } variable "grafana_password" { - description = "Grafana admin password" + description = "Grafana admin password (optional, defaults to 'demo')" type = string + default = "demo" sensitive = true } -- GitLab From 726ecdc4428e3eb34b3f4016a64ef2036c425624 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Wed, 8 Oct 2025 14:35:08 +0000 Subject: [PATCH 14/17] Apply 1 suggestion(s) to 1 file(s) --- terraform/aws/QUICKSTART.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/terraform/aws/QUICKSTART.md b/terraform/aws/QUICKSTART.md index 6f1bac9..d6501dc 100644 --- a/terraform/aws/QUICKSTART.md +++ b/terraform/aws/QUICKSTART.md @@ -75,9 +75,6 @@ ssh ubuntu@IP "sudo cat /var/log/user-data.log" # Restart services ssh ubuntu@IP "sudo systemctl restart postgres-ai" -# Create backup -aws ec2 create-snapshot --volume-id $(terraform output -raw data_volume_id) - # Destroy terraform destroy ``` -- GitLab From a961d220b84a30c46dad1798075570abf4f99374 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Wed, 8 Oct 2025 22:12:55 +0700 Subject: [PATCH 15/17] Enables configurable EBS volume types Allows users to specify different EBS volume types for both the root and data volumes, providing flexibility and cost optimization. Adds a validation script to verify the Terraform configuration and required variables. --- terraform/aws/main.tf | 10 ++-- terraform/aws/terraform.tfvars.example | 4 ++ terraform/aws/validate.sh | 63 ++++++++++++++++++++++++++ terraform/aws/variables.tf | 12 +++++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100755 terraform/aws/validate.sh diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf index 0c58a91..3c31a67 100644 --- a/terraform/aws/main.tf +++ b/terraform/aws/main.tf @@ -25,17 +25,17 @@ provider "aws" { data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical - + filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } - + filter { name = "virtualization-type" values = ["hvm"] } - + filter { name = "root-device-type" values = ["ebs"] @@ -135,7 +135,7 @@ resource "aws_security_group" "main" { resource "aws_ebs_volume" "data" { availability_zone = aws_subnet.main.availability_zone size = var.data_volume_size - type = "gp3" + type = var.data_volume_type encrypted = true tags = { @@ -155,7 +155,7 @@ resource "aws_instance" "main" { root_block_device { volume_size = 30 - volume_type = "gp3" + volume_type = var.root_volume_type encrypted = true } diff --git a/terraform/aws/terraform.tfvars.example b/terraform/aws/terraform.tfvars.example index f777855..93d188c 100644 --- a/terraform/aws/terraform.tfvars.example +++ b/terraform/aws/terraform.tfvars.example @@ -22,6 +22,10 @@ instance_type = "t3.medium" # EBS volume size for data (GiB) data_volume_size = 50 +# Storage types +# data_volume_type = "gp3" # gp3 (SSD), st1 (HDD throughput optimized, min 125 GiB), sc1 (HDD cold, min 125 GiB) +# root_volume_type = "gp3" # gp3 (SSD), gp2 (older SSD) + # SECURITY # ------------------------- diff --git a/terraform/aws/validate.sh b/terraform/aws/validate.sh new file mode 100755 index 0000000..d6b4e28 --- /dev/null +++ b/terraform/aws/validate.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo "Validating Terraform configuration..." +echo + +# Check terraform +if ! command -v terraform &> /dev/null; then + echo -e "${RED}ERROR: Terraform not installed${NC}" + exit 1 +fi +echo -e "${GREEN}OK${NC} Terraform $(terraform version -json | grep -o '"version":"[^"]*' | cut -d'"' -f4)" + +# Check AWS CLI +if command -v aws &> /dev/null && aws sts get-caller-identity &> /dev/null 2>&1; then + ACCOUNT=$(aws sts get-caller-identity --query Account --output text) + echo -e "${GREEN}OK${NC} AWS credentials (Account: $ACCOUNT)" +else + echo -e "${YELLOW}WARN${NC} AWS credentials not configured" +fi + +# Init +terraform init -backend=false > /dev/null 2>&1 || { echo -e "${RED}ERROR: Terraform init failed${NC}"; exit 1; } +echo -e "${GREEN}OK${NC} Terraform init" + +# Validate +terraform validate > /dev/null 2>&1 || { echo -e "${RED}ERROR: Validation failed${NC}"; terraform validate; exit 1; } +echo -e "${GREEN}OK${NC} Configuration valid" + +# Check terraform.tfvars +if [ ! -f "terraform.tfvars" ]; then + echo -e "${RED}ERROR: terraform.tfvars not found${NC}" + echo "Run: cp terraform.tfvars.example terraform.tfvars" + exit 1 +fi + +# Check required variables +grep -q "ssh_key_name.*=" terraform.tfvars && ! grep -q 'ssh_key_name.*=.*""' terraform.tfvars || \ + { echo -e "${RED}ERROR: ssh_key_name not set in terraform.tfvars${NC}"; exit 1; } + +echo -e "${GREEN}OK${NC} Required variables configured" + +# Plan +echo +echo "Running terraform plan..." +if terraform plan -out=tfplan > /tmp/tfplan.log 2>&1; then + RESOURCES=$(terraform show -json tfplan 2>/dev/null | grep -o '"to_create":[0-9]*' | cut -d: -f2) + echo -e "${GREEN}OK${NC} Plan successful (${RESOURCES} resources to create)" +else + echo -e "${RED}ERROR: Plan failed${NC}" + cat /tmp/tfplan.log + exit 1 +fi + +echo +echo "Validation complete. Ready to deploy." +echo "Run: terraform apply tfplan" +echo diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf index c021b14..736cf3a 100644 --- a/terraform/aws/variables.tf +++ b/terraform/aws/variables.tf @@ -22,6 +22,18 @@ variable "data_volume_size" { default = 50 } +variable "data_volume_type" { + description = "EBS volume type for data disk (gp3 for SSD, st1 for HDD throughput optimized, sc1 for HDD cold)" + type = string + default = "gp3" +} + +variable "root_volume_type" { + description = "EBS volume type for root disk (gp3 for SSD, gp2 for older SSD)" + type = string + default = "gp3" +} + variable "ssh_key_name" { description = "Name of SSH key pair for EC2 access" type = string -- GitLab From e4da5c390eed0554911d8d429bc948b21b17ef94 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Thu, 9 Oct 2025 17:14:54 +0700 Subject: [PATCH 16/17] Adds dynamic instance configuration Enables dynamic configuration of monitored instances using a template file. This allows for easier management of monitored PostgreSQL instances by defining them in a variable. Also updates documentation with instructions for adding new monitoring instances. --- terraform/aws/QUICKSTART.md | 51 +++++++++++++++++---------------- terraform/aws/instances.yml.tpl | 30 +++++++++++++++++++ terraform/aws/main.tf | 42 +++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 terraform/aws/instances.yml.tpl diff --git a/terraform/aws/QUICKSTART.md b/terraform/aws/QUICKSTART.md index d6501dc..4f8df5d 100644 --- a/terraform/aws/QUICKSTART.md +++ b/terraform/aws/QUICKSTART.md @@ -12,15 +12,39 @@ chmod 400 ~/.ssh/postgres-ai-key.pem aws configure ``` -## Deploy +## Configure ```bash cd terraform/aws -# Configure +# Copy example config cp terraform.tfvars.example terraform.tfvars -vim terraform.tfvars # Set ssh_key_name +vim terraform.tfvars +``` + +Set required parameters: +- `ssh_key_name` - your AWS SSH key name +- `grafana_password` - custom password (optional, defaults to "demo") + +## Add monitoring instances + +Edit `terraform.tfvars` to add PostgreSQL instances to monitor: + +```hcl +monitoring_instances = [ + { + name = "prod-db" + conn_str = "postgresql://monitor:pass@db.example.com:5432/postgres" + environment = "production" + cluster = "main" + node_name = "primary" + } +] +``` + +## Deploy +```bash # Validate ./validate.sh @@ -45,27 +69,6 @@ open $(terraform output -raw grafana_url) ssh -i ~/.ssh/postgres-ai-key.pem ubuntu@$(terraform output -raw public_ip) ``` -## Add monitoring instances - -Edit `terraform.tfvars`: - -```hcl -monitoring_instances = [ - { - name = "prod-db" - conn_str = "postgresql://monitor:pass@db.example.com:5432/postgres" - environment = "production" - cluster = "main" - node_name = "primary" - } -] -``` - -Apply changes: -```bash -terraform apply -``` - ## Operations ```bash diff --git a/terraform/aws/instances.yml.tpl b/terraform/aws/instances.yml.tpl new file mode 100644 index 0000000..eada2a0 --- /dev/null +++ b/terraform/aws/instances.yml.tpl @@ -0,0 +1,30 @@ +%{ if length(monitoring_instances) > 0 ~} +%{ for instance in monitoring_instances ~} +- name: ${instance.name} + conn_str: ${instance.conn_str} + preset_metrics: full + custom_metrics: + is_enabled: true + group: default + custom_tags: + env: ${instance.environment} + cluster: ${instance.cluster} + node_name: ${instance.node_name} + sink_type: ~sink_type~ + +%{ endfor ~} +%{ endif ~} +%{ if enable_demo_db ~} +- name: demo-db + conn_str: postgresql://postgres:postgres@target-db:5432/postgres + preset_metrics: full + custom_metrics: + is_enabled: true + group: default + custom_tags: + env: demo + cluster: demo + node_name: demo + sink_type: ~sink_type~ +%{ endif ~} + diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf index 3c31a67..df06ebd 100644 --- a/terraform/aws/main.tf +++ b/terraform/aws/main.tf @@ -6,6 +6,10 @@ terraform { source = "hashicorp/aws" version = "~> 5.0" } + local = { + source = "hashicorp/local" + version = "~> 2.0" + } } } @@ -193,3 +197,41 @@ resource "aws_eip" "main" { } } +# Generate instances.yml from template +resource "local_file" "instances_config" { + content = templatefile("${path.module}/instances.yml.tpl", { + monitoring_instances = var.monitoring_instances + enable_demo_db = var.enable_demo_db + }) + filename = "${path.module}/.terraform/instances.yml" +} + +# Deploy instances.yml to EC2 when config changes +resource "terraform_data" "deploy_config" { + triggers_replace = { + config_hash = local_file.instances_config.content_md5 + } + + depends_on = [aws_instance.main, aws_volume_attachment.data] + + provisioner "remote-exec" { + inline = [ + "if ! sudo test -f /home/postgres_ai/postgres_ai/postgres_ai; then echo 'Skipping - installation not complete'; exit 0; fi", + "cat > /tmp/instances.yml << 'EOF'", + local_file.instances_config.content, + "EOF", + "sudo mv /tmp/instances.yml /home/postgres_ai/postgres_ai/instances.yml", + "sudo chown postgres_ai:postgres_ai /home/postgres_ai/postgres_ai/instances.yml", + "sudo -u postgres_ai /home/postgres_ai/postgres_ai/postgres_ai update-config", + "echo 'Config updated successfully'" + ] + + connection { + type = "ssh" + user = "ubuntu" + private_key = file("~/.ssh/${var.ssh_key_name}.pem") + host = var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip + } + } +} + -- GitLab From 1fd26012b91d0b6d4ce2c5d315d939014a8c8bc6 Mon Sep 17 00:00:00 2001 From: Bogdan Tsechoev Date: Thu, 9 Oct 2025 17:19:34 +0700 Subject: [PATCH 17/17] Clarifies deployment output instructions Updates deployment instructions in Terraform output. Improves clarity by referencing terraform.tfvars for sensitive values and adding a command to display detailed deployment information. --- terraform/aws/outputs.tf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/terraform/aws/outputs.tf b/terraform/aws/outputs.tf index 182eaf9..a44cec2 100644 --- a/terraform/aws/outputs.tf +++ b/terraform/aws/outputs.tf @@ -54,13 +54,14 @@ Deployment complete Grafana URL: http://${var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip}:3000 Username: monitor -Password: (from terraform.tfvars) +Password: see terraform.tfvars Monitoring: ${length(var.monitoring_instances)} instance(s) -API key: ${var.postgres_ai_api_key != "" ? "configured" : "not configured"} +API key: see terraform.tfvars SSH: ssh -i ~/.ssh/${var.ssh_key_name}.pem ubuntu@${var.use_elastic_ip ? aws_eip.main[0].public_ip : aws_instance.main.public_ip} -Backup: aws ec2 create-snapshot --volume-id ${aws_ebs_volume.data.id} + +For detailed deployment info: terraform output deployment_info EOT } -- GitLab