Hablemos de S3 – parte 2

Hello word 🙂
En el artículo anterior sobre Amazon S3, exploramos de manera teórica muchas de las funcionalidades que ofrece S3. Vimos ejemplos de casos de uso y analizamos por qué S3 es una pieza fundamental en tantas arquitecturas basadas en AWS.
En esta nueva entrada, vamos a dar un paso más y pasar a la práctica. Crearemos 3 bucket desde cero y configuraremos algunas de sus funcionalidades más destacadas, como:
-
Reglas de ciclo de vida (Lifecycle Rules)
-
Reglas de replicación (Replication Rules)
-
Versionado de objetos
-
Server Access Logging
- Event notifications usando un tema de SNS
Todo esto lo haremos utilizando Terraform, lo que nos permitirá replicar fácilmente el ejemplo en diferentes entornos, en esta demo vamos a crear un total de tres buckets:
-
source– Será el bucket principal, donde habilitaremos las funciones destacadas anteriormente. destination– Aquí se replicará automáticamente todo el contenido del bucketsource.-
log_bucket– Este bucket almacenará los logs de acceso del bucketsource.
Requisitos
- Tener una cuenta de AWS.
- Tener instalado Terraform.
El proyecto estará organizado en una única carpeta, dentro de la cual definiremos todos los archivos de Terraform necesarios para crear los recursos.
1- Primero necesitamos exportar nuestras credenciales de AWS en nuestra consola local;
-
export AWS_ACCESS_KEY_ID="XXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="XXXXXXXXXX"- Comprobamos que estemos conectados de forma correcta
aws sts get-caller-identity
2- En el archivo 0-provider.tf, definiremos los proveedores que utilizaremos en este proyecto.
terraform { required_providers { aws = { source = "hashicorp/aws" } }}provider "aws" { region = local.region default_tags { tags = local.tags }}3- En el archivo 01-locals.tf, definiremos las variables locales del proyecto. Estas variables ayudan a centralizar valores que se utilizarán en múltiples recursos, simplificando la configuración y facilitando el mantenimiento del código.
locals { region = var.region bucket_source = "${var.environment}-${var.region}-${var.project_name}-source" bucket_log = "${var.environment}-${var.region}-${var.project_name}-log" bucket_destination = "${var.environment}-${var.region}-${var.project_name}-destination" tags = var.tags common_tags = { Environment = var.environment Terraform = "true" }}resource "aws_s3_bucket" "source_bucket" { bucket = local.bucket_source tags = local.tags}resource "aws_s3_bucket" "destination_bucket" { bucket = local.bucket_destination tags = local.tags}resource "aws_s3_bucket" "log_bucket" { bucket = local.bucket_log tags = local.tags}resource "aws_s3_bucket_versioning" "source_bucket_versioning" { bucket = aws_s3_bucket.source_bucket.id versioning_configuration { status = "Enabled" }}resource "aws_s3_bucket_versioning" "destination_bucket_versioning" { bucket = aws_s3_bucket.destination_bucket.id versioning_configuration { status = "Enabled" }}resource "aws_s3_bucket_logging" "bucket_logging" { bucket = aws_s3_bucket.source_bucket.id target_bucket = aws_s3_bucket.log_bucket.id target_prefix = "log/"}resource "aws_s3_bucket_policy" "log_bucket_policy" { bucket = aws_s3_bucket.log_bucket.id policy = jsonencode({ Version = "2012-10-17", Statement = [ { Sid = "AllowS3Logging", Effect = "Allow", Principal = { Service = "logging.s3.amazonaws.com" }, Action = [ "s3:PutObject" ], Resource = "${aws_s3_bucket.log_bucket.arn}/log/*" # El Resource debe coincidir con el target_prefix que definiste en aws_s3_bucket_logging (ej. "log/*"). } ] })}resource "aws_s3_bucket_lifecycle_configuration" "source_lifecycle" { bucket = aws_s3_bucket.source_bucket.id rule { id = "rule-1" filter { prefix = "" } status = "Enabled" transition { days = 30 storage_class = "STANDARD_IA" } expiration { days = 365 } }}resource "aws_s3_bucket_notification" "source_notification" { bucket = aws_s3_bucket.source_bucket.id depends_on = [aws_sns_topic_policy.allow_s3_publish] topic { topic_arn = aws_sns_topic.topic_sns_s3.arn events = ["s3:ObjectCreated:*", "s3:ObjectRemoved:*", "s3:Replication:*", "s3:ObjectRestore:*", "s3:LifecycleExpiration:*"] filter_suffix = "" }}resource "aws_s3_bucket_replication_configuration" "source_replication" { depends_on = [aws_s3_bucket_versioning.source_bucket_versioning] role = aws_iam_role.replication.arn bucket = aws_s3_bucket.source_bucket.id rule { id = "examplerule" status = "Enabled" delete_marker_replication { status = "Disabled" } filter { prefix = "" # Si querés que se repliquen todos los objetos, simplemente usá un filtro vacío } destination { bucket = aws_s3_bucket.destination_bucket.arn storage_class = "STANDARD" } }}5- En el archivo 03-iam.tf, definiremos todos los elementos relacionados con la gestión de identidad y acceso (IAM) en el proyecto. Esto incluirá la creación de roles específicos, la definición de políticas de permisos, y la asociación de estas políticas a los roles correspondientes.
resource "aws_iam_role" "replication" { name = "tf-iam-role-replication-12345" assume_role_policy = data.aws_iam_policy_document.assume_role.json}resource "aws_iam_policy" "replication" { name = "tf-iam-role-policy-replication-12345" policy = data.aws_iam_policy_document.replication.json}resource "aws_iam_role_policy_attachment" "replication" { role = aws_iam_role.replication.name policy_arn = aws_iam_policy.replication.arn}6- En el archivo 04-sns.tf, definiremos todo lo necesario para crear el tema de SNS, que será utilizado por el bucket source para enviar notificaciones sobre los eventos que ocurran dentro de él. De esta forma, podremos monitorear y reaccionar fácilmente ante cualquier cambio o actividad en el bucket.
# Aqui estamos creado el topic SNSresource "aws_sns_topic" "topic_sns_s3" { name = "topic-sns-s3" display_name = "Test From SNS"}resource "aws_sns_topic_subscription" "user_updates_mail_target" { topic_arn = aws_sns_topic.topic_sns_s3.arn protocol = "email" endpoint = "tucorreo@gmail.com"}resource "aws_sns_topic_policy" "allow_s3_publish" { arn = aws_sns_topic.topic_sns_s3.arn policy = data.aws_iam_policy_document.topic_policy.json}7- En el archivo 05-variables.tf, definiremos todas las variables que necesitaremos para parametrizar el proyecto. Estas variables permitirán una mayor flexibilidad y reutilización del código, facilitando la personalización de los recursos y haciendo que el despliegue sea más adaptable a diferentes entornos y requisitos específicos.
variable "region" { description = "Region" type = string default = ""}variable "environment" { description = "Environment Name" type = string default = "Dev"}variable "project_name" { type = string description = "Name of the project, will be used as prefix in resources names"}variable "tags" { description = "A map of tags that get added to all resources" type = map(string) default = {}}8- En el archivo 06-demo-eks.auto.tfvars, definiremos los valores de las variables según el entorno en el que estemos trabajando, lo que nos permite adaptar fácilmente la configuración del proyecto a distintos ambientes como desarrollo, pruebas o producción.
region = "us-west-1"environment = "poc"project_name = "s3-demo"tags = { Country = "UY" Region = "AMERICA"}10- En el archivo 07-output.tf, definiremos las salidas que Terraform nos mostrará tras ejecutar terraform apply. Estas salidas incluirán detalles de los recursos creados, que nos ayudarán a verificar rápidamente el estado del despliegue y a acceder fácilmente a los recursos configurados en el proyecto
output "source_bucket_id" { description = "ID of the source S3 bucket" value = aws_s3_bucket.source_bucket.bucket}output "destination_bucket_id" { description = "ID of the destination S3 bucket" value = aws_s3_bucket.destination_bucket.id}output "log_bucket_id" { description = "ID of the log S3 bucket" value = aws_s3_bucket.log_bucket.id}11- En el archivo 08-data.tf, definiremos todos los data sources utilizados en el proyecto, los cuales nos permiten obtener información existente de AWS para integrarla dinámicamente en la creación de nuestros recursos.
data "aws_iam_policy_document" "assume_role" { statement { effect = "Allow" principals { type = "Service" identifiers = ["s3.amazonaws.com"] } actions = ["sts:AssumeRole"] }}data "aws_iam_policy_document" "replication" { statement { effect = "Allow" actions = [ "s3:GetReplicationConfiguration", "s3:ListBucket" ] resources = [ aws_s3_bucket.source_bucket.arn ] } statement { effect = "Allow" actions = [ "s3:GetObjectVersion", "s3:GetObjectVersionAcl", "s3:GetObjectVersionTagging", "s3:GetObjectLegalHold", "s3:GetObjectRetention", "s3:GetObject", "s3:GetObjectVersionForReplication" ] resources = [ "${aws_s3_bucket.source_bucket.arn}/*" ] } statement { effect = "Allow" actions = [ "s3:ReplicateObject", "s3:ReplicateDelete", "s3:ReplicateTags", "s3:PutObject" ] resources = [ "${aws_s3_bucket.destination_bucket.arn}/*" ] }}data "aws_iam_policy_document" "topic_policy" { statement { effect = "Allow" principals { type = "Service" identifiers = ["s3.amazonaws.com"] } actions = ["SNS:Publish"] resources = [aws_sns_topic.topic_sns_s3.arn] condition { test = "ArnLike" variable = "aws:SourceArn" values = [aws_s3_bucket.source_bucket.arn] } }}Con todos los archivos configurados, ya estamos listos para ejecutar Terraform y desplegar la infraestructura.
A continuación veremos algunas de las funcionalidades habilitadas y recursos creados.
- Versionado
- Notificación de eventos.
- Reglas de ciclo de vida y replicación.
- Bucket que configuramos para guardar los logs de S3.
- Tema SNS generado.
- Ejemplo de los correos recibidos al crear o eliminar objetos en el bucket, lo que demuestra cómo las notificaciones de S3 pueden alertarnos de forma inmediata sobre cualquier cambio realizado en el almacenamiento.
Terraform destroy
- Como siempre, al finalizar tus pruebas, recuerda eliminar todos los recursos creados ejecutando
terraform destroy --auto-approve. Este paso es crucial para evitar sorpresas desagradables en tu factura a final del mes. No subestimes la importancia de esta tarea, ya que la acumulación de recursos no utilizados puede generar costos innecesarios. Tómate el tiempo para limpiar tu entorno, eso te ahorrará dolores de cabeza en el futuro 🙂
Conclusiones
En esta demo pudimos ver algunas de las potentes funcionalidades que ofrece Amazon S3, demostrando su gran flexibilidad y versatilidad dentro del ecosistema de AWS. Vimos cómo es posible replicar la información de un bucket principal hacia otro y recibir notificaciones automáticas de los eventos que ocurren en el bucket de origen, lo que evidencia el enorme potencial de S3 para integrarse en arquitecturas complejas y automatizadas.
Una vez más, Terraform nos permitió implementar todos estos recursos de forma rápida, ordenada y fácilmente replicable en distintos entornos.
Como recordatorio importante, el nombre de cada bucket debe ser único a nivel global en AWS, no puede existir otro bucket con el mismo nombre en ninguna región, ni cuenta de AWS.
¡Nos vemos en la próxima entrada!
Nota: Todo el código lo pueden encontrar en el siguiente repositorio.














