Bluetab

Empoderando a las decisiones en diversos sectores con árboles de decisión en AWS

Jeanpierre Nuñez
Senior Data Engineer

En este artículo, te revelaremos cómo en Bluetab utilizamos esta poderosa herramienta de análisis de datos con el fin de impulsar a las decisiones de nuestros clientes en diversas industrias usando AWS.

Árboles de decisión: herramientas poderosas para la toma de decisiones

Los árboles de decisión son diagramas que representan las posibles opciones, resultados y consecuencias de una decisión. Se construyen a partir de nodos que contienen preguntas o condiciones, y ramas que conectan las respuestas o acciones. De esta forma, se puede visualizar el flujo lógico de la decisión y elegir la mejor alternativa.

En Bluetab, creemos en la toma de decisiones inteligentes basadas en datos. Para lograrlo, utilizamos herramientas poderosas como los árboles de decisión, que son valiosos en una variedad de sectores. A continuación, exploraremos cómo aplicamos los árboles de decisión y los beneficios que ofrecen a nuestros clientes en diferentes industrias.

Clasificación y regresión con árboles de decisión

Clasificación: los árboles de decisión de clasificación son excelentes para segmentar y categorizar datos. En el contexto empresarial, se utilizan para tomar decisiones sobre la asignación de recursos, la identificación de oportunidades de mercado y la personalización de ofertas. En el sector de la salud, los árboles de clasificación ayudan a diagnosticar enfermedades en función de síntomas y pruebas.

Regresión: los árboles de regresión se centran en predecir valores numéricos. Se utilizan para proyectar rendimientos financieros, tasas de crecimiento y otros valores cuantitativos. En el sector financiero, son esenciales para predecir el rendimiento de inversiones y carteras.

Caso práctico: Árboles de decisión en el sector de la salud

Sector de la salud: En el sector de la salud, utilizamos los árboles de decisión para diagnosticar enfermedades en función de síntomas y pruebas. Por ejemplo, si un paciente tiene fiebre, dolor de garganta y tos, el árbol de decisión nos indica que lo más probable es que tenga una infección respiratoria y nos recomienda el tratamiento adecuado.

Beneficios de los árboles de decisión en diferentes sectores

  • Toma de decisiones personalizadas: los árboles de decisión permiten decisiones precisas y personalizadas en función de los datos y las necesidades de cada sector.
  • Eficiencia operativa: al automatizar procesos repetitivos y mejorar la toma de decisiones, las empresas pueden optimizar sus operaciones.

Caso práctico: árboles de decisión en el sector bancario

Sector bancario: En el sector bancario, los árboles de decisión son vitales para la gestión de riesgos y la personalización de servicios. Los bancos utilizan estos árboles para segmentar a los clientes en grupos según su comportamiento financiero, lo que les permite ofrecer productos y servicios financieros a medida. También son esenciales en la evaluación crediticia, donde garantizan préstamos más seguros y tasas de interés adecuadas. Además, los árboles de decisión se aplican en la prevención de fraudes, donde detectan patrones sospechosos y protegen los activos de los clientes.

Beneficios en banca:

  • Gestión de riesgos: los árboles de decisión ayudan a evaluar y gestionar los riesgos crediticios, lo que es esencial en la industria bancaria.
  • Personalización de servicios: los clientes bancarios reciben ofertas personalizadas que se ajustan a sus necesidades financieras, lo que mejora su satisfacción y lealtad.
  • Prevención de fraudes: la detección temprana de transacciones fraudulentas protege a los clientes y a los bancos.

Decisiones Inteligentes en AWS: implementación de árboles de decisión y migración de datos

Introducción: 

En un mundo empresarial en constante evolución, la toma de decisiones informadas es clave para el éxito. Exploraremos ahora cómo la implementación de árboles de decisión en AWS y la migración de datos desde SQL Server pueden empoderar a las organizaciones en diferentes sectores. Analizaremos la arquitectura de soluciones en AWS que permite esta implementación y migración, detallando cada paso del proceso.

Arquitectura en AWS para Implementar árboles de decisión:

  • Ingesta de datos: comenzamos considerando la ingesta de datos desde diversas fuentes. AWS Glue se destaca como una solución versátil que puede conectarse a una amplia variedad de fuentes de datos.
  • Almacenamiento de datos: una vez que los datos se han ingestado, se almacenan de manera centralizada en Amazon S3. Esto proporciona un lugar único para la administración y acceso eficiente a los datos.
  • Procesamiento de datos y generación de árboles de decisión: utilizamos AWS Glue para transformar y preparar los datos para su análisis. En esta etapa, AWS Lambda y AWS SageMaker entran en juego para implementar algoritmos de árboles de decisión, brindando un enfoque avanzado de aprendizaje automático.
  • Análisis y consultas: una vez que se han generado los árboles de decisión, AWS Athena permite realizar consultas SQL interactivas en los datos almacenados en S3. Esto facilita la exploración de datos y la toma de decisiones basadas en los resultados de los árboles.

Migración de datos desde SQL Server a AWS:

  • Ingesta de datos: cuando se trata de la migración de datos desde SQL Server de Microsoft, AWS Database Migration Service (DMS) es una herramienta valiosa. Facilita la migración de bases de datos completas o datos específicos de SQL Server a bases de datos compatibles con AWS, como Amazon RDS o Amazon Redshift.

Arquitectura de solución en AWS utilizando AWS S3, Glue, Lambda, SageMaker, S3 y Athena:

  • Ingesta de datos: utilizamos AWS Database Migration Service (DMS) para transferir datos desde la fuente de datos SQL Server a una base de datos compatible con AWS.
  • Transformación de datos: AWS Glue se encarga de transformar y preparar los datos recién migrados para su análisis.
  • Generación de árboles de decisión: implementamos algoritmos de árboles de decisión utilizando AWS Lambda o AWS SageMaker para llevar a cabo análisis avanzados.
  • Almacenamiento de datos: los datos procesados y los resultados de los árboles de decisión se almacenan en Amazon S3.
  • Consultas y análisis: utilizamos AWS Athena para realizar consultas SQL interactivas en los datos almacenados en S3 y tomar decisiones basadas en los resultados.

Para la implementación de un arbol de clasificación vamos a utilizar los siguientes servicios: Amazon S3, Aws Glue Crawler, Aws Glue Job, IAM , Cloud Watch. 

Se seleccionan como mínimo estos servicios para poder mostrar los beneficios de este modelo.

Buscaremos ‘S3’ en la barra de busqueda y seleccionaremos el nombre del servicio para visualizar lo siguiente:

Daremos clic en ‘Crear Bucket’ y visualizaremos la siguiente pantalla:

Debemos colocar un nombre que se asocie a nuestro flujo de trabajo y seleccionar tambien la región como minimo.

Colocaran siguiente y podran observan una vista previa del bucket, luego dar en clic en crear bucket:

Para nuestro ejemplo práctico estamos seleccionando un csv publico llamado Iris. 

El conjunto de datos Iris contiene medidas de 150 flores (setosa, versicolor, virginica) con 4 características, usado comúnmente para clasificación.

Lógicamente para un modelo de ML deberiamos brindar una data ya transformada y trabajada con el propósito de tener las caracteristias y hacer las predicciones necesarias.

Daras clic en cargar y seleccionaras el csv:

Finalmente visualizaremos la carga con éxito y ya seremos capaces de obtener esta información desde otro servicios en AWS.

AWS Glue Crawler es una herramienta que descubre y cataloga automáticamente datos almacenados en diversos formatos y ubicaciones, facilitando la preparación y análisis de datos en AWS.

Buscaremos el servicio AWS Glue en la barra de busqueda, nos desplazaremos en la sección lateral izquierdo y daremos clic en Crawlers:

Daremos un nombre a nuestro Crawler y una descripción a elección personal, luego daremos clic en Next:

En el dropdown list de data source elegiremos el servicio de S3.

Luego colocaras Browse S3 y podras seleccionar el bucket creado previamente, deberías ser capaz de visualizar lo siguiente:

En la siguiente seccion podrás seleccionar algun rol si tienes configurado previamente, para este ejemplo crearemos un nuevol, le pondremos un nombre asociado a nuestro proceso.

Luego veremos la selección de nuestra base de datos Catalogo , sin embargo tendremos que crearlo de la siguiente manera dando clic en Add Database:

Ingresamores el nombre de la base de datos asociada a nuestro ejemplo:

Luego podremos agregar un prefijo de forma opcional cuando disponibilice los datos en el catalogo de AWS Glue, podría estar vacio también si no lo necesita. Seleccionaremos ejecución a demanda para nuestro caso.

Finalmente en la siguiente pestaña podremos visualizar creado nuestro crawler y le daremos clic en ‘Run crawler’. Deberiamos ver lo siguiente:

Luego en la misma seccion lateral izquierda de AWS Glue buscaremos la seccion de ETL Job y crearemos un flujo con la interfaz grafica, haciendolo de esta manera podremos tener facilmente el código generado para tener acceso a nuestra fuente.

Luego de agregas nuestro objeto de catálogo y seleccionar lo que hemos creado previamente. Deberiamos visualizar el job de la siguiente manera:

Hasta aquí ya procederemos a colocar un nombre al job y agregar nuestro código. Haremos uso de las siguientes librerias:

