Configuración de AWS RDS de extremo a extremo con Bastion Host usando Terraform

Introducción

Cualquier canal de datos, fuentes de datos, especialmente bases de datos, son la columna vertebral. Para simular una tubería realista, necesitaba un entorno de base de datos seguro y confiable para servir como fuente de verdad para los trabajos ETL aguas abajo.

En lugar de aprovisionar esto manualmente, elegí automatizar todo usando Terraformadoalineando con la ingeniería de datos moderna y las mejores prácticas de DevOps. Esto no solo ahorró tiempo, sino que también aseguró que el medio ambiente podría recrearse, escalar o destruir fácilmente con un solo comando, como en la producción. Y si está trabajando en el nivel gratuito de AWS, esto es aún más importante: la automatización asegura que pueda limpiar todo sin olvidar un recurso que podría generar costos.

Requisitos previos

Para seguir con este proyecto, necesitará las siguientes herramientas y configuración:

  • Terraformado instalado https://developer.hashicorp.com/terraform/install
  • Configuración de AWS CLI y IAM
    • Instale el AWS CLI
    • Cree un usuario de IAM con acceso programático que tenga permiso para:
      • Adjuntar la política AdministratorAccess (o crear una política personalizada con permisos limitados para crear todos los recursos incluidos)
      • Descargue la ID de clave de acceso y la tecla de acceso secreto
    • Configurar el AWS CLI
  • Un par de claves de AWS
    Requerido para ssh en el host de bastión. Puede crear uno en la consola AWS en pares de teclas EC2>.
  • Un entorno basado en unix (Linux/macOS, o WSL para Windows)
    Esto garantiza la compatibilidad con los comandos de script y terraza de shell.

Comenzando: lo que estamos construyendo

Pasemos a través de cómo construir un Configuración de base de datos AWS segura y automatizada Usando Terraform.

Descripción general de la infraestructura

Este proyecto provoca un entorno AWS completo de estilo de producción utilizando Terraform. Se crearán los siguientes recursos:

Networking

  • A VPC personalizado con un bloque CIDR (10.0.0.0/16)
  • Dos subredes privadas En diferentes zonas de disponibilidad (para la instancia de RDS)
  • Una subred pública (para el anfitrión de Bastion)
  • Puerta de enlace de Internet y Mesas de ruta para enrutamiento de subred público
  • A Grupo de subred de DB para la implementación de Multi-AZ RDS

Calcular

  • A instancia de Bastion EC2 En la subred pública
    • Se utiliza para ssh en las subredes privadas y acceder a la base de datos de forma segura
    • Provisado con un grupo de seguridad personalizado que permite solo el acceso al puerto 22 (SSH)

Base de datos

  • A Instancia mysql rds
    • Implementado en subredes privadas (no accesible desde Internet público)
    • Configurado con un grupo de seguridad dedicado que permite el acceso solo desde el host de Bastion

Seguridad

  • Grupos de seguridad:
    • Bastion SG: Permite SSH entrante (puerto 22) desde su IP
    • RDS SG: Permite MySQL entrante (Puerto 3306) del SG del bastión

Automatización

  • A Configuración de script (setup.sh) eso:
    • Exporta variables de terraformación

Diseño modular con terraform

Rompí la infraestructura en módulos como Network, Bastion y RDS. Esto me permite reutilizar, escalar y probar diferentes componentes de forma independiente.

El siguiente diagrama ilustra cómo Terraform entiende y estructura las dependencias entre diferentes componentes de la infraestructura, donde cada nodo representa un recurso o módulo.

Esta visualización ayuda a verificar eso:

  • Los recursos están correctamente conectados (por ejemplo, la instancia de RDS depende de las subredes privadas),
  • Los módulos están aislados pero interoperables (por ejemplo, network, bastiony rds),
  • No hay dependencias circulares.
Gráfico de dependencia de Terraform (imagen por autor)

Para mantener la configuración modular mencionada anteriormente, estructuré el proyecto en consecuencia y proporcioné explicaciones para cada componente para aclarar sus roles dentro de la configuración.

