• Saltar a la navegación principal
  • Saltar al contenido principal
  • Saltar al pie de página
Bluetab

Bluetab

an IBM Company

  • Soluciones
    • DATA STRATEGY
    • DATA READINESS
    • DATA PRODUCTS AI
  • Assets
    • TRUEDAT
    • FASTCAPTURE
    • Spark Tune
  • Conócenos
  • Oficinas
    • España
    • Mexico
    • Perú
    • Colombia
  • talento
    • España
    • TALENT HUB BARCELONA
    • TALENT HUB BIZKAIA
    • TALENT HUB ALICANTE
    • TALENT HUB MÁLAGA
  • Blog
  • English

Practices

Snowflake Advanced Storage Guide

octubre 3, 2022 by Bluetab

Guía avanzada sobre almacenamiento en Snowflake

Roberto García Parra

Technical Delivery Manager

Introducción a Snowflake

Snowflake es una plataforma avanzada de datos que se consume en modalidad SaaS 100% en cloud. El principal factor diferenciador de Snowflake es que proporciona capacidades avanzadas para todas las necesidades de datos de las compañías (Almacenamiento, procesamiento, explotación y soluciones de analítica avanzada) de una manera más flexible y sencilla que las soluciones de Datawarehouse tradicionales. 

El motor de queries y procesamiento de Snowflake está basado 100% en SQL para facilitar el acceso a la mayoría de los profesionales de datos, aunque Snowflake está haciendo esfuerzos por ampliar las posibilidades de desarrollo (Por ejemplo, recientemente ha sacado Snowpark, una API que permite a los desarrolladores que estén habituados a trabajar con Spark tanto en Scala cómo en Java y recientemente en Python, a poder migrar sus códigos de forma sencilla a Snowflake). Además, dispone de conectores nativos con una serie de partners que abarca todas las fases de la ingeniería de datos, cómo por ejemplo partners de integración de datos tan importantes cómo Matillion, Informatica, DBT o DataStage; de Business Intelligence cómo Domo, Cognos o Looker; o de Machine Learning cómo Alteryx, Dataiku o AWS Sagemaker.

La otra ventaja diferenciadora de Snowflake es que tiene unas capacidades de optimización que no requieren apenas de mantenimiento y cubren un abanico muy amplio de casos de uso, entre las que se podrían destacar la clusterización automática, el cacheo y el search optimization service, elementos en los que ahondaremos en detalle en futuros artículos, ya que en éste nos vamos a centrar sobre todo en las capacidades de almacenamiento.

Principales características diferenciadoras de Snowflake:

  • Pone al alcance de los usuarios funcionalidades avanzadas que se gestionan de forma sencilla, abstrayendo a los usuarios de lo que se maneja por debajo.
  • Multi-cloud: Se puede desplegar en cualquiera de los tres clouds más importantes (Amazon, Azure y Google) e incluso permite implementar una estrategia multi-cloud dónde la mayoría de la administración y operación corre por cuenta de Snowflake.
  • No hay que mantener ni hardware ni software. Todo gestionado por Snowflake y sin pérdida de servicio.
  • Gestión sencilla de las unidades de procesamiento (Llamadas Virtual Warehouses). Es muy sencillo subir o bajar la talla del procesamiento (a golpe de click o una sencilla sentencia SQL), y los cluster se pueden configurar para que se bajen automáticamente tras un tiempo de inactividad, y vuelvan a levantarse de forma rápida cuándo entre una nueva petición (en menos de un segundo la mayor de las veces). Dado que una de las variables que marcan el coste es el tiempo de actividad de un warehouse, esto permite eficientar los costes, sin tener que preocuparnos de estar bajando-levantando instancias en función del uso de la plataforma.

La arquitectura de Snowflake está basada en tres principales capas:

  1. La capa de almacenamiento, que es en la que nos centraremos en este artículo. Esta capa basada en microparticiones es la base de algunas de las funcionalidades más disruptivas de Snowflake cómo por ejemplo el Zero-copy cloning o el Time-to-Travel, que veremos también en futuros artículos.
  2. Capa de procesamiento.
  3. Cloud Services, que es la capa con la que se interactúa con Snowflake y es el cerebro que gestiona y coordina el resto de capas y componentes.

Objetivo del artículo

Vamos a entender en profundidad cómo funciona Snowflake en la capa de almacenamiento. A grandes líneas, veremos:

  • Cómo se almacenan, distribuyen y comprimen los datos.
  • La importancia de los metadatos a la hora de escanear de forma eficiente el almacenamiento cuándo se hace tanto una consulta, cómo una operación DML de inserción, actualización o borrado.
  • Cómo es este proceso de búsqueda en los datos, para reducir al máximo el número de bytes a escanear (y por tanto, la reducción en los tiempos de consulta).

Esto será la base para entender varias de las funcionalidades diferenciales que ofrece Snowflake:

  • A nivel rendimiento: Clustering, caching, search optimization service y query acceleration service (Recientemente liberada). Estos servicios-funcionalidades ayudan a optimizar diferentes casos de uso dónde lo proporcionado por el almacenamiento no sea suficiente para obtener el rendimiento deseado.
  • Data Sharing, sin necesidad de replicar los datos físicamente.
  • Resiliencia: Zero-copy cloning, Time Travel y Fail Safe.

Introducción al almacenamiento

El almacenamiento en Snowflake se basa en la generación de ficheros comprimidos con un tamaño máximo aproximado de 16MB y que se almacenan en un repositorio orientado a objetos tipo el S3 de AWS. Estos ficheros son inmutables, y cualquier operación de inserción-borrado-actualización siempre se hace generando un nuevo fichero de datos y actualizando los metadatos para saber cuáles son los ficheros que están activos en cada momento, además de otros metadatos que veremos más adelante en profundidad para eficientar la cantidad de bytes escaneados a la hora de ejecutar una query.

Objetivos del almacenamiento Snowflake

La forma en la que almacena los datos Snowflake está enfocada a dos objetivos principales:

  • Optimizar el rendimiento de las consultas, con una combinación de organización automática de los datos, almacenamiento columnar y el mantenimiento de una metadata.
  • Posibilitar varias de las características diferenciales que tiene Snowflake frente a otros Datawarehouse tradicionales, cómo por ejemplo:
    • Zero-copy cloning.
    • Time Travel.
    • Data Sharing sin necesidad de replicar el dato físicamente.

Principales características del almacenamiento en Snowflake

Compresión columnar: Snowflake analiza y comprime automáticamente los datos durante la carga de la tabla, agrupándolos por columnas. En función del tipo de datos de cada una de las columnas, selecciona el esquema de compresión más óptimo para cada una de ellas: Cada columna puede tener su propio esquema de compresión y aumentar-reducir de forma independiente. Gracias a esta eficiencia en la compresión, se obtiene una mejora significativa en los rendimientos al reducir la cantidad de datos a escanear, además de un ahorro en costes de almacenamiento, ya que Snowflake factura por la cantidad almacenada ya comprimida.

Microparticiones: Son unidades de almacenamiento contiguo en las que Snowflake va almacenando los datos en el orden de la ingesta. A diferencia de otros motores de bases de datos, en Snowflake no es necesario declarar una forma de particionar los datos por una o más columnas, sino que él ya lo hace de manera automática de la siguiente forma: Por un lado, va insertando los datos según le llegan en bloques de almacenamiento que oscilan entre los 50 y los 500MB antes de compresión (16MB aprox comprimidos). Cuándo se llena un bloque, pasa al siguiente, y así sucesivamente hasta que todos los datos son insertados. Snowflake también encripta tanto en tránsito cómo en destino todos los datos.

Cada una de estas particiones son inmutables: en el caso en el que haya una actualización en alguna de las microparticiones, lo que se hace es crear una nueva versión de la misma, y se mantienen las versiones antiguas por el tiempo parametrizado en el time travel (propiedad DATA_RETENTION_TIME_IN_DAYS en la tabla Snowflake). La inmutabilidad permite cosas cómo por ejemplo poder acceder a versiones de los datos en diferentes momentos del tiempo o hacer clonados de tablas sin tener que replicar los datos.

Metadatos en las microparticiones Snowflake

Para cada micropartición, Snowflake genera una metadata con la siguiente información:

A nivel columna

  • El rango de valores para cada una de las columnas de la micropartición.
  • Valores mínimo y máximo.
  • Conteo de valores diferentes.
  • Conteo de nulos.

A nivel tabla

  • Tamaño de tabla (en bytes).
  • Referencias de archivos y extensiones de tabla.
  • Conteo de filas.
  • Otras propiedades adicionales usadas tanto para la optimización cómo para el procesamiento de las queries.

Principales características del microparticionamiento de Snowflake

  • Automático y transparente para el usuario: A diferencia de otros sistemas tradicionales, no hay que declarar previamente un campo de partición, ni hacer un mantenimiento posterior.
  • Asegura la eficiencia en el podado tanto en las consultas, cómo en las operaciones DML.
  • Todas las particiones tienen un tamaño similar: En otros sistemas, el tamaño de las particiones depende del campo elegido, y puede haber un claro desbalance de particiones en función del número de ocurrencias que tenga cada valor del campo particionado (Hot partition Keys). El trade-off para tener estos tamaños similares es que pueden solaparse valores: Un determinado valor de columna (por ejemplo una fecha) puede estar en más de una micropartición. Cuánto mayor es el solapamiento en las particiones de un valor, menor será el podado, ya que habrá que recorrer más particiones para filtrar los valores correctos en una búsqueda.
  • Según Snowflake, este método de particionado automático sería suficiente para tablas con tamaños de hasta 1TB sin tener que plantearse otras opciones cómo por ejemplo el clusterizado.
  • En campos secuenciales cómo fechas o numéricos es dónde más vemos que se puede obtener un beneficio en esta forma de particionar, ya que si la inserción de los datos está ordenada por dichos campos, el podado (pruning) será altamente eficiente, y en consecuencia la cantidad de datos a escanear y la rapidez en la resolución de las queries.
  • El almacenamiento columnar permite que Snowflake solamente escanee aquellas columnas incluídas en la consulta. De ahí que sea importante incluir solamente las columnas que realmente necesitemos y evitar queries del tipo SELECT * si no es necesario consultar todas las columnas.

Entendiendo la organización de datos en Snowflake

Partiendo de los siguientes datos de ejemplo:

Ordenados por fecha. Al insertarlos en Snowflake, para ilustrar este ejemplo se supone que se generan dos microparticiones, que se van llenando en el orden en el que entran los datos:

Si por ejemplo, hacemos la siguiente query:

Select Fecha, sum(importe)

From ventas

Where fecha = ‘01/01/2022’

Snowflake recorrería los siguientes datos:

  • Primero se podan las microparticiones que no estén en el rango. En este caso, cómo estamos buscando el 1 de Enero, ignorará la segunda micropartición.
  • Dentro de la primera micropartición, dado que en la query solamente se están seleccionando las columnas fecha e importe de venta, no recorre la parte de los datos del cliente. Esto es posible gracias al almacenamiento columnar.

Si se buscan las ventas de un cliente específico:

Select sum(importe)

From ventas

Where cliente = ‘C2’

En este ejemplo, recorre las dos microparticiones, ya que C2 está dentro del rango de valores de ambas, aunque realmente C2 no está en la micropartición 1. Esto es lo que se comentaba en el apartado anterior de la posible dependencia que puede haber en la búsqueda de rangos en cada micropartición de cómo están distribuidos los datos.

DML’s en Snowflake

Para ver cómo funcionan las principales operaciones de DML en Snowflake, hemos reproducido el siguiente experimento: Creamos una nueva tabla, partiendo de una tabla origen que tiene las ventas de varios días de 60 call centers, seleccionando solamente los Call Center 1 y 20. Lo que haremos será operaciones atómicas de inserción, actualización y borrado para ver cómo se gestionan tanto los datos cómo los metadatos.

  • Inserción: Para comprobar cómo funciona la inserción insertamos dos nuevos registros con Call Center que no existen: El 10 y el 11.
    Los ficheros que componen las microparticiones son inmutables, por lo que Snowflake en la inserción puede ejecutar dos posibles acciones:
    • Crear un nuevo fichero con los registros existentes más el nuevo, y archivar el antiguo.
    • Crear una nueva partición para ese dato.
  • Actualización: Las acciones que realiza Snowflake para ejecutar una actualización son:
    • Identificar las microparticiones afectadas por la actualización.
    • Generar nuevos ficheros de micropartición que incluyan las modificaciones.
    • Archivar las versiones anteriores de los ficheros durante el tiempo marcado por el DATA_RETENTION_TIME_IN_DAYS.

Para verificar esto, partiendo del ejemplo anterior hemos lanzado una consulta que actualice los call center 10 y 11 a 15 por ejemplo. Comprobamos que efectivamente Snowflake solamente recorre esa partición, y genera un nuevo fichero con los nuevos valores, archivando el anterior:

Si se actualiza alguno de los otros dos call center, el número de particiones recorridas sería mayor, lo cuál implica que el coste de las operaciones DML también se ve afectado por la manera en que estén organizados los datos.

  • Borrado: Snowflake procede de manera similar a la actualización:
    • Identifica las microparticiones afectadas por el borrado.
    • Genera nuevos ficheros de micropartición dónde no aparezcan los registros eliminados.
    • Archiva las versiones anteriores de los ficheros durante el tiempo marcado por el DATA_RETENTION_TIME_IN_DAYS.

La importancia de entender cómo gestiona Snowflake estas operaciones es por las implicaciones que tiene a nivel rendimiento y almacenamiento. Sobre todo en el segundo caso, hay que tener en cuenta que si tenemos un alto número de días de retención en tablas (DATA_RETENTION_TIME_IN_DAYS) que se modifican frecuentemente, estaremos archivando muchas versiones de los datos que pueden incrementar considerablemente nuestro almacenamiento.

La principal ventaja es que Snowflake se encarga de todo este complejo mantenimiento siendo la gestión del almacenamiento transparente para el usuario.

En estos casos, para eficientar el almacenamiento es fundamental conocer los tres tipos principales de tablas que pone a nuestra disposición Snowflake, así cómo el concepto de Fail-Safe y Time-Travel:

Time-Travel: Periodo que, en función de la edición de Snowflake, (hasta un día en Standard y hasta 90 días en tablas permanentes a partir de edición Enterprise) permite almacenar todas las versiones por las que pasa una tabla, y habilita funcionalidades cómo poder restaurar datos en cualquier punto dentro de ese periodo, o hacer queries sobre un estado específico de los datos.

Fail-Safe: período de siete días durante el cuál se almacena cada versión de los datos en la que ha expirado su DATA_RETENTION_TIME_IN_DAYS y que permite la restauración de los mismos durante ese periodo pero solamente a través del soporte de Snowflake (Los usuarios no tienen acceso directo al Fail-Safe). Este periodo no es configurable y solamente está disponible en las tablas permanentes, cómo veremos a continuación.

Con estos dos conceptos claros, pasamos a describir los tres tipos principales de tablas en Snowflake:

  • Temporales: Solamente persisten durante la sesión, y no tienen Fail-Safe. Se puede definir Time-Travel de cero o 1 día.
  • Transitorias: A diferencia de las temporales, sí pueden persistir más allá de la sesión, pero solo permiten tener Time-Travel de hasta un día y tampoco incorporan Fail-Safe.
  • Permanentes: Igual que las transitorias, persisten más allá de una única sesión, pero permiten extender el Time-Travel hasta 90 días (siempre y cuándo se esté trabajando en una edición Enterprise o superior) e incorporan de caja el Fail-Safe (No configurable ni removible).

Por la naturaleza de cada una de las tablas, vemos que por ejemplo debemos tener en cuenta que si nuestra tabla se puede ver afectada por continuas operaciones DML de actualización-inserción, en el caso que tengamos una tabla permanente con un alto número de días de Time-Travel, nuestros costes de almacenamiento pueden verse incrementados.

La recomendación general para optimizar el almacenamiento es que se utilicen tablas temporales para tablas que simplemente utilicemos cómo tablas intermedias o staging, las transitorias para tablas permanentes que puedan ser fácilmente reproducibles desde fuera, y las permanentes para tablas críticas que tengan que estar siempre disponibles y que el coste de reprocesamiento en caso de desastre sería elevado.

Aspectos a tener en cuenta respecto al almacenamiento

  • Consultas por columnas no ordenadas en la inserción: Esta forma de particionar proporcional implica que haya solapes de valores en las diferentes microparticiones. En columnas de baja cardinalidad (por ejemplo con 2-3 valores diferentes) si los datos no están ordenados por esa columna y hacemos un filtro exclusivamente por dicha columna, hay que controlar el nivel de podado de microparticiones, porque puede pasar que esos 2-3 valores se encuentren en todas las particiones y que Snowflake no pueda podar ninguna. En estos casos, se recomienda para solucionarlo bien añadir al filtro un campo tipo fecha o numérico por el que estén ordenados los datos, o plantear la posibilidad de añadir una cluster key por dicho campo, que es uno de los servicios de optimización con los que cuenta Snowflake. Otra opción sería crear una vista tanto standard cómo materializada que ordene por ese campo.

    Ejemplo dónde queda evidenciado esto, es, lanzamos una consulta sobre una gran tabla de unos 14.000 millones de filas, cuyos datos están ordenados por fecha y cliente. En esta tabla, queremos consultar los diferentes tipos de envío que se han hecho. Si lanzamos la consulta sin filtro:

Primero vemos que se escanean las 49.448 microparticiones, lo cuál es lógico ya que no hemos incluído filtro alguno. Por otro lado, se escanean 13,58GB de los 770GB que tiene la tabla. Esto se debe a que en la query hemos incluído una única columna, y ya que Snowflake cómo hemos comentado almacena los datos de forma columnar y comprimida, solamente accede a los datos de la columna que consultamos.

Si aplicamos un filtro sobre la columna Call Center, que es un numérico que toma valores entre 1 y 60, y es un campo por el que no se ha ordenado en la inserción de los datos, y buscamos por ejemplo el call center número 20:

select distinct cr_ship_mode_sk from «SNOWFLAKE_SAMPLE_DATA».»TPCDS_SF100TCL».»CATALOG_RETURNS» where cr_call_center_sk = 20 

Vemos que efectivamente, apenas se han podado valores: De las 49,448 microparticiones, 49.447 tenían en su rango de call center el 20, con lo cuál ha habido que recorrerlas igualmente.