`DecisionTreeClassifier` construye un modelo de árbol de decisión para clasificación.

`MulticlassClassificationEvaluator` evalúa su rendimiento multiclase. `CrossValidator` realiza validación cruzada, ajustando hiperparámetros con `ParamGridBuilder`.

Juntos, permiten la construcción, evaluación y ajuste eficaz de modelos de árboles de clasificación en PySpark MLlib.

A continuación, se muestra el código utilizado:

import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from pyspark.ml.feature import StringIndexer, VectorAssembler
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml import Pipeline
from pyspark.sql.functions import col,udf
from pyspark.sql.window import Window
from pyspark.sql import functions as F
from awsglue.dynamicframe import DynamicFrame
from pyspark.sql.types import ArrayType, DoubleType

# Inicializar contexto de Spark y Glue
args = getResolvedOptions(sys.argv, ["JOB_NAME"])
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args["JOB_NAME"], args)
# Nombre de la base de datos y tabla en el catálogo de AWS Glue
database_name = "db-decision-tree-iris"
input_table_name = "mliris_csv"
output_table_name = "mliris_results"
# Crear DynamicFrame desde el catálogo de Glue
input_dyf = glueContext.create_dynamic_frame.from_catalog(
    database=database_name,
    table_name=input_table_name,
)
# Convertir DynamicFrame a DataFrame
df = input_dyf.toDF()
# Añadir una columna de índice
df = df.withColumn("row_index", F.monotonically_increasing_id())
# Preprocessing: StringIndexer for categorical labels
stringIndexer  = StringIndexer(inputCol="species", outputCol="label")
# Preprocessing: VectorAssembler for feature columns
assembler = VectorAssembler(inputCols=["sepal_length", "sepal_width", "petal_length", "petal_width"], outputCol="features")
#data = assembler.transform(data)
# Split data into training and testing sets
train_data, test_data = df.randomSplit([0.8, 0.2], seed=42)
# Create a Decision Tree Classifier instance
dt = DecisionTreeClassifier(labelCol='label', featuresCol='features')
# Assemble all the steps (indexing, assembling, and model building) into a pipeline.
pipeline = Pipeline(stages=[stringIndexer, assembler, dt])
paramGrid = ParamGridBuilder() \
    .addGrid(dt.maxDepth,[3, 5, 7]) \
    .addGrid(dt.minInstancesPerNode, [1,3,5]) \
    .build()
crossval = CrossValidator(estimator=pipeline, estimatorParamMaps=paramGrid,
                      evaluator=MulticlassClassificationEvaluator(
                      labelCol='label', predictionCol='prediction', metricName='accuracy'),
                      numFolds=5)

cvModel = crossval.fit(train_data)
best_model = cvModel.bestModel
predictions = best_model.transform(test_data)
predictions.show(100,truncate=False)


# Evaluate the model performance
evaluator = MulticlassClassificationEvaluator(labelCol="label", predictionCol="prediction", metricName="accuracy")
accuracy = evaluator.evaluate(predictions)
print(f"Test Accuracy: {accuracy:.2f}")
precision = evaluator.evaluate(predictions, {evaluator.metricName: "weightedPrecision"})
print(f"Weighted Precision: {precision:.2f}")
recall = evaluator.evaluate(predictions, {evaluator.metricName: "weightedRecall"})
print(f"Weighted Recall: {recall:.2f}")
f1_score = evaluator.evaluate(predictions, {evaluator.metricName: "f1"})
print(f"F1 Score: {f1_score:.2f}")
# Acceder al modelo de árbol de decisión dentro del pipeline
tree_model = best_model.stages[-1]  # Suponiendo que el clasificador de árbol de decisión es el último paso en tu pipeline
# Obtener el resumen del modelo
print(tree_model._java_obj.toDebugString())
output_path = "s3://bucket-training-tree-decision/output/parquet_data/"
# Función UDF para convertir VectorUDT a lista
vector_to_list_udf = udf(lambda v: v.toArray().tolist(), returnType=ArrayType(DoubleType()))
# Convertir la columna probability a lista
df = predictions.withColumn("probability", vector_to_list_udf(col("probability")))
df = df.withColumn("rawPrediction", vector_to_list_udf(col("rawPrediction")))
df = df.withColumn("features", vector_to_list_udf(col("features")))
df_subset = df.select(*["sepal_length", "sepal_width", "petal_length", "petal_width", "species", "row_index", "label", "features", "prediction", "probability"])
# Escribir el DataFrame en formato Parquet en S3
df_subset.write.parquet(output_path, mode="overwrite")
# Finalizar trabajo
job.commit()


Como se visualiza el estado del job se encuentra en ‘Succeeded’:

A continuación, debemos también configurar las políticas de IAM para el acceso desde Glue hacia S3. Iremos a la sección de permisos en nuestro bucket:

Editaremos la política de la siguiente manera:

Luego en IAM a nuestro rol de machine que creamos en la sección de AWS Grawler, añadiremos el rol de s3Full Access. Seleccionamos nuestro rol:

Verificamos que tengamos nuestra relacion de confianza de la siguiente manera:

Luego añadiremos el permiso de AmazonS3FullAccess.

Finalmente al agregar el permiso deberiamos de ver nuestros permisos de la siguiente manera:

Ahora mediante CloudWatch podremos visualizar el log de nuestro job, verificar si se guardo correctamente nuestro modelo y tambien poder ver una previsualización de las reglas y predicciones.

Aquí podemos visualizar nuestros resultados e incluso la regla de clasificación:

Aquí se puede visualizar los valores de Accuracy , weighted precision y recall, y el F1 Score. A continuación, se explica el significado de cada métrica.

  1. Test Accuracy (Exactitud de Prueba):
    • Significado: proporción de predicciones correctas respecto al total de predicciones en el conjunto de prueba.
    • Utilidad: mide la precisión general del modelo y es especialmente útil cuando las clases están balanceadas.
  2. Weighted Recall (Recuperación Ponderada):
    • Significado: promedio ponderado de las tasas de verdaderos positivos para cada clase.
    • Utilidad: evalúa la capacidad del modelo para recuperar instancias de cada clase, considerando el desequilibrio en la distribución de las clases.
  3. Weighted Precision (Precisión Ponderada):
    • Significado: promedio ponderado de las precisiones para cada clase.
    • Utilidad: indica la proporción de instancias correctamente clasificadas entre las que el modelo predijo como positivas, considerando el desequilibrio de clases
  4. F1 Score:
    • Significado: media armónica de precisión y recuperación. Cuantifica la relación equilibrada entre la precisión y la capacidad de recuperación del modelo.
    • Utilidad: útil en problemas de clasificación desbalanceados, ya que considera tanto los falsos positivos como los falsos negativos, proporcionando una métrica global de rendimiento del modelo. Un F1 Score alto indica un equilibrio entre precisión y recuperación.

Finalmente se guardaron los resultados de nuestro modelo en nuestro bucket en un folder llamado output, deberia verse de la siguiente manera:

Esta arquitectura de solución en AWS permite a las organizaciones no solo implementar árboles de decisión eficaces sino también migrar datos desde sistemas heredados como SQL Server. Ambos procesos se combinan para empoderar a las organizaciones en la toma de decisiones informadas, mejorando la eficiencia operativa y maximizando las oportunidades de mercado. Como puedes ver, los árboles de decisión son una herramienta muy útil para tomar decisiones inteligentes basadas en datos.

En Bluetab, te ayudamos a implementarlos en tu organización para que puedas mejorar tu eficiencia operativa, personalizar tus ofertas y aprovechar las oportunidades de mercado. Si quieres saber más sobre cómo podemos ayudarte, no dudes en contactarnos.

FinOps

FinOps

La Ingeniería del Ahorro en la Era de la Nube y como aplicarlo funcional y técnicamente

Francis Josue De La Cruz

Big Data Architect

Wiener Morán

Data Engineer

En el mundo empresarial actual, la eficiencia en la gestión de costos en la nube se ha convertido en una prioridad. Aquí es donde entra en juego FinOps (Financial Operations), una práctica emergente que combina estratégicas presupuestales, la tecnología de la información (servicios en la nube), estrategias de negocios para maximizar el valor de la nube y sobretodo la inventiva y desarrollo para tratar de sacar provecho económico. En este artículo queremos compartir desde Bluetab, cómo nuestros clientes pueden implementar FinOps, con un enfoque particular en los servicios de Azure de Microsoft.

Estos servicios en la nube brindan una poderosa flexibilidad en la construcción de soluciones y hacen realidad la meta común de pasar del CAPEX al OPEX. Esto se enfrenta al nuevo desafío que las empresas ahora tienen, de monitorear cientos o miles de recursos a la vez para mantener las finanzas alineadas a los presupuestos tecnológicos definidos. Una buena (y necesaria) práctica para esto, es el monitoreo activo de los recursos y costos cloud, lo que permite identificar oportunidades de optimización de recursos no utilizados o infrautilizados, para aplicar los cambios de configuración, prendido y apagado oportuno que pueden conllevar a una significativa reducción de costos.

¿Qué es FinOps?

FinOps es un enfoque, paradigma cultural y operativo que permite a las organizaciones equilibrar y alinear mejor los costos con el valor en entornos de nube. Se trata de una práctica colaborativa, que involucra a equipos de finanzas, operaciones y desarrollo, para gestionar los costos de la nube de manera más efectiva y eficiente.

