Skip to content

Commit

Permalink
feature: código terraform de infraestructura y prototipo de función l…
Browse files Browse the repository at this point in the history
…ambda.
  • Loading branch information
guillevalin committed Nov 10, 2024
1 parent 74ed2be commit 7fe7c80
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 111 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Deploy Lambda Docker Function

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Check out the code
uses: actions/checkout@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Log in to Amazon ECR
uses: aws-actions/amazon-ecr-login@v1

- name: Build and push Docker image
env:
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPOSITORY }} # Define el repositorio de ECR en los secretos de GitHub
AWS_REGION: us-east-1
LAMBDA_IMAGE_TAG: "latest"
run: |
docker build -t $ECR_REPOSITORY:$LAMBDA_IMAGE_TAG .
docker tag $ECR_REPOSITORY:$LAMBDA_IMAGE_TAG $ECR_REPOSITORY:$LAMBDA_IMAGE_TAG
docker push $ECR_REPOSITORY:$LAMBDA_IMAGE_TAG
- name: Deploy to AWS Lambda
env:
LAMBDA_FUNCTION_NAME: "docker_lambda_function" # Nombre de tu función Lambda
ECR_REPOSITORY_URI: "${{ secrets.AWS_ECR_REPOSITORY }}:${{ LAMBDA_IMAGE_TAG }}"
run: |
aws lambda update-function-code \
--function-name $LAMBDA_FUNCTION_NAME \
--image-uri $ECR_REPOSITORY_URI
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
.terraform
.terraform.lock.hcl
terraform.tfstate
terraform.tfstate.backup
.terraform.tfstate.lock.info
65 changes: 57 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,64 @@
# Parte 1: Infraestructura e IaC

## Diagrama Infraestructura
- Base de Datos: Se utilizará una base de datos relacional dado que es más compatible para procesar analítica avanzada y conectarla en un futuro con alguna solución de Data Analytics como Google BigQuery, Amazon Redshift o Snowflake, que es parte del requerimiento. Dado que en este caso estamos proponiendo una arquitectura en AWS, la tecnología escogida será Aurora PostgreSQL.
- Tecnología Pub/Sub: Existen diversas tecnologías para lograr este propósito, en particular por experiencia personal he utilizado SNS/SQS, RabbitMQ y Redis, sintiendo que todas en general cumplen el propósito y la elección en particular tiene que ir por decisiones previas (si ya se cuenta con una tecnología para Pub/Sub utilizada es mejor mantenerla para evitar fragmentación de tecnologías) o features específicos de una cola de mensajería (como AMQP). Dado que estamos proponiendo una solución simple administrada por AWS, escogeremos SNS/SQS para este ejemplo.
- Endpoint HTTP para servir datos almacenados: Soy muy fanático de armar arquitecturas de microservicios basadas en contenedores orquestadas por Kubernetes, utilizando las opciones administradas en la nube como Google Cloud GKE o Amazon EKS, si bien la curva de aprendizaje no es menor, permiten mucha flexibilidad y escalabilidad, y con un buen pipeline de CI/CD se pueden abstraer lo suficiente de los desarrolladores para que solo se centren en escribir código y no preocuparse por la infraestructura. En este caso sin embargo, por simplicidad escogeremos implementar funciones AWS Lambda para lograr el propósito, dado que por ejemplo, en un contexto de piloto, puede ser muy útil levantar una prueba de concepto de bajo costo antes de hacer un deploy más altamente disponible y enterprise grade.
## Identificación Infraestructura
- **Base de Datos**: Se utilizará una base de datos relacional dado que es más compatible para procesar analítica avanzada y conectarla en un futuro con alguna solución de Data Analytics como Google BigQuery, Amazon Redshift o Snowflake, que es parte del requerimiento. Dado que en este caso estamos proponiendo una arquitectura en AWS, la tecnología escogida será Aurora PostgreSQL.
- **Tecnología Pub/Sub**: Existen diversas tecnologías para lograr este propósito, en particular por experiencia personal he utilizado SNS/SQS, RabbitMQ y Redis, sintiendo que todas en general cumplen el propósito y la elección en particular tiene que ir por decisiones previas (si ya se cuenta con una tecnología para Pub/Sub utilizada es mejor mantenerla para evitar fragmentación de tecnologías) o features específicos de una cola de mensajería (como AMQP). Dado que estamos proponiendo una solución simple administrada por AWS, escogeremos SNS/SQS para este ejemplo.
- **Endpoint HTTP para servir datos almacenados**: En general soy más de la idea de utilizar de microservicios basadas en contenedores orquestadas por Kubernetes, utilizando las opciones administradas en la nube como Google Cloud GKE o Amazon EKS, si bien la curva de aprendizaje no es menor, permiten mucha flexibilidad y escalabilidad, y con un buen pipeline de CI/CD se pueden abstraer lo suficiente de los desarrolladores para que solo se centren en escribir código y no preocuparse por la infraestructura. En este caso sin embargo, por simplicidad escogeremos implementar funciones AWS Lambda para lograr el propósito, dado que por ejemplo, en un contexto de piloto, puede ser muy útil levantar una prueba de concepto de bajo costo antes de hacer un deploy más altamente disponible y enterprise grade.