Sin embargo, si incluímos en el filtro uno de los campos de clusterizado, por ejemplo el código de cliente:

Vemos que sólo se ha recorrido un 10% aprox de las microparticiones, y el tiempo de query ha bajado de 1 minuto 45 segundos a 12 segundos. 

Con esto se puede concluir que el principal factor de rendimiento en las consultas es el número de bytes que tenga que escanear Snowflake el cuál viene principalmente determinado por el número de particiones a escanear, y la cantidad de datos de cada columna, y que si solamente incluimos en el filtro columnas por las que no estén ordenados los datos o no estén incluídos en la cluster key, en tablas de gran tamaño el rendimiento puede verse afectado. Es recomendable incluir en los filtros al menos uno de los campos de ordenación o de las cluster key para que las queries sean eficientes, o de no poder ser así, Snowflake nos proporciona otras alternativas para mejorar el rendimiento cómo las vistas materializadas, el cacheo o el search optimization service.

  • Búsqueda por rangos en las microparticiones: A la hora de podar microparticiones, Snowflake busca en la metadata si el valor buscado está en el rango de valores mínimo-máximo de la columna filtrada en la micropartición. Esto genera una dependencia a la hora de podar valores en base a cómo estén distribuidos dichos rangos en las microparticiones, lo cuál puede afectar a la cantidad de microparticiones podadas cuándo buscamos por columnas por las que no estén ordenados o clusterizados los datos: Por ejemplo, nos podemos encontrar casos dónde busquemos un valor que no existe, pero que por estar dentro del rango de valores en la metadata, obligue a Snowflake a recorrer igualmente todas las microparticiones.

En estos casos, Snowflake dice que en tablas con tamaños por debajo de 1TB la organización automática de datos debe ser suficiente para obtener buen rendimiento en las consultas.

Pruebas con Snowflake para entender cómo funciona el microparticionado y los metadatos asociados a las microparticiones

La tabla que se ha utilizado para estas pruebas contiene 100 millones de registros y seis columnas, dónde los datos se han distribuido en 49 particiones ocupando un total de 708MB (unos 14,5MB de media por micropartición). Los datos están ordenados por un campo de fecha.

Comentar que para estas pruebas, se ha utilizado la herramienta de Profiling de Snowflake, que está disponible desde el historial de queries. Hemos encontrado esta herramienta muy completa e intuitiva, y permite de un solo vistazo encontrar dónde se están generando los cuellos de botella en las queries, todo el plan de ejecución por el que pasa una query, así cómo las filas que salen de cada paso (lo cuál nos permite por ejemplo detectar cosas habituales de mal rendimiento cómo joins explosivos) y las microparticiones que se van podando en cada estado. Gracias a esta herramienta, hemos podido entender qué es lo que pasaba exactamente en cada una de las situaciones que hemos querido investigar y entender la gestión de Snowflake del almacenamiento.

Esta herramienta de profiling está disponible en el menú History de la UI, pinchando en la query que queramos analizar.

El objetivo de estas pruebas es entender la forma en la que Snowflake selecciona las microparticiones a recorrer y cómo de importante es la forma en la que se insertan los datos para mejorar el rendimiento en nuestras consultas, así cómo las columnas por las que se filtre.

En la tabla existe una columna, Call Center, dónde hay diferentes valores entre el 1 y el 60 pero con saltos (no están todos los posibles valores). Si hacemos una búsqueda por un call center específico de los que están:

Apreciamos que sea cuál sea el Call Center que incluyamos en el filtro siempre se recorren todas las microparticiones. La explicación es que Snowflake para determinar las microparticiones a recorrer, mira en la metadata de la columna Call Center si el valor buscado está dentro del rango, y en este caso, dónde los datos están ordenados por fecha, siempre se cumple que el valor está dentro del rango, por lo que tiene que recorrer todas las microparticiones.

Probamos a meter un nuevo registro de un Call Center con ID 11 que se sabe no aparece en los datos. Tras la inserción, el número de microparticiones se mantiene en 49, por lo que Snowflake ha debido generar un nuevo archivo que incluye el nuevo registro, y ha archivado la versión anterior de la micropartición.

Hacemos una búsqueda por ese Call Center, que a priori está en una única micropartición, y al revisar el Profile:

Se aprecia que Snowflake ha tenido que escanear las 49 microparticiones aunque se sabe que el valor 11 está en una micropartición específica. Esto confirma que Snowflake busca en base a rangos de valores por columna, y no conoce los valores específicos de una columna que hay en cada micropartición.

Para evidenciar aún más este hecho, insertamos un nuevo registro de Call Center que esté fuera del posible rango de búsqueda: Call Center con ID 61. Tras la inserción, verificamos que el número de particiones se mantiene, pero cuando se hace una búsqueda por ese valor:

Únicamente ha escaneado una micropartición. Esto se debe a que el 61 es un valor que está fuera del rango de la metadata del resto de las microparticiones, con lo cuál, ha podido saber que el Call Center 61 estaba en una única micropartición.

La siguiente comprobación es ver cómo Snowflake ejecuta la búsqueda de un valor de la columna Call Center que no está en los datos, pero sí en los posibles rangos de valores de la columna en las microparticiones. Por ejemplo, tenemos Call Centers 10, 11 y 13, pero no el 12. Si buscamos por el 12:

Cómo era de esperar, recorre todas las microparticiones, ya que el 12 entra en todos los posibles rangos de valores.

Para terminar de confirmar si Snowflake busca exclusivamente por rangos de valores, se crea una nueva tabla únicamente con los Call Center 1, 10 y 11. Esta nueva tabla tiene 8 microparticiones.

Si buscamos por el Call Center 5 (dentro de rango), recorre las 8 microparticiones aunque el Call Center no exista.

Si buscamos por el Call Center 12, directamente la metadata devuelve que ese Call Center no existe, y por tanto, no recorre ninguna micropartición.

Pero ahora, si buscamos por el valor 11, que recordemos fue una nueva inserción que metimos y justo está en el final del rango, en este caso Snowflake sí es capaz de podar el resto de microparticiones dónde no está el valor:

El motivo está en que se sabe que el resto de microparticiones tienen un rango 1-10, con lo cuál, la única que cumple estar en rango 1-11 es dónde verdaderamente está el valor. Sin embargo, en la otra tabla dónde era altamente probable que todas las microparticiones en la columna Call Center estuviesen en rango 1-60, ahí sí que tuvo que recorrerlas todas para saber dónde estaba el Call Center 11.

Conclusión de las pruebas:

Cuándo tengamos bajo rendimiento en consultas, hay dos indicadores principales a revisar en el profiling: Número de particiones escaneadas y cantidad de datos procesados.

Para mejorar la consulta, el objetivo es reducir el número de ambas: Para recorrer menos particiones hay que añadir filtros por campos en base a los cuáles se estén ordenando los datos (generalmente fechas o id’s numéricos) o replantearnos si ese campo es importante a la hora de filtrar, que los datos estén ordenados por dicho campo. Por supuesto, revisar también si las columnas que utilizamos en la consulta se pueden reducir. 

Si esto no es posible, tendríamos que plantearnos otras estrategias de optimización, cómo clusterizar la tabla en base a ese campo, utilización de cachés, ver si el caso de uso se ajusta a la utilización del search optimization service, o la utilización de vistas materializadas que pueden a su vez estar clusterizadas o no. El detalle de estas estrategias queda fuera del alcance de este artículo.

Principales conclusiones del funcionamiento del almacenamiento en Snowflake

  • El orden de inserción de los datos importa. Es recomendable insertar los datos ordenadamente en base a los filtrados más frecuentes que se vayan a hacer en la explotación.
  • Al almacenar de forma columnar los datos, el solamente seleccionar las columnas necesarias para la consulta reduce el número de bytes escaneados y por tanto el tiempo de resolución de consulta. Es recomendable evitar los SELECT * o añadir columnas innecesarias en las queries.
  • Es muy importante de cara al rendimiento seleccionar el tipo de datos más adecuado para cada columna, ya que Snowflake podrá reducir de manera más eficiente el tamaño de los datos, y esto se traduce en menores tiempos de escaneo, y por tanto de respuestas en las queries.
  • Para que las queries tengan un buen rendimiento, es aconsejable incluir un filtro de la columna por la que estén ordenados-clusterizados los datos y revisar en el profile de la query que tenga un buen porcentaje de poda de particiones.
  • En columnas de cardinalidad muy baja (1-10 valores diferentes), si hacemos búsquedas exclusivamente por ellas, y los datos no están ordenados o clusterizados por estas columnas, puede que no se poden particiones en las búsquedas. Con volúmenes de GB, el recorrer todas las particiones incluso con la talla más pequeña no perjudica el rendimiento y Snowflake maneja perfectamente, pero en volúmenes en el rango de centenas de GB, la diferencia entre tener o no la cluster key para buscar un valor en concreto, sí puede afectar en el número de bytes a escanear y por tanto en los tiempos de respuesta, con lo cuál es importante hacer un estudio de tiempos de consulta, para lo cuál Snowflake nos proporciona una potente herramienta de profiling, que a nosotros particularmente nos ha sido de gran utilidad para poder elaborar este artículo.

Entendiendo cómo Snowflake gestiona el almacenamiento a nivel inserción, actualización y borrado de datos y cómo se gestionan estos datos a la hora de realizar consultas, estaríamos en disposición de dar el siguiente paso que es entender todas las funciones avanzadas que proporciona Snowflake a nivel de optimización, compartición y seguridad-resiliencia en los datos. Éste será el objetivo de siguientes artículos.

Referencias

Documentación oficial de Snowflake https://docs.snowflake.com/en/

Navegación

Introducción

Objetivo

Introducción al almacenamiento

Objetivos del almacenamiento

Principales características del almacenamiento

Metadatos en las microparticiones

Principales características del microparticionamiento

Entendiendo la organización de datos

DML’s en Snowflake

Aspectos a tener en cuenta respecto al almacenamiento

Pruebas con Snowflake

Principales conclusiones

Referencias

Autores

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

Roberto García Parra

Technical Delivery Manager

SOLUCIONES, SOMOS EXPERTOS

DATA STRATEGY
DATA FABRIC
AUGMENTED ANALYTICS

Te puede interesar

Espiando a tu kubernetes con kubewatch

septiembre 14, 2020
LEER MÁS

¿Qué está pasando en el mundo de la AI?

marzo 6, 2023
LEER MÁS

Serverless Microservices

octubre 14, 2021
LEER MÁS

LakeHouse Streaming en AWS con Apache Flink y Hudi (Parte 2)

octubre 4, 2023
LEER MÁS

De documentos en papel a datos digitales con Fastcapture y Generative AI

junio 7, 2023
LEER MÁS

Databricks on Azure – An architecture perspective (part 2)

marzo 24, 2022
LEER MÁS

Publicado en: Blog, Practices, Tech

Databricks on Azure – An architecture perspective (part 2)

marzo 24, 2022 by Bluetab

Databricks sobre Azure - Una perspectiva de arquitectura (parte 2)

Francisco Linaje

AWS Solutions Architect

Gabriel Gallardo Ruiz

Senior Data Architect

En esta segunda entrega nos centraremos en analizar los diferentes servicios que ofrece Databricks para asegurar el escalado de nuestros servicios y la recuperación ante fallas del sistema, así como otros aspectos relativos a la seguridad como encriptación de los datos tanto reposo como en tránsito.

Primera entrega (link):

  • Arquitectura alto nivel
  • Planes y tipos de carga de trabajo
  • Networking
  • Identidad y Gestión de accesos

Segunda entrega:

  • Disaster Recovery
  • Escalabilidad
  • Seguridad
  • Logging y monitorización

Glosario

  • All Purpose Compute: Diseñado para entornos colaborativos en los que se recurra de forma simultánea al clúster por parte de Data Engineers y Data Scientist
  • Azure Data Lake: Permite almacenar múltiples formatos de datos en un mismo lugar para su explotación y análisis, actualmente Azure dispone la versión Gen2 .
  • Azure Key Vault: Servicio administrado de Azure que permite el almacenamiento seguro de secretos.
  • Azure Virtual Network (VNET): Red virtual aislada lógicamente en Azure.
  • DBFS (Databricks File Systen): Sistema de archivos de Databricks que se monta sobre los sistema de archivos distribuido de los Cloud Providers.
  • Data Lake: Paradigma de almacenamiento distribuido de datos provenientes de multitud de fuentes y formatos, estructurados, semi estructurados y sin estructurar.
  • Identity Provider (IdP): Entidad que mantiene la información de identidad de los individuos dentro de una organización.
  • Infraestructura como código o IaC: gestión y aprovisionamiento de la infraestructura a partir de código declarativo.
  • Jobs Compute: Enfocado a procesos orquestados mediante pipelines gestionados por data engineers que puedan conllevar autoescalado en ciertas tareas
  • Jobs Light Compute: Diseñado para procesos cuya consecución no sea crítica y no conlleve una carga computacional muy elevada
  • Network Security Group o NSG: Especifican las reglas que regulan el tráfico de entrada y salida de la red y los clusters en Azure
  • Private Link: Permite el acceso privado (IP privada) a Azure PaaS a través de tu VNET, de la misma forma que los service endpoints el tráfico se enruta a través del backbone de Azure.
  • SQL Compute: Cluster reservados a queries para la visualización de la información almacenada en el Data Lake
  • Secret scope: Colección de secretos identificados por un nombre.
  • Secure Cluster Connectivity (SCC):  Comunicación a través de túnel inverso SSH entre Control Plane y cluster. Permite no tener puertos abiertos ni IPs públicas en las instancias.
  • Security Assertion Markup Language (SAML): Estándar abierto utilizado para la autenticación. Basado en XML, las aplicaciones web utilizan SAML para transferir datos de autenticación entre dos entidades, el Identity Provider y el servicio en cuestión.
  • Service endpoints: Componente de red que permite conectar una VNET con los diferentes servicios dentro de Azure a través de la propia red de Azure.
  • TLS/ TLS1.2 (Transport Layer Security): es un protocolo de cifrado y comunicación que proporciona comunicaciones seguras por una red, comúnmente Internet.
  • Workspace: Entorno compartido para acceder a todos los activos de Databricks. En este se organizan los diferentes objetos (notebooks, librerias, etc…) en carpetas y se administran los accesos a recursos computacionales como clusters y jobs.

Disaster Recovery

Entendemos por Disaster Recovery al conjunto de políticas, herramientas y procedimientos que permiten la recuperación de la infraestructura cuando el sistema en su conjunto cae, como por ejemplo una caída de una región de Azure.

No debemos confundir estas políticas y herramientas con las empleadas en materia de alta disponibilidad de nuestro sistema (mínimo nivel de servicios).

Para ello, cuando implementamos una solución en la nube, una de las principales preguntas que debemos plantearnos a la hora de diseñar e implementar nuestra solución es:

  • ¿Qué piezas son críticas en nuestro sistema?
  • ¿Qué daños pueden provocar en el servicio?
  • ¿Cómo puede el sistema adaptarse y recuperarse ante estos errores?

Dar respuesta a estas preguntas es de vital importancia si deseamos que nuestra solución pueda cumplir adecuadamente el estándar de calidad que hayamos planteado.

Para este punto debemos analizar en que ámbito de nuestra solución opera Databricks y que herramientas o pautas debemos seguir para que la plataforma pueda cumplir con su servicio.

Debemos recordar que Databricks ofrece soluciones en materia de transformación y almacenamiento de datos tanto batch como en streaming, utilizando Azure Blob storage como capa de persistencia de datos no estructurados, como asimismo diferentes herramientas relacionadas con orquestación de jobs o análisis ad-hoc de datos vía SQL como servicio de analitica. Por lo tanto en este punto veremos que diferentes herramientas pueden ser propuestas para sincronizar nuestros workspaces,activos/recursos involucrados entre nuestras regiones.


Conceptos DR

Para poder comprender que es Disaster Recovery, deberemos primero comprender dos conceptos importantes:

Recovery Point Objective (RPO)

Hace referencia a la cantidad de datos máxima pérdida (medida en minutos) aceptable después de una caída del sistema. En este caso al disponer de Azure Blob Storage como sistema de persistencia distribuido, el concepto aplicaría a los datos de usuario temporales almacenados por Databricks, como por ejemplo cambios realizados en nuestros notebooks.

Recovery Time Objective (RTO)

Entendemos por RTO al periodo de tiempo desde la caída del sistema hasta la recuperación del nivel de servicio marcado.

En la siguiente imagen, podemos observar ambos conceptos de una forma visual:

Esquema conceptual sobre los conceptos RPO/RTO (fuente: Databricks)

Es importante indicar que la corrupción existente en los datos no se verá mitigada por las políticas asociadas a DR, sin embargo Databricks ofrece Delta time travel como sistema de versionado.

Tipos de región y redundancia

Una vez comprendido los conceptos de RPO y RTO, deberemos comprender los diferentes tipos de regiones en los que operará nuestra solución:

  • Región primaria: Región principal donde opera el sistema de forma normal.
  • Región secundaria: Región alternativa que entrará en operativa en caso de caída de la región primaria.

En nuestro caso de uso, estamos implementando un workspace de Databricks, por lo tanto emplearemos como capa de persistencia principal Blob Storage. Este servicio ofrece diferentes posibilidades a la hora de replicar nuestros datos entre regiones, vamos a verlas.

Region primaria

  • Almacenamiento con redundancia local (LRS): se realizan tres copias síncronas dentro de una única ubicación física en la región primaria, reduciendo así el coste, pero afectando a la disponibilidad y durabilidad (once nueves) de los datos.
Diagrama de replicación LRS (fuente: Azure)
  • Almacenamiento con redundancia de zona (ZRS): copia síncrona de los datos en tres zonas de alta disponibilidad en la región primaria (doce nueves).
Diagrama de replicación ZRS (fuente: Azure)

Region primaria y secundaria

  • Almacenamiento con redundancia geográfica (GRS): Se realiza una copia LRS en la región primaria y secundaria.
Diagrama de replicación GRS (fuente: Azure)
  • Almacenamiento con redundancia de zona geográfica (GZRS): Se realiza una copia con ZRS en la región primaria y mediante LRS en la región secundaria.
Diagrama de replicación GZRS (fuente: Azure)

En ambos casos, el acceso a los datos en la región secundaria no estará disponible salvo activación de la opción de lectura RA.