¿En qué etapa del proyecto se debe considerar el FinOps?

El FinOps, o la gestión financiera de la nube, se debe considerar desde las primeras etapas de un proyecto que involucra servicios en la nube, ya que ayuda a entender y optimizar los costos asociados.

No siempre demanda tener un equipo dedicado desde el inicio, especialmente en proyectos pequeños o medianos, pero sí requiere que alguien asuma la responsabilidad de monitorear y optimizar los costos. A medida que el proyecto crece y el gasto en la nube se incrementa, puede ser beneficioso establecer un equipo dedicado o función de FinOps para gestionar estos costos de manera más efectiva.

Pasos para Implementar FinOps en una empresa usando Servicios de Azure

1. Comprensión y adopción de la cultura FinOps

Educación y Conciencia: capacite a su equipo sobre los principios de FinOps. Esto incluye entender cómo el gasto en la nube afecta a la empresa y cómo pueden contribuir a una gestión más eficiente.

Combinando la Agilidad (Scrum-finOps) en la empresa

Es posible combinar el framework scrum con el enfoque FinOps agregando nuevas ceremonias enfocadas en el mismo, con la finalidad de implementar nuevas prácticas de comunicación y gestión. Por ejemplo, reuniones semanales donde se presentan como va el presupuesto del proyecto y reuniones diarias de cómo va el consumo de los servicios en los diferentes ambientes (development, quality assurance y production).

Colaboración interdepartamental: fomente la colaboración entre los equipos de finanzas, operaciones y desarrollo. La comunicación efectiva es clave para el éxito de FinOps.

2. Análisis del gasto actual en Azure

Auditoría de servicios: realice un inventario de todos los servicios de Azure utilizados. Comprenda cómo se están utilizando y si son esenciales para las operaciones comerciales.

Herramientas de gestión de costos: utilice herramientas como Azure Cost Management y Billing para obtener una visión clara del gasto.

3. Optimización de recursos y costos

Identificación de recursos subutilizados: busque instancias, almacenamiento o servicios que estén infrautilizados. Azure ofrece herramientas para identificar estos recursos.

Implementación de mejores prácticas: aplique prácticas recomendadas para la optimización de recursos, como el escalado automático, la elección de instancias reservadas o el apagado de recursos no utilizados.

3. Optimización de recursos y costos

Establecimiento de presupuestos: defina presupuestos claros para diferentes equipos o proyectos. Azure Cost Management puede ayudar en este proceso.

Pronósticos de gasto: antes de empezar el desarrollo de ahorro de costos en una empresa, se debe tener claro el uso y gasto actual, para estimar el ahorro proyectado. Imagínese cuanto ahorraría en una empresa con más de diez mil pipelines.

Ejemplo: estrategias de ahorro de costos de migración All-purpose hacia Job Clusters en 1 aplicación realtime (13,5 horas prendidas) ahorro de 44% aproximadamente.

Tipos Dbu/hora #DBU/hora Horas activas RT #Clusters Total diario Total mensual Total Anual
Job Cluster-REALTIME
$0,30
3,75
13,5
1
$15,19
$455,63
$5.467,50
All Purpose-REALTIME
$0,55
3,75
13,5
1
$27,84
$835,31
$10.023,75

5. Gobernanza y políticas de uso

Políticas de uso: establezca políticas claras para el uso de recursos en Azure. Esto incluye quién puede aprovisionar recursos y qué tipos de recursos están permitidos.

Automatización de la gobernanza: implemente políticas automatizadas para garantizar el cumplimiento y evitar gastos innecesarios.

Comparativa de características de servicios de gobernanza de costos:

Google Cost Management AWS Cost Management Microsoft Cost Management
Informes y paneles de control
Visualización de informes de costos, personalización mediante Looker Studio.
Proporciona informe de gastos y sus detalles (AWS Cost Explorer)
Reportes y análisis de la organización. Extensible el análisis con Power BI.
Control de Acceso
Uso de políticas para permisos granulares y nivel de jerarquía. Puede establecer quien visualiza los costos y quien realiza los pagos.
Permite establecer mecanismos de gobernanza y seguimiento de la información de facturación en la organización
Control de acceso basado en roles de Azure RBAC. Los usuarios con Azure Enterprise usan una combinación de permisos del Azure Portal y Enterprise (EA)
Presupuesto y alertas
Configuración de presupuestos que envíen alerta por correo si se supera el umbral.
Presupuestos con notificaciones de alerta automáticas (AWS Budgets)
Las vistas de presupuestos pueden estar limitadas por suscripción, grupos de recursos o colección de recursos. Soporta alertas.
Recomendaciones
Recomendaciones inteligentes para optimizar costos, las cuales son fácilmente aplicables.
Alinea el tamaño de los recursos asignados con la demanda real de la carga de trabajo (Rightsizing Recommendations)
Optimizaciones de costos según recomendaciones usando Azure Advisor.
Cuotas
Configuracion de limites de cuotas, que restringe la cantidad de recursos que se pueden utilizar.
Mediante el Service Quotas, limitan los servicios siendo los valores máximos para los recursos.
Dispone de diferentes clasificaciones de límites, algunos de ellos: generales, grupo de administración, suscripción, grupo de recursos, plantilla.

6. Desarrollo e innovación

Ahorro de Costos con el Despliegue de Job Clusters en Databricks

En el mundo de la ciencia de datos y el análisis de grandes volúmenes de datos, Databricks se ha establecido como una plataforma líder. Una de las características clave de Databricks es su capacidad para manejar diferentes tipos de clústeres, siendo los más comunes los «All-Purpose Clusters» y los «Job Clusters». En este artículo, nos centraremos en cómo el despliegue de Job Clusters puede ser una estrategia efectiva para ahorrar costos, especialmente en comparación con los All-Purpose Clusters.

Diferencia entre All-Purpose Clusters y Job Clusters

Antes de sumergirnos en las estrategias de ahorro de costos, es crucial entender la diferencia entre estos dos tipos de clústeres:

All-Purpose Clusters: están diseñados para ser utilizados de manera interactiva y pueden ser compartidos por varios usuarios. Son ideales para el desarrollo y la exploración de datos.

Job Clusters: se crean específicamente para ejecutar un trabajo y se cierran automáticamente una vez que el trabajo ha finalizado. Son ideales para trabajos programados y procesos automatizados. La desventaja en el entorno de Azure de este tipo de despliegue de cluster es que al ser más barato no tiene funciones de gestión de control de uso.

Ejemplo Despliegue Job Cluster

				
					# Configuración de Databricks API
DATABRICKS_INSTANCE = 'https://tu-instancia-databricks.com'
TOKEN = 'tu-token-de-acceso'
JOB_ID = 'tu-job-id'

# Headers para la autenticación
HEADERS = {
    'Authorization': f'Bearer {TOKEN}'
}

# Verificar el estado del job
def check_job_status(job_id):
    response = requests.get(f'{DATABRICKS_INSTANCE}/api/2.0/jobs/get?job_id={job_id}', headers=HEADERS)
    if response.status_code == 200:
        return response.json()['state']
    else:
        raise Exception("Error al obtener el estado del job")

# Cancelar el job si está en ejecución
def cancel_job(job_id):
    response = requests.post(f'{DATABRICKS_INSTANCE}/api/2.0/jobs/runs/cancel', json={"job_id": job_id}, headers=HEADERS)
    if response.status_code != 200:
        raise Exception("Error al cancelar el job")

# Desplegar el job
def deploy_job(job_id):
    response = requests.post(f'{DATABRICKS_INSTANCE}/api/2.0/jobs/run-now', json={"job_id": job_id}, headers=HEADERS)
    if response.status_code == 200:
        return response.json()['run_id']
    else:
        raise Exception("Error al desplegar el job")

# Verificar si el job está corriendo y esperar hasta que lo esté
def wait_for_job_to_run(job_id):
    while True:
        status = check_job_status(job_id)
        if status['life_cycle_state'] == 'RUNNING':
            print("El job está corriendo.")
            break
        elif status['life_cycle_state'] == 'TERMINATED':
            print("El job ha terminado o ha sido cancelado.")
            break
        elif status['life_cycle_state'] == 'PENDING':
            print("El job está pendiente de ejecución, esperando...")
            time.sleep(10)  # Esperar un tiempo antes de volver a verificar
        else:
            raise Exception(f"Estado del job desconocido: {status['life_cycle_state']}")

# Script principal
if __name__ == '__main__':
    # Verificar si el job está actualmente en ejecución y cancelarlo si es necesario
    try:
        status = check_job_status(JOB_ID)
        if status['life_cycle_state'] == 'RUNNING':
            print("Cancelando el job en ejecución...")
            cancel_job(JOB_ID)
    except Exception as e:
        print(f"Error al verificar o cancelar el job: {e}")

    # Desplegar el job
    try:
        print("Desplegando el job...")
        run_id = deploy_job(JOB_ID)
        print(f"Job desplegado con run_id: {run_id}")
    except Exception as e:
        print(f"Error al desplegar el job: {e}")

    # Esperar a que el job esté en 'RUNNING'
    try:
        wait_for_job_to_run(JOB_ID)
    except Exception as e:
        print(f"Error al esperar que el job esté corriendo: {e}")

				
			