.
├── data
│   └── mysqlsampledatabase.sql       # Sample dataset to be imported into the RDS database
├── scripts
│   └── setup.sh                      # Bash script to export environment variables (TF_VAR_*), fetch dynamic values, and upload Glue scripts (optional)
└── terraform
    ├── modules                       # Reusable infrastructure modules
    │   ├── bastion
    │   │   ├── compute.tf            # Defines EC2 instance configuration for the Bastion host
    │   │   ├── network.tf            # Uses data sources to reference existing public subnet and VPC (does not create them)
    │   │   ├── outputs.tf            # Outputs Bastion host public IP address
    │   │   └── variables.tf          # Input variables required by the Bastion module (AMI ID, key pair name, etc.)
    │   ├── network
    │   │   ├── network.tf            # Provisions VPC, public/private subnets, Internet gateway, and route tables
    │   │   ├── outputs.tf            # Exposes VPC ID, subnet IDs, and route table IDs for downstream modules
    │   │   └── variables.tf          # Input variables like CIDR blocks and availability zones
    │   └── rds
    │       ├── network.tf            # Defines DB subnet group using private subnet IDs
    │       ├── outputs.tf            # Outputs RDS endpoint and security group for other modules to consume
    │       ├── rds.tf                # Provisions a MySQL RDS instance inside private subnets
    │       └── variables.tf          # Input variables such as DB name, username, password, and instance size
    └── rds-bastion                   # Root Terraform configuration
        ├── backend.tf                # Configures the Terraform backend (e.g., local or remote state file location)
        ├── main.tf                   # Top-level orchestrator file that connects and wires up all modules
        ├── outputs.tf                # Consolidates and re-exports outputs from the modules (e.g., Bastion IP, DB endpoint)
        ├── provider.tf               # Defines the AWS provider and required version
        └── variables.tf              # Project-wide variables passed to modules and referenced across files

Con la estructura modular en su lugar, el main.tf el archivo se encuentra en el rds-bastion el directorio actúa como el orquestador. Une los componentes centrales: la red, la base de datos RDS y el host Bastion. Cada módulo se invoca con las entradas requeridas, la mayoría de las cuales se definen en variables.tf o pasado a través de variables de entorno (TF_VAR_*).

module "network" {
  source                = "../modules/network"
  region                = var.region
  project_name          = var.project_name
  availability_zone_1   = var.availability_zone_1
  availability_zone_2   = var.availability_zone_2
  vpc_cidr              = var.vpc_cidr
  public_subnet_cidr    = var.public_subnet_cidr
  private_subnet_cidr_1 = var.private_subnet_cidr_1
  private_subnet_cidr_2 = var.private_subnet_cidr_2
}


module "bastion" {
  source = "../modules/bastion"
  region              = var.region
  vpc_id              = module.network.vpc_id
  public_subnet_1     = module.network.public_subnet_id
  availability_zone_1 = var.availability_zone_1
  project_name        = var.project_name

  instance_type = var.instance_type
  key_name      = var.key_name
  ami_id        = var.ami_id

}


module "rds" {
  source              = "../modules/rds"
  region              = var.region
  project_name        = var.project_name
  vpc_id              = module.network.vpc_id
  private_subnet_1    = module.network.private_subnet_id_1
  private_subnet_2    = module.network.private_subnet_id_2
  availability_zone_1 = var.availability_zone_1
  availability_zone_2 = var.availability_zone_2

  db_name       = var.db_name
  db_username   = var.db_username
  db_password   = var.db_password
  bastion_sg_id = module.bastion.bastion_sg_id
}

En esta configuración modular, cada componente de infraestructura está libremente acoplado pero se conecta a través de bien definido entradas y salidas.

Por ejemplo, después de aprovisionar el VPC y subredes en el network módulo, recupero sus ID usando su salidasy pasarlos como Variables de entrada a otros módulos como rds y bastion. Esto evita la codificación difícil y permite que Terraform Resolver dependencias y construir el gráfico de dependencia internamente.

En algunos casos (como dentro del bastion módulo), también uso data fuentes para Referencia a los recursos existentes Creado por módulos anteriores, en lugar de recrearlos o duplicarlos.

El dependencia entre módulos confía en el correcto Definición y exposición de resultados de módulos creados anteriormente. Estas salidas se pasan como variables de entrada a módulos dependientes, lo que permite a Terraform para construir un gráfico de dependencia interna y orquestar el orden de creación correcto.

Por ejemplo, el network módulo exponido la ID de VPC y las ID de subred usando outputs.tf. Estos valores son entonces consumado por módulos posteriores como rds y bastion a través del main.tf Archivo de la configuración raíz.