Dadas estas configuraciones, en la siguiente imagen se pueden ver los escenarios planteados en los que nuestros datos dejarían de ser accesibles.

Disponibilidad y acceso al dato según la redundancia configurada (fuente: Azure)

Deberemos configurar el nivel de replicación y redundancia entre zonas con el fin de disponer de nuestros datos sincronizados y disponibles en las regiones secundarias con el fin de que estás puedan estar operativas.


Tipos de despliegue

Dentro de los tipos de despliegue, podemos encontrar diferentes combinaciones según la necesidad de respuesta y los costes que deseamos asumir por su disponibilidad.

  • Activo: Despliegue principal que ejecuta las funcionalidad y servicios propios del sistema.
  • Pasivo: Procesos que no operan en el despliegue principal y permanecen inactivos/pasivos hasta que el despliegue activo deje de funcionar por una caída.

Es posible encontrar combinaciones de estos: activo-pasivo, activo-activo. De forma general:

Backup Restore
Es la estrategia más económica y lenta que podemos implementar. El objetivo principal es tener un conjunto de puntos de restauración en ambas regiones que podamos emplear para recuperar el servicio, sin necesidad de aprovisionar elementos core del sistema en otras regiones. 

Pilot Light 
Las piezas más importantes de nuestro sistema se encuentran desplegadas de forma activa pero bajo mínimos dentro de nuestra región secundaria, de forma que ante una caída del sistema los servicios principales podrían estar operativos y podrían escalarse de forma gradual (activo-pasivo).

Warn Standby
Estaríamos en un escenario muy similar a Pilot Light pero donde no solo tendríamos activos nuestros sistemas principales sino también una buena parte de los secundarios funcionando bajo mínimos pero listos para ser escalados (activo-pasivo).

Multi-site
Este plan ofrece el mayor grado de respuesta ya que implica disponer de forma activa todas nuestras piezas en una región secundaria, listas para dar servicio en caso de caída de la región principal (activo-activo)

Deberemos elegir la estrategia que mejor se adapte a nuestro caso de uso que dependerá principalmente del nivel de respuesta y coste asumible.


Workflow típico de recuperación

Dentro de los diferentes procedimientos, encontramos la estrategia activa-pasiva como la solución más sencilla y barata pero a la vez efectiva a la hora de ofrecer respuesta y servicio en el caso donde tras una caída del sistema en la región principal, el sistema pasivo entra en funcionamiento dando soporte al servicio.

La estrategia podría ser implementada de forma unificada para toda la organización o por grupos/departamentos de forma independiente basados en sus propias reglas y procedimientos.

De una forma global nos encontraremos que el procedimientos típico a alto nivel sería el siguiente:

  • Caída de un servicio crítico en la región primaria: red, origen de datos, etc
  • Se levanta el servicio en la segunda región si ésta no está afectada.
    • Se deben parar todas las actividades relacionadas con el workspace que sigan en funcionamiento en la región primaria y realizar un backup de los cambios recientes si es posible.
    • Se inicia el proceso de recuperación de los servicios sobre la región secundaria. Actualizando el enrutamiento y direcciones de dominio a la nueva región.
  • Se verifica que el servicio funciona correctamente y con normalidad.
  • En algún punto, la incidencia en la región primaria se ve resuelta y los servicios de Azure vuelven a un funcionamiento normal. Por lo tanto se deberá restablecer el sistema sobre la región primaria.
    • De forma idéntica al punto 2.a se deben parar todos los servicios y cargas de trabajo en la región secundaria.
    • Además se deben de volver a actualizar el enrutamiento y las direcciones de dominio a la región primaria.
    • Por último se debe de realizar un backup de los datos generados durante la caída de la región primaria para ser replicados en esta.
  • Finalmente se verifica que el servicio vuelva a funcionar correctamente y con normalidad en la región primaria.

Una vez nos hacemos una idea general de como sería un workflow típico de recuperación activo-pasivo, estudiaremos como podemos aplicarlo dentro de Databricks en nuestros workspaces.


Disaster Recovery en Azure Databricks

Databricks como plataforma de Data Analytics, tiene los datos como principal activo. Por ello se deben de definir las estrategias que permitan no solo poder seguir operando los servicios de la plataforma y workflows productivos en la región de soporte, sino la estrategia que permita generar consistencia en la propia replicación de los diferentes data sources.

En la siguiente imagen se especifican a modo de diagrama los diferentes activos que se verían involucrados en la replicación del plano de control o de datos.

Estrategia y herramientas en la sincronización.

Una vez realizado un análisis de nuestro sistema, deberemos analizar pieza por pieza como podemos realizar el procedimiento de réplica y sincronización.

Existen dos principales estrategias:

  • Un cliente que sincroniza los datos productivos y activos de la región primaria a la secundaria en un flujo programado.
  • Herramientas de integración/despliegue continuo (CI/CD) para el despliegue de forma paralela de la infraestructura, código y otros recursos principales del sistema en ambas regiones, de forma que la región secundaria se encuentre sincronizada con todos los cambios y desarrollos para ser operativa en caso necesario.
Esquema de sincronización vía CI/CD (fuente: Databricks)

Herramientas

Databricks ofrece en la siguiente tabla un resumen del conjunto de estrategias que se podrían aplicar según el recurso/activo involucrado de nuestro workspace. 

Es importante señalar que a día de hoy no hay ningún servicio oficial por parte de Databricks que permita administrar e implementar una política activa-pasiva de los workspaces en Azure.

Herramientas de replicación
FEATURE
Sync Client
CI/CD
Código fuente, notebooks, librerías
Sincronización con la región secundaria
Despliegue en ambas regiones
Usuarios y grupos
Empleo SCIM para la sincronización en ambas regiones
Control de los metadatos de los usuarios y grupos a través de GIT.
Configuración de los pools
Empleo del CLI o API para la creación en la segunda región
Empleo de templates. Configurar la región secundara con min_idle_instances a 0
Configuración de los jobs
Empleo del CLI o API para la sincronización con la segunda región
Empleo de templates. Configurar la región secundaria con concurrencia a 0
ACLs
Mediante la API de Permisos 2.0 es posible replicar los controles de acceso sobre los recursos copiados
Empleo de templates.
Librerias
DBFS
Repositorio central
Scripts de inicialización del cluster
Replicar de una región a otra a través del almacenamiento en el workspace
Repositorio central
Metadata
Incluir las DDL en el código fuente.
Secretos
Replicacion via API o CLI en el momento de creación
Configuraciones del cluster
Replicacion via API o CLI en el momento de creación
Empleo de templates en GIT.
Permisos de Notebooks, jobs y directorios
Replicación mediante la API de Permisos 2.0
Empleo de templates en GIT.

Implementación

Una vez, tenemos clara nuestra estrategia deberemos estudiar como podemos implementarla, para ello disponemos un conjunto de herramientas que van desde IaC, librerías de sincronización de data sources y migración de workspaces. Sin embargo, ninguna de las librerías de sincronizado/migración es oficial y aún se encuentran en desarrollo.

  • Módulo Databricks de Terraform [1]: para replicar la infraestructura, workspaces, metadatos, etc
  • Databricks Workspace Migration Tools [2]: paquete de librerías para generar un punto de restauración y migración de nuestros workspaces en otras regiones e incluso otros proveedores cloud.
  • Databricks Sync (DBSync) [3]: especializado en la sincronización, creación de copias de seguridad y  restauración de workspaces.

Escalabilidad

En este punto, veremos las diferentes opciones que ofrece Databricks en materia de escalabilidad, debido a que este punto ya ha sido tratado profundamente por nuestros compañeros dentro de la entrada Databricks sobre AWS – Una perspectiva de arquitectura (parte 2), nos limitaremos a comentar las características equivalentes en Azure.

Auto Escalado de workers

De la misma forma que en AWS, Databricks ofrece sobre Azure la posibilidad de escalar horizontalmente de una forma dinámica el número de workers dependiendo el mínimo y máximo que hayamos definido, permitiendo mejorar el tiempo de los trabajos sin sobre asignar recursos y por lo tanto reduciendo el coste global por trabajo en hasta un 30%.

Por lo general, en la forma tradicional cuando se definían las políticas de escalado para nuestros clusters se tenían que establecer una serie de umbrales estáticos donde si estos son rebasados se aprovisionan recursos extra, en forma de nodos de cómputo de bajo coste y efímeros (Spot). En muchos casos el escalado in/out de estos recursos no es lo suficientemente rápido, generando una ralentización global del job y una utilización subóptima de los recursos.

Para ello Databricks propone un  nuevo tipo de escalado optimizado [6], donde a partir de la información de los ejecutores es capaz de adaptar rápidamente los recursos del trabajo a sus necesidades de una forma rápida y eficiente, sin necesidad de esperar a que el trabajo completo termine para comenzar el desescalado.

Auto escalado tradicional: Ejecutores activos vs total de ejecutores (fuente: Databricks)
Auto escalado optimizado de Databricks: Ejecutores activos vs total de ejecutores (fuente: Databricks)

Caracteristicas:

  • Posibilidad de escalado desde el mínimo al máximo en dos pasos.
  • Posibilidad de desescalado aun cuando el cluster no está en idle viendo el shuffle file
  • Desescalado en base al porcentaje de nodos trabajando
  • En cluster del tipo job, el desescalado puede producirse si estos están infrautilizados tras 40 segundos, en all-purpose tras 150 segundos. 
  • Posibilidad de configurar la frecuencia de escalado mediante la propiedad spark.databricks.agressiveWindowDownS


Pools

Para reducir al máximo el tiempo de lanzamiento de una nueva instancia, Databricks permite mantener un set de clusters o pool pre-inicializado en estado idle listo para su empleo en nuestros trabajos o en los procesos de escalado. Si se llega al caso de que todo el pool de instancias se ha consumido, de forma automática se asignarán nuevas instancias al pool.

De la misma forma al escalado de los clusters, podremos definir un número máximo y mínimo de instancias que el pool podrá tener en estado idle para su posterior asignación al trabajo demandante y el tiempo que estas pueden permanecer desasignadas hasta su eliminación.

Respecto al tipo de instancias asignado al pool, no podrán cambiarse, tanto el driver como los workers del trabajo consumirán el mismo tipo de instancias.


Auto escalado del almacenamiento

Databricks ofrece la posibilidad de asignar un auto escalado en el almacenamiento local en disco del cluster con el fin de acotar la necesidad de dimensionado de estos. 

Databricks monitoriza el espacio libre en el disco de forma que en caso necesario se montará un disco externo sobre éste. Es importante señalar que estos discos una vez asignados no podrán desmontarse hasta que el cluster no sea eliminado, por ello se recomienda emplearlos en instancias Spot o que en instancias tengan una política de auto finalizado

Seguridad

Encriptación de datos databricks

Uno de los aspectos más importantes cuando vamos a seleccionar una plataforma para  el tratamiento de datos es la seguridad de los mismos. Debe ofrecer mecanismos de encriptación de datos tanto en los sistemas de almacenamiento, comúnmente conocido como datos en reposo (at rest), como cuando están en movimiento (in-transit).


En transito

Databricks encripta todos los datos que circulan por cada uno de sus diferentes componentes  y orígenes con TLS.  Además de la encriptación de datos, se encriptan con TLS todas las comunicaciones que se realizan entre el plano de control y el plano de datos, por tanto los comandos, consultas y meta-data viajan también encriptados.

Para plataformas que requieran un nivel alto de protección, se puede realizar la encriptación entre los nodos del cluster utilizando la encriptación RPC de Spark [7]. Está se realiza con cifrado AES de 128 bits a través de una conexión TLS 1.2. Está opción solo está disponible con el plan premiun y es necesario establecer los parámetros de configuración de Spark en el script de init del cluster o en el global si necesitamos que se aplique a todos los cluster del workspace. Es importante que tengamos en cuenta que la encriptación entre los nodos del cluster puede suponer una disminución en el rendimiento de los procesos y dado que la red privada de los nodos suele estar aislada, en la mayoría de los casos no será necesario este tipo de encriptación.

 
En reposo

Para el cifrado de los datos en reposo se utiliza SSE [8] (server-side encryption), cifra automáticamente los datos cuando se guardan en el almacenamiento distribuido (blob storage, ADLS y ADLS2).

Por defecto DBFS está encriptado usando claves administradas por Microsoft pero también permite la opción de usar claves administradas por el cliente, comúnmente conocidas como (CMK), permitiendo de este modo utilizar tu propia clave de cifrado para cifrar la cuenta de almacenamiento del DBFS. Además, tanto si se usa clave administradas como tu propia clave, también se ofrece la posibilidad de una capa adicional de cifrado utilizando un algoritmo/modo de cifrado diferente en la capa de infraestructura utilizando claves de cifrado administradas por la plataforma.

Para tener un completo cifrado de los datos en reposo, además del cifrado datos en el almacenamiento distribuido, se puede habilitar la encriptación de los disco locales de los nodos del clúster con lo que se permite la encriptación de los datos temporales que se guardan en las ejecuciones de los procesos. Actualmente está característica se encuentra en en versión preliminar pública y sólo está disponible para la creación del cluster desde el api REST utilizando la configuración siguiente:

{"enable_local_disk_encryption": true} 

También hay que tener en cuenta que activar esta opción puede suponer cierto impacto en el rendimiento de los procesos.

Diagrama de Arquitectura Databricks (fuente: Azure)

Logging

Para el correcto gobierno de una plataforma de ejecución de datos es necesario disponer de las herramientas necesarias para poder realizar el seguimiento y comprobación de ejecución de los workloads. Databricks integra en su plataforma todos elementos necesarios para realizar el mismo en un entorno de Spark. A continuación, vamos a resumir las opciones que integra Databricks out of the box aunque se pueden realizar monitorizaciones más avanzadas utilizando otras herramientas o servicios.

 

Cluster logs

Para cada uno de los cluster o job cluster creados en la plataforma podemos consultar de forma visual:

Captura Workspace - Sección Clusters

Event log: Se muestran todos los eventos relacionados con el ciclo de vida del cluster que han sucedido, como pueden ser, creación, terminación, cambios en la configuración…

Spark UI: Permite el acceso a la GUI ofrecida por Spark. Esta GUI es fundamental para poder detectar y solventar los problemas de performance en las aplicaciones de Spark.

Driver Logs : Permite ver los logs de ejecución tanto de la salida estándar , error y  log4j. Databricks también permite que se realice el volcado de logs en un filesystem determinado, para ellos es necesario configurarlo en las opciones avanzadas del cluster o indicándolo en la creación del cluster si se realiza desde crea desde API o CLI.

Metrics: Databricks proporciona acceso a Ganglia Metrics para obtener un mayor detalle del rendimiento que está ofreciendo el cluster

Captura Workspace - Ganglia Metrics

Registro de diagnóstico en Azure Databricks 

Azure Databricks nos ofrece la posibilidad de descargar los registros de las actividades realizadas por los usuarios a través del registro de diagnóstico [9]. Activando esta opción se enviarán los registros de la actividad de usuario a un destino seleccionado, Azure tiene disponibles 3 opciones para el envío de los registros: Cuenta de Almacenamiento, Event y Log Analytics.

Estos son los servicios que se pueden seleccionar para obtener registros de diagnóstico.

SERVICIOS DISPONIBLES PARA DIAGNÓSTICO
DBFS
sqlanalytics
modelRegistry
clusters
genie
repos
accounts
globalInitScripts
unityCatalog
jobs
iamRole
instancePools
notebook
mlflowExperiment
deltaPipelines
ssh
featureStore
sqlPermissions
workspace
RemoteHistoryService
databrickssql
secrets
mlflowAcledArtifact

La activación se puede realizar desde Azure Portal, API REST, CLI, ó powershell. Los registros están disponibles en un plazo de 15 minutos después de la activación.

Este sería el esquema de un registro de diagnóstico de salida 

Campo Descripción
operationversion
Versión del esquema del formato del registro de diagnóstico.
time
Marca de tiempo UTC de la acción.
properties.sourceIPAddress
Dirección IP de la solicitud de origen.
properties.userAgent
Explorador o cliente de API usado para realizar la solicitud.
properties.sessionId
Identificador de sesión de la acción.
identities

Información sobre el usuario que realiza las solicitudes:
* * : dirección de correo electrónico del usuario.

category
Servicio que registró la solicitud.
operationName
La acción, como el inicio de sesión, el cierre de sesión, la lectura, la escritura, etc.
properties.requestId
Identificador de solicitud único.
properties.requestParams

Pares clave-valor de parámetro usados en el evento.

El requestParams campo está sujeto a truncamiento. Si el tamaño de su representación JSON supera los 100 KB, los valores se truncan … truncated y la cadena se anexa a las entradas truncadas. En raras ocasiones, cuando un mapa truncado sigue siendo mayor que 100 KB, TRUNCATED en su lugar hay una sola clave con un valor vacío.

properties.response
Respuesta a la solicitud: * * : mensaje de error si se ha producido un error. * * : resultado de la solicitud. * * : código de estado HTTP que indica si la solicitud se realiza correctamente o no.
properties.logId
Identificador único de los mensa jes de registro.

Tabla Esquema Registro Salida (fuente: Azure)

Para la explotación de los registros, si se ha seleccionado la opción de Logs Analytics, podremos explotarlos de forma sencilla utilizando Azure Monitor. Pero si lo que se desea es explotar estos registros con cualquier otra plataforma, servicio o herramienta es posible tomando estos registros JSON del lugar del envio seleccionando en la activación.

Referencias

[1] Databricks Terraform Provider. [link]

[2] Databricks Workspace Migration Tools. [link]

[3] Databricks Sync. [link]

[4] Databricks Disaster Recovery [link]

[5] Cifrado entre nodos de trabajo[link]

[6] Optimized AutoScaling [link]

[7] Spark Security [link]

[8] Azure encriptación discos [link]

[9] Registro de diagnostico [link]

Navegación

Glosario

Disaster Recovery

Escalabilidad

Seguridad

Logging

Referencias

Autores

Do you want to know more about what we offer and to see other success stories?
DISCOVER BLUETAB

Francisco Linaje

AWS Solutions Architect

Gabriel Gallardo Ruiz

Senior Data Architect

SOLUTIONS, WE ARE EXPERTS

DATA STRATEGY
DATA FABRIC
AUGMENTED ANALYTICS

You may be interested in

Hashicorp Boundary

diciembre 3, 2020
READ MORE