Finalmente, esto se ve representado en el siguiente diagrama de arquitectura de la solución:
![Diagrama](diagrama-infra.png)

## Código Terraform
DISCLAIMER: Los códigos Terraform fueron generados mediante ChatGPT.
## Despliegue de infraestructura mediante código Terraform
### Disclaimers
- Cómo correr los scripts terraform son parte del [quick start guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-build).
- Los códigos Terraform fueron generados mediante ChatGPT, sin embargo, se probaron contra una cuenta AWS y editados para corregir errores o detalles durante la generación de las propuestas de scripts (ej: El script de creación de VPC utilizaba un argumento deprecado en la creación del NAT Gateway).
- La contraseña de RDS está definida en variables.tf lo que es una mala práctica. Se realizó por simplicidad pero se recomendaría utilizar un almacenamiento de secretos como AWS Secrets Manager.

- VPC: Este código despliega una VPC con dos subnets privadas y dos públicas, en las zonas 1A y 1B de Virginia del Norte (us-east-1).
- SNS/SQS: Se utilizará SNS/SQS de AWS como tecnología para tener una arquitectura Pub/Sub. Existen otras tecnologías como Apache Kafka, RabbitMQ o Redis
### Descripción de los scripts:
- Configuración Base (main.tf / variables.tf): Contiene definiciones generales como la zona AWS del despliegue y variables a utilizar.
- VPC (vpc.tf): Este código despliega una VPC con dos subnets privadas y dos públicas, en las zonas 1A y 1B de Virginia del Norte (us-east-1).
- RDS (rds.tf): Se levantará un servidor de Aurora PostgreSQL Serverless para almacenamiento de datos.
- SNS/SQS (pubsub.tf): Se utilizará SNS/SQS de AWS como tecnología para tener una arquitectura Pub/Sub. Existen otras tecnologías como Apache Kafka, RabbitMQ o Redis.
- Lambda (lambda.tf): Levanta una función AWS Lambda a partir de una imagen Docker compatible.

# Parte 2: Aplicaciones y flujo CI/CD

## API HTTP
- Se creó un handler simple de Lambda que expone dos endpoints. Un endpoint GET el que consultará la base de datos y devolverá las rows de una tabla en formato JSON

## Deploy
- Se creó workflow de Github Actions que hace deploy de la función Lambda a una cuenta AWS cuyos parámetros se definen en secrets de Github.

## Ingesta a BBDD
- Se creó método POST que ingesta directamente a la base de datos.

## Diagrama de arquitectura.
- El diagrama general fue explicado en la parte 1 de este README.

# Parte 3: Pruebas de Integración

## Implementar flujo de CI/CD que verifique que la API opera correctamente.

## Proponer otras pruebas de integración.

## Identificar puntos críticos del sistema

## Cómo robustecer la solución

# Parte 4: Métricas y Monitoreo

## Proponer 3 métricas para entender la salud y rendimiento.

## Proponer una herramienta de observabilidad

## Describe como sería la implementación de esta heramienta en la nube

## Escalabilidad de la solución propuesta

## Dificultades o limitaciones de la observabilidad

# Parte 5: Alertas y SRE

## Reglas y/o umbrales

## SLI y SLO.
11 changes: 11 additions & 0 deletions aplicacion/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Usa la imagen base de Lambda para Node.js
FROM public.ecr.aws/lambda/nodejs:20

# Copia el archivo de código y dependencias
COPY package*.json ./
RUN npm install

COPY index.js ./

# Define el punto de entrada para la función Lambda
CMD ["index.handler"]
62 changes: 62 additions & 0 deletions aplicacion/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const { Pool } = require('pg');

// Parámetros de configuración de la base de datos
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: 5432
};