A continuación se muestra cómo funciona esto en la práctica:

Adentro modules/network/outputs.tf:

output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

Adentro modules/bastion/variables.tf:

variable "vpc_id" {
  description = "ID of the VPC"
  type        = string
}

Adentro modules/bastion/network.tf:

data "aws_vpc" "main" {
  id = var.vpc_id
}

Para aprovisionar la instancia de RDS, creé Dos subredes privados en diferentes zonas de disponibilidadcomo lo requiere AWS al menos dos subredes en AZS separados Para configurar un grupo de subred DB.

Aunque cumplí con este requisito para la configuración correcta, yo despliegue múltiple discapacitado Durante la creación de RDS a Manténgase dentro de los límites de nivel gratuito de AWS y Evite los costos adicionales. Esta configuración aún simula un diseño de red de grado de producción mientras permanece rentable para el desarrollo y las pruebas.

Flujo de trabajo de implementación

Con todos los módulos correctamente conectados a través de entradas y salidas, y la lógica de infraestructura encapsulada en bloques reutilizables, el siguiente paso es automatizar el proceso de aprovisionamiento. En lugar de pasar manualmente las variables cada vez, un guión auxiliar setup.sh se utiliza para exportar las variables de entorno necesarias (TF_VAR_*).

Una vez que se obtiene el script de configuración, la implementación de la infraestructura se vuelve tan simple como ejecutar algunos comandos de Terraform.

source scripts/setup.sh
cd terraform/rds-bastion
terraform init
terraform plan
terraform apply

Para optimizar el proceso de implementación de Terraform, creé un script de ayuda (setup.sh) que exporta automáticamente las variables de entorno requeridas utilizando el TF_VAR_ Convención de nombres. Terraform recoge automáticamente variables con prefijo con TF_VAR_por lo que este enfoque evita los valores de codificación en .tf archivos o requerir entrada manual cada vez.

#!/bin/bash
set -e
export de_project=""
export AWS_DEFAULT_REGION=""

# Define the variables to manage
declare -A TF_VARS=(
  ["TF_VAR_project_name"]="$de_project"
  ["TF_VAR_region"]="$AWS_DEFAULT_REGION"
  ["TF_VAR_availability_zone_1"]="us-east-1a"
  ["TF_VAR_availability_zone_2"]="us-east-1b"

  ["TF_VAR_ami_id"]=""
  ["TF_VAR_key_name"]=""
  ["TF_VAR_db_username"]=""
  ["TF_VAR_db_password"]=""
  ["TF_VAR_db_name"]=""
)

for var in "${!TF_VARS[@]}"; do
    value="${TF_VARS[$var]}"
    if grep -q "^export $var=" "$HOME/.bashrc"; then
        sed -i "s|^export $var=.*|export $var=$value|" "$HOME/.bashrc"
    else
        echo "export $var=$value" >> "$HOME/.bashrc"
    fi
done

# Source updated .bashrc to make changes available immediately in this shell
source "$HOME/.bashrc"

Después de correr terraform applyTerraform aprovisionará todos los recursos definidos: VPC, subredes, tablas de ruta, instancia de RDS y host de bastión. Una vez que el proceso se complete con éxito, verá valores de salida similares a los siguientes:

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

Outputs:

bastion_public_ip      = "<Bastion EC2 Public IP>"
bastion_sg_id          = "<Security Group ID for Bastion Host>"
db_endpoint            = "<RDS Endpoint>:3306"
instance_public_dns    = "<EC2 Public DNS>"
rds_db_name            = "<Database Name>"
vpc_id                 = "<VPC ID>"
vpc_name               = "<VPC Name>"

Estas salidas se definen en el outputs.tf archivos de sus módulos y reexportados en el módulo raíz (rds-bastion/outputs.tf). Son cruciales para:

  • Ssh-ing en el anfitrión de Bastion
  • Conectarse de forma segura a la instancia privada de RDS
  • Validación de la creación de recursos

Conectarse con el RDS a través de Bastion Host y Sembrar la base de datos

