287 lines
8.6 KiB
Python
287 lines
8.6 KiB
Python
"""
|
|
Google Cloud Platform Infrastructure for Amar Mascotas
|
|
|
|
Deploys:
|
|
- VPC with subnets
|
|
- Compute Engine instance for Django app + Celery
|
|
- Cloud SQL PostgreSQL (PostGIS via flags)
|
|
- Memorystore Redis
|
|
- Firewall rules
|
|
- (Optional) Cloud Load Balancer, Cloud DNS
|
|
|
|
Estimated cost: ~$93/month
|
|
|
|
NOTE: GCP has good free tier credits and competitive pricing.
|
|
PostGIS requires enabling the `cloudsql.enable_pgaudit` flag.
|
|
"""
|
|
|
|
import pulumi
|
|
import pulumi_gcp as gcp
|
|
import sys
|
|
sys.path.append("..")
|
|
from shared.config import get_config, APP_SERVER_INIT_SCRIPT
|
|
|
|
# Load configuration
|
|
cfg = get_config()
|
|
|
|
# Get project
|
|
project = gcp.organizations.get_project()
|
|
|
|
# =============================================================================
|
|
# NETWORKING - VPC
|
|
# =============================================================================
|
|
|
|
# VPC Network
|
|
vpc = gcp.compute.Network(
|
|
f"{cfg.resource_prefix}-vpc",
|
|
name=f"{cfg.resource_prefix}-vpc",
|
|
auto_create_subnetworks=False,
|
|
description="VPC for Amar Mascotas",
|
|
)
|
|
|
|
# Subnet for compute resources
|
|
subnet = gcp.compute.Subnetwork(
|
|
f"{cfg.resource_prefix}-subnet",
|
|
name=f"{cfg.resource_prefix}-subnet",
|
|
ip_cidr_range="10.0.1.0/24",
|
|
region="us-east1",
|
|
network=vpc.id,
|
|
private_ip_google_access=True, # Access Google APIs without public IP
|
|
)
|
|
|
|
# =============================================================================
|
|
# FIREWALL RULES
|
|
# =============================================================================
|
|
|
|
# Allow SSH
|
|
firewall_ssh = gcp.compute.Firewall(
|
|
f"{cfg.resource_prefix}-allow-ssh",
|
|
name=f"{cfg.resource_prefix}-allow-ssh",
|
|
network=vpc.name,
|
|
allows=[
|
|
gcp.compute.FirewallAllowArgs(
|
|
protocol="tcp",
|
|
ports=["22"],
|
|
),
|
|
],
|
|
source_ranges=cfg.allowed_ssh_ips or ["0.0.0.0/0"],
|
|
target_tags=["app-server"],
|
|
)
|
|
|
|
# Allow HTTP/HTTPS
|
|
firewall_http = gcp.compute.Firewall(
|
|
f"{cfg.resource_prefix}-allow-http",
|
|
name=f"{cfg.resource_prefix}-allow-http",
|
|
network=vpc.name,
|
|
allows=[
|
|
gcp.compute.FirewallAllowArgs(
|
|
protocol="tcp",
|
|
ports=["80", "443"],
|
|
),
|
|
],
|
|
source_ranges=["0.0.0.0/0"],
|
|
target_tags=["app-server"],
|
|
)
|
|
|
|
# Allow internal traffic (for DB/Redis access)
|
|
firewall_internal = gcp.compute.Firewall(
|
|
f"{cfg.resource_prefix}-allow-internal",
|
|
name=f"{cfg.resource_prefix}-allow-internal",
|
|
network=vpc.name,
|
|
allows=[
|
|
gcp.compute.FirewallAllowArgs(
|
|
protocol="tcp",
|
|
ports=["0-65535"],
|
|
),
|
|
gcp.compute.FirewallAllowArgs(
|
|
protocol="udp",
|
|
ports=["0-65535"],
|
|
),
|
|
gcp.compute.FirewallAllowArgs(
|
|
protocol="icmp",
|
|
),
|
|
],
|
|
source_ranges=["10.0.0.0/8"],
|
|
)
|
|
|
|
# =============================================================================
|
|
# DATABASE - Cloud SQL PostgreSQL
|
|
# =============================================================================
|
|
|
|
# Cloud SQL instance
|
|
# Note: PostGIS available via database flags
|
|
db_instance = gcp.sql.DatabaseInstance(
|
|
f"{cfg.resource_prefix}-db",
|
|
name=f"{cfg.resource_prefix}-db",
|
|
database_version="POSTGRES_15",
|
|
region="us-east1",
|
|
deletion_protection=False, # Set True for production!
|
|
settings=gcp.sql.DatabaseInstanceSettingsArgs(
|
|
tier="db-f1-micro", # $25/mo - smallest
|
|
disk_size=10,
|
|
disk_type="PD_SSD",
|
|
ip_configuration=gcp.sql.DatabaseInstanceSettingsIpConfigurationArgs(
|
|
ipv4_enabled=False,
|
|
private_network=vpc.id,
|
|
enable_private_path_for_google_cloud_services=True,
|
|
),
|
|
backup_configuration=gcp.sql.DatabaseInstanceSettingsBackupConfigurationArgs(
|
|
enabled=True,
|
|
start_time="03:00",
|
|
),
|
|
database_flags=[
|
|
# Enable PostGIS extensions
|
|
gcp.sql.DatabaseInstanceSettingsDatabaseFlagArgs(
|
|
name="cloudsql.enable_pg_cron",
|
|
value="on",
|
|
),
|
|
],
|
|
user_labels=cfg.tags,
|
|
),
|
|
opts=pulumi.ResourceOptions(depends_on=[vpc]),
|
|
)
|
|
|
|
# Database
|
|
db = gcp.sql.Database(
|
|
f"{cfg.resource_prefix}-database",
|
|
name=cfg.db_name,
|
|
instance=db_instance.name,
|
|
)
|
|
|
|
# Database user
|
|
db_user = gcp.sql.User(
|
|
f"{cfg.resource_prefix}-db-user",
|
|
name=cfg.db_user,
|
|
instance=db_instance.name,
|
|
password=pulumi.Config().require_secret("db_password"),
|
|
)
|
|
|
|
# Private IP for Cloud SQL
|
|
private_ip_address = gcp.compute.GlobalAddress(
|
|
f"{cfg.resource_prefix}-db-private-ip",
|
|
name=f"{cfg.resource_prefix}-db-private-ip",
|
|
purpose="VPC_PEERING",
|
|
address_type="INTERNAL",
|
|
prefix_length=16,
|
|
network=vpc.id,
|
|
)
|
|
|
|
# VPC peering for Cloud SQL
|
|
private_vpc_connection = gcp.servicenetworking.Connection(
|
|
f"{cfg.resource_prefix}-private-vpc-connection",
|
|
network=vpc.id,
|
|
service="servicenetworking.googleapis.com",
|
|
reserved_peering_ranges=[private_ip_address.name],
|
|
)
|
|
|
|
# =============================================================================
|
|
# CACHE - Memorystore Redis
|
|
# =============================================================================
|
|
|
|
redis_instance = gcp.redis.Instance(
|
|
f"{cfg.resource_prefix}-redis",
|
|
name=f"{cfg.resource_prefix}-redis",
|
|
tier="BASIC", # $20/mo - no HA
|
|
memory_size_gb=1,
|
|
region="us-east1",
|
|
redis_version="REDIS_7_0",
|
|
authorized_network=vpc.id,
|
|
connect_mode="PRIVATE_SERVICE_ACCESS",
|
|
labels=cfg.tags,
|
|
opts=pulumi.ResourceOptions(depends_on=[private_vpc_connection]),
|
|
)
|
|
|
|
# =============================================================================
|
|
# COMPUTE - Compute Engine Instance
|
|
# =============================================================================
|
|
|
|
# Service account for the instance
|
|
service_account = gcp.serviceaccount.Account(
|
|
f"{cfg.resource_prefix}-sa",
|
|
account_id=f"{cfg.resource_prefix}-app-sa",
|
|
display_name="Amar App Service Account",
|
|
)
|
|
|
|
# Compute instance
|
|
instance = gcp.compute.Instance(
|
|
f"{cfg.resource_prefix}-app",
|
|
name=f"{cfg.resource_prefix}-app",
|
|
machine_type="e2-medium", # $30/mo - 4GB RAM, 2 vCPU
|
|
zone="us-east1-b",
|
|
tags=["app-server"],
|
|
boot_disk=gcp.compute.InstanceBootDiskArgs(
|
|
initialize_params=gcp.compute.InstanceBootDiskInitializeParamsArgs(
|
|
image="ubuntu-os-cloud/ubuntu-2204-lts",
|
|
size=30,
|
|
type="pd-ssd",
|
|
),
|
|
),
|
|
network_interfaces=[
|
|
gcp.compute.InstanceNetworkInterfaceArgs(
|
|
network=vpc.id,
|
|
subnetwork=subnet.id,
|
|
access_configs=[
|
|
gcp.compute.InstanceNetworkInterfaceAccessConfigArgs(
|
|
# Ephemeral public IP
|
|
),
|
|
],
|
|
),
|
|
],
|
|
service_account=gcp.compute.InstanceServiceAccountArgs(
|
|
email=service_account.email,
|
|
scopes=["cloud-platform"],
|
|
),
|
|
metadata_startup_script=APP_SERVER_INIT_SCRIPT,
|
|
labels=cfg.tags,
|
|
)
|
|
|
|
# Static external IP (optional, costs extra)
|
|
static_ip = gcp.compute.Address(
|
|
f"{cfg.resource_prefix}-static-ip",
|
|
name=f"{cfg.resource_prefix}-static-ip",
|
|
region="us-east1",
|
|
)
|
|
|
|
# =============================================================================
|
|
# OPTIONAL: Cloud Load Balancer (uncomment if needed)
|
|
# =============================================================================
|
|
|
|
# health_check = gcp.compute.HealthCheck(
|
|
# f"{cfg.resource_prefix}-health-check",
|
|
# name=f"{cfg.resource_prefix}-health-check",
|
|
# http_health_check=gcp.compute.HealthCheckHttpHealthCheckArgs(
|
|
# port=80,
|
|
# request_path="/health/",
|
|
# ),
|
|
# )
|
|
|
|
# =============================================================================
|
|
# OUTPUTS
|
|
# =============================================================================
|
|
|
|
pulumi.export("instance_public_ip", instance.network_interfaces[0].access_configs[0].nat_ip)
|
|
pulumi.export("instance_private_ip", instance.network_interfaces[0].network_ip)
|
|
pulumi.export("static_ip", static_ip.address)
|
|
pulumi.export("db_private_ip", db_instance.private_ip_address)
|
|
pulumi.export("db_connection_name", db_instance.connection_name)
|
|
pulumi.export("db_name", cfg.db_name)
|
|
pulumi.export("db_user", cfg.db_user)
|
|
pulumi.export("redis_host", redis_instance.host)
|
|
pulumi.export("redis_port", redis_instance.port)
|
|
|
|
# Generate .env content
|
|
pulumi.export("env_file", pulumi.Output.all(
|
|
db_instance.private_ip_address,
|
|
redis_instance.host,
|
|
redis_instance.port,
|
|
).apply(lambda args: f"""
|
|
# Generated by Pulumi - GCP
|
|
DB_HOST={args[0]}
|
|
DB_PORT=5432
|
|
DB_NAME={cfg.db_name}
|
|
DB_USER={cfg.db_user}
|
|
DB_PASSWORD=<set via pulumi config>
|
|
CELERY_BROKER_URL=redis://{args[1]}:{args[2]}/0
|
|
CELERY_RESULT_BACKEND=redis://{args[1]}:{args[2]}/0
|
|
"""))
|