MDM como ventaja competitiva en las organizaciones

junio 18, 2024
READ MORE

Gobierno de Datos: ¿tendencia o necesidad?

octubre 13, 2022
READ MORE

Databricks sobre Azure – Una perspectiva de Arquitectura (parte 1)

febrero 15, 2022
READ MORE

Mi experiencia en el mundo de Big Data – Parte II

febrero 4, 2022
READ MORE

Algunas de las capacidades de Matillion ETL en Google Cloud

julio 11, 2022
READ MORE

Publicado en: Blog, Blog, Destacado, Practices, Tech

Databricks on Azure – An Architecture Perspective (part 1)

febrero 15, 2022 by Bluetab

Databricks on Azure - An architecture perspective (part 1)

Francisco Linaje

AWS Solutions Architect

Gabriel Gallardo Ruiz

Senior Data Architect

Databricks aims to provide an intuitive environment for the non-specialist users to develop the different functions in data engineering and data science, also providing a data governance and management layer.
Our goal with this article is not focus so much to describe and analyze how to use these tools, but to see how they are integrated from an architectural point of view within the Azure provider.

Databricks as a Lakehouse solution

The Databricks platform follows the Lakehouse paradigm, in which the benefits of the Data Warehouse are combined with those of the Data Lake, allowing to have a good performance both in its analytical queries thanks to indexing, and transactionality through Delta Lake, without losing the flexibility of an open and scalable data architecture, along with better data governance and access to the resources and services of the lake, allowing in a general way to have a less complex and more integrated architecture.

This article will be divided into two deliverys.

  • The first one, will explain how Databricks organizes and deploys its product on Azure, as well as the different configurations in terms of communication/security between Databricks and other Azure services.
  • The second, will be focused on the data security layer and scalability of the infrastructure as well as monitoring, deployment and failover.

First delivery:

  • Architecture Overview
  • Workload types and plans
  • Networking
  • Identity and Access Management

Second delivery (coming soon):

  • Disaster Recovery
  • Encryption
  • Scalability
  • Logging and monitoring
  • Deployment

Glossary

  • Azure Data Lake: Allows to store multiple data formats in the same place for its exploitation and analysis, currently Azure has the Gen2 version.
  • All Purpose Compute: Designed for collaborative environments in which the cluster is used simultaneously by Data Engineers and Data Scientist.
  • Azure Key Vault: Azure managed service that enables secure storage of secrets.
  • Azure Virtual Network (VNET): Logically isolated virtual network in Azure.
  • Azure role-based access control (RBAC): Authorization system integrated into Azure Resource Manager that allows you to assign granular permissions on resources to Azure users.
  • Continuous integration and continuous delivery CI/CD: A set of automated tools and guidelines for continuous integration and production start-up.
  • Data Lake: Paradigm of distributed storage of data from a multitude of sources and formats, structured, semi-structured and unstructured.
  • Identity Provider (IdP): Entity that maintains the identity information of individuals within an organization.
  • Jobs Compute: Focused on processes orchestrated through pipelines managed by data engineers that may involve auto-scaling in certain tasks.
  • Jobs Light Compute: Designed for processes whose achievement is not critical and does not involve a very high computational load.
  • Network Security Group or NSG: Specifies the rules that regulate inbound and outbound network traffic and clusters in Azure.
  • Notebook: Web interface to execute code in a cluster, abstracting from the access to it.
  • PrivateLink: Allows private access (private IP) to Azure PaaS through your VNET, in the same way that service endpoints traffic is routed through the Azure backbone.
  • Security Assertion Markup Language (SAML): Open standard used for authentication. Based on XML, web applications use SAML to transfer authentication data between two entities, the Identity Provider and the service in question.
  • Secure Cluster Connectivity (SCC): SSH reverse tunnel communication between Control Plane and cluster. It allows not having open ports or public IPs in the instances.
  • Service endpoints: Network component that allows connecting a VNET with the different services within Azure through Azure’s own network.
  • Service Principal: Entity created for the administration and management of tasks that are not associated to a particular member of the organization but to a service.
  • Secret scope: Collection of secrets identified by a name.
  • Single Sign On (SSO): Allows users to authenticate through an Identity Provider (IdP) provided by the organization, requiring SAML 2.0 compatibility.
  • Workspace: Shared environment to access all Databricks assets. It organizes the different objects (notebooks, libraries, etc…) in folders and manages access to computational resources such as clusters and jobs.

Architecture

Databricks as a product

Databricks remains integrated within Azure as its own service unlike other providers, allowing the deployment in a more direct and simple way either from the console itself or through templates.

Among the services offered by Databricks, the following stand out:

  • Databricks SQL: offers a platform to perform ad-hoc SQL queries against the Data Lake, as well as multiple visualizations of the data with dashboards.
  • Databricks Data Science & Engineering: provides a workspace that allows collaboration between different roles (data engineers, data scientists, etc.) for the development of different pipelines for the ingestion and exploitation of the Data Lake.
  • Databricks Machine Learning: provides an environment for the development and exploitation of end-to-end machine learning models.

Databricks also offers Spark as a distributed programming framework, as well as integration with Delta Lake and its support for ACID transactions for structured and unstructured data, unification of batch sources and streaming.

Databricks also offers a solution in terms of orchestration and deployment of jobs in a productive way, allowing parallelism between them, up to 1000 concurrently. It can be used only within the Data Science & Engineering workspace.

Orchestrated process diagram (source: Databricks)

Among the added benefits offered by Databricks is the use of Databricks File System (DBFS), a distributed file system for cluster access.

  • It allows mounting storage points to access objects without the need for credentials.
  • It avoids the need to use urls to access objects, facilitating access via directories and semantics.
  • It provides a layer of persistence by storing data in the file system, preventing it from being lost when the cluster is terminated.

Databricks Repos: offers integration and synchronization with GIT repositories, including an API for the use of CI/CD pipelines. Current Git providers included are:

  • GitHub
  • Bitbucket
  • GitLab
  • Azure DevOps

 

Architecture Overview

In this section we will discuss how Databricks is deployed within the customer’s account in their cloud provider, in this case Azure.

Databricks is primarily composed of two layers; a Control Plane (internal) and a Data Plane (external/client).

High level diagram of the architecture (source: Databricks)

In the previous image we can see how the Control Plane remains in the databricks subscription, under its control, design and internal administration being shared by all users.
The main services contained are:

  • Notebooks: All notebooks, results and configurations remain encrypted.
  • Job Scheduler
  • Rest API
  • Metastore: Hive metastore managed by databricks
  • Cluster manager: Requests virtual machines for clusters to be launched on the Data Plane.

The Data Plane is inside the customer’s subscription and will therefore be managed by him. In this layer we find the jobs and clusters used for the execution of the ETLs, as well as the data used in them.

It is important to note that Databricks provides two network interfaces in each deployed node, one of them will route the traffic to the Control Plane and the other one will route the internal traffic between nodes (driver – executors).

Databricks offers two main methods to deploy the Data Plane, which we will discuss in depth later:

  • On the one hand we have Databricks managed VNET, this being the deployment given by default where Databricks takes care of deploying the necessary resources within the client account.
  • On the other hand we have a second type of deployment Databricks VNET injection where the client is the one that provides the minimum resources necessary for the correct operation and communication against the control-plane.

In both cases, the network topology in the Data Plane will be composed of two subnets.

  • Container subnet or «private» subnet.
  • Host subnet or «public» subnet.
Databricks architecture in Azure (source: Databricks)

Secure Cluster Connectivity [2]

In more restrictive security contexts, it will be possible to assign a NAT gateway or other egress traffic devices such as a load balancer, firewall, etc, as a gateway to eliminate the need to assign public IP addresses to hosts.

Workspace connection with SCC (source: Databricks)

Workload plans and types

In addition to the cost of the infrastructure used for processing and storage in Azure, Databricks performs a load expressed in DBU (processing units) depending on the type of instance lifted and its size, as well as the type of workload used. We distinguish 2 main types:

  • Jobs Cluster: for execution of scheduled non-iterative pipelines, distinguished according to the size of the provisioned cluster into light or normal.Jobs are usually used by creating ephemeral clusters and being deleted after the execution of the jobs.
  • All purpose: Clusters used to work iteratively (MANDATORY for this use) allowing to run and develop different notebooks concurrently.

In addition, depending on the type of Standard or Premium account contracted, additional charges will be made on the cost of the DBU.

AZURE PLAN
Standard
Premium
One platform for your data analytics and ML workloads
Data analytics and ML at scale across your business
Job Light Compute
$0,07/DBU
$0,22/DBU
Job Compute
$0,15/DBU
$0,30/DBU
SQL Compute
N/A
$0,22/DBU
All-Purpose Compute
$0,40/DBU
$0,55/DBU

Imputed cost per DBU for computational and architectural factors

WORKLOAD TYPE (STANDARD TIER)
FEATURE
Jobs Light Comput
Jobs compute
All-purpose compute
Managed Apache Spark
Job scheduling with libraries
Job scheduling with notebooks
Autopilot clusters
Databricks Runtime for ML
Managed MLflow
Delta Lake with Delta Engine
Interactive clusters
Notebooks and collaboration
Ecosystem integrations

Characteristics by type of workload Standard plan

WORKLOAD TYPE (STANDARD TIER)
FEATURE
Jobs Light Comput
Jobs compute
All-purpose compute
Role Based Access Control for clusters, jobs,
notebooks and tables
JDBC/ODBC Endpoints Authentication
Audit Logs
All Standard Plan Features
Azure AD credential passthrough
Conditional Authentication
Cluster Policies
IP Access List
Token Management API

Features by workload type Premium plan

It is important to note that it is also possible to obtain discounts of up to 37% in the prices per DBU, by making purchases of these (DBCU or Databricks Commit Units) for 1 or 3 years.

Networking

In this section we will explain the two different types of deployment discussed above and their peculiarities in terms of connection and access to the Control Plane, as well as incoming/outgoing traffic control.

Network managed by Databricks

In this alternative, Azure allows Databricks to deploy the Data Plane over our subscription, making available the resources that will allow the connection against the Control Plane and the deployment of jobs, clusters and other resources.

  • The communication between the Data Plane and the Control Plane, regardless of having Secure Cluster Connectivity (SCC) enabled, will be done through Azure’s internal backbone, without routing traffic over the public network.
  • Secure Cluster Connectivity (SCC) can be enabled to work without public IPs.
  • The inbound/outbound traffic of the clusters will be controlled by different rules by the network security group NSG that cannot be modified by the user.


Customer managed network (VNET injection) [1]

Databricks offers the possibility of being able to deploy the Data Plane over our own VNET managed by us. This solution offers greater versatility and control over the different components of our architecture.

  • The communication between the Data plane and Control Plane will be done over the internal Azure backbone in the same way as in the network managed by Databricks seen above, also in the same way we can activate SCC.
  • In this case when owning our own VNET, we will have control over the rules defined in our NSGs.
NSG provisioned by Databricks by delegation on the customer's VNET (source: Databricks).
  • You must be the owner of the VNET to allow Databricks to be delegated its configuration or resource deployment [3].
  • We will be able to enable any architecture component we consider within our VNET as it will be managed by us:
    • Connect Azure Databricks to other Azure services in a more secure way employing service endpoints or private endpoints.
    • Connect to your on-premise resources using user-defined routes.
    • Allows you to deploy a virtual network appliance to inspect traffic.
    • Custom DNS
    • Custom egress NSG rules
    • Increase the CIDR range of the network mask for the VNET between /16 – /24 and /26 for the subnets.
Diagram of connection via PrivateLink with native Azure services (source: Databricks)

Among the peculiarities of both deployments, it is important to point out:

  • It is not possible to replace an existing VNet in a workspace with another one, if it was necessary a new workspace, a new VNET must be created.
  • It is also not possible to add SCC to the workspace once it has already been created, if it was necessary, the workspace must also be recreated.

 

Connections against the Control Plane

Databricks Control and Data Plane connection (source: Databricks)

As we have previously discussed, all communication with the Control Plane is done inside the Azure backbone by default [2]. It should also be noted:

  • At the network level, any connection made against the Control Plane when creating a cluster in the Data Plane is made via HTTPS (443) and over a different IP address than the one used for other Web application services or APIs.
  • When the Control Plane launches new jobs or performs other cluster administration tasks, these requests are sent to the cluster through this reverse tunnel.
  • To make connections between the Control and Data Plane, a public IP address will be enabled on the public subnet even if the traffic is subsequently routed within the backbone, and no ports will be left open or public IP addresses will be assigned on the clusters.
  • If in our use case more restrictive security conditions must be used, Databricks offers the possibility to activate the secure cluster connectivity option or , allowing to remove all public IP addresses to make the connection between the control and Data Plane, for this purpose will be used:
    • By default in the network managed by Databrics (managed VNET) a NAT is enabled to be able to perform this communication.
    • If the customer deploys the infrastructure on its own network (VNET Injection deployment) it must provide a network device for outgoing traffic, which could be a NAT Gateway, Load Balancer, Azure Firewall or a third party device.

Identity and Access Management

Databricks offers different tools to manage access to our Azure resources and services in a simple and integrated way in the platform itself.

We can find tools such as IP filtering, SSO, usage permissions on Databricks services, access to secrets, etc.

IP access lists

Databricks allows administrators to define IP access lists to restrict access to the user interface and API to a specific set of IP addresses and subnets, allowing access only from the organization’s networks, and administrators can only manage IP access lists with the REST API.

Single sign on (SSO)

Through Azure Active Directory we will be able to configure SSO for all our Databricks users avoiding duplication in identity management.

System for Cross-domain Identity Management (SCIM)

Allows through an IdP (currently Azure Active Directory) to create users in Azure Databricks and grant them a level of permissions and stay synchronized, you must have a PREMIUM plan. If permissions are revoked the resources linked to this user are not deleted.

Access to resources

The main access to the different Databricks services will be given by the entitlements where it will be indicated if the group/user will have access to each one of them (cluster creation, Databricks SQL, Workspaces).

On the other hand, within Databricks ACLs can be used to configure access to different resources such as clusters, tables, pools, jobs and workspace objects (notebooks, directories, models, etc). Granting this granularity on access to resources is only available through the PREMIUM plan, by default all users will have access to resources.

These permissions are managed from the administrator user or other users with delegated permissions.

There are 5 levels of permissions with their multiple implications depending on the resource to which they apply; No permissions, can read, can run, can edit, can manage.

The permissions associated with the resource to be used are indicated below. If two policies may overlap, the more restrictive option will take precedence over the other.

Permissions according to the level associated to the user on the workspace directories (Source: Databricks)
Permissions according to the level associated to the user on the notebook (Source: Databricks)
Permissions according to the level associated to the user on the repository (Source: Databricks)


Azure Datalake Storage

Through Azure Active Directory (Azure AD) you can authenticate directly from Databricks with Azure Datalake Storage Gen1 and 2, allowing the Databricks cluster to access these resources directly without the need of a service principal. Requires PREMIUM plan and enable credential passthrough in advanced options at the time of cluster creation in Databricks. Available in Standard and High Concurrency clusters.

Enabling credentials passthrough in the cluster configuration options (Source: Databricks)

Credential passthrough is an authentication method that uses the identity (Azure AD) used for authentication in Databricks to connect to Datalake. Access to data will be controlled through the RBAC roles (user level permissions) and ACLs (directory and file level permissions) configured.

Access control lists (ACLs) control access to the resource by checking if the entity you want to access has the appropriate permissions.

 

Secrets [5].

Access

By default, all users regardless of the contracted plan can create secrets and access them (MANAGE permission). Only through the PREMIUM plan it is possible to configure granular permissions to control access. The management of these can be done through Secrets API 2.0 or Databricks CLI (0.7.1 onwards).

Secrets are managed at the scope level (collection of secrets identified by a name), specifically an ACL controls the relationship between the principal (user or group), the scope and the permission level. For example: when a user accesses the secret from a notebook via Secrets utility the permission level is applied based on who executes the command.

By default, when a scope is created a MANAGE permission level is applied to it, however the user who creates the scope can add granular permissions.

We distinguish 3 permission levels in Databricks-backed scopes:

  • MANAGE: can modify ACLs and also has read and write permissions on the scope.
  • WRITE: has read and write permissions on the scope.
  • READ: only has read permissions on the scope and the secrets to which it has access.

The administrator users of the workspace have access to all the secrets of all the scopes.

Storage

The secrets can be referenced from the scopes that in turn will reference their respective vaults where the secrets are stored.

There are two types of storage media for secrets:

  • Databricks-backed
  • Azure Key Vault

We can use Databricks-backed as a storage medium for the secrets without the need for a PREMIUM plan, however either to use Azure Key Vault or on the other hand the use of granular permissions in both cases, it will be necessary to hire the PREMIUM plan.

It is important to note that if the Key Vault exists in a different tenant than the one hosting the Databricks workspace, the user creating the scope must have permissions to create service principals on the tenant’s key vault, otherwise the following error will be thrown.

Unable to grant read/list permission to Databricks service principal to KeyVault 

Because Azure Key Vault is external to Databricks, only read operations will be possible by default and cannot be managed from the Secrets API 2.0, Azure SetSecrets REST API or from the Azure UI portal must be used instead.

It is important to note, that all users will have access to the secrets of the same KEY VAULT even if they are in different scopes, it is considered good practice to replicate the secrets in different Key Vaults according to subgroups even if they may be redundant.

Now with RBAC [4] (role-based access control) it is possible to control the access to the secrets of the Vault that have this service activated through different roles, these roles must be assigned to the user.

The scopes can be consumed from the dbutils library, if the value is loaded correctly it appears as REDACTED.

dbutils.secrets.get(scope = "scope_databricks_scope_name", key = "secret_name") 

On-premise connections

Finally, it is necessary to comment that it is also possible to establish an on-premise connection for our Data Plane in Azure, for this it is essential that it is hosted in our own network (VNET injection).

Databricks architecture in Azure (source: Databricks)

Azure defines as the main method to establish this on-premise connection using Transit Virtual Network, following these steps:

  1. Create a Network Gateway (VPN or ExpressRoute) between the transit network and on-premise, for this we must create both the Customer Gateway on the on-premise side and the Virtual Gateway on the Azure side.
  2. Establish the peering between the Data Plane and the transit network. Once the peering is established Azure Transit configures all the routes, however the return routes to the Control Plane for the Databricks clusters are not included, for this the user-defined routes should be configured and associated to the subnets of the Data Plane.

