pulumi_rds.py
| import pulumi_random as random | |
| import pulumi | |
| import pulumi_aws as aws | |
| def create_internal_vpc(name_prefix: str, vpc_cidr: str = "10.0.0.0/16") -> dict: | |
| """ | |
| Creates an isolated VPC for internal AWS communication. | |
| Includes private subnets across different AZs for Multi-AZ capabilities, | |
| and a Security Group locked down to internal PostgreSQL traffic. | |
| """ | |
| # VPC: The foundational isolated network layer. | |
| # enable_dns_hostnames and enable_dns_support ensure RDS endpoints resolve correctly. | |
| vpc = aws.ec2.Vpc(f"{name_prefix}-vpc", | |
| cidr_block=vpc_cidr, | |
| enable_dns_hostnames=True, | |
| enable_dns_support=True) | |
| # AZ 1 Subnet: Private isolation from the internet. | |
| subnet_a = aws.ec2.Subnet(f"{name_prefix}-subnet-a", | |
| vpc_id=vpc.id, | |
| cidr_block="10.0.1.0/24", | |
| availability_zone="us-east-1a") | |
| # AZ 2 Subnet: Required by RDS to enable Multi-AZ failover. | |
| subnet_b = aws.ec2.Subnet(f"{name_prefix}-subnet-b", | |
| vpc_id=vpc.id, | |
| cidr_block="10.0.2.0/24", | |
| availability_zone="us-east-1b") | |
| # DB Subnet Group: Instructs the RDS service on which subnets it is allowed to use. | |
| db_subnet_group = aws.rds.SubnetGroup(f"{name_prefix}-subnet-group", | |
| subnet_ids=[subnet_a.id, subnet_b.id]) | |
| # Security Group: Locked down to PostgreSQL traffic. | |
| # The ingress rule uses self=True to allow only resources within this same SG to connect. | |
| postgres_sg = aws.ec2.SecurityGroup(f"{name_prefix}-postgres-sg", | |
| vpc_id=vpc.id, | |
| ingress=[aws.ec2.SecurityGroupIngressArgs( | |
| protocol="tcp", | |
| from_port=5432, | |
| to_port=5432, | |
| self=True, | |
| )], | |
| egress=[aws.ec2.SecurityGroupEgressArgs( | |
| protocol="-1", | |
| from_port=0, | |
| to_port=0, | |
| cidr_blocks=["0.0.0.0/0"], | |
| )]) | |
| # Export values so they can be consumed by other stacks or retrieved via the CLI. | |
| pulumi.export(f"{name_prefix}_vpc_id", vpc.id) | |
| pulumi.export(f"{name_prefix}_subnet_group_name", db_subnet_group.name) | |
| pulumi.export(f"{name_prefix}_postgres_sg_id", postgres_sg.id) | |
| return { | |
| "vpc": vpc, | |
| "db_subnet_group": db_subnet_group, | |
| "postgres_sg": postgres_sg, | |
| } | |
| def create_rds_postgres_cluster( | |
| name_prefix: str, | |
| primary_instance_class: str, | |
| dr_instance_class: str, | |
| subnet_group_name: str, | |
| security_group_id: str, | |
| ): | |
| """ | |
| Provisions a highly available RDS PostgreSQL cluster with an in-region replica for read scaling and a cross-region | |
| replica for disaster recovery. Auto-scaling is employed to prevent performance issues. | |
| The RDS instance will ony be available over a private AWS network. | |
| The master password is created and stored in AWS Secrets Manager. The workflow supports easy rotation of the | |
| master password. | |
| pulumi taint urn:pulumi:stackname::projectname::random:random_password:RandomPassword$app-db-password | |
| pulumi up | |
| Automated RDS Features & DBA Workflows: | |
| * Automatic Backups (PITR): Continuously streams Write-Ahead Logs (WAL) to S3, enabling | |
| point-in-time recovery to any second within the retention period. | |
| * Automated Maintenance: Applies OS patching and DB engine updates automatically | |
| during a specified weekly maintenance window. | |
| * Encryption at Rest: Transparently leverages AWS KMS to encrypt primary storage, | |
| read replicas, and automated snapshots. | |
| * IOPS & Storage Autoscaling: Provisions baseline IOPS based on storage size/type (e.g., GP3). | |
| Storage autoscaling handles burst capacity dynamically without manual disk tuning. | |
| * DBA Automation: Completely manages failover orchestration (via Multi-AZ), host | |
| provisioning, and zero-downtime volume expansions, eliminating routine sysadmin tasks. | |
| """ | |
| db_password = random.RandomPassword(f"{name_prefix}-password", | |
| length=24, | |
| special=True, | |
| override_special="!#$%&*()-_=+[]{}<>:?") | |
| db_secret = aws.secretsmanager.Secret(f"{name_prefix}-secret") | |
| db_secret_version = aws.secretsmanager.SecretVersion(f"{name_prefix}-secret-version", | |
| secret_id=db_secret.id, | |
| secret_string=db_password.result) | |
| # 1. Primary DB: Handles write traffic. | |
| # max_allocated_storage: Flips on storage autoscaling. | |
| # multi_az: Provisions a synchronous standby in another AZ for automatic failover. | |
| primary_db = aws.rds.Instance(f"{name_prefix}-primary", | |
| engine="postgres", | |
| engine_version="18.3", | |
| instance_class=primary_instance_class, | |
| allocated_storage=50, | |
| max_allocated_storage=200, # Provides autoscaling up to this limit | |
| db_name="appdb", | |
| username="dbadmin", | |
| password=db_password.result, | |
| db_subnet_group_name=subnet_group_name, | |
| vpc_security_group_ids=[security_group_id], | |
| multi_az=True, # Multi-AZ failover | |
| storage_encrypted=True, # Encryption at rest | |
| backup_retention_period=7, # Must be > 0 to enable replication | |
| publicly_accessible=False, # Explicitly blocks internet access, available only on private network | |
| skip_final_snapshot=True | |
| ) | |
| # 2. In-Region Read Replica: Offloads read queries from the primary. | |
| # replicate_source_db uses the primary identifier for in-region replication. | |
| in_region_replica = aws.rds.Instance(f"{name_prefix}-local-replica", | |
| instance_class=primary_instance_class, | |
| replicate_source_db=primary_db.identifier, | |
| vpc_security_group_ids=[security_group_id], | |
| storage_encrypted=True, | |
| skip_final_snapshot=True | |
| ) | |
| # 3. Cross-Region Replica: For Disaster Recovery. | |
| dr_provider = aws.Provider(f"{name_prefix}-dr-provider", region="us-west-2") | |
| dr_kms_key = aws.kms.get_key(key_id="alias/aws/rds", opts=pulumi.InvokeOptions(provider=dr_provider)) | |
| # Custom VPC in us-west-2 (using the DR provider) | |
| dr_vpc = aws.ec2.Vpc("dr-vpc", | |
| cidr_block="10.1.0.0/16", | |
| enable_dns_hostnames=True, | |
| enable_dns_support=True, | |
| opts=pulumi.ResourceOptions(provider=dr_provider)) | |
| # Private Subnets in us-west-2 | |
| dr_subnet_a = aws.ec2.Subnet("dr-subnet-a", | |
| vpc_id=dr_vpc.id, | |
| cidr_block="10.1.1.0/24", | |
| availability_zone="us-west-2a", | |
| opts=pulumi.ResourceOptions(provider=dr_provider)) | |
| dr_subnet_b = aws.ec2.Subnet("dr-subnet-b", | |
| vpc_id=dr_vpc.id, | |
| cidr_block="10.1.2.0/24", | |
| availability_zone="us-west-2b", | |
| opts=pulumi.ResourceOptions(provider=dr_provider)) | |
| # Secondary DB Subnet Group in us-west-2 | |
| dr_db_subnet_group = aws.rds.SubnetGroup("dr-db-subnet-group", | |
| subnet_ids=[dr_subnet_a.id, dr_subnet_b.id], | |
| opts=pulumi.ResourceOptions(provider=dr_provider)) | |
| # Secondary Security Group in us-west-2 | |
| dr_postgres_sg = aws.ec2.SecurityGroup("dr-postgres-sg", | |
| vpc_id=dr_vpc.id, | |
| ingress=[aws.ec2.SecurityGroupIngressArgs( | |
| protocol="tcp", from_port=5432, to_port=5432, self=True | |
| )], | |
| opts=pulumi.ResourceOptions(provider=dr_provider)) | |
| # Pass the DR Subnet Group and Security Group to the DR Replica | |
| dr_replica = aws.rds.Instance("dr-replica", | |
| instance_class=dr_instance_class, | |
| replicate_source_db=primary_db.arn, | |
| db_subnet_group_name=dr_db_subnet_group.name, # Bound to us-west-2 VPC | |
| vpc_security_group_ids=[dr_postgres_sg.id], # Bound to us-west-2 VPC | |
| publicly_accessible=False, | |
| storage_encrypted=True, | |
| kms_key_id=dr_kms_key.arn, | |
| skip_final_snapshot=True, | |
| opts=pulumi.ResourceOptions(provider=dr_provider)) | |
| # Exports for application consumption | |
| pulumi.export("write_endpoint", primary_db.endpoint) | |
| pulumi.export("read_endpoint", in_region_replica.endpoint) | |
| pulumi.export("dr_endpoint", dr_replica.endpoint) | |
| # Export the automatically generated secret ARN so your app services (ECS/EC2) can retrieve it | |
| pulumi.export("db_secret_arn", primary_db.master_user_secrets[0].secret_arn) | |
| return primary_db, in_region_replica, dr_replica | |
| if __name__ == "__main__": | |
| network = create_internal_vpc(name_prefix="core-prod") | |
| # ~$50 per month cost for all three instances | |
| # trivial to scale up CPU, RAM, and IOPS (can be provisioned separately) | |
| database = create_rds_postgres_cluster( | |
| name_prefix="app-db", | |
| primary_instance_class="db.t3.small", # consistent baseline of 3,000 IOPS and 125 MiB/s; 2 vCPUs and 1 GB of RAM; | |
| dr_instance_class="db.t3.micro", | |
| subnet_group_name=network["db_subnet_group"].name, | |
| security_group_id=network["postgres_sg"].id, | |
| ) |
评论
?
参与讨论