Automatización y programación Cosmos DB:

Automatizar y programar un script para reducir al mínimo los Request Units (RU/s) en Azure Cosmos DB puede ayudar a optimizar los costos, especialmente durante los períodos de baja demanda o basándose en métricas de rendimiento o un horario predefinido. Puedes hacer esto usando PowerShell o Python primero debes obtener el throughput actual (RU/s configuradas) del contenedor y luego calcular el 1% de este valor para establecer el nuevo throughput. A continuación, un ejemplo básico en ambos lenguajes.

Usando PowerShell

				
					from azure.cosmos import CosmosClient
import os

# Configuración de Azure Cosmos DB
URL = os.environ.get('AZURE_COSMOS_DB_URL')
KEY = os.environ.get('AZURE_COSMOS_DB_KEY')
DATABASE_NAME = 'tuBaseDeDatos'
CONTAINER_NAME = 'tuContenedor'

# Inicializar cliente de Cosmos DB
client = CosmosClient(URL, credential=KEY)
database = client.get_database_client(DATABASE_NAME)
container = database.get_container_client(CONTAINER_NAME)

# Obtener el throughput actual
throughput_properties = container.read_throughput()
if throughput_properties:
    current_ru = throughput_properties['throughput']
    min_ru = max(int(current_ru * 0.01), 400)  # Calcula el 1%, pero no menos de 400 RU/s

    # Actualizar RU/s
    try:
        container.replace_throughput(min_ru)
    except Exception as e:
        print(f"Error al actualizar RU/s: {e}") 

				
			

Usando Python para reducir RUS bajo demanda o horario de bajo rendimiento

				
					from azure.cosmos import CosmosClient
import os
from datetime import datetime

# Configuración de Azure Cosmos DB
URL = os.environ.get('AZURE_COSMOS_DB_URL')
KEY = os.environ.get('AZURE_COSMOS_DB_KEY')
DATABASE_NAME = 'tuBaseDeDatos'
CONTAINER_NAME = 'tuContenedor'

# Inicializar cliente de Cosmos DB
client = CosmosClient(URL, credential=KEY)
database = client.get_database_client(DATABASE_NAME)
container = database.get_container_client(CONTAINER_NAME)

# Obtener la hora actual
current_hour = datetime.now().hour

# Definir RU/s según la hora del día
if 0 <= current_hour < 7 or 18 <= current_hour < 24:
    new_ru = 400  # RU/s más bajas durante la noche
else:
    new_ru = 1000  # RU/s estándar durante el día

# Actualizar RU/s
container.replace_throughput(new_ru)

				
			

7. Monitoreo continuo y mejora

Revisiones regulares: realice revisiones periódicas del gasto y la utilización de recursos.

Ajustes y mejoras: esté dispuesto a ajustar políticas y prácticas según sea necesario para mejorar continuamente la eficiencia del gasto.

Capacitación y conciencia continua

Un factor diferencial de este tipo de servicios y acompañamiento a nuestros socios tecnológicos es la posibilidad de descubrir, probar a escala y medir objetivamente los beneficios de cada plan de optimización de recursos y costos en sus plataformas, mismo que se plantea como resultado de una revisión regular, proactiva y basada en los cambios liberados por los proveedores de nube.

En Bluetab estamos comprometidos con el ahorro y nuestros especialistas en administración de datos con gusto podrán ayudarte a alcanzar niveles eficientes de monitoreo de costos, servicios y consumo en nubes, que apalanquen tu inversión tecnológica y gastos de operación.

7. Monitoreo continuo y mejora

Implementar FinOps en una empresa que utiliza servicios de Azure no es solo una estrategia para reducir costos, sino una transformación cultural y operativa. Requiere un cambio en la forma en que las organizaciones piensan y actúan respecto al uso de la nube.

Al adoptar FinOps, las empresas pueden no solo optimizar sus gastos en la nube, sino también mejorar la colaboración entre equipos, aumentar la agilidad y fomentar una mayor innovación.

 

Francis Josue De La Cruz

Big Data Architect

Wiener Morán

Data Engineer

¿Quieres saber más de lo que ofrecemos y ver otros casos de éxito?

Oscar Hernández, nuevo CEO de Bluetab LATAM

Oscar Hernández, nuevo CEO de Bluetab LATAM

Bluetab

Oscar Hernández Rosales asume la responsabilidad como CEO de Bluetab LATAM y estará a cargo de desarrollar, liderar y ejecutar la estrategia de Bluetab en la región, con el objetivo de expandir los productos y servicios de la empresa para respaldar la transformación digital continua de sus clientes y la creación de valor.

Durante esta transición, Oscar seguirá desempeñando su función como Country Manager de México, lo que garantizará una coordinación efectiva entre nuestras operaciones locales y nuestra estrategia regional, fortaleciendo aún más nuestra posición en el mercado.

"Este nuevo desafío es un privilegio para mí. Estoy comprometido con liderar con visión, seguir fortaleciendo la cultura Bluetab y continuar trabajando por el bienestar de los colaboradores y por el éxito del negocio. Un reto importante en una industria que constantemente se adapta a la evolución de las nuevas tecnologías. Bluetab innova, se adelanta y está dedicada a brindar la mejor experiencia de cliente, apoyada por un equipo profesional, talentoso y apasionado, que conoce las necesidades de las organizaciones", afirma Oscar.

¿Quieres saber más de lo que ofrecemos y ver otros casos de éxito?

Cambios de liderazgo en Bluetab EMEA

Cambios de liderazgo en Bluetab EMEA

Bluetab

Foto: Luis Malagón, CEO de Bluetab EMEA, y Tom Uhart, Co-Fundador y Data & AI Offering Lead

Luis Malagón se convierte en el nuevo CEO de Bluetab EMEA tras más de 10 años de experiencia en la compañía y habiendo contribuido enormemente a su éxito y posicionamiento. Sus probadas cualidades de liderazgo lo posicionan perfectamente para impulsar Bluetab en su siguiente fase de crecimiento.

“Este nuevo reto al frente de la región de EMEA es una gran oportunidad para seguir fomentando una cultura orientada al cliente y potenciar sus procesos de transformación. La colaboración forma parte de nuestro ADN y esto sumado a un equipo excepcional nos posiciona en el lugar adecuado y en el momento adecuado. Junto a IBM Consulting vamos a continuar liderando el mercado de las soluciones de Datos e Inteligencia Artificial”, afirma Luis.

“En Bluetab llevamos casi 20 años liderando el sector de los datos. En todo este tiempo, hemos ido adaptándonos a las diferentes tendencias y acompañando a nuestros clientes en su transformación digital, y ahora seguimos haciéndolo con la llegada de la IA Generativa.»

El nuevo rumbo de Tom Uhart

Tom Uhart, Co-Fundador de Bluetab y hasta ahora CEO de EMEA, continuará impulsando el proyecto desde su nuevo rol de Data & AI Offering Lead. De esta manera Tom seguirá impulsando el posicionamiento de la compañía y su expansión internacional de la mano del grupo IBM y otros key players del sector.

“Echando la vista atrás, me siento muy orgulloso de haber visto a Bluetab crecer durante todos estos años. Un equipo que sobresale por su gran talento técnico, espíritu inconformista y cultura de cercanía. Hemos alcanzado grandes metas, superado obstáculos y creado un legado del que todos y todas podemos sentirnos orgullosos. Ahora es la hora de dejar en manos de Luis la siguiente etapa de crecimiento de Bluetab, que estoy seguro será un gran éxito y llevará a la compañía al siguiente nivel”, afirma Tom.

¿Quieres saber más de lo que ofrecemos y ver otros casos de éxito?

Potencia Tu Negocio con GenAI y GCP: Simple y para Todos

Alfonso Zamora
Cloud Engineer

El objetivo principal de este artículo es presentar una solución para el análisis y la ingeniería de datos desde el punto de vista del personal de negocio, sin requerir unos conocimientos técnicos especializados. 

Las compañías disponen de una gran cantidad de procesos de ingeniería del dato para sacarle el mayor valor a su negocio, y en ocasiones, soluciones muy complejas para el caso de uso requerido. Desde aquí, proponemos simplificar la operativa para que un usuario de negocio, que anteriormente no podía llevar a cabo el desarrollo y la implementación de la parte técnica, ahora será autosuficiente, y podrá implementar sus propias soluciones técnicas con lenguaje natural.

Para poder cumplir nuestro objetivo, vamos a hacer uso de distintos servicios de la plataforma Google Cloud para crear tanto la infraestructura necesaria como los distintos componentes tecnológicos para poder sacar todo el valor a la información empresarial.

