.Net core CI/CD en AWS Serverless

Desde primeros de año esta disponible .Net Core 2.1.
AWS lambda soporta el runtime de .Net core (tanto 1.0, como 2.0 y 2.1).
AWS anuncia que la duración máxima de sus lambdas aumenta hasta los 15 minutos.
Para VSTS está disponible un toolkit de aws con tareas para gestionar el delivery
Para VS 2017 está disponible otro toolkit de aws con sdk para sus componentes.
La infraestructura como código cada vez es potente (IaC – Infrastucture as code).
Los skills DevOps cada vez son mas valorados.

Con todas estas noticias juntas es imposible no plantearse implementar pipelines para desplegar soluciones de .Net core en AWS Serverless a través de cloudformation templates, gestionando la pipelines desde VSTS y creando código debugable con VS 2017. Así que vamos a ello.

Disponer de una cuenta de AWS

Todo comienza por disponer de una cuenta de AWS. Si no dispones aún de una, su capa gratuita de duración indefinida es bastante amplia como para que vayas haciendo tus pinitos. Además incorpora una serie de características gratuitas durante 12 meses.

Necesitarás disponer de una cuenta para introducir las credenciales de AccessKey y SecretAccess en VS2017 para administrar tus componentes de AWS desde el toolkit para visual studio, además tus componentes podrán acceder a AWS con ejecuciones en local.