exports.handler = async (event) => {
const client = new Pool(dbConfig);

try {
await client.connect();

if (event.httpMethod === "GET") {
const res = await client.query('SELECT * FROM users;');
await client.end();

return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(res.rows)
};
} else if (event.httpMethod === "POST") {
const body = JSON.parse(event.body);
const { name, email } = body;

if (!name || !email) {
return {
statusCode: 400,
body: JSON.stringify({ message: "Los campos 'name' y 'email' son obligatorios" })
};
}

const insertQuery = 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *';
const res = await client.query(insertQuery, [name, email]);
await client.end();

return {
statusCode: 201,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "Usuario insertado correctamente", user: res.rows[0] })
};
} else {
return {
statusCode: 405,
headers: { "Allow": "GET, POST" },
body: JSON.stringify({ message: "El método utilizado no está permitido." })
};
}
} catch (error) {
console.error("Ha ocurrido un error al procesar la request:", error);

return {
statusCode: 500,
body: JSON.stringify({ message: "Ha ocurrido un error inesperado, por favor intentarlo nuevamente." })
};
}
};
File renamed without changes.
File renamed without changes.
51 changes: 51 additions & 0 deletions infraestructura/lambda.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Crear el rol IAM para Lambda con permisos básicos
resource "aws_iam_role" "lambda_execution_role" {
name = "lambda_execution_role"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}

# Adjuntar una política administrada de AWS que otorga permisos básicos de ejecución a Lambda
resource "aws_iam_role_policy_attachment" "lambda_execution_policy" {
role = aws_iam_role.lambda_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

# Crear la función Lambda con la imagen Docker
resource "aws_lambda_function" "docker_lambda" {
function_name = "docker_lambda_function"
role = aws_iam_role.lambda_execution_role.arn
image_uri = var.docker_image
package_type = "Image"
environment {
variables = {
EXAMPLE_VAR = "example_value"
}
}
vpc_config {
subnet_ids = [aws_subnet.private_subnet_1.id, aws_subnet.private_subnet_2.id]
security_group_ids = [aws_security_group.lambda_security_group.id]
}
tags = {
Name = "docker-lambda"
}
}

# Configuración opcional de permisos de invocación para otros servicios
resource "aws_lambda_permission" "allow_invoke" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.docker_lambda.function_name
principal = "apigateway.amazonaws.com"
}
39 changes: 39 additions & 0 deletions infraestructura/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
provider "aws" {
region = "us-east-1"
}

# Grupo de seguridad para la función Lambda que permitirá el acceso a la base de datos
resource "aws_security_group" "lambda_security_group" {
vpc_id = aws_vpc.main_vpc.id
name = "lambda_security_group"

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

# Crear un grupo de seguridad para la base de datos
resource "aws_security_group" "db_security_group" {
vpc_id = aws_vpc.main_vpc.id
name = "db_security_group"
description = "Permitir acceso a la base de datos desde la red permitida"

# Regla de entrada
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

# Regla de salida
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
57 changes: 57 additions & 0 deletions infraestructura/pubsub.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Crear el tema SNS FIFO
resource "aws_sns_topic" "devops_challenge_sns_fifo" {
name = "devops-challenge-topic.fifo"
fifo_topic = true
content_based_deduplication = true

tags = {
Name = "devops-challenge-sns-topic"
}
}

# Crear la cola SQS FIFO
resource "aws_sqs_queue" "devops_challenge_sqs_fifo" {
name = "devops-challenge-queue.fifo"
fifo_queue = true
content_based_deduplication = true
message_retention_seconds = 86400 # Opcional: Ajusta el tiempo de retención según sea necesario (24 horas)

tags = {
Name = "devops-challenge-sqs-queue"
}
}

# Suscripción de la cola SQS al tema SNS
resource "aws_sns_topic_subscription" "sns_to_sqs_subscription" {
topic_arn = aws_sns_topic.devops_challenge_sns_fifo.arn
protocol = "sqs"
endpoint = aws_sqs_queue.devops_challenge_sqs_fifo.arn

# Configura los atributos para mensajes FIFO
raw_message_delivery = true

# Establece permisos para que SNS pueda enviar mensajes a SQS
depends_on = [aws_sqs_queue_policy.allow_sns_to_sqs]
}

# Política de permisos para que SNS envíe mensajes a la cola SQS
resource "aws_sqs_queue_policy" "allow_sns_to_sqs" {
queue_url = aws_sqs_queue.devops_challenge_sqs_fifo.id

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = { Service = "sns.amazonaws.com" },
Action = "sqs:SendMessage",
Resource = aws_sqs_queue.devops_challenge_sqs_fifo.arn,
Condition = {
ArnEquals = {
"aws:SourceArn" = aws_sns_topic.devops_challenge_sns_fifo.arn
}
}
}
]
})
}
Loading

0 comments on commit 7fe7c80

Please sign in to comment.