Antes de comenzar con el desarrollo del artículo, vamos a explicar algunos conceptos básicos sobre los servicios y sobre distintos frameworks de trabajo que vamos a utilizar para la implementación:

  1. Cloud Storage[1]: Es un servicio de almacenamiento en la nube proporcionado por Google Cloud Platform (GCP) que permite a los usuarios almacenar y recuperar datos de manera segura y escalable.
  2. BigQuery[2]: Es un servicio de análisis de datos totalmente administrado que permite realizar consultas SQL en conjuntos de datos masivos en GCP. Es especialmente eficaz para el análisis de datos a gran escala.
  3. Terraform[3]: Es una herramienta de infraestructura como código (IaC) desarrollada por HashiCorp. Permite a los usuarios describir y gestionar la infraestructura utilizando archivos de configuración en el lenguaje HashiCorp Configuration Language (HCL). Con Terraform, puedes definir recursos y proveedores de manera declarativa, facilitando la creación y gestión de infraestructuras en plataformas como AWS, Azure y Google Cloud.
  4. PySpark[4]: Es una interfaz de Python para Apache Spark, un marco de procesamiento distribuido de código abierto. PySpark facilita el desarrollo de aplicaciones de análisis de datos paralelas y distribuidas utilizando la potencia de Spark.
  5. Dataproc[5]: Es un servicio de gestión de clústeres para Apache Spark y Hadoop en GCP que permite ejecutar eficientemente tareas de análisis y procesamiento de datos a gran escala. Dataproc admite la ejecución de código PySpark, facilitando la realización de operaciones distribuidas en grandes conjuntos de datos en la infraestructura de Google Cloud.

Un LLM (Large Language Model) es un tipo de algoritmo de inteligencia artificial (IA) que utiliza técnicas de deep learning y enormes conjuntos de datos para comprender, resumir, generar y predecir nuevos contenidos. Un ejemplo de LLM podría ser ChatGPT que hace uso del modelo GPT desarrollado por OpenAI. 

En nuestro caso, vamos a hacer uso del modelo Codey[6] (code-bison) que es un modelo implementado por Google que está optimizado para generar código ya que ha sido entrenado para esta especialización que se encuentra dentro del stack de VertexAI[7]

Y no solo es importante el modelo que vamos a utilizar, sino también el cómo lo vamos a utilizar. Con esto, me refiero a que es necesario comprender los parámetros de entrada que afectan directamente a las respuestas que nos dará nuestro modelo, en los que podemos destacar los siguientes:

  • Temperatura (temperature): Este parámetro controla la aleatoriedad en las predicciones del modelo. Una temperatura baja, como 0.1, genera resultados más deterministas y enfocados, mientras que una temperatura alta, como 0.8, introduce más variabilidad y creatividad en las respuestas del modelo.
  • Prefix (Prompt): El prompt es el texto de entrada que se proporciona al modelo para iniciar la generación de texto. La elección del prompt es crucial, ya que guía al modelo sobre la tarea específica que se espera realizar. La formulación del prompt puede influir en la calidad y relevancia de las respuestas del modelo, aunque hay que tener en cuenta la longitud para que cumpla con el  número máximo de tokens de entrada que es 6144.
  • Tokens de salida (max_output_tokens): Este parámetro limita el número máximo de tokens que se generarán en la salida. Controlar este valor es útil para evitar respuestas excesivamente largas o para ajustar la longitud de la salida según los requisitos específicos de la aplicación.
  • Recuento de candidatos (candidate_count): Este parámetro controla el número de respuestas candidatas que el modelo genera antes de seleccionar la mejor opción. Un valor más alto puede ser útil para explorar diversas respuestas potenciales, pero también aumentará el costo computacional.

Una vez que hemos definido los parámetros y sabemos bien para qué sirve cada uno de ellos y comprendemos lo que es un prompt, vamos a enfocarnos en cómo utilizarlo e implementar uno que se pueda adaptar a nuestras necesidades.

Como se ha comentado anteriormente, el objetivo es generar tanto código Pyspark como terraform para poder realizar las tareas de creación de infraestructura y tratamiento del dato. Como son tareas totalmente distintas, como primera decisión importante para nuestro prompt se ha optado por dividirlo en dos partes específicas para que cada prompt esté entrenado con ejemplos para generar un lenguaje u otro.

Para cada uno de los prompt, se hace una introducción para especificar cuál va a ser el objetivo y qué peticiones se van a realizar, y después una serie de ejemplos en los que se le da un input en lenguaje natural simulando una petición, y posteriormente también se le da el output deseado para asignar el texto al código específico. El objetivo es generar un prompt estructurado que pueda ser procesado de manera eficiente por el modelo para que en los siguientes casos, pueda asociar los ejemplos que tiene disponibles con respuestas adecuadas.

Vamos a poner en práctica estos pequeños tips para ver una de las entradas al prompt de terraform:

input: I need to read a file named `myfile.csv` inside the folder `data/input` in a bucket called `application-data-input`, and I need to create a new dataset and table in BigQuery called `tst_tf_ai`. The process has to read the file, remove the first three characters of the name, and write the result in BigQuery. The schema of the table will be ‘name’ of String type and ‘surname’ of String type. The infrastructure has to be deployed in the project `project-cloud-223` in GCP, and region `europe-southwest1`.
output: «`hcl
terraform {
 required_version = «>= 0.15»
 required_providers {
  google = «~> 5.0»
 }
}

resource «google_storage_bucket» «temporary_files» { 
 project       = «project-cloud-223» 
 name          = «temporary-files-dataproc» 
 location      = «europe-southwest1» 
 force_destroy = true 

resource «google_storage_bucket» «bucket1» { 
 project       = «project-cloud-223» 
 name          = «application-data-input» 
 location      = «europe-southwest1» 
 force_destroy = true 

resource «google_storage_bucket_object» «folder1» {
 name    = «data/input/»
 bucket  = google_storage_bucket.bucket1.name
 content = » «
}

resource «google_bigquery_dataset» «dataset» { 
 project       = «project-cloud-223» 
 dataset_id    = «tst_tf_ai» 
 friendly_name = «tst_tf_ai» 
 description   = «Description» 
 location      = «europe-southwest1» 

resource «google_bigquery_table» «table» { 
 project              = «project-cloud-223» 
 dataset_id           = google_bigquery_dataset.dataset.dataset_id 
 deletion_protection  = false
 table_id             = «tst_tf_ai» 
 schema               = <<EOF

 { 
  «name»: «name», 
  «type»: «STRING», 
  «mode»: «NULLABLE», 
  «description»: «The name» 
 }, 
 { 
  «name»: «surname», 
  «type»: «STRING», 
  «mode»: «NULLABLE», 
  «description»: «The surname» 
 }

EOF 

«`

Author Name

Es importante implementar ejemplos lo más parecido posible a tu caso de uso para que las respuestas sean más precisas, y también que tenga bastantes ejemplos con variedad de peticiones para que sea más inteligente a la hora de devolver las respuestas. Una de las prácticas para que sea más interactiva la implementación del prompt, puede ser ir probando con distintas peticiones, y si no es capaz de hacer lo que se le ha pedido, se debería modificar las instrucciones.

Como hemos podido observar, el desarrollo del prompt sí necesitamos conocimientos técnicos para poder traducir las peticiones a código, por lo que esta tarea sí se debería de abordar por una persona técnica para posteriormente evadir a la persona de negocio. En otras palabras, necesitamos que una persona técnica genere la primera base de conocimiento para que luego las personas de negocio puedan hacer uso de este tipo de herramientas.

También se ha podido ver, que la generación de código en terraform es más compleja que la generación en Pyspark, por lo que se han requerido de más ejemplos de entrada en la realización del prompt de terraform para que se ajuste a nuestro caso de uso. Por ejemplo, hemos aplicado en los ejemplos que en terraform siempre cree un bucket temporal (temporary-files-dataproc) para que pueda ser utilizado por Dataproc.

Se han realizado tres ejemplos con peticiones distintas, requiriendo más o menos infraestructura y transformaciones para ver si nuestro prompt es lo suficientemente robusto. 

En el archivo ai_gen.py vemos el código necesario para hacer las peticiones y los tres ejemplos, en el que cabe destacar la configuración escogida para los parámetros del modelo:

  • Se ha decidido darle valor 1 a candidate_count para que no tenga más que una respuesta final válida para devolver. Además que como se ha comentado, aumentar este número también lleva aumento de costes.
  • El max_output_tokens se ha decidido 2048 que es el mayor número de tokens para este modelo, ya que si se necesita generar una respuesta con diversas transformaciones, no falle por esta limitación.
  • La temperatura se ha variado entre el código terraform y Pyspark, para terraform se ha optado por 0 para que siempre dé la respuesta que se considera más cercana a nuestro prompt para que no genere más de lo estrictamente necesario para nuestro objetivo. En cambio para Pyspark se ha optado por 0.2 que es una temperatura baja para que no sea muy creativo, pero para que también pueda darnos diversas respuestas con cada llamada para también poder hacer pruebas de rendimiento entre ellas.

Vamos a realizar un ejemplo de petición que está disponible en el siguiente repositorio github, en el que está detallado en el README paso por paso para poder ejecutarlo tú mismo. La petición es la siguiente:

In the realm of ‘customer_table,’ my objective is the seamless integration of pivotal fields such as ‘customer_id’, ‘name’, and ‘email’. These components promise to furnish crucial insights into the essence of our valued customer base.

Conversely, when delving into the nuances of ‘sales_table,’ the envisioned tapestry includes essential elements like ‘order_id’ ‘product’ ‘price’, ‘amount’ and ‘customer_id’. Theseattributes, meticulously curated, will play a pivotal role in the nuanced exploration and analysis of sales-related data.

The ‘bigtable_info’ table will have all the fields resulting from the union of the two tables, ‘customer_table’ and ‘sales_table.’ Here, the outcome of joining the two tables by the ‘customer_id’ numeric field will be stored.