Una vez creas una cuenta en AWS (https://aws.amazon.com/es/) podrás acceder a la consola y crear un usuario con programmatic access, el tipo de acceso necesario para trabajar con el AWS toolkit para VS2017. Con este toolkit podrás crear aplicaciones .Net Core 2.1 para AWS Serverless, teniendo algunas templates disponibles de muestra.

Crear un usuario con programmatic access

Es bastante fácil, el sistema te guía por un formulario de 4 pasos, aunque quizá podríamos pensar en dotarlo de una mejor protección creando grupos a los que unir a los usuarios, dotando a los grupos de los permisos necesarios, en lugar de darlos directamente a los usuarios. Pero para una prueba rápida, creas tu usuario, le añades tus permisos, generas unas credenciales y listo.

Primero creas un usuario, con programmatic access.

Segundo le asignas los permisos para los componentes que desees que administre desde VS2017, para el ejemplo he seleccionado full access con AdministratorAccess.

Revisas y confirmas la creación de usuario.

Por último, se te mostrarán el AccessKey y el SecretKey, que deberás guardar para utilizarlos en VS 2017 AWS toolkit.

Instalar AWS Toolkit para VS 2017

Desde extensiones y actualizaciones de Visual studio puedes buscar por “AWS toolkit for visual studio 2017”.

Una vez instalado verás una pagina de inicio como la siguiente, se te presentarán algunas instrucciones y campos en los que poder introducir tus credenciales.

Una vez tengas tus credenciales asociadas a VS, verás una ventana “AWS Explorer” con todos los elementos de AWS.

Pues listo, ya deberías poder crear proyectos nuevos .Net Core para AWS.

Crear un nuevo proyeto AWS Serverless Application (.Net Core)

Al crear nuevo proyecto, tendremos disponible una nueva gama de tipos de proyectos que podemos seleccionar dentro de C#. AWS Lambda y AWS Samples.

Para este ejemplo vamos a crear un AWS Serverless Application (.Net core), que ya incorporará una template de cloudformation por defecto que nos proveerá de todos los componentes necesarios para que la App funcione.

Al igual que Azure tiene su “Azure Resource Manager”, AWS tiene AWS CloudFormation, de forma que podremos crear infraestructura basada en código que será interpretado para crear recursos. Pero de esto ya iré hablando en otras entradas, de momento será suficiente con la template que este proyecto trae por defecto para que podamos notar su potencia.

Al crear el proyecto se ofrecerán una serie de templates, podemos empezar con algo completamente nuevo, con algunos ejemplos de lambdas como webapi, una app con vistas razor, una api que gestiona objetos almacenados en dynamoDB… para esta entrada me he basado en la API básica.

Una vez creado el proyecto se crea con la siguiente estructura.

Hay varias partes a destacar.

Controladores

Por un lado se crean dos controladores, con su propio routing definido por annotations como conocemos habitualmente en C#. Esto quiere decir que los triggers no son gestionados por un API Gateway.

El API Gateway se crea como trigger HTTP pero como un simple proxy que acepta cualquier tipo de verbo y path. De este modo serán nuestros controladores quienes gestionarán el flujo por el código de la aplicación, como una WebApi .Net Framework tradicional. No obstante que solo se admitan triggers HTTP para la lambda, procedentes de un API Gateway que coincida con la API definida por nuestros controladores, será algo que se podrá definir fácilmente en el template de serverless.

Por otro lado, si tenemos unos controladores que gestionan todo nuestro flujo de negocio de la App, nuestro proyecto es perfectamente susceptible de tener arquitecturas de código o patrones como los describo en mis entradas sobre:

Únicamente habría que trasladar el código de esas entradas desde .Net Framework a .Net Core.

Además podríamos definir una capa de servicio donde poder comprobar que todo el flujo de nuestra lógica de negocio se respeta, tal como explico en otra entrada sobre arquitectura software como este:

serverless.template

Esta configuración de un AWS API Gateway como un mero proxy es posible gracias a serverless.template y LambdaEntryPoint. El serverless template configurará todos los componentes utilizados, como el API Gateway y la lambda, sus permisos de acceso y su conexión mutua.

Si observamos no se reconoce a primera vista un recurso de tipo RestApi o sus metodos y sus integraciones, esto es porque la plantilla es de tipo AWS Serverless Application Model (AWS SAM), podemos ver esto en la propiedad “transform” que existe. AWS SAM consigue simplificar las plantillas. Esta, por ejemplo, dentro de resources tiene 2 objetos, una vez transformada a una template de cloudformation convencional el total de resources serán 8. Es por esto que no debes perder el tiempo en tratar de mapear toda tu template contra cloudformation a ver que hace, si no que habrá también que darse una vuelta por AWS SAM.

LambdaEntryPoint

El LambdaEntryPoint mapeará toda la request de forma que los controladores puedan hacer su trabajo, ya que hereda del objeto APIGatewayProxyFunction. De momento para esta entrada será suficiente con esta información.

LocalEntryPoint

Bajo el lambda entry point se puede ver un LocalEntryPoint, esto suena genial para labores de debug durante el desarrollo, y así es, nada mas arrancar la aplicación en local con unos cuantos puntos de interrupción observamos que es perfectamente debugable, algo que no es tan sencillo de conseguir en implementaciones con nodejs donde debemos andar con ejecuciones y console.logs.

Startup

Estos dos entrypoint hacen uso de startup, donde estará por ejemplo el contenedor de Dependency Injection nativo de .Net core, donde podremos añadir tantos servicios del SDK de AWS como deseemos, además de EF core o configuraciones de OAuth, de forma que el comportamiento de local no tendrá nada que envidiar al comportamiento en la nube una vez desplegada la app a una lambda.

DevOps

Pues bien, tu aplicación ya funciona en local, ahora quieres desplegarla. Podriamos desplegarla haciendo uso de visual studio y el AWS dotnet cli, pero lo que buscamos es una perspectiva Devops.

Por ello vamos a configurar una build con integración continua (CI) que reciba nuestros commits de código, de momento vamos a dejar la parte de test para otra entrada, pero si que vamos a configurar también una release que haga el despliegue de la aplicación una vez que la build se ha completado con éxito (CD). El CI y CD con la build y la release formarán parte de un pipeline de Visual Studio Team Services (VSTS) o Azure Devops desde su cambio de imagen hace unos meses.

Preparando el proyecto

La tarea que utilizaremos en vsts hace uso de comandos dotnet cli que deberemos instalar con Amazon.Lambda.Tools. En algunos proyectos su instalación mediante nugets con visual studio produce un error como el siguiente:

Package ‘Microsoft.DotNet.Watcher.Tools 1.1.0-preview4-final’ has a package type ‘DotnetCliTool’ that is not supported by project…

Si este es tu caso basta con editar tu csproj para incluir la herramienta de la siguiente manera (para editar el csproj de tu proyecto, simplemente click derecho en tu proyecto y editar “proyectName.csproj”)

Dentro de <ItemGroup> </ItemGroup> deberás añadir:
<DotNetCliToolReference Include=”Amazon.Lambda.Tools” Version=”2.2.0″ />

De esta forma, la tarea que utilizaremos en vsts será capaz de utilizar el comando que necesita, dotnet-lambda.

Preparando la integración

Para hacer posible la CI, necesitarás que tu proyecto esté en un repositorio enlazada a tu espacio de trabajo en VSTS. Si no sabes cómo hacerlo, hablaré de ello en otra entrada. Es sencillo, pero puede hacerse de diferentes formas que merecen su propio espacio si eres nuevo en CI.

Preparando VSTS

Para usar VSTS contra AWS necesitarás instalar el AWS Toolkit for VSTS, es free y puedes encontrarlo en el buscador del marketplace.

 

Tan solo debes instalarlo en tu espacio de trabajo de VSTS y refrescar para que cuando crees una nueva build, puedas añadir tareas relacionadas con AWS.

Las tareas de aws te pedirán unas credenciales, para ello podemos establecer un servicio para consumir nuestras credenciales en cada una de las tareas.

Para ello vamos a la configuración de nuestro espacio de trabajo.

Seleccionamos el espacio que nos interesa configurar si tuviésemos mas de uno.

Ahora en su menú de configuración debes seleccionar “Service connections” en la sección de pipelines.

En los tipos de service connections podrás encontrar AWS, y al seleccionarlo, deberás introducir las credenciales y un nombre para este service connection.

 

Creando la build para CI

Desde VSTS con el AWS toolkit para vsts instalado, podrás crear una build desde la sección de pipelines.

El primer paso que te mostrará al crear la nueva build es el repositorio que quieres vincular y la rama. Esta será la rama que provocará un trigger en la build cuando hagas un commit.

A continuación podrás crear la build partiendo desde una template o iniciar una nueva build completamente vacía con “empty job”. No hay que confundir las templates con las tareas. Una template esta formada por una serie de tareas. El AWS Toolkit for VSTS no instala templates, solo tareas. Es por esto que comenzaremos con una template vacía.

Una vez seleccionada una plantilla vacía, podrás añadir una tarea específica de aws para desplegar codigo c# a una lambda, ya sea como código en la lambda sin más, o una template completa serverless con cloud formation.

La tarea que vamos a añadir en este caso ya incorpora todas las funciones necesarias para dejar listo el paquete a ser desplegado, como restaurar los nugets o desplegar el zip de código compilado a S3.

La documentación de AWS para esta tarea se encuentra disponible aquí

Algunos puntos a destacar son:

Además de introducir las credenciales y la región, es importante seleccionar el deployment type serverless application, ya que nuestro proyecto de ejemplo no contiene únicamente código para una lambda, si no que contiene especificaciones de otros tipos de recursos, como una Api, un bucket de S3…

Además vamos a señalar “create deployment package only”, ya que esto creará el zip que se desplegará posteriormente desde una tarea propia de la release que integraremos en este pipeline, donde podremos realizar algunas sustituciones a variables de la template de cloudformation. De esta forma, prepararemos el CD para diferentes entornos.

El código tendrá solo una build en la pipeline, pero tendrá múltiples releases enganchadas en la pipeline, por los múltiples entornos de que dispongamos.

Además nuestro proyecto en la serverless.template contiene toda la implementación de infraestructura necesaria para que la aplicación funcione, en la release existirá una tarea de cloudformation para este fin, pero necesitamos hacer que el fichero serverless.template esté disponible, para ello producimos un output de la build, que será el serverless.template.

Además en la misma tarea, habrá que especificar el bucket de S3 donde se ubicará el archivo .zip que se desplegará a la lambda, este bucket debe existir. S3 prefix hará referencia a los folders que querramos incluir dentro del bucket.

En “path to lambda project” tendremos que incluir la ruta relativa a la ubicación donde está el archivo csproj del proyecto que alberga el código que debe ser desplegado a la lambda.

Por último añadimos una tarea de tipo “publish build artifact”, para publicar el fichero que resultó como “output” de la build, el “serverless.template”. Este será el artifact que usará la release, dentro del folder por defecto /drop.

Con esto ya tenemos lista la build que se ejecutará con el CI, para activar el CI solo debemos ir a la tab trigger de la build y activar el CI (Continuous Integration).

Creando la release para CD

Para crear una release vamos a “releases” de la sección “pipelines”, y creamos una nueva release. Al igual que ocurre con las builds, lo primero que nos ofrecerá es seleccionar una template, pero continuaremos con una nueva release vacía.

Al crear la release vacía, tendremos el siguiente escenario.

Podremos ver que tenemos un Stage y una sección para añadir artifacts.

En artifacts tendremos que añadir el artifact que se generó durante la build que queremos vincular a este pipeline.

Una vez añadido un artifact, aparecerá una opción para configurar el CD (Continuous deployment).

Ahora hay que configurar la opción de deployment añadiendo tareas al stage, usando el link donde pone “1 job, 0 task”, que nos llevará a la pantalla de tareas de la release. Desde donde añadiremos una tarea de create/update cloudformation stack.

La documentación de esta tarea puede encontrarse aquí.

En primer lugar tenemos parametros relacionados con AWS y el stack de cloudformation que vamos a crear, como su nombre.

El stack no tiene porque existir, se creará durante la ejecución de la tarea, y si ya existiese, se actualizará. El stack name por tanto es un campo de texto libre.

La región que seleccionemos será aquella donde el stack se creará.

Un stack de cloudformation no tiene costes adicionales en AWS, unicamente pagaremos por el uso que hagamos de los recursos que sean creados con dicho stack.

En segundo lugar están una serie de parametros para usar el fichero con la template serverless que queremos usar para cloudformation. En este caso lo creamos durante la build, y lo publicamos en el directorio por defecto “drop”.

El bucket será aquel donde sera subido el template de cloudformation, o donde se encuentra el fichero a ser usado en caso de seleccionar “template source” – “Amazon S3 bucket”. En este caso usamos localfile, porque el fichero esta contenido en el proyecto y fue publicado durante la build, así que podríamos dejarlo en blanco.

La template consta de una sección parameters, cada uno de los parametros aqui indicados deben incluirse en el campo template parameters para asignarles un valor.

Los parametros indicados en la template se indicarán en este template parameters como “ParameterKey”: “nombreParametro”, y su valor con “ParameterValue”: “valorParametro”. Como se observa en la imagen anterior, donde nuestra que la template tendría los parametros BucketName y ShouldCreateBucket.

Por último hay una serie de campos relacionados con un “change set”, un change set es un elemento de cloudformation que registrará todos los cambios hechos sobre un stack o una template de un stack, y se podrá observar a través de la consola de aws dentro del stack al que pertenece el change set.

También hay otras opciones que será recomendable habilitar, relacionadas con la capacidad del stack de cloudformation de definir recursos IAM. Nuestra serverless template podría definir componentes de AWS IAM para los que el stack de cloudformation necesite autorización para poder hacer acciones sobre ellos. En este caso que estos checks estén marcados será importante.

En nuestro ejemplo concreto, con el proyecto creado, necesitará habilitar roles que permitan conectar la lambda y la Api Gateway, o que permitan acceso a S3 a la lambda. En estos caben diferentes opciones:

Los permisos IAM ya existen, y los hemos asignado en la serverless.template, en este caso nuestro cloudformation no creará o actaulizará ningún rol.

Los permisos IAM no existen, en la serverless.template hemos podido especificar unos nuevos que vincularán los elementos entre sí. En este caso nuestro cloudformation deberá crear o actualizar roles, siendo el caso donde estos checks deberán ser marcados.

Pues listo, ahora cada vez que ejecutemos un commit en nuestro código .Net Core 2.1 en el proyecto hacía master, la build se creará, y si tiene éxito, la release se ejecutará. Los recursos de AWS serán provistos por cloudformation (creados o actualizados) y el código será automaticamente desplegado a la lambda, estando todo funcionando desde una perspectiva DevOps.

Cabría añadir algunas policies a la pipeline, como test, code coverage, necesidad de pull request para no subir código a master sin asegurarnos de una build exitosa, vincular elementos de trabajo al commit… Pero todas estas cosas son complementos adicionales, no es necesarios tenerlos para que nuestro código pueda desplegarse, y de ellos hablare en otras entradas.

Un saludo y a disfrutar, no solo programando, ¡también operando tus espacios de trabajo!

Deja un comentario