Other alternative solutions could also be employed through the use of Custom DNS or the use of a virtual appliance or firewalls.

Referencias

[1] Customer-managed VNET Databricks guide. [link] (January 26, 2022)

[2] Secure Cluster Connectivity. [link] (January 26, 2022)

[3] Subnetwork Delegation. [link] (January 3, 2022)

[4] Role-based access control [link] (October 27, 2021)

[5] Databricks secret scopes [link] (January 26, 2022)

Navegation

Glossary

Architecture

Workload plans and types

Networking

Identity and Access Management

References

Authors

Do you want to know more about what we offer and to see other success stories?
DISCOVER BLUETAB

Francisco Linaje

AWS Solutions Architect

Gabriel Gallardo Ruiz

Senior Data Architect

SOLUTIONS, WE ARE EXPERTS

DATA STRATEGY
DATA FABRIC
AUGMENTED ANALYTICS

You may be interested in

Databricks sobre Azure – Una perspectiva de Arquitectura (parte 2)

marzo 24, 2022
READ MORE

Starburst: Construyendo un futuro basado en datos.

mayo 25, 2023
READ MORE

Detección de Fraude Bancario con aprendizaje automático II

septiembre 17, 2020
READ MORE

Entrena y Despliega tu Modelo de Machine Learning en 15 Minutos con Databricks

junio 10, 2025
READ MORE

Cómo depurar una Lambda de AWS en local

octubre 8, 2020
READ MORE

LakeHouse Streaming on AWS with Apache Flink and Hudi (Part 2)

octubre 4, 2023
READ MORE

Publicado en: Blog, Outstanding, Practices, Tech

Cómo preparar la certificación AWS Data Analytics – Specialty

noviembre 17, 2021 by Bluetab

Cómo preparar la certificación AWS Data Analytics - Specialty

Sergi Lehkyi

Data Engineer

Sobre la certificación

El examen de especialidad AWS Data Analytics se centra principalmente en los servicios de AWS relacionados con datos, cubriendo los dominios:

  • Recolección de datos.
  • Gestión de datos y almacenamiento.
  • Procesamiento.
  • Análisis y visualización.
  • Seguridad.

El examen trata en profundidad los siguientes servicios de AWS:

  • Amazon S3
  • Amazon Redshift
  • Amazon Kinesis
  • Amazon EMR
  • Amazon ElasticSearch
  • Amazon Athena
  • AWS Glue
  • Amazon QuickSight
  • AWS Lake Formation
  • Amazon Managed Streaming for Apache Kafka (Amazon MSK)

*Todos los servicios de datos que puedan interactuar con los anteriores (SageMaker, Backup, Glacier, GuardDuty etc.)

Se puede encontrar más en la página web de [AWS Datalakes and Analytics], también aquí está la [guía oficial del examen] y [preguntas de muestra].

Todo el contenido de este artículo es válido para el examen de 2021. Recomendamos que revises si existe alguna actualización del examen.


Sobre el coste

El coste del examen es de $300 (€270 + 21% IVA si estás en España). Además, si ya has realizado otros exámenes de AWS, obtienes un 50% de descuento en el siguiente, por lo que éste examen sale por solo €163,35. Antes de la prueba oficial se puede hacer un examen de práctica que cuesta $40 (€36 + 21% de IVA si estás en España) y nuevamente, si ya obtuviste cualquier otra certificación de AWS, el coste es cero.


Dónde y cómo

Puedes realizar el examen en línea o ir a un centro de pruebas. Recomendamos ir a un centro de pruebas oficial, principalmente por la estabilidad de la conexión a internet. Es obligatorio el uso de mascarilla en la realización presencial. Sobre la documentación, es importante presentar dos acreditaciones.


Preparación

Con aproximadamente 2 horas al día (seis días a la semana) se puede preparar para el examen en un periodo de 6-8 semanas, siempre contando con experiencia previa en los servicios mencionados.

Recursos útiles durante la preparación:

[AWS Certified Data Analytics Specialty Exam Study Path from Tutorials Dojo] – es una lista bastante completa de temas que debes verificar antes del examen, con algunas preguntas de muestra, hojas de referencia para los servicios de análisis y algunos escenarios comunes en las preguntas del examen.

[AWS Analytics Overview] – un documento técnico con la descripción general de todos los servicios de análisis de AWS.

[Data Lakes and Analytics] – otra descripción general de los servicios de análisis de AWS.

[Data Analytics Fundamentals] – curso oficial de AWS, recomendable para comenzar la preparación.

[Exam Readiness: AWS Certified Data Analytics – Specialty] – curso oficial de AWS que te ayudará a cubrir todos los temas cubiertos en el examen.

[Visualizing with QuickSight] – un plan de estudio para mejorar tu comprensión sobre QuickSight. Imprescindible para dominar la parte de visualización del examen.

[AWS Hadoop Fundamentals] – aunque está un poco fuera de alcance, ayuda a comprender mejor Hadoop y cómo se integra en AWS EMR. Si conoces perfectamente Hadoop, este curso no es necesario.

Otros cursos de [AWS Training] cubriendo S3, RDS, SageMaker, etc. serán buenos para expandir su conocimiento ya que hay algunas preguntas que tocan estos temas y los cursos son realmente breves, pero informativos. En especial, echa un vistazo a todo lo relacionado con S3.

[Tutorials Dojo’s AWS Certified Data Analytics Specialty Practice Exams 2021] – esencial para poner a prueba tus conocimientos y descubrir dónde están tus puntos débiles. También es un buen indicador de su preparación para el examen; es recomendable obtener un 90% antes del examen.


Conclusión

Los exámenes de certificación de AWS necesitan esfuerzo y dedicación pero además, tener experiencia práctica ayuda mucho para enfrentarte a los mismos. Por ejemplo, si trabajas habitualmente con AWS Glue, seguramente no te hará falta estudiar mucho acerca de este servicio porque ya conoces sus capacidades y funcionalidad a partir de tu trabajo del día a día. Muchas veces esta experiencia es más relevante de cara al examen que revisar algunos posibles escenarios teóricos: si te has enfrentado directamente con los problemas estarás mucho más seguro de cara al examen.

Espero que este pequeño resumen te ayude a preparar la certificación y consigas mejorar tus capacidades profesionales en el análisis de datos. Anímate a realizarla y veras que, aunque requiere esfuerzo, es una meta perfectamente alcanzable.

¿Quieres saber más de lo que ofrecemos y ver otros casos de éxito?
DESCUBRE BLUETAB
Sergi Lehkyi
Data Engineer

En mi camino profesional he pasado por desarrollo web, administración de bases de datos, ciencia de datos y últimamente estoy enfocado en las tecnologías y soluciones de Cloud, especialmente AWS.

SOLUCIONES, SOMOS EXPERTOS

DATA STRATEGY
DATA FABRIC
AUGMENTED ANALYTICS

Te puede interesar

Los Incentivos y el Desarrollo de Negocio en las Telecomunicaciones

octubre 9, 2020
LEER MÁS

Detección de Fraude Bancario con aprendizaje automático

septiembre 17, 2020
LEER MÁS

5 errores comunes en Redshift

diciembre 15, 2020
LEER MÁS

Cómo preparar la certificación AWS Data Analytics – Specialty

noviembre 17, 2021
LEER MÁS

KubeCon 2023: Una mirada hacia el futuro de Kubernetes

abril 26, 2023
LEER MÁS

Snowflake, el Time Travel sin DeLorean para unos datos Fail-Safe.

febrero 23, 2023
LEER MÁS

Publicado en: Blog, Practices, Tech

Serverless Microservices

octubre 14, 2021 by Bluetab

Serverless Microservices

Francisco Linaje

AWS Solutions Architect

En esta práctica cloud veremos como construir microservicios dentro de AWS siguiendo el paradigma serverless. Este tipo de solución permite disponer de sistemas completamente administrados por AWS donde nosotros no deberemos preocuparnos por disponibilizar los recursos o administrarlos, simplemente especificaremos dentro de su configuración las políticas de ejecucion y escalado si se necesitasen, el pago por tanto es exclusivamente por uso.

Requisitos

Para poder realizar esta práctica deberemos disponer de las siguientes instalaciones que nos permitirán poder desarrollar los microservicios y operar el entorno.

  • Terraform: Desde donde crearemos y desplegaremos nuestro backend e infra.
  • Un usuario con credenciales de acceso al cli de AWS (access_key y secret_key) y permisos necesarios para operar los servicios empleados en la práctica.
  • AWS cli v2: Permitirá configurar los credenciales con aws configure, opcionalmente se pueden realizar exports del access_key y secret_key.
  • Un IDE en este caso visual-studio-code
  • Conocimiento básico en Api Rest y AWS
  • Postman

Overview

Se plantea un escenario donde diferentes usuarios mediante aplicaciones multiplataforma acceden a diferentes recursos dentro de la app consumiendo una API Rest segura desplegada en AWS, para ello deberán en primer lugar autenticarse contra el pool de cognito para obtener un token JWT que les permitirá consumir los diferentes endpoints de la API.

Autenticación y Autorización

Para poder ofrecer seguridad a la API y que sus servicios solo sean consumidos por usuarios autorizados, debemos emplear un broker que nos pueda ofrecer autenticacion y a su vez autorizacion sobre estos servicios.

En primer lugar deberemos de entender el flujo que sigue un usuario para poder ser autenticado y autorizado para consumir un microservicio. En el siguiente diagrama se pueden ver como en una serie de pasos un usuario puede consumir el endpoint.

  1. El usuario se autentica mediante sus credenciales contra el broker.
  2. Si los credenciales son correctos, el broker generara un token JWT que serviría como mecanismo de autorización al poder validar que el usuario es quien dice ser.
  3. Este token deberá emplearse en todas las llamadas a la API Rest, para ello se enviará en el header de Authorization junto a un prefijo Bearer.
  4. Si el token es validado por el broker, se le otorgará acceso al consumo del microservicio.

¿Que es JWT o Json Web Token?

Json Web Token es un estándar abierto (RFC 7519) donde se define una forma autónoma de asegurar integridad en los datos enviados gracias a que va firmada digitalmente con HMAC (firma simetrica – misma clave) o RSA(firma asimetrica – par de claves privada/publica) y asi asegurar que el peticionario es una entidad segura. Además estos tokens pueden estar encriptados adicionalmente para ocultar los reclamos a terceros. El escenario principal donde se emplea es en la autorización del peticionario.

Cada token JWT consta de tres partes separados por un “.” de la forma header.payload.signed codificados en base64 por separado.

Header: Indica el tipo de token y su firma, en el caso de RS256 incluirá el kid que identifica la clave pública del emisor del Token que se empleara para verificar la firma. Mediante el iss del payload y el kid de la clave podremos ver en el navegador https:///.well-known/jwks.json la firma publica empleada para descifrar el token.

Payload: Contiene los claims que permiten verificar los atributos de la generación del token: iss (emisor), aud (audience), exp( expiracion) y otros campos que se puedan añadir adicionalmente para enviar información.

AWS Cognito

Cognito es un servicio completamente administrado que ofrece autenticación de usuarios para aplicaciones multiplataforma, además permite el empleo de identidades federadas como Google, Amazon, Facebook, etc para su registro.

Los usuarios podrán registrarse y loguearse contra un pool de usuarios que funciona como directorio de usuarios donde serán albergados los parámetros de autenticación: email, password, número de teléfono, etc. Se ofrecen además opciones de confirmación de usuario mediante código o enlace vía email o sms. Los usuarios autenticados recibirán un token de acceso que podrán emplear para recibir autorización a los microservicios de Api Gateway. Mediante otras opciones no planteadas en este caso de uso, se podrá recibir credenciales temporales STS de AWS para acceder directamente a servicios AWS mediante la función AssumeRoleWithWebIdentity mediante los pool de identidades.

Los usuarios emplearan la interfaz web de Cognito para autenticarse y recibir un código que podrán intercambiar por un token JWT que emplearán como autorización en los microservicios desplegados en Api Gateway.

Empezaremos creando nuestro pool de usuarios.

  • Asignaremos un nombre al pool.
  • Añadiremos el atributo de acceso y verificación, en este caso email.
  • Definiremos como se realiza la verificación: via enlace. Además si se hará por defecto via SNS “COGNITO_DEFAULT” o via SES “DEVELOPER”.
  • La recuperación de la cuenta se hará vía el email verificado, siendo la prioridad 1, como maxima prioridad en caso de añadir futuros métodos opcionales.
resource "aws_cognito_user_pool" "user_pool" {
        name = var.cognito.user_pool_name
        auto_verified_attributes = ["email"]
        username_attributes = ["email"]

        verification_message_template {
            email_subject_by_link = "APP Notification - Account Verification"
            email_message_by_link = "Please click the link to verify your email address: {##VERIFY EMAIL##}\n<br><br>\n"
            default_email_option = "CONFIRM_WITH_LINK"
        }

        email_configuration {
                email_sending_account = "COGNITO_DEFAULT"
        }

        account_recovery_setting {
            recovery_mechanism {
            name = "verified_email"
            priority =  1
            }
        }
    } 

Por otro lado, activaremos la interfaz propia de AWS a modo de pruebas para poder realizar el proceso de autenticado contra cognito sin tener que realizar un desarrollo del frontend propio.

  • Indicamos que recibiremos un code para poder intercambiarlo por un token JWT, teniendo como scope el email.
  • El proveedor de identidad sera por default Cognito.
  • Indicaremos una url callback de prueba desde donde nos indicarán el code dentro de la url de la forma “?code=”.
resource "aws_cognito_user_pool_client" "client" {
  name = var.cognito.app_client_name
  user_pool_id = aws_cognito_user_pool.user_pool.id
  supported_identity_providers = ["COGNITO"]
  callback_urls = var.cognito.callback_urls
  allowed_oauth_flows_user_pool_client = var.cognito.user_pool_client
  allowed_oauth_flows = ["code"]
  allowed_oauth_scopes = ["openid","email"]
}

resource "aws_cognito_user_pool_domain" "main" {
  domain       = var.cognito.domain_name
  user_pool_id = aws_cognito_user_pool.user_pool.id
} 

Autorización de los microservicios

En Api Gateway configuraremos un autorizador que recibirá el token JWT y comprobará la firma del token, como el emisor y audiencia añadidos en los propios scopes. El proceso de comprobación es automático permitiendo el acceso directo al servicio si este es válido o denegando mediante un unauthorized la request.

resource "aws_apigatewayv2_authorizer" "jwtAuth" {
  api_id           = aws_apigatewayv2_api.api.id
  authorizer_type  = "JWT"
  identity_sources = ["$request.header.Authorization"]
  name             = var.api.jwt_authorizer_name

  jwt_configuration {
    audience = [aws_cognito_user_pool_client.client.id]
    issuer   = "https://${aws_cognito_user_pool.user_pool.endpoint}"
  }
} 

Obtención del token JWT

En primer lugar, deberemos abrir la interfaz proporcionada por Cognito para realizar los procesos de sign-up, sign-in. La podremos encontrar dentro de la consola de AWS, en el servicio de Cognito, dentro de la configuración del cliente de aplicación «Lanzar interfaz de usuario alojada».

    1. Procederemos a registrar un usuario

    2. Nos pedira que confirmemos el usuario a través del enlace enviado a nuestra cuenta de correo introducida.

    3. El correo recibido tendrá la siguiente forma:

    4. Accedemos al enlace de VERIFY EMAIL

    5. Finalmente tendremos ya nuestro usuario confirmado en nuestro pool de usuarios de Cognito.

    6. Procedemos a logearnos en la interfaz de AWs empleada anteriormente y si los credenciales son correctos nos devolverá a la url de callback configurada cuando hemos creado el pool de usuarios con Terraform junto a un code que emplearemos posteriormente para obtener el token.

  1. Por último, para poder obtener el token debemos realizar una llamada al endpoint de Cognito /oauth2/token con los siguientes atributos en el body.
  • Metodo POST
  • Body
    • Aplicacion x-www-form-urlencoded.
    • Grant_type: authorization_code.
    • Client_id: tu id de la aplicación de Cognito.
    • Redirect_uri: url callback.
    • Code: código obtenido en la url de callback después de “?code=”

Obtendremos como respuesta el identity token (id_token) que contiene toda la informacion personal del usuario y es el que generalmente se empleará para la autorización y el access_token empleado principalmente para llamar a servicios externos sin incluir informacion personal del usuario. Dependiendo del caso de uso, si realmente la información aportada por el identity token no es necesaria, es recomendable emplear el access_token. Por ultimo el refresh token, se emplea principalmente para obtener un identity o access tokens nuevos.

Microservicios

Los microservicios seran desplegados en Api Gateway y tendrán como backend Lambda integrada como proxy y DynamoDB como bbdd. Todos estos servicios funcionan de forma completamente administrada siguiendo los objetivos serverless de esta práctica.

Vamos a definir brevemente estos servicios y ver cual es su papel dentro de la arquitectura.


AWS Api Gateway v2

Mediante Api Gateway podremos desarrollar APIs de una forma sencilla, segura y escalable, ademas de ofrecernos la integración con Lambda para poder operar sin aprovisionamiento. Solo funciona con HTTPs

Dispone de integración proxy para exponer por completo el request como input al backend. En el caso concreto del workshop se empleará Lambda Proxy Integration para poder consumir los parámetros desde el handler de la función vía el evento, para ello deberemos definir en la etapa de implementación POST como tipo, independientemente de que definamos el metodo HTTP del endpoint como GET.

Api Gateway es compatible con CloudFront como de CDN de la API, además es posible incorporar WAF como servicio de mitigación de ataques DDoS.

AWS actualmente dispone de dos versiones de Api Gateway, nosotros desplegaremos la última version, v2.

¿Que diferencias podemos encontrar? Los principales cambios introducidos con la version 2, se basan en:

  • Reducción de costes: 70%, 3.5$ vs 1$ por millón de peticiones.
  • Reducción de la latencia sobre el 50%.
  • Soporte a referencias cruzadas CORS.
  • JWT Authorizers a traves de OIDC y OAuth 2.0.
  • Disponible ruta y stages predeterminadas.
  • Integrado con SAM y CloudFormation.

Para poder crear nuestra Api deberemos crear los siguientes recursos

  • Api: indicando el nombre y su protocolo.
  • Un grupo de registros de Cloudwatch para la Api.
  • Un stage de implementación donde indicaremos el nombre de la implementacion y los parametros de los logs.