Furthermore, as part of our meticulous data collection strategy, I plan to inaugurate a dedicated Google Cloud Storage bucket christened ‘sales-etl-bucket.’ This repository is strategically designed to serve as a robust container for collating data, particularly hailing from CSV files. This endeavor is set to enhance the efficiency and organizational prowess of our data management processes.

To enhance the structure, two distinct subfolders, ‘input/sales’ and ‘input/customers’ have been ingeniously incorporated within the ‘sales-etl-bucket,’ ensuring a refined organization of the incoming data streams.

You will need to read the CSV files within the ‘input/sales’ folder to write the data into the ‘sales_table’. Additionally, you should perform the same operation with the files from the ‘input/customers’ folder to write the data into the ‘customer_table’. Finally, you are required to perform a join between the sales and customers information based on the ‘customer_id’ number field and write the merged information into the ‘bigtable_info’.

Author Name
terraform {
 required_version = ">= 0.15"
 required_providers {
   google = "~> 5.0"
 }
}


resource "google_storage_bucket" "temporary_files" {
 project       = "my-cloud-project"
 name          = "temporary-files-dataproc"
 location      = "europe-southwest1"
 force_destroy = true
}


resource "google_storage_bucket" "sales_etl_bucket" {
 project       = "my-cloud-project"
 name          = "sales-etl-bucket"
 location      = "europe-southwest1"
 force_destroy = true
}


resource "google_storage_bucket_object" "sales_folder" {
 name    = "input/sales/"
 bucket  = google_storage_bucket.sales_etl_bucket.name
 content = " "
}


resource "google_storage_bucket_object" "customers_folder" {
 name    = "input/customers/"
 bucket  = google_storage_bucket.sales_etl_bucket.name
 content = " "
}


resource "google_bigquery_dataset" "raw_sales_data" {
 project       = "my-cloud-project"
 dataset_id    = "raw_sales_data"
 friendly_name = "Raw Sales Data"
 description   = "This dataset contains the raw sales data."
 location      = "europe-southwest1"
}


resource "google_bigquery_table" "customer_table" {
 project              = "my-cloud-project"
 dataset_id           = google_bigquery_dataset.raw_sales_data.dataset_id
 deletion_protection  = false
 table_id             = "customer_table"
 schema               = <<EOF
[
 {
   "name": "customer_id",
   "type": "INT64",
   "mode": "REQUIRED",
   "description": "The customer ID."
 },
 {
   "name": "name",
   "type": "STRING",
   "mode": "NULLABLE",
   "description": "The customer's name."
 },
 {
   "name": "email",
   "type": "STRING",
   "mode": "NULLABLE",
   "description": "The customer's email address."
 }
]
EOF
}


resource "google_bigquery_table" "sales_table" {
 project              = "my-cloud-project"
 dataset_id           = google_bigquery_dataset.raw_sales_data.dataset_id
 deletion_protection  = false
 table_id             = "sales_table"
 schema               = <<EOF
[
 {
   "name": "order_id",
   "type": "INT64",
   "mode": "REQUIRED",
   "description": "The order ID."
 },
 {
   "name": "product",
   "type": "STRING",
   "mode": "NULLABLE",
   "description": "The product name."
 },
 {
   "name": "price",
   "type": "FLOAT64",
   "mode": "NULLABLE",
   "description": "The product price."
 },
 {
   "name": "amount",
   "type": "INT64",
   "mode": "NULLABLE",
   "description": "The product amount."
 },
 {
   "name": "customer_id",
   "type": "INT64",
   "mode": "REQUIRED",
   "description": "The customer ID."
 }
]
EOF
}


resource "google_bigquery_dataset" "master_sales_data" {
 project       = "my-cloud-project"
 dataset_id    = "master_sales_data"
 friendly_name = "Master Sales Data"
 description   = "This dataset contains the master sales data."
 location      = "europe-southwest1"
}


resource "google_bigquery_table" "bigtable_info" {
 project              = "my-cloud-project"
 dataset_id           = google_bigquery_dataset.master_sales_data.dataset_id
 deletion_protection  = false
 table_id             = "bigtable_info"
 schema               = <<EOF
[
 {
   "name": "customer_id",
   "type": "INT64",
   "mode": "REQUIRED",
   "description": "The customer ID."
 },
 {
   "name": "name",
   "type": "STRING",
   "mode": "NULLABLE",
   "description": "The customer's name."
 },
 {
   "name": "email",
   "type": "STRING",
   "mode": "NULLABLE",
   "description": "The customer's email address."
 },
 {
   "name": "order_id",
   "type": "INT64",
   "mode": "REQUIRED",
   "description": "The order ID."
 },
 {
   "name": "product",
   "type": "STRING",
   "mode": "NULLABLE",
   "description": "The product name."
 },
 {
   "name": "price",
   "type": "FLOAT64",
   "mode": "NULLABLE",
   "description": "The product price."
 },
 {
   "name": "amount",
   "type": "INT64",
   "mode": "NULLABLE",
   "description": "The product amount."
 }
]
EOF
}


Vemos que va a añadir un bucket con dos carpetas para poder subir los datos a la carpeta correspondiente dependiendo si es sales o customer. También ha creado dos datasets, uno para la capa de raw para tener los datos según nos han venido de origen con las tablas de customers y sales, y el segundo dataset para la tabla final que almacenará el resultado de juntar la información de ambas tablas.

Vamos a revisar el código pyspark:

from pyspark.sql import SparkSession
from pyspark.sql import functions as F


# Configure Spark session
spark = SparkSession.builder.appName("SalesETL").getOrCreate()


# Define file paths
sales_path = "gs://sales-etl-bucket/input/sales/*.csv"
customers_path = "gs://sales-etl-bucket/input/customers/*.csv"


# Read CSV files into DataFrames
sales_df = spark.read.csv(sales_path, header=True, inferSchema=True)
customers_df = spark.read.csv(customers_path, header=True, inferSchema=True)


# Write tables to BigQuery
sales_df.write.format("bigquery") \
   .option("temporaryGcsBucket", "temporary-files-dataproc") \
   .option("table",  "raw_sales_data.sales_table") \
   .mode("overwrite") \
   .save()
customers_df.write.format("bigquery") \
   .option("temporaryGcsBucket", "temporary-files-dataproc") \
   .option("table",  "raw_sales_data.customer_table") \
   .mode("overwrite") \
   .save()


# Join sales and customers tables
bigtable_info_df = sales_df.join(customers_df, on="customer_id", how="inner")


# Write joined table to BigQuery
bigtable_info_df.write.format("bigquery") \
   .option("temporaryGcsBucket", "temporary-files-dataproc") \
   .option("table",  "master_sales_data.bigtable_info") \
   .mode("overwrite") \
   .save()


# Stop the Spark session
spark.stop()

Se puede observar que el código generado realiza la lectura por cada una de las carpetas e inserta cada dato en su tabla correspondiente. 

Para poder asegurarnos de que el ejemplo está bien realizado, podemos seguir los pasos del README en el repositorio GitHub[8] para aplicar los cambios en el código terraform, subir los ficheros de ejemplo que tenemos en la carpeta example_data y a ejecutar un Batch en Dataproc. 

Finalmente, vemos si la información que se ha almacenado en BigQuery es correcta:

  • Tabla customer:
  • Tabla sales:
  • Tabla final:

De esta forma, hemos conseguido de a través de lenguaje natural, tener un proceso funcional totalmente operativo. Hay otro ejemplo que se puede ejecutar, aunque también animo a hacer más ejemplos, o incluso mejorar el prompt, para poder meterle ejemplos más complejos, y también adaptarlo a tu caso de uso.

Al ser ejemplos muy concretos sobre unas tecnologías tan concretas, cuando se hace un cambio en el prompt en cualquier ejemplo puede afectar a los resultados, o también, modificar alguna palabra de la petición de entrada. Esto se traduce en que el prompt no es lo suficientemente robusto como para poder asimilar distintas expresiones sin afectar al código generado. Para poder tener un prompt y un sistema productivo, se necesita más entrenamiento y distinta variedad tanto de soluciones, peticiones, expresiones,. … Con todo ello, finalmente podremos tener una primera versión que poder presentar a nuestro usuario de negocio para que sea autónomo.

Especificar el máximo detalle posible a un LLM es crucial para obtener resultados precisos y contextuales. Aquí hay varios consejos que debemos tener en cuenta para poder tener un resultado adecuado:

  • Claridad y Concisión:
    • Sé claro y conciso en tu prompt, evitando oraciones largas y complicadas.
    • Define claramente el problema o la tarea que deseas que el modelo aborde.
  • Especificidad:
    • Proporciona detalles específicos sobre lo que estás buscando. Cuanto más preciso seas, mejores resultados obtendrás.
  • Variabilidad y Diversidad:
    • Considera incluir diferentes tipos de ejemplos o casos para evaluar la capacidad del modelo para manejar la variabilidad.
  • Feedback Iterativo:
    • Si es posible, realiza iteraciones en tu prompt basándote en los resultados obtenidos y el feedback del modelo.
  • Prueba y Ajuste:
    • Antes de usar el prompt de manera extensa, realiza pruebas con ejemplos y ajusta según sea necesario para obtener resultados deseados.