Ahora que la infraestructura está aprovisionada, el siguiente paso es Sembra la base de datos MySQL alojada en la instancia de RDS. Dado que la base de datos está dentro de un subred privadano podemos acceder a ella directamente desde nuestra máquina local. En su lugar, usaremos el Instancia de Bastion EC2 Como un anfitrión de salto para:

  • Transfiera el conjunto de datos de muestra (mysqlsampledatabase.sql) al bastión.
  • Conéctese desde el bastión a la instancia de RDS.
  • Importe los datos SQL para inicializar la base de datos.

Puede mover dos directorios del directorio principal de Terraform y enviar el contenido SQL al EC2 remoto (Bastion) después de leer el archivo SQL local dentro del directorio de datos.

cd ../.. 
cat data/mysqlsampledatabase.sql | ssh -i your-key.pem ec2-user@<BASTION_PUBLIC_IP> 'cat > ~/mysqlsampledatabase.sql'

Una vez que el conjunto de datos se copia en la instancia de Bastion EC2, el siguiente paso es SSH en la máquina remota y::

ssh -i ~/.ssh/new-key.pem ec2-user@<BASTION_PUBLIC_IP>

Después de conectarse, puede usar el cliente MySQL (ya instalado si usó mariadb105 En su configuración de EC2) para importar el archivo SQL en su base de datos RDS:

mysql -h <DATABASE_ENDPOINT> -P 3306 -u <DATABASE_USERNAME> -p < mysqlsampledatabase.sql 

Ingrese la contraseña cuando se le solicite.

Una vez que se completa la importación, puede conectarse a la base de datos RDS MySQL nuevamente para verificar que la base de datos y sus tablas se hayan creado con éxito.

Ejecute el siguiente comando desde el host Bastion:

mysql -h <DATABASE_ENDPOINT> -P 3306 -u <DATABASE_USERNAME> -p 

Después de ingresar su contraseña, puede enumerar las bases de datos y tablas disponibles:

Lista de bases de datos (imagen por autor)
Lista de tablas en las bases de datos (imagen por autor)

Para garantizar que el conjunto de datos se importara correctamente a la instancia de RDS, ejecuté una consulta simple:

Resultados de la consulta de la tabla de clientes (imagen del autor)

Esto devolvió una fila del customers tabla, confirmando que:

  • La base de datos y las tablas se crearon con éxito
  • El conjunto de datos de muestra se sembró en la instancia de RDS
  • La configuración de Bastion Host y Private RDS funcionan según lo previsto

Esto completa la configuración de infraestructura y el proceso de importación de datos.

Destruyendo la infraestructura

Una vez que haya terminado de probar o demostrar su configuración, es importante destruir los recursos de AWS para Evite los cargos innecesarios.

Dado que todo se aprovisionó con Terraform, derribar toda la infraestructura es tan simple como ejecutar un comando después de navegar a su directorio de configuración raíz:

cd terraform/rds-bastion
terraform destroy

Conclusión

En este proyecto, demostré cómo provocar una infraestructura de base de datos segura y similar a la producción utilizando Terraformado en AWS. En lugar de exponer la base de datos a Internet público, implementé las mejores prácticas colocando la instancia de RDS en subredes privadasaccesible solo a través de un anfitrión de Bastion En una subred pública.

Estructurando el proyecto con Configuraciones modulares de TerraformAseguré cada componente (Neta, Base de datos y Host de Bastion) fue acoplado librementereutilizable y fácil de administrar. También exhibí cómo la Terraform es interna gráfico de dependencia Maneja la orquestación y secuenciación de la creación de recursos sin problemas.

Gracias a la infraestructura como código (IAC), todo el entorno puede ser criado o derribado con un solo comando, lo que lo hace ideal para Prototipos ETL, práctica de ingeniería de datoso tuberías de prueba de concepto. Lo más importante, esta automatización ayuda a evitar costos inesperados al permitirle destruir todos los recursos de manera limpia una vez que haya terminado.

Puede encontrar el código fuente completo, la configuración de Terraform y la configuración de scripts en mi repositorio de GitHub:

https://github.com/yagmurgulec/rds-ec2-terraform.git

Siéntase libre de explorar el código, clonar el repositorio y adaptarlo a sus propios proyectos de AWS. ¡Las contribuciones, los comentarios y las estrellas siempre son bienvenidas!

¿Qué sigue?

Puede extender esta configuración por:

  • Conectando un trabajo de pegamento AWS a la instancia de RDS para el procesamiento de ETL.
  • Agregar monitoreo para su base de datos RDS e instancia de EC2

Referencias