resource "aws_apigatewayv2_api" "api" {
  name          = var.api.api_name
  protocol_type = "HTTP"
}

resource "aws_cloudwatch_log_group" "api_gw" {
  name = "/aws/api_gw/${aws_apigatewayv2_api.api.name}"
  retention_in_days = 30
}

resource "aws_apigatewayv2_stage" "stage" {
  api_id = aws_apigatewayv2_api.api.id

  name        = var.api.stage_name
  auto_deploy = true


  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gw.arn

    format = jsonencode({
      requestId               = "$context.requestId"
      sourceIp                = "$context.identity.sourceIp"
      requestTime             = "$context.requestTime"
      protocol                = "$context.protocol"
      httpMethod              = "$context.httpMethod"
      resourcePath            = "$context.resourcePath"
      routeKey                = "$context.routeKey"
      status                  = "$context.status"
      responseLength          = "$context.responseLength"
      integrationErrorMessage = "$context.integrationErrorMessage"
    }
    )
  }
} 

Una vez creada la Api, crearemos los dos endpoints que emplearemos en este workshop: GET,POST

Para ambos indicaremos:

  • Tipo de autorización: JWT
  • Tipo de integración AWS_PROXY, para recibir integramente en la funcion lambda la request
  • Metodo de integración sera POST aun cuando el método del endpoint se declare como GET, ya que Lambda solo se activa a traves de peticiones POST.
  • Id del autorizador creado jwtAuth que tendrá como audience el id de la aplicación Cognito creada y su endpoint como issuer.
  • Se creará además un permiso de ejecución de la función Lambda especificada para su ejecución por parte de ApiGateway

 

GET

#### GET
resource "aws_apigatewayv2_integration" "get_item_app_integration" {
  api_id           = aws_apigatewayv2_api.api.id
  integration_type = "AWS_PROXY"
  description               = "Lambda GET example"
  integration_method        = "POST"
  integration_uri           = aws_lambda_function.get_item_app.invoke_arn
}

resource "aws_apigatewayv2_route" "get_item_app_route" {
  api_id = aws_apigatewayv2_api.api.id

  route_key = "GET /user"
  target    = "integrations/${aws_apigatewayv2_integration.get_item_app_integration.id}"
  authorization_type = "JWT"
  authorizer_id = aws_apigatewayv2_authorizer.jwtAuth.id
}

resource "aws_lambda_permission" "get_item_app_execution" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.get_item_app.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*/*"
} 

POST

resource "aws_apigatewayv2_integration" "create_item_app_integration" {
  api_id           = aws_apigatewayv2_api.api.id
  integration_type = "AWS_PROXY"
  description               = "Lambda example"
  integration_method        = "POST"
  integration_uri           = aws_lambda_function.create_item_app.invoke_arn
}

resource "aws_apigatewayv2_route" "create_item_app_route" {
  api_id = aws_apigatewayv2_api.api.id

  route_key = "POST /user"
  target    = "integrations/${aws_apigatewayv2_integration.create_item_app_integration.id}"
  authorization_type = "JWT"
  authorizer_id = aws_apigatewayv2_authorizer.jwtAuth.id
}

resource "aws_lambda_permission" "create_item_app_execution" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.create_item_app.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*/*"
} 

AWS Lambda

Para el desarrollo de la lógica de nuestros microservicios emplearemos AWS Lambda, servicio de computación completamente administrado con escalado automático. Donde se definen unos recursos de memoria y CPU para realizar la ejecución de cada función. Soporta de forma nativa lenguajes como Java, NodeJS, Python, etc. Las funciones son albergadas en un paquete de implementación del tipo zip alojadas en un bucket de S3 interno o creado por nosotros.

 

En primer lugar, crearemos el rol de ejecución, donde además definiremos que acciones se pueden ejecutar dentro de estas y sobre que servicios, en nuestro caso simplemente permitiremos acciones CRUD sobre la tabla DynamoDB especifica que crearemos posteriormente.

  • Crearemos el rol de Lambda y daremos permisos al servicio de Lambda para asumirlo
  • Crearemos dos políticas básicas que serán asignadas a este rol: ejecución (permiso para cargar registros en CloudWatch) y una segunda política custom donde estarán definidas las acciones que podrán realizarse sobre la tabla de DynamoDb empleada para el workshop.
resource "aws_iam_role" "lambda_exec_dev" {
  name = "serverless_lambda_dev"

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

resource "aws_iam_role_policy_attachment" "lambda_policy_attachment_dev" {
  role       = aws_iam_role.lambda_exec_dev.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_role_policy_attachment" "lambda_dynamodb_policy_attachment_dev" {
  role       = aws_iam_role.lambda_exec_dev.name
  policy_arn = aws_iam_policy.lambda_dynamodb_policy_dev.arn
}

resource "aws_iam_policy" "lambda_dynamodb_policy_dev" {
  name        = "lambda_dynamodb_policy_dev"
  description = "Lambda DynamoDB access"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "dynamodb:Query",
          "dynamodb:GetItem",
          "dynamodb:PutItem",
          "dynamodb:UpdateItem",
          "dynamodb:BatchWriteItem",
          "dynamodb:BatchGetItem",
        ]
        Effect   = "Allow"
        Resource = [aws_dynamodb_table.app_table.arn]
      },
    ]
  })
  depends_on = [aws_dynamodb_table.app_table]
} 

Las funciones irán recogidas en un zip con el codigo Python que será subido a un bucket interno que crearemos expresamente para albergarlas. En cada función indicaremos su handler, runtime, rol de ejecución y el bucket/objeto donde poder encontrar el paquete de funciones.

data "archive_file" "lambda_functions_package" {
  type = "zip"

  source_dir  = "${path.module}/scripts/"
  output_path = "${path.module}/scripts/crud_lambdas.zip"
}

resource "aws_s3_bucket_object" "lambda_functions_package_object" {
  bucket = aws_s3_bucket.internal_dev.bucket
  key    = "crud_lambdas.zip"
  source = data.archive_file.lambda_functions_package.output_path
  etag = filemd5(data.archive_file.lambda_functions_package.output_path)
}

resource "aws_s3_bucket" "internal_dev" {
  bucket = var.bucket_name
  acl    = "private"
} 

Para su creacion simplemente indicaremos el nombre de la función Lambda, rol de ejecución, runtime, la función de ejecucíon, el bucket y el zip donde estan alojadas.

  • La función get_item_app nos devolvera el usuario buscado por id.
  • La función create_item_app permitira guardar el usuario en bbdd.
resource "aws_lambda_function" "get_item_app" {
  function_name = "get_user"
  handler       = "get_user.lambda_handler"
  runtime       = "python3.6"

  s3_bucket = aws_s3_bucket.internal_dev.bucket
  s3_key    = aws_s3_bucket_object.lambda_functions_package_object.key
  source_code_hash = data.archive_file.lambda_functions_package.output_base64sha256
  role = aws_iam_role.lambda_exec_dev.arn
}

resource "aws_lambda_function" "create_item_app" {
  function_name = "create_user"
  handler       = "create_user.lambda_handler"
  runtime       = "python3.6"

  s3_bucket = aws_s3_bucket.internal_dev.bucket
  s3_key    = aws_s3_bucket_object.lambda_functions_package_object.key
  source_code_hash = data.archive_file.lambda_functions_package.output_base64sha256
  role = aws_iam_role.lambda_exec_dev.arn
} 

Estas funciones Python emplearán la librería boto3 para poder realizar de una forma sencilla y rápida el conector contra la tabla de DynamoDB, estarán alojadas bajo el directorio de /scrips.

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('AppDummy') 

A partir de este conector mediante table.get_item() o table.create_item() podremos realizar nuestras operaciones GET y POST respectivamente. Si la acción se ejecuta correctamente lanzaremos un codigo 200 y devolveremos el objeto añadido/obtenido.

  • En la funcion create_user obtendremos los atributos del body de la request que se encontrarán en el propio evento al ser una función integrada como AWS_PROXY.
item = json.loads(event["body"])
user = item["User"]
...
table = dynamodb.Table('AppDummy')
    response = table.put_item(
        Item=user
    )
... 
  • En la función get_user encontraremos el atributo de búsqueda UserId dentro del evento en “queryStringParameters”.
id = str(event["queryStringParameters"]['UserId'])
...
response = table.get_item(
        Key={
            'UserId': id
        }
    )
... 

AWS DynamoDB

Por último, crearemos una tabla básica para albergar los atributos de nuestros usuarios, tendrá simplemente como PK el id de usuario en string para soportar alfanuméricos.

resource "aws_dynamodb_table" "app_table" {
  name           = "AppDummy"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "UserId"

  attribute {
    name = "UserId"
    type = "S"
  }
} 

Despliegue

Mediante Terraform desplegaremos nuestra infraestructura, primero deberemos lanzar un init que descargará los plugins y inicializará nuestro directorio de trabajo con los archivos de configuración de AWS, para posteriormente ejecutar un plan, si el despliegue de recursos planificado por el plan concuerda con lo que buscamos finalmente ejecutaremos un apply para desplegar toda nuestra infra y realizar las pruebas.

En esta práctica, nuestro estado permanecera en local, no configuraremos AWS como backend para los estados de Terraform

terraform init

terraform plan -var-file="env/dev.tfvars"

terraform apply -var-file="env/dev.tfvars" 

Pruebas

Una vez desplegado nuestro proyecto, comprobaremos el correcto funcionamiento de los microservicios que hemos programado. Además en ambos casos, deberemos añadir el campo authorization en el header de la peticion HTTP con el prefijo Bearer y el access_token obtenido anteriormente para poder ser autorizados a consumir el microservicio.

POST /user

En primer lugar, probaremos la peticion HTTP POST /user, debería añadir el usuario a la tabla de DynamoDB creada y enviando como respuesta un codigo 200 y el usuario añadido a la tabla.

GET /user

De la misma forma, consumiremos el microservicio que a partir del UserId nos devolverá la información del usuario, recordando que al ser una petición GET la información relativa a la consulta irá en el Query Params (propia url)

Conclusión

En esta práctica hemos podido aprender a como desarrollar una API Rest segura y completamente administrada dentro del entorno de AWS, sirviendonos de la última versión de Api Gateway que facilita la integración nativa con autorizadores de JWT.

La integración de Cognito con Api Gateway nos torga la capa de seguridad y administración de los usuarios. Respectivamente con Lambda y DynamoDB disponemos de la capa de lógica/persistencia de nuestra API. La integración nativa de todos estos servicios nos facilita el desarrollo de estas aplicaciones al disminuir la carga de trabajo dedicada tanto al desarrollo puro, como a la integracion de los distintos servicios involucrados y su administración, además gracias a Terraform disponemos de toda la infraestructura como código facilitando su futura evolución y disponibilización en otros entornos de una forma mucho más rápida y comprensible.

En futuras entradas veremos como desarrollar otros escenarios típicos que podemos encontrar en nuestro día a día dentro de AWS, con el fin de tener unas primeras herramientas para poder solventar futuros escenarios que se nos planteen.

Espero que la práctica haya sido de vuestro agrado e interés, os espero en futuras entregas!

Enlaces de interés

  • Documentación general de Cognito.
  • Documentación general de Api Gateway.
  • Documentación general de Lambda.
  • Documentación general de DynamoDB.
  • Documentación general de JWT.
Navegación

Introducción

Requisitos

Overview

Autenticación y Autorización

Microservicios

Despliegue

Pruebas

Conclusión

Enlaces de interés

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

Francisco Linaje

AWS Solutions Architect

SOLUCIONES, SOMOS EXPERTOS

DATA STRATEGY
DATA FABRIC
AUGMENTED ANALYTICS

Te puede interesar

Bluetab se incorporará a IBM

julio 9, 2021
LEER MÁS

Leadership changes at Bluetab EMEA

abril 3, 2024
LEER MÁS

Data Mesh

julio 27, 2022
LEER MÁS

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

junio 4, 2024
LEER MÁS

Análisis de vulnerabilidades en contenedores con trivy

marzo 22, 2024
LEER MÁS

Mitos y verdades de los ingenieros de software

junio 13, 2022
LEER MÁS

Publicado en: Blog, Practices, Tech

Desplegando una plataforma CI/CD escalable con Jenkins y Kubernetes

septiembre 22, 2021 by Bluetab

Desplegando una plataforma CI/CD escalable con Jenkins y Kubernetes

Lucas Calvo Berlanga

Cloud Engineer

En este artículo de la práctica cloud veremos cómo crear una plataforma de CI/CD de una forma totalmente automatizada. Para ello nos apoyaremos en una metodología GitOps para así realizar nuestros despliegues de una forma más sencilla, escalable e industrializada.

La idea de este taller es crearnos un cluster de GKE donde tengamos desplegado Jenkins como nuestra pieza central de CI/CD y que este vaya escalando en agentes de una forma totalmente automatizada para que según la demanda de ejecuciones de jobs nuestro cluster crezca o decrezca de una forma transparente para nosotros. Otra de las grandes ventajas de esta arquitectura es que podemos crear diferentes tipos de slaves para así cubrir todo tipo de ejecuciones dentro de nuestra compañía.

Los componentes/herramientas que usaremos en este proyecto serán los siguientes:

  • Terraform.
  • GKE.
  • Jenkins.
  • Prometheus.
  • Grafana.
  • Slack.

Objetivos

  1. Creación de la las vpcs donde se desplegará la infraestructura.
  2. Creación de la infraestructura, en nuestro caso GKE, de una forma totalmente automatizada.
  3. Despliegue de Jenkins como componente principal haciendo uso del provider del helm.
  4. Configuración de Jenkins usando el plugin de Jcasc.
  5. Despliegue de prometheus para la monitorización de nuestro sistema haciendo uso de helm.
  6. Despliegue de grafana para la monitorización de nuestro sistema haciendo uso de helm.
  7. Configuración de grafana haciendo uso de prometheus y unos dashboard configurados automáticamente.
  8. Revisión de toda la infraestructura levantada y chequeo de la monitorización.
  9. Ejecución de un Job de ejemplo para ver el flujo completo.
  10. Comprobar sistema de alertas tanto caída de sistemas como de jobs completos.
  11. Comprobar el escalado de nuestra infraestructura y de los componentes desplegados.

Introducción a Terraform

Terraform es una herramienta open-source para automatizar la creación de infraestructura como código. Para nuestro caso de uso usaremos Terraform tanto para desplegar la infraestructura, tanto GKE como la VPC donde se hará el despliegue de este último.

Terraform no solo nos permite desplegar infraestructura como código sino que también nos da la opción de usar otros provedores como el de Kubernetes para la creación de namespace (entre otras muchas cosas) o la posibilidad de realizar despliegues de otros componentes dentro del cluster de GKE con el proveedor de Helm. Terraform.

Introducción a GKE

GKE es el servicio de Kubernetes gestionado y autoescalado por GCP. Es donde se realizarán todos los despliegues tanto de Jenkins como de la monitorización que tendrá nuestra plataforma. GKE. Este componente lo vamos a automatizar haciendo uso de Terraform donde se harán las implementaciones necesarias para realizar el despliegue correctamente.

Introducción a Jenkins

Jenkins es nuestra pieza central de la plataforma de CI/CD. Jenkins es una herramienta de construcción, implementación y automatización de proyectos software. Para el despliegue de este componente nos apoyaremos en el provider de Helm de Terraform. Jenkins.

Introducción a Prometheus

Prometheus es un sistema de monitorización que usaremos para comprobar el estado de todos los pods desplegados en nuestra plataforma, así como para monitorizar el estado de nuestra infraestructura como tal(Picos de consumo, nodos caídos…). Para el despliegue de este componente nos apoyaremos en el provider de Helm de Terraform. Prometheus.

Introducción a Grafana

Grafana es nuestra herramienta de visualización de la monitorización. Nos decantamos por esta herramienta ya que se integra perfectamente con prometheus y nos permite crear dashboards personalizados de nuestra infraestructura. Para el despliegue de este componente nos apoyaremos en el provider de Helm de Terraform. Grafana.

Introducción a Slack

Por último, haremos uso de slack como herramienta de envió de alertas tanto en la ejecución de los jobs de Jenkins como alertas de monitorización de caídas en alguno de los pods desplegados. Slack

Preparación de entorno

Para la ejecución de la plataforma CI/CD será necesario hacer la instalación de estas herramientas:
  1. Terraform. Aquí se usa la versión v1.0. Se puede descargar aquí. Para instalar una versión anterior consultar aquí.
  2. Helm. Guía de instalación de Helm.
  3. GCP. Para la realización del taller será necesario la creación de una cuenta en GCP.

Clonación de repositorio

El código fuente está disponible en github.

git clone https://github.com/lucasberlang/gitops-kubernetes-jenkins/
cd gitops-kubernetes-jenkins 

Índice de ficheros

Dentro de la carpeta src tendremos todo el código necesario para hacer el despliegue de nuestra infraestructura de una forma automatizada. En este apartado haremos un breve resumen de lo que contienen cada uno de los ficheros para que nuestro proyecto funcione.

  • providers.tf

Definición de los proveedores que usaremos para hacer el despliegue con Terraform. En nuestro caso haremos uso del provider de Google, helm y kubernetes.

  • terraform.tfvars

Variables que usaremos dentro de los módulos de Terraform.

  • variables.tf

Definición de las variables que usaremos tanto en los módulos de Terraform como en los secretos que desplegaremos para hacer uso en Jenkins.

  • outputs.tf

Información del proyecto que nos interesa conocer.

  • main.tf

Contendrá la lógica realizada en Terraform para hacer el despliegue de los componentes de infraestructura que necesitamos, en este caso VPC y GKE. Para ello haremos uso de dos módulos desarrollados por Bluetab.

  • kubernetes_secrets.tf

Archivo que contendrá toda la información sensible que usaremos dentro de los despliegues que realizaremos (Grafana, Prometheus, Jenkins), como contraseñas, tokens, etc… .

  • helm_monitoring.tf

Archivo que contiene la configuración que se realizará con el proveedor de Helm para el despliegue de los componentes Prometheus y Grafana.

  • helm_jenkins.tf

Archivo que contiene la configuración que se realizará con el proveedor de Helm para el despliegue de los componentes Jenkins.

  • grafana.yaml