En el ámbito de LLM, las líneas futuras de desarrollo se centran en mejorar la eficiencia y la accesibilidad de la implementación de modelos de lenguaje. Aquí se detallan algunas mejoras clave que podrían potenciar significativamente la experiencia del usuario y la eficacia del sistema:

1. Uso de distintos modelos de LLM:

La inclusión de una función que permita a los usuarios comparar los resultados generados por diferentes modelos sería esencial. Esta característica proporcionaría a los usuarios información valiosa sobre el rendimiento relativo de los modelos disponibles, ayudándoles a seleccionar el modelo más adecuado para sus necesidades específicas en términos de precisión, velocidad y recursos requeridos.

2. Capacidad de retroalimentación del usuario:

Implementar un sistema de retroalimentación que permita a los usuarios calificar y proporcionar comentarios sobre las respuestas generadas podría ser útil para mejorar continuamente la calidad del modelo. Esta información podría utilizarse para ajustar y refinar el modelo a lo largo del tiempo, adaptándose a las preferencias y necesidades cambiantes de los usuarios.

3. RAG (Retrieval-augmented generation)

RAG (Retrieval-augmented generation) es un enfoque que combina la generación de texto y la recuperación de información para mejorar las respuestas de los modelos de lenguaje. Implica el uso de mecanismos de recuperación para obtener información relevante de una base de datos o corpus textual, que luego se integra en el proceso de generación de texto para mejorar la calidad y la coherencia de las respuestas generadas.

Cloud Storage[1]: https://cloud.google.com/storage/docs

BigQuery[2]: https://cloud.google.com/bigquery/docs

Terraform[3]: https://developer.hashicorp.com/terraform/docs

PySpark[4]: https://spark.apache.org/docs/latest/api/python/index.html

Dataproc[5]: https://cloud.google.com/dataproc/docs

Codey[6]: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-generation

VertexAI[7]: https://cloud.google.com/vertex-ai/docs

GitHub[8]: https://github.com/alfonsozamorac/etl-genai

Análisis de vulnerabilidades en contenedores con trivy

Análisis de vulnerabilidades en contenedores con trivy

Ángel Maroco

AWS Cloud Architect

Dentro del marco de la seguridad en contenedores, la fase de construcción adquiere vital importancia debido a que debemos seleccionar la imagen base sobre la que ejecutarán las aplicaciones. El no disponer de mecanismos automáticos para el análisis de vulnerabilidades puede desembocar en entornos productivos con aplicaciones inseguras con los riesgos que ello conlleva.

En este artículo cubriremos el análisis de vulnerabilidades a través de la solución Trivy de Aqua Security, pero antes de comenzar, es preciso explicar en qué se basan este tipo de soluciones para identificar vulnerabilidades en las imágenes docker.

Introducción a CVE (Common Vulnerabilities and Exposures)

CVE es una lista de información mantenida por MITRE Corporation cuyo objetivo es centralizar el registro de vulnerabilidades de seguridad conocidas, en la que cada referencia tiene un número de identificación CVE-ID, descripción de la vulnerabilidad, que versiones del software están afectadas, posible solución al fallo (si existe) o como configurar para mitigar la vulnerabilidad y referencias a publicaciones o entradas de foros o blog donde se ha hecho pública la vulnerabilidad o se demuestra su explotación.

El CVE-ID ofrece una nomenclatura estándar para identificar de forma inequívoca una vulnerabilidad. Se clasifican en 5 tipologías, las cuales veremos en la sección Interpretación del análisis. Dichas tipologías son asignadas basándose en diferentes métricas (si tenéis curiosidad, consultad CVSS v3 Calculator)

CVE se ha convertido en el estándar para el registro de vulnerabilidades, por lo que la amplia mayoría de empresas de tecnología y particulares hacen uso de la misma.

Disponemos de múltiples canales para estar informados de todas las novedades referentes a vulnerabilidades: blog oficialtwitter, repositorio cvelist en github o LinkedIn.

Adicionalmente, si queréis información más detallada sobre una vulnerabilidad, podéis consultar la web del NIST, en concreto la NVD (National Vulnerability Database)

Os invitamos a buscar alguna de las siguientes vulnerabilidades críticas, es muy posible que de forma directa o indirecta os haya podido afectar. Os adelantamos que han sido de las más sonadas 🙂

  • CVE-2017-5753
  • CVE-2017-5754

Si detectas una vulnerabilidad, te animamos a registrarla a través del siguiente formulario

Aqua Security – Trivy

Trivy es una herramienta open source enfocada en la detección de vulnerabilidades en paquetes a nivel OS y ficheros de dependencias de distintitos lenguajes:

  • OS packages; (Alpine, Red Hat Universal Base Image, Red Hat Enterprise Linux, CentOS, Oracle Linux, Debian, Ubuntu, Amazon Linux, openSUSE Leap, SUSE Enterprise Linux, Photon OS and Distroless)

  • Application dependencies: (Bundler, Composer, Pipenv, Poetry, npm, yarn and Cargo)

Aqua Security, empresa especializada en el desarrollo de soluciones de seguridad, adquirió trivy en 2019. Junto a un amplio número de colaboradores, son los encargados del desarrollo y mantenimiento de la misma.

Instalación

Trivy dispone de instaladores para la mayor parte de sistemas Linux and macOS. Para nuestras pruebas vamos a utilizar el instalador genérico:

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sudo sh -s -- -b /usr/local/bin 

Si no queremos persistir el binario en nuestro sistema, disponemos de una imagen docker:

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/trivycache:/root/.cache/ aquasec/trivy python:3.4-alpine 

Operaciones básicas

  • Imágenes locales

Trivy dispone de instaladores para la mayor parte de sistemas Linux and macOS. Para nuestras pruebas vamos a utilizar el instalador genérico:

#!/bin/bash
docker build -t cloud-practice/alpine:latest -<<EOF
FROM alpine:latest
RUN echo "hello world"
EOF

trivy image cloud-practice/alpine:latest 
  • Imágenes remotas
#!/bin/bash
trivy image python:3.4-alpine 
  • Proyectos locales:
    Permite analizar ficheros de dependencias (salidas):
    • Pipfile.lock: Python
    • package-lock_react.json: React
    • Gemfile_rails.lock: Rails
    • Gemfile.lock: Ruby
    • Dockerfile: Docker
    • composer_laravel.lock: PHP Lavarel
    • Cargo.lock: Rust
#!/bin/bash
git clone https://github.com/knqyf263/trivy-ci-test
trivy fs trivy-ci-test 
  • Repositorios públicos:
#!/bin/bash
trivy repo https://github.com/knqyf263/trivy-ci-test 
  • Cache database
    La base de datos de vulnerabilidades se aloja en github. Para evitar descargar dicha base de datos en cada operación de análisis, podemos utilizar el parámetro --cache-dir <dir>:
#!/bin/bash trivy –cache-dir .cache/trivy image python:3.4-alpine3.9 
  • Filtrar por criticidad
#!/bin/bash
trivy image --severity HIGH,CRITICAL ruby:2.4.0 
  • Filtrar vulnerabiliades no resueltas
#!/bin/bash
trivy image --ignore-unfixed ruby:2.4.0 
  • Especificar código de salida
    Esta opción en muy util en el proceso de integración continua, ya que podemos especificar que nuestro pipeline finalice con error cuando se encuentre vulnerabilidad de tipo critical pero las tipo medium y high finalicen correctamente.
#!/bin/bash
trivy image --exit-code 0 --severity MEDIUM,HIGH ruby:2.4.0
trivy image --exit-code 1 --severity CRITICAL ruby:2.4.0 
  • Ignorar vulnerabilidades específicas
    A través del fichero .trivyignore, podemos especificar aquellas CVEs que nos interesa descartar. Puede resultar útil si la imagen contiene una vulnerabilidad que no afecta a nuestro desarrollo.
#!/bin/bash
cat .trivyignore
# Accept the risk
CVE-2018-14618

# No impact in our settings
CVE-2019-1543 
  • Exportar salida en formato JSON:
    Esta opción es interesante si quieres automatizar un proceso antes una salida, visualizar los resultados en un front personalizado o persistir la salida con un formato estructurado.
#!/bin/bash
trivy image -f json -o results.json golang:1.12-alpine
cat results.json | jq 
  • Exportar salida en formato SARIF:
    Existe un estandar llamado SARIF (Static Analysis Results Interchange Format) que define el formato que deben tener las salidas cualquier herramienta de análisis de vulnerabilidades.
#!/bin/bash
wget https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/sarif.tpl
trivy image --format template --template "@sarif.tpl" -o report-golang.sarif  golang:1.12-alpine
cat report-golang.sarif   

VS Code dispone de la extensión sarif-viewer para la visualización de vulnerabilidades.

Procesos de integración contínua

Trivy dispone de plantillas para las principales soluciones de CI/CD:

#!/bin/bash
$ cat .gitlab-ci.yml
stages:
  - test