En este archivo contiene la configuración inicial que usará Helm cuando realicemos el despliegue de grafana.

  • prometheus.yaml

En este archivo contiene la configuración inicial que usará Helm cuando realicemos el despliegue de prometheus.

  • jenkins.yaml

En este archivo contiene la configuración inicial que usará Helm cuando realicemos el despliegue de jenkins.

  • vars.example.env

Archivo que contendrá todas las variables de entorno que usaremos en nuestro proyecto. En este caso le pasaremos las variables más sensibles como pueden ser la contraseña de grafana o el token usado en slack para no tener que subirlo al repositorio.

Configuración de Slack

  1. Primero de todo nos tendremos que registrar en Slack.
  2. Una vez que tengamos el registro se deberá seguir la guían de configuración de Slack con Jenkins. De este paso solo necesitaremos el token de Slack que pasaremos como variable de entorno TF_VAR_slack_token.
  3. Además, nos crearemos un canal en Slack el cual pasaremos posteriormente como variable de entorno, TF_VAR_slack_channel.
  4. Será necesario también coger el nombre del dominio de slack y pasárselo como variable,TF_VAR_team_domain.
  5. Por último nos crearemos un webhook para el envío de alertas desde Prometheus. Para ello podemos seguir esta guía. De este paso solo necesitaremos el endpoint para luego pasarlo como variable de entorno, TF_VAR_slack_api_url.

Configuración de variables

Antes de realizar la ejecución de nuestro proyecto debemos definir una serie de variables de entorno que necesitaremos para el correcto funcionamiento de este. Para ello haremos uso del archivo vars.example.env donde tenemos ya definidas las variables más importantes. Para efectos de la demo las únicas variables que se tendrán que modificar son las siguientes:

  • TF_VAR_project_id: el id del proyecto de la cuenta de GCP donde se realizará el despliegue de la infraestructura.
  • TF_VAR_vault_addr: se añadirá la dirección de vault para la autenticación en el despliegue del proyecto de prueba. En caso de no tener vault se podrá también configurar con las credenciales de Azure o crearse otra proyecto de ejemplo.
  • TF_VAR_vault_token: token de vault que se utilizará para autenticarse contra Azure en nuestro proyecto de demo. Igual que con la variable de arriba no será necesaria si se configura el proyecto demo para autenticarse con las credenciales de Azure.
  • TF_VAR_slack_api_url: El endpoint de Slack que configuraremos para que se envíen las alertas de nuestra plataforma.
  • TF_VAR_slack_channel: Canal de Slack donde se publicarán los mensajes de las alertas.
  • TF_VAR_slack_token: El token de Slack que configuraremos para que se envíen las alertas de nuestra plataforma.
  • TF_VAR_team_domain: El dominio de Slack que configuraremos para que se envíen las alertas de nuestra plataforma.
  • TF_VAR_user_grafana: Nombre del usuario de Grafana que usaremos para loguearnos.
  • TF_VAR_password_grafana: Contraseña para el usuario de Grafana definido anteriormente para hacer el login.

Una vez se hayan configuradas todas estas variables se tendrá que lanzar el siguiente comando para que queden como variables de entornos.

source vars.example.env 

Configuración VPC

Para la configuración de la VPC haremos uso del módulo corporativo desarrollado por Bluetab. Este módulo está publica en el repositorio de github y está totalmente documentado por si se tiene alguna duda de su funcionamiento o se quiere realizar alguna modificación en la VPC. Para que esto funcione lo único que debemos de hacer es instanciar nuestro módulo con las variables necesarias para hacerle funcionar:

module "network" {
  source = "git@github.com:lucasberlang/gcp-network.git"

  project_id         = var.project_id
  description        = var.description
  enable_nat_gateway = true

  intra_subnets = [
    {
      subnet_name           = "private-subnet01"
      subnet_ip_cidr        = "10.0.0.0/16"
      subnet_private_access = false
      subnet_region         = var.region
    }
  ]

  secondary_ranges = {
    private-subnet01 = [
      {
        range_name    = "private-subnet01-01"
        ip_cidr_range = var.ip_range_pods
      },
      {
        range_name    = "private-subnet01-02"
        ip_cidr_range = var.ip_range_services
      },
    ]
  }

  labels = var.labels
} 

Lo único necesario para ejecutar la creación de la VPC será el id del proyecto de GCP. Además de desplegar la VPC con la creación del módulo se realizará el despliegue de una subred a nuestra VPC con el direccionamiento 10.0.0.0/16. Este es totalmente modificable así como el nombre de la subred o la región en donde se despliega. También se han creado dos rangos secundarios de direccionamientos para los pods y servicios que desplegaremos en GKE.

Configuración GKE

Para el despliegue del proyecto se ha optado por usar infraestructura totalmente gestionado, en este caso haremos uso de GKE. Para automatizar el despliegue usaremos el módulo corporativo desarrollado por Bluetab. Este módulo está publica en el repositorio de github y esta totalmente documentado por si se tiene alguna duda de su funcionamiento o se quiere realizar alguna modificación en el GKE. Para que esto funcione lo único que debemos de hacer es instanciar nuestro módulo con las variables necesarias para hacerle funcionar:

module "gke" {
  source = "git@github.com:lucasberlang/gcp-gke.git"

  project_id              = var.project_id
  name                    = "gitops"
  regional                = true
  region                  = var.region
  network                 = module.network.network_name
  subnetwork              = module.network.intra_subnet_names.0
  ip_range_pods           = "private-subnet01-01"
  ip_range_services       = "private-subnet01-02"
  enable_private_endpoint = false
  enable_private_nodes    = false
  master_ipv4_cidr_block  = "172.16.0.0/28"
  kubernetes_version      = "latest"

  master_authorized_networks = [
    {
      cidr_block   = module.network.intra_subnet_ips.0
      display_name = "VPC"
    },
    {
      cidr_block   = "0.0.0.0/0"
      display_name = "shell"
    }
  ]

  node_pools = [
    {
      name         = "default-node-pool"
      machine_type = "n1-standard-4"
    },
  ]

  istio     = var.istio
  dns_cache = var.dns_cache
  labels    = var.labels
} 

Como se puede observar en el código la implementación del módulo es bastante intuitivo solo será necesario declarar algunas variables. Las variables más importantes son el tipo de instancia que usaran los node-pools y la subred donde se desplegará el cluster de GKE que para esto usaremos la red declarada anteriormente.

El funcionamiento de GKE en nuestra VPC será este:

Configuración Jenkins

Para la configuración de Jenkins como hemos dicho anteriormente usaremos el provider de Helm. Para hacer el despliegue nos crearemos un namespace llamado gitops donde se desplegará Jenkins. El fichero donde se hará esta configuración es helm_jenkins.tf.

resource "kubernetes_namespace" "jenkins" {
  metadata {
    name = "gitops"
  }
} 

Luego procederemos a hacer el despliegue de Jenkins con Helm.

data "local_file" "helm_chart_values" {
  filename = "${path.module}/templates/jenkins.yaml"
}

resource "helm_release" "jenkins" {
  name       = "jenkins"
  repository = "https://charts.jenkins.io"
  chart      = "jenkins"
  version    = "3.5.3"
  namespace  = kubernetes_namespace.jenkins.metadata.0.name
  timeout    = 180

  values = [data.local_file.helm_chart_values.content]

} 

Cogeremos el chart del repositorio oficial de Jenkins y le pasaremos el fichero jenkins.yaml como configuración inicial.

De este fichero vamos a destacar algunos puntos que hemos modificado para realizar una automatización de nuestro servicio.

 image: "jenkins/jenkins"
  imagePullPolicy: "Always"
  adminSecret: true
  adminUser: "admin"
  jenkinsUrl: "http://${kubernetes_endpoint}:80" 

Se ha realizado la modificación del nombre del adminUser para que sea más genérico y jenkinsUrl para que nos de el endpoint de Jenkins en el GKE cuando nos leguen las alertas de los jobs en el slack.

  containerEnv:
    - name: kubernetes_endpoint
      valueFrom:
        secretKeyRef:
            name: jenkins-k8s-config
            key: kubernetes_endpoint
    - name: gitlab_username
      valueFrom:
        secretKeyRef:
            name: gitlab-credentials
            key: gitlab_username
    - name: gitlab_ssh_key
      valueFrom:
        secretKeyRef:
            name: gitlab-credentials
            key: gitlab_ssh_key
    - name: vault_token
      valueFrom:
        secretKeyRef:
            name: vault-credentials
            key: vault_token
    - name: vault_addr
      valueFrom:
        secretKeyRef:
            name: vault-credentials
            key: vault_addr
    - name: arm_access_key
      valueFrom:
        secretKeyRef:
            name: azure-credentials
            key: arm_access_key
    - name: slack_token
      valueFrom:
        secretKeyRef:
            name: slack-credentials
            key: slack_token
    - name: team_domain
      valueFrom:
        secretKeyRef:
            name: slack-credentials
            key: team_domain            
  servicePort: 80
  serviceType: LoadBalancer 

Estos son todos los secrets que hemos definido en el archivo kubernetes_secrets.tf. Estos son necesarios para el correcto funcionamiento de Jenkins ya que haremos uso de muchos de estos secretos cuando realicemos la configuración con el plugin de Jcasc de Jenkins.

Además, en la configuración del serviceType lo hemos definido como LoadBalancer externo y el servicePort 80 para que sea visible desde el exterior.

installPlugins:
    - kubernetes
    - docker-custom-build-environment
    - ansicolor
    - aws-credentials
    - azure-credentials
    - gitlab-api
    - gitlab-branch-source
    - docker-java-api
    - github-branch-source
    - pipeline-graph-analysis
    ... 

Listado de todos los plugins que se instalarán por defecto en la configuración inicial de Jenkins.

Configuración inicial de Jcasc

Jcasc es el plugin de configuración como código de Jenkins. Lo usaremos para hacer una serie de configuraciones previas como pueden ser:

  • Configuración de los slaves:

Esta parte es donde definiremos la creación de un cloud de kubernetes donde se desplegarán todos nuestros pods cada vez que se lance una ejecución de un job en nuestro Jenkins. También configuraremos la imagen que se usará para desplegar cada slave y el namespace donde se realizará dicho despliegue. Para nuestro ejemplo usaremos una imagen ya modificada con la instalación de Terraform y algunos componentes como el SDK de Google o el cli de azure (lucasbluetab/jnlp-agent-Terraform-gcloud:latest). Además, se configurará los recursos que gaste cada vez que se levante un pod en cualquier ejecución de nuestros, consiguiendo así una infraestructura totalmente escalable.

      cloud: |
        jenkins:
          clouds:
            - kubernetes:
                name: "Terraform-executors"
                serverUrl: "https://kubernetes.default"
                jenkinsTunnel: "jenkins-agent:50000"
                jenkinsUrl: "http://jenkins:80"
                skipTlsVerify: true
                namespace: "gitops"
                templates:
                    - name: "jenkins-jnlp"
                      namespace: "gitops"
                      nodeUsageMode: NORMAL
                      label: "jnlp-exec"
                      containers:
                        - name: "jnlp"
                          image: "jenkins/jnlp-slave"
                          alwaysPullImage: false
                          workingDir: "/home/jenkins/agent"
                          ttyEnabled: true
                          command: ""
                          args: ""
                          resourceRequestCpu: "500m"
                          resourceLimitCpu: "1000m"
                          resourceRequestMemory: "1Gi"
                          resourceLimitMemory: "2Gi"
                      volumes:
                        - emptyDirVolume:
                            memory: false
                            mountPath: "/tmp"
                      idleMinutes: "1"
                      activeDeadlineSeconds: "120"
                      slaveConnectTimeout: "1000"
                    - name: "Terraform"
                      namespace: "gitops"
                      nodeUsageMode: NORMAL
                      label: "Terraform-exec"
                      containers:
                        - name: "Terraform"
                          image: "lucasbluetab/jnlp-agent-Terraform-gcloud:latest"
                          alwaysPullImage: false
                          workingDir: "/home/jenkins/agent"
                          ttyEnabled: true
                          command: "/bin/sh -c"
                          args: "cat"
                          resourceRequestCpu: "100m"
                          resourceLimitCpu: "500m"
                          resourceRequestMemory: "500Mi"
                          resourceLimitMemory: "1Gi"
                      volumes:
                        - emptyDirVolume:
                            memory: false
                            mountPath: "/tmp"
                      podRetention: "never"
                      activeDeadlineSeconds: "900"
                      slaveConnectTimeout: "1000" 
  • Configuración de las credenciales: se definirán todas las credenciales que se usarán dentro de Jenkins, en nuestro caso las credenciales de Jenkins y alguna extras que podremos configurar si es necesario.
      credentials: |
          credentials:
              system:
                  domainCredentials:
                  - credentials:
                    - basicSSHUserPrivateKey:
                        scope: GLOBAL
                        id: "GitLab"
                        username: ${gitlab_username}
                        passphrase: ""
                        privateKeySource:
                          directEntry:
                            privateKey: ${gitlab_ssh_key}
                    - string:
                        scope: GLOBAL
                        id: vaultUrl
                        secret: ${vault_addr}
                    - string:
                        scope: GLOBAL
                        id: vaultToken
                        secret: "${vault_token}"
                    - string:
                        scope: GLOBAL
                        id: azureARMKey
                        secret: "${arm_access_key}"
                    - string:
                        scope: GLOBAL
                        id: slackToken
                        secret: "${slack_token}"
                    - string:
                        scope: GLOBAL
                        id: teamDomain
                        secret: "${team_domain}" 
  • Configuración de Slack: Aquí se pasará la configuración que realizamos en Jenkins en nuestro caso le indicaremos el dominio de nuestro Slack, para nuestras pruebas Bluetabmundo, y la sala por defecto donde se escribirán todas las alertas que manden nuestros jobs. Para terminar, le pasaremos el token de Slack para que haga correctamente la conexión. Tanto el teamDomain como la room deberán ser modificados con los que habéis creado en el apartado de configuración de Slack.
      unclassified: |
        unclassified:
          slackNotifier:
            teamDomain: bluetabmundo
            room: "#jenkins"
            tokenCredentialId: slackToken 
  • Arreglo de bugs: Una de las ventajas más interesantes de Jcasc es que nos permite la utilización de scripts de groovy dentro de su configuración. En nuestro caso crearemos un script para quitar un bug que salta en Jenkins al usar el plugin de de env-injector. Básicamente este script nos quita un warning que no debería salir pero por un bug de la versión del plugin salta.
      scriptgroovy: |
        groovy:
          - script: >
              import jenkins.model.*;
              import jenkins.security.*;
              import hudson.security.*;
              import hudson.model.*;
              import static groovy.json.JsonOutput.*;
              import hudson.ExtensionList;

              ExtensionList<UpdateSiteWarningsConfiguration> configurations = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
              println configurations;
              
              UpdateSiteWarningsConfiguration configuration = configurations.get(0);
              HashSet<UpdateSite.Warning> activeWarnings = new HashSet<>();
              
              activeWarnings.add('SECURITY-248');
              
              configuration.ignoredWarnings = activeWarnings;
              
              configuration.save(); 
  • Uso de Jobdsl: Con el plugin de Jcasc también lo podemos combinar con el plugin de Jobdsl que nos permite definirnos jobs cuando arranque nuestro Jenkins y así tener ya creado todos nuestros principales jobs.
      init-jobs: |
            jobs:
              - script: >
                  folder('Terraform')
              - script: >
                  multibranchPipelineJob('Terraform/azure-test') {
                      branchSources {
                          branchSource {
                              source {
                                  git {
                                      remote('https://github.com/lucasberlang/Terraform-azure-test.git')
                                  }
                              }
                              strategy {
                                  defaultBranchPropertyStrategy {
                                      props {
                                          noTriggerBranchProperty()
                                      }
                                  }
                              }
                          }
                      }
                      configure {
                          it / sources / data / 'jenkins.branch.BranchSource' / source / traits {
                            'jenkins.plugins.git.traits.BranchDiscoveryTrait'()
                          }
                      }
                      triggers {
                          periodic(60)
                      }
                  } 

Configuración Prometheus

Para la configuración de Prometheus como hemos dicho anteriormente usaremos el provider de Helm. Para hacer el despliegue nos crearemos un namespace llamado monitoring donde se desplegará Prometheus. El fichero donde se hará esta configuración es helm_monitoring.tf.

resource "kubernetes_namespace" "jenkins" {
  metadata {
    name = "gitops"
  }
} 

Luego procederemos a hacer el despliegue de Prometheus con Helm.

data "template_file" "file" {
  template = "${file("${path.module}/templates/prometheus.yaml")}"
  vars = {
    slack_api_url = "${var.slack_api_url}"
    slack_channel = "${var.slack_channel}"
  }
}

resource "helm_release" "prometheus" {
  chart      = "prometheus"
  name       = "prometheus"
  namespace  = kubernetes_namespace.monitoring.metadata.0.name
  repository = "https://charts.helm.sh/stable"

  values = [data.template_file.file.rendered]
} 

Para ello haremos unas modificaciones en el template prometheus.yaml sustituyendo las variables slack_api_url y slack_channel dentro del fichero por las variables de entorno que le pasamos en el paso inicial. Estas variables serán usadas para el envío de alertas si existiera algún problema en el cluster de GKE o el algún pod desplegado en este.

De este fichero vamos a destacar algunos puntos que hemos modificado para realizar una automatización de nuestro servicio.

  • Configuración del servicio: Configuraremos el servicio de Prometheus como un ClusterIP para que solo sea visible dentro del cluster.
    type: ClusterIP 
  • Configuración de las alertas de Slack: Añadiremos la configuración de envío de alertas por si se cae algún nodo de la infraestructura o hay alguna caída de servicio como puede ser un fallo en el pod de Jenkins. Si sucede alguno de los dos puntos anteriores nos llegará una alerta a nuestro canal de Slack.
alertmanagerFiles:
  alertmanager.yml:
    global:
      slack_api_url: ${slack_api_url}

    receivers:
      - name: slack-notifications
        slack_configs:
          - channel: ${slack_channel}
            send_resolved: true
            icon_url: https://avatars3.githubusercontent.com/u/3380462
            title: |
              [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}
              {{- if gt (len .CommonLabels) (len .GroupLabels) -}}
                {{" "}}(
                {{- with .CommonLabels.Remove .GroupLabels.Names }}
                  {{- range $index, $label := .SortedPairs -}}
                    {{ if $index }}, {{ end }}
                    {{- $label.Name }}="{{ $label.Value -}}"
                  {{- end }}
                {{- end -}}
                )
              {{- end }}
            text: |
              {{ range .Alerts -}}
              *Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - `{{ .Labels.severity }}`{{ end }}
          
              *Description:* {{ .Annotations.description }}
          
              *Details:*
                {{ range .Labels.SortedPairs }} • *{{ .Name }}:* `{{ .Value }}`
                {{ end }}
              {{ end }}

    route:
      group_wait: 10s
      group_interval: 5m
      receiver: slack-notifications
      repeat_interval: 3h 

Aquí es donde se hará la sustitución de nuestro webhook de Slack(slack_api_url) configurado en el paso inicial y también la sustitución del canal donde va a escribir las alertas(slack_channel).

  • Configuración de recolección de logs: por defecto Prometheus hace la recolección de todos los logs de kubernetes al nivel de infraestructura. La única modificación que haremos es que recopile los logs del pod de Jenkins (Este funciona ya que hemos instalado el plugin de prometheus en Jenkins) así recibiremos información valiosa de Jenkins que luego explotaremos con Grafana.
      - job_name: 'jenkins'
        metrics_path: /prometheus
        static_configs:
          - targets: [jenkins.gitops.svc.cluster.local:80] 

Configuración Grafana

Como último paso realizaremos la configuración de Grafana. Para esto como hemos dicho anteriormente usaremos el provider de Helm. Para hacer el despliegue usaremos el namespace creado anteriormente llamado monitoring donde se desplegará Grafana. El fichero donde se realizaremos el despliegue de Grafana es helm_monitoring.tf.

Para realizar el despliegue con helm usaremos el siguiente código:

data "local_file" "helm_chart_grafana" {
  filename = "${path.module}/templates/grafana.yaml"
}

resource "helm_release" "grafana" {
  chart      = "grafana"
  name       = "grafana"
  namespace  = kubernetes_namespace.monitoring.metadata.0.name
  repository = "https://charts.helm.sh/stable"

  values = [data.local_file.helm_chart_grafana.content]
} 

Todas las configuraciones iniciales de Grafana vienen modificadas en el template grafana.yaml.

Las configuraciones más importantes que hemos realizado en dicho archivo son las siguientes:

  • Configuración del servicio: el servicio de Grafana se levantará como LoadBalancer para que sea visible desde el exterior y lo podamos revisar desde nuestro navegador web.
service:
  type: LoadBalancer
  port: 80
  targetPort: 3000
    # targetPort: 4181 To be used with a proxy extraContainer
  annotations: {}
  labels: {}
  portName: service 
  • Configuración de credenciales: para loguearnos en Grafana haremos uso del secret de kubernetes grafana-secrets que hemos definido con usuario y contraseña.
admin:
  existingSecret: grafana-credentials
  userKey: adminUser
  passwordKey: adminPassword 
  • Configuración del datasource: como configuración inicial también añadiremos Prometheus como fuente de datos en Grafana. Para ello tendremos que añadir la url interna de prometheus, en nuestro caso será el nombre del servicio (prometheus-server) más el namespace donde esta desplegado (monitoring) y por último la resolución del DNS en GKE.
datasources:
 datasources.yaml:
   apiVersion: 1
   datasources:
   - name: Prometheus
     type: prometheus
     url: http://prometheus-server.monitoring.svc.cluster.local
     isDefault: true 
  • Configuración de los dashboards: añadiremos unos dashboards inciales cuando se levante el servicio de Grafana. Estos dashboards serán los siguientes:

    • Jenkins-Performance: Encargado de la monitorización del servicio de Jenkins a nivel de ejecución de jobs y de uso de recursos.

    • Kubernetes-nodes: Monitorización de la infraestructura de GKE.

    • kubernetes-cluster: Monitorización de la infraestructura de GKE.

dashboards:
  default:
    Jenkins-Performance:
      gnetId: 14764
      revision: 1
      datasource: Prometheus
    Kubernetes-nodes:
      gnetId: 315
      revision: 3
      datasource: Prometheus
    Kubernetes-cluster:
      gnetId: 6417
      revision: 1
      datasource: Prometheus 

Despliegue de la infraestructura

Una vez realizado el paso de configuración de variables pasaremos a realizar el despliegue de toda la infraestructura (GKE y VPC) y de todos los componentes que vamos a desplegar (Jenkins, Prometheus y Grafana).

Para ello al tenerlo todo configurado con Terraform solo debemos ejecutar los siguientes comandos dentro de nuestro proyecto:

cd src/
terraform init
terraform apply 

Una vez ejecutado los comandos anteriores la salida esperada de nuestro proyecto será la siguiente:

Revisión de la infraestructura

Una vez desplegado toda la infraestructura empezaremos a hacer la revisión de todo lo desplegado para ello nos conectaremos al cluster de GKE de la siguiente forma:

gcloud container clusters get-credentials $cluster_name --region $region --project $project_id 

Para ello se deberá sustituir las variables cluster_name por el nombre del cluster, region por la región donde se ha desplegado el cluster de GKE y la variable project_id haciendo referencia al proyecto donde se desplegará.

Una vez conectado al cluster de GKE haremos una revisión de los nodos desplegados:

kubectl get nodes 

Esto nos devolverá:

Así como pods y servicios que tenemos disponibles:Así como pods y servicios que tenemos disponibles:

kubectl get pods,svc -n gitops
kubectl get pods,svc -n monitoring 

Con la siguiente salida:

Revisión de Jenkins

Una vez hemos verificado nuestra infraestructura vamos a realizar la revisión de Jenkins.

Con la información sacada del servicio de jenkins accederemos a la interfaz web donde nos tendremos que loguear, en nuestro caso 34.79.219.11.

Para sacar la contraseña de nuestro usuario de Jenkins tendremos que ejecutar el siguiente comando:

JENKINS_PASSWORD=$(kubectl get secret jenkins -n gitops -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
echo $JENKINS_PASSWORD 

Esto nos devolverá la contraseña y ya podremos hacer el login:

Como se puede observar una vez dentro de Jenkins tenemos la configuración que hemos realizado con Jcasc y la creación del job que hemos definido con JobDsl.

Revisión de Grafana

Para la revisión de Grafana debemos acceder también a la interfaz web y loguearnos. La dirección de la interfaz web la podemos observar con el comando de kubectl lanzado anteriormente en nuestro caso es 34.140.61.173.

Para loguearnos usaremos la información que pasamos por nuestras variables de entorno, TF_VAR_password_grafana y TF_VAR_user_grafana.

Podemos comprobar que la configuración que hemos añadido al template de creación de datasources y dashboards es correcta.

  • Datasources:
  • Dashboards:

Job de prueba

Para nuestro ejemplo usaremos un proyecto que desplegará un resource group en Azure, se encuentra en el siguiente repositorio.

Lo más destacable de este proyecto es la definición de nuestro pipeline de ejecución:

pipeline {
  agent {
      label "terraform-exec"
  }
 stages {
  stage('checkout') {
   steps {
    container('terraform') {
     echo "La rama de la que se va a hacer el checkout es: master"
     git branch: "master", url: 'https://github.com/lucasberlang/terraform-azure-test.git'
    }
   }
  }
   stage('Review Terraform version') {
    steps {
    container('terraform') {
      sh 'terraform --version'
    }
    }
   }
   stage('Terraform init') {
    steps {
    container('terraform') {
      sh 'terraform init -upgrade'
    }
   }
   }
  stage('Terraform plan infrastructure') {
    steps {
    container('terraform') {
     withCredentials([string(credentialsId: 'vaultUrl', variable: 'VAULT_ADDR'),
     string(credentialsId: 'vaultToken', variable: 'VAULT_TOKEN')
     ]) {
      sh 'export VAULT_ADDR=${VAULT_ADDR} && export VAULT_TOKEN=${VAULT_TOKEN} && terraform plan'
     slackSend channel: "#practica-cloud-deployments",color: '#BADA55', message: "Plan completed! Do you approve deployment? ${env.RUN_DISPLAY_URL}"
     }
    }
   }
  }
  stage('Approval') {
   when{
    not {
     branch 'poc'
    }
   }
   steps {
    container('terraform') {
     script {
     def userInput = input(id: 'confirm', message: 'Apply Terraform?', parameters: [ [$class: 'BooleanParameterDefinition', defaultValue: false, description: 'Apply terraform', name: 'confirm'] ])
     }
    }
   }
  }
  stage('Terraform Apply') {
    steps {
    container('terraform') {
     withCredentials([string(credentialsId: 'vaultUrl', variable: 'VAULT_ADDR'),
     string(credentialsId: 'vaultToken', variable: 'VAULT_TOKEN')
     ]) {
      sh 'export VAULT_ADDR=${VAULT_ADDR} && export VAULT_TOKEN=${VAULT_TOKEN} && terraform apply -auto-approve'
     slackSend color: '#BADA55', message: "Apply completed! Build logs from jenkins server ${env.RUN_DISPLAY_URL}"
     }
    }
    }
  }
  stage('Waiting to review the infrastructure') {
    steps {
    container('terraform') {
     slackSend channel: "#practica-cloud-deployments", color: '#BADA55', message: "Waiting 5 minutes before destroy the infrastructure!"
     sh 'sleep 300'
    }
   }
    
  }
  stage('Destroy Infra') {
    steps {
    container('terraform') {
     withCredentials([string(credentialsId: 'vaultUrl', variable: 'VAULT_ADDR'),
     string(credentialsId: 'vaultToken', variable: 'VAULT_TOKEN')
     ]) {
      sh 'export VAULT_ADDR=${VAULT_ADDR} && export VAULT_TOKEN=${VAULT_TOKEN} && terraform destroy -auto-approve'
     slackSend channel: "#practica-cloud-deployments", color: '#BADA55', message: "Destroy completed! Build logs from jenkins server ${env.RUN_DISPLAY_URL}"
     }
    }
    }
  }
 }
} 

En el pipeline se han definido las siguientes stages:

  • Checkout: se hará el checkoput de nuestro proyecto.
  • Review Terraform Version: revisaremos la versión de terraform desplegada en nuestro contenedor agente.
  • Terraform init: stage de inicialización de entorno.
  • Terraform plan infrastructure: revisión de la infraestructura a desplegar en nuestro caso un resource group de Azure.
  • Approval: paso de aprobación manual para comprobar si la infraestructura a desplegar es correcta.
  • Terraform Apply: ejecución y creación de toda la infraestructura que vamos a realizar en Azure.
  • Waiting to review the infrastructure: solo como para la demo haremos una espera de 5 minutos para revisar toda nuestra infraestructura.
  • Destroy Infra: destrucción de toda la infraestructura una vez esperado estos 5 minutos.

Ejecución end to end

Una vez realizado todas las comprobaciones pertinentes vamos a ejecutar el job de prueba en Jenkins para ver como escala nuestra plataforma así como el envío de alertas y la monitorización que realizaremos en Grafana.

Accederemos a nuestro job de prueba y lo ejecutaremos, dándole al botón construir ahora:

Una vez que el job esta lanzado podemos comprobar cómo se instancia un nuevo agente de Jenkins para nuestra ejecución teniendo así una infraestructura totalmente escalable:

watch -n 1 kubectl get pods -n gitops 

Cuando el job este ejecutando podemos observar cómo nos van a ir llegando alertas automáticamente al Slack. La primera alerta llegará en el stage de approval donde nos pedirá Jenkins que revisemos la infraestructura que vamos a desplegar y si es correcta aprobaremos el cambio:

Y una vez que entremos en el link de slack nos saltará directamente la interfaz de blueocean:

Una vez aprobado el cambio se realizará el despliegue en nuestra cuenta de Azure, donde podemos revisar que esta todo correcto y el resource group, en nuestro caso example-test, se ha desplegado correctamente:

Por último podemos observar todas las alertas que hemos recibido en nuestro canal de Slack las cuales nos avisaran en el stage de approval, waiting y destroy de la infra:

Escalabilidad de la solución

Una vez que hemos comprobado que nuestra solución está totalmente automatizada y que funciona correctamente vamos a comprobar la escalabilidad de nuestra plataforma.

Para ello lo que vamos a hacer es lanzar múltiples ejecuciones de nuestro job de Jenkins para ver cómo va desplegando la solución tanto a nivel de agentes (Levantará un pod por ejecución) como la escalabilidad de nuestros nodos cuando tengamos más peticiones y consumo de memoria y CPU.

El primer paso será ejecutar múltiples jobs desde la interfaz web:

Como se puede observar nuestros pods irán escalando según el número de ejecuciones que tenemos:

Para ver como escala a nivel de máquinas usaremos el siguiente comando:

watch -n 1 kubectl get nodes 

Como se puede observar se ha agregado un nuevo nodo hace 23 segundos por lo que nuestra plataforma es capaz de ir escalando según las peticiones que se realicen desde nuestro Jenkins.

Monitorización de la plataforma

Dado que hemos realizado tanto la configuración de Grafana como la de Prometheus vamos a hacer uso de nuestros dashboards predefinidos para monitorizar la plataforma en Grafana y el envío de alertas con Prometheus.

Dashboards

Estos dashboards que hemos creado son los siguientes:

  • Jenkins: Performance and Health Overview: dashboard con información relativa a nuestras ejecuciones en Jenkins, número de jobs, memoria utilizada… .
  • Kubernetes Cluster: en este dahsboard tendremos información del cluster de kubernetes a nivel de pods corriendo en el cluster memoria consumida por pod etc.
  • Kubernetes Cluster: en el último dahsboard se mostrará gráficos de picos de red, CPUs y memoria a nivel de máquinas en el Cluster de GKE.

Alertas del sistema

Además de alertas cuando se ejecuta un job también se hemos realizado la configuración de alertas de la infraestructura vía Prometheus. Este nos enviará alertas cuando el pod de Jenkins este caído o alguno de los nodos del cluster se caiga.

Para hacerlo un poco más ilustrativo usaremos el ejemplo de autoescalado que hemos realizado antes para que Prometheus nos envíe una alerta cuando empiece a quitarse máquinas porque el cluster de GKE detecte que no se está consumiendo tanta memoria.

Cuando hicimos la prueba de autoescalado vimos como se había agregado un nuevo nodo llamado gke-go-euw2-bk-sca-g-default-node-poo-c9c04d95-sgmz. Este después del pico de ejecuciones de jobs en Jenkins se auto eliminará ya que no tendremos tanta actividad en nuestro GKE.

Prometheus al detectar que nuestro nodo ha desaparecido nos enviará una alerta de nodo caído automáticamente a Slack.

Así sabremos de una forma totalmente automatizada si nuestra infraestructura ha sufrido algún problema.

Para el caso de los pods lo que vamos a provocar es una caída del componente de Jenkins para que así Prometheus nos envíe una alerta de pod caído.

Para ello lo que vamos a realizar es la destrucción del pod de Jenkins para provocar una caída del servicio:

kubectl delete pod jenkins-0 -n gitops 

Una vez que el pod este eliminado se arrancará automáticamente:

Y pasado un minuto Prometheus nos avisará de la caída del servicio de Jenkins y de su posterior recuperación:

Eliminación de recursos

El último paso que haremos tras probar el éxito de nuestra plataforma CI/CD será realizar la destrucción de todos sus componentes para ello ejecutaremos el siguiente comando:

cd src/
terraform destroy 

Conclusión

Es de sobra conocido la importancia de automatizar el ciclo de vida del desarrollo software pero en este artículo le hemos querido dar una vuelta más a esta automatización consiguiendo industrializar toda nuestra plataforma CI/CD. Para ello hemos automatizado tanto el levantamiento de toda nuestra infraestructura con Terraform así como la configuración inicial de nuestras tres piezas centrales Jenkins, Prometheus y Grafana.

Haciendo uso de plugin como Jcasc conseguimos automatizar toda la configuración inicial de Jenkins que muchas veces es bastante tediosa de hacer de arranque. Esto nos permite que si alguna vez hay una caída de nuestro servicio siempre mantendremos una configuración base en este, evitando tiempos de configuración que al final se reduce en tiempo de espera para el usuario.

Además, añadiendo una monitorización inicial tanto con Prometheus como con Grafana somos capaces de crear un sistema de avisos que nos ayude a comprobar y chequear el correcto comportamiento de toda nuestra plataforma para así poder evitar futuras incidencias.

Por último el uso de GKE en este tipo de plataformas consiguen un ahorro de costes bastante importante en nuestra organización ya que nos da la posibilidad de tener un autoescalado dependiendo del uso que se esté realizando sobre nuestra plataforma, pudiendo así tener picos de gran carga sin tener un deterioro del servicio.

Navegación

Introducción

Objetivos

Introducción a Terraform

Introducción a GKE

Introducción a Jenkins

Introducción a Prometheus

Introducción a Grafana

Introducción a Slack

Preparación de entorno

Clonación de repositorio

Índice de ficheros

Configuración de Slack

Configuración de variables

Configuración VPC

Configuración GKE

Configuración Jenkins

Configuración Prometheus

Configuración Grafana

Despliegue de la infraestructura

Job de prueba

Ejecución end to end

Escalabilidad de la solución

Monitorización de la plataforma

Eliminación de recursos

Conclusión

Autor

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

Lucas Calvo Berlanga

Cloud Engineer

SOLUCIONES, SOMOS EXPERTOS

DATA STRATEGY
DATA FABRIC
AUGMENTED ANALYTICS

Te puede interesar

Oscar Hernández, nuevo CEO de Bluetab LATAM

mayo 16, 2024
LEER MÁS

Azure Data Studio y Copilot

octubre 11, 2023
LEER MÁS

El futuro del Cloud y GenIA en el Next ’23

septiembre 19, 2023
LEER MÁS

Guía avanzada sobre almacenamiento en Snowflake

octubre 3, 2022
LEER MÁS

DataOps

octubre 24, 2023
LEER MÁS

Gobierno del Dato: Una mirada en la realidad y el futuro

mayo 18, 2022
LEER MÁS

Publicado en: Blog, Practices, Tech

  • « Ir a la página anterior
  • Página 1
  • Página 2
  • Página 3
  • Página 4
  • Ir a la página siguiente »

Footer

LegalPrivacidadPolítica de cookies
LegalPrivacy Cookies policy

Patrono

Patron

Sponsor

Patrocinador

© 2025 Bluetab Solutions Group, SL. All rights reserved.