trivy:
  stage: test
  image: docker:stable-git
  before_script:
    - docker build -t trivy-ci-test:${CI_COMMIT_REF_NAME} .
    - export VERSION=$(curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
    - wget https://github.com/aquasecurity/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz
    - tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz
  variables:
    DOCKER_DRIVER: overlay2
  allow_failure: true
  services:
    - docker:stable-dind
  script:
    - ./trivy --exit-code 0 --severity HIGH --no-progress --auto-refresh trivy-ci-test:${CI_COMMIT_REF_NAME}
    - ./trivy --exit-code 1 --severity CRITICAL --no-progress --auto-refresh trivy-ci-test:${CI_COMMIT_REF_NAME} 

Interpretación del análisis

#!/bin/bash
trivy image httpd:2.2-alpine
2020-10-24T09:46:43.186+0200    INFO    Need to update DB
2020-10-24T09:46:43.186+0200    INFO    Downloading DB...
18.63 MiB / 18.63 MiB [---------------------------------------------------------] 100.00% 8.78 MiB p/s 3s
2020-10-24T09:47:08.571+0200    INFO    Detecting Alpine vulnerabilities...
2020-10-24T09:47:08.573+0200    WARN    This OS version is no longer supported by the distribution: alpine 3.4.6
2020-10-24T09:47:08.573+0200    WARN    The vulnerability detection may be insufficient because security updates are not provided

httpd:2.2-alpine (alpine 3.4.6)
===============================
Total: 32 (UNKNOWN: 0, LOW: 0, MEDIUM: 15, HIGH: 14, CRITICAL: 3)

+-----------------------+------------------+----------+-------------------+------------------+--------------------------------+
|        LIBRARY        | VULNERABILITY ID | SEVERITY | INSTALLED VERSION |  FIXED VERSION   |             TITLE              |
+-----------------------+------------------+----------+-------------------+------------------+--------------------------------+
| libcrypto1.0          | CVE-2018-0732    | HIGH     | 1.0.2n-r0         | 1.0.2o-r1        | openssl: Malicious server can  |
|                       |                  |          |                   |                  | send large prime to client     |
|                       |                  |          |                   |                  | during DH(E) TLS...            |
+-----------------------+------------------+----------+-------------------+------------------+--------------------------------+
| postgresql-dev        | CVE-2018-1115    | CRITICAL | 9.5.10-r0         | 9.5.13-r0        | postgresql: Too-permissive     |
|                       |                  |          |                   |                  | access control list on         |
|                       |                  |          |                   |                  | function pg_logfile_rotate()   |
+-----------------------+------------------+----------+-------------------+------------------+--------------------------------+
| libssh2-1             | CVE-2019-17498   | LOW      | 1.8.0-2.1         |                  | libssh2: integer overflow in   |
|                       |                  |          |                   |                  | SSH_MSG_DISCONNECT logic in    |
|                       |                  |          |                   |                  | packet.c                       |
+-----------------------+------------------+----------+-------------------+------------------+--------------------------------+ 
  • Library: librería/paquete donde se ha identificado la vulnerabilidad.

  • Vulnerability ID: Identificador de vulnerabilidad (según estandar CVE).

  • Severity: existe una clasificación con 5 tipologías [fuente] las cuales tienen asignado una puntuación CVSS (Common Vulnerability Scoring System):

    • Critical (puntuación CVSS 9.0-10.0): fallos que podría aprovechar fácilmente un atacante no autenticado y llegar a comprometer el sistema (ejecución de código arbitrario) sin interacción por parte del usuario.

    • High (puntuación CVSS 7.0-8.9): fallos que podrían comprometer fácilmente la confidencialidad, integridad o disponibilidad de los recursos.

    • Medium (puntuación CVSS 4.0-6.9): fallos que, aún siendo más difíciles de aprovechar, pueden seguir comprometiendo la confidencialidad, integridad o disponibilidad de los recursos en determinadas circunstancias.

    • Low (puntuación CVSS 0.1-3.9): resto de problemas que producen un impacto de seguridad. Son los tipos de vulnerabilidades de los que se considera que su aprovechamiento exige unas circunstancias poco probables o que tendría consecuencias mínimas.

    • Unknow (puntuación CVSS 0.0): se otorga a vulnerabilidades que no tienen asignada puntuación.

  • Installed version: versión instalada en el sistema analizado.

  • Fixed version: versión en la que se resuelve el problema. Si no se informa la versión quiere decir que está pendiente de resolución.

  • Title: Descripción corta de la vulnerabilidad. Para más información, consultar NVD.

Ya sabemos interpretar a alto nivel la información que nos muestra el análisis. Ahora bien, ¿qué acciones debería tomar? En la sección Recomendaciones te damos alguna pista.

Recomendaciones

  • En esta sección describimos algunos aspectos más importantes dentro del ámbito de vulnerabilidades en contenedores:

    • Evitar (en la medida de lo posible) hacer uso de imágenes donde se hayan identificado vulnerabilidades critical y high

    • Incluir el análisis de imágenes en procesos de CI
      La seguridad en tu desarrollo no es opcional, automatiza tus pruebas y no dependas de procesos manuales.

    • Utilizar imágenes ligeras, menos exposiciones:
      Las imágenes tipo Alpine / BusyBox están construidas con el menor número de paquetes posible (la imagen base pesa 5MB), lo que se traduce en una reducción de vectores de ataque. Soportan múltiples arquitecturas y se actualizan con bastante frecuencia.
REPOSITORY  TAG     IMAGE ID      CREATED      SIZE
alpine      latest  961769676411  4 weeks ago  5.58MB
ubuntu      latest  2ca708c1c9cc  2 days ago   64.2MB
debian      latest  c2c03a296d23  9 days ago   114MB
centos      latest  67fa590cfc1c  4 weeks ago  202MB 
Si por algún motivo de dependencias no podéis customizar una imagen base de alpine, buscad imágenes tipo slim de proveedores de software confiables. Además del componente de seguridad, las personas que compartan red contigo lo agradecerán al no tener que bajar imágenes de 1GB
  • Obtener imágenes de repositorios oficiales: Lo recomendable es utilizar DockerHub y preferentemente imágenes de publishers oficiales. DockerHub y CVEs

  • Mantener actualizadas las imágenes En el siguiente ejemplo vemos un análisis sobre dos versiones diferentes de apache:

    Imagen publicada el 11/2018

httpd:2.2-alpine (alpine 3.4.6)
 Total: 32 (UNKNOWN: 0, LOW: 0, MEDIUM: 15, **HIGH: 14, CRITICAL: 3**) 

Imagen publicada el 01/2020

httpd:alpine (alpine 3.12.1)
 Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, **HIGH: 0, CRITICAL: 0**) 

Como podéis observar, si un desarrollo finalizó en 2018 y no se realizan tareas de mantenimiento, podría estar exponiendo un apache relativamente vulnerable. No es un problema derivado del uso de contenedores, pero debido a la versatilidad que nos proporciona docker para testar nuevas versiones de productos, ahora no tenemos excusa.

  • Especial atención a vulnerabilidades que afecten a la capa de aplicación:
    Según el estudio realizado por la compañía edgescan, el 19% de las vulnerabilidades detectadas en 2018 corresponden a capa 7 (Modelo OSI), destacando por encima de todos ataques de tipo XSS (Cross-site Scripting).

  • Seleccionar imágenes latest con especial cuidado:
    Aunque este consejo está muy relacionado con el uso de imágenes ligeras, consideramos hacer un inciso sobre las imágenes latest:

Imagen latest Apache (base alpine 3.12)

httpd:alpine (alpine 3.12.1)
 Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) 

Imagen latest Apache (base debian 10.6)

httpd:latest (debian 10.6)
 Total: 119 (UNKNOWN: 0, LOW: 87, MEDIUM: 10, HIGH: 22, CRITICAL: 0) 

En ambos casos estamos utilizando la misma versión de apache (2.4.46), la diferencia está en el número de vulnerabilidades críticas.
¿Quiere decir que la imagen basada en debian 10 convierte en vulnerable la aplicación que ejecuta en ese sistema? Puede que si o puede que no, hay que evaluar si las vulnerabilidades pueden comprometer nuestra aplicación. La recomendación es utilizar la imagen de alpine.

  • Evaluar el uso de imágenes docker distroless
    El concepto distroless es de Google y consiste en imágenes docker basadas en debian9/debian10, sin gestores de paquetes, shells ni utilidades. Las imágenes están enfocadas a lenguajes de programación (Java, Python, Golang, Node.js, dotnet y Rust), contiene exclusivamente lo necesario para ejecutar las aplicaciones. Al no disponer de gestores de paquetes, no puedes instalar tus propias dependencias, lo que se puede traducir en una gran ventaja y en otros casos, un gran obstáculo. Realizad pruebas y si encaja con los requisitos de vuestro proyecto, adelante, siempre es beneficioso disponer de alternativas. El mantenimiento corre a cuenta de Google, así que el aspecto de seguridad estará bien acotado.

Ecosistema de analizadores de vulnerabilidades para contenedores

En nuestro caso hemos utilizado trivy ya que se trata de una herramienta open source, fiable, estable y en continua evolución, pero disponemos de multitud de herramientas para el análisis de contenedores:
¿Quieres saber más de lo que ofrecemos y ver otros casos de éxito?
Ángel Maroco
AWS Cloud Architect

Ángel Maroco llevo en el sector IT más de una década, iniciando mi carrera profesional con el desarrollo web, pasando una buena etapa en distintas plataformas informacionales en entornos bancarios y los últimos 5 años dedicado al diseño de soluciones en entornos AWS.

En la actualidad, compagino mi papel de arquitecto junto al de responsable de la Pŕactica Cloud /bluetab, cuya misión es impulsar la cultura Cloud dentro de la compañía.