En otras ocasiones hemos hablado de .Net Core, de los webjobs y aunque nunca he escrito mucho sobre ello tengo interés por automatizarlo todo lo más posible. Para esto último, intentando no sobretrabajar he usado siempre las funcionalidades de Visual Studio Team Services.
El problema que voy a describir, es un problema con una solución tremendamente sencilla una vez que das con ella, pero que al no haber ningún tipo de documentación y no haber nadie por el mundo que haya hecho exactamente eso y lo haya contado, acabas teniendo que ir deduciendo cosas, poniendo parches y testeando multiples variantes hasta que das con el conjunto de pasos buenos que te salvan la vida.
Como sabéis .Net Core ha roto el cascarón de su primera versión hace muy poquito, y aunque mola mucho porque es muy ligero, es multiplataforma, te facilita (o fuerza) a usar ciertos patrones que merecen la pena… A pesar de todas esas cosas también tiene sus pegas, y las principales creo que son sin ninguna duda el tooling y las librerías que permiten la integración con otras partes del entramado Microsoft.
Del mismo modo, el SDK de los webjobs también es relativamente nuevo, y tiene muy pocas funciones y sólo vale para el Framework de .Net (el de toda la vida).
El tercer componente de nuestra ecuación es el más maduro, ya que Visual Studio Team Services viene de TFS que lleva ya dando vueltas mucho tiempo y tiene multiples opciones con las que solventar las cosas cuando no hay un componente específico (a las malas siempre puedes subir tu propio script de PowerShell por ejemplo).
El problema de juntar estos tres elementos es que no hay SDK de webjobs para .Net Core, y que las herramientas de core no permiten añadir un webjob a tu proyecto.
Las estrategias posibles para afrontar este problema son múltiples:
- Despliegues independientes. Siempre puedes montar despliegues independientes, pero tendrás que gestionar todo por separado y no en todos casos va a ser lo ideal. No tiene porque suponer un incremento en costes porque lo puedes tener rulando todo en el mismo Service Plan para que compartan recursos y esperar a que todo crezca mucho para separa cada cosa por su lado en ejecución. Sin embargo, a mi personalmente, me deja mal sabor de boca el hacer algo forzado porque no sé, cuando en el caso que teníamos entre manos el ideal era llevarlo junto ya que hay cierta dependencia entre proyectos.
- Montar el webjob con .Net Core sin el SDK. Al final un webjob no deja de ser un programa o script que ejecutas. Puedes montar un ejecutable independiente o una dll que invocarás con el comando dotnet y las librerías de .Net Core para acceder al storage de Azure y hacerte tu propio acceso a datos y tus disparadores y esas cosas. Esto… es una opción pero multiplica el tiempo de desarrollo de cada webjob al menos por tres, te hace picar mucho más código y asumir más responsabilidades de las que deberías. Sí que es cierto que el rendimiento mejoraría, así que puede ser la opción buena en función de lo intensivo que vaya a ser el webjob. En nuestro caso el webjob necesitaba usar unas librerías que sólo existen en .Net Framework y por tanto no era una opción que pudiésemos tomar sin desarrollar un montón de componentes para .Net Core.
- Hacer tu webapp con .Net Framework. Siempre es una opción, pero la diferencia de rendimiento creo que justifica suficientemente el no tomar esta opción por defecto nunca.
- Hacer que las dos tecnologías se entiendan y todos los despliegues funcionen bien. Generalmente todo es posible, y aunque no hubiese documentación al respecto, siempre podríamos haber hecho scripts propios e intentar que se lanzasen cuando tocase (que en una pequeña parte es lo que hemos hecho). Aunque llegar al conjunto de pasos más simple posible para no condenar a tu yo del futuro puede llegar a ser complicado. Esta ha sido la opción que tomé para ponerlo todo a andar.
Lo primero de todo es hacer que cuando publicas desde tu Visual Studio a maneja a Azure para hacer alguna prueba puntual montando una nueva webapp se despliegue con ella el webjob para que no haya problemas y todo funcione correctamente.
Para hacer esto solo hay que seguir unos muy muy simples pasos, y aunque todos tienen su lógica y explicación, voy a pasar muy rápido por ellos para no enrollarme:
- Lo primero es crearte en tu proyecto de aplicación web una carpeta app_data si no la tienes ya. En ella tendrás que crear otra que se llame jobs y dentro de esta tienes que actuar en función del tipo de webjob que tengas:
- Si es un webjob que se ejecuta continuamente esperando a que salte algún trigger desde el storage, tendrás que crear una carpeta que se llame Continuous.
- Si el webjob es de ejecución manual y lo vas a lanzar tú cuando te convenga (¿por qué nadie querría hacer esto?), tendrás que crear una carpeta que se llame Triggered.
- Si el webjob es de ejecución programada o agendada (scheduled, vamos), tendrás que crear también una carpeta que se llame Triggered, pero posteriormente deberás dar un paso adicional.
- En esa carpeta (o carpetas si tienes distintos tipos de webjobs), deberás crear una carpeta por cada webjob con el nombre que quieras ver en Kudu.
- En esa carpeta tienes que meter un script que se llame run.cmd (hay otras opciones, pero no nos compliquemos) invocando al ejecutable del webjob como si estuviese en la carpeta aunque no haya nada en ella. Vamos, que hay que poner el nombre del ejecutable en el archivo y si quieres primero una línea con un «@echo off«, y listo
- Si el webjob es de ejecución programada (o si queremos usar otras features como hacer un webjob singleton por ejemplo), meteremos en esa carpeta el archivo settings.job correspondiente.
- A continuación hay que decirle a quien corresponda que cuando publique ese proyecto incluya esa carpeta. En el archivo project.json tendremos la propiedad de primer nivel (y si no la creamos) «publishOptions«, y en su interior deberíamos tener (y si no lo creamos) una propiedad de tipo array llamada «include«. Ahí deberemos añadir la ruta de lo creado, por ejemplo «app_data/jobs/**/*.*«. De este modo conseguiremos que cuando se publique se copien esas carpetas con el archivo o los dos archivos que hemos creado.
- Por último deberemos decirle que hay que compilar el proyecto del webjob y copiar los archivos a la carpeta de publicación en la ruta adecuada «app_data/jobs/…«. Para esto, en el mismo archivo project.json tienes una propiedad de primer nivel llamada «scripts» y en su interior otra de tipo array que se llama «postpublish» (si no existe alguna, sólo tenéis que escribir). Como el proyecto es de .Net Framework, tendremos que invocar al comando msbuild que nos toque en función del tipo de nuestro proyecto (que espero que sea 2015, no me fastidiéis) la ruta del proyecto y la del destino. En mi caso: «\»%ProgramFiles(x86)%\\MSBuild\\14.0\\Bin\\msbuild.exe\» ..\\ProjectFolder\\ProjectName.csproj /property:OutDir=%publish:OutputPath%\\app_data\\jobs\\Continuous\\WebjobName\\«
Con esto, ya podremos usar la opción de publicar del Visual Studio y que todo funcione. Cuando se publique el proyecto compilará guardando el resultado en la carpeta del webjob en el destino de la publicación.
Como os decía es una chorrada una vez que das con los pasos adecuados.
Lo siguiente es meterlo en el VSTS, y aunque sé que está en preview os recomiendo usar la plantilla de deploy de .Net Core añadiendo los pasos que necesitéis para desplegar en vuestro servidor (por ejemplo un «Azure App Service Deploy» que coja el paquete del destino de la compilación «$(build.artifactstagingdirectory)\**\*.zip«), o si tenéis montado un sistema de release y validaciones que pase por distintos entornos, simplemente la plantilla en cuestión.
Veréis que os pasa en verde, pero que ni webjob ni nada, y si atendéis a los logs de vuestro build, en concreto al del step Publish, veréis que algo no ha ido del todo bien al ejecutar el script de postpublish que introdujimos, y parece que no encuentra muchos archivos. El problema es que en vuestro sistema están todas las librerías necesarias dónde toca porque el plugin de NuGet que tenéis en Visual Studio lo ha hecho sin molestaros.
La solución es tan sencilla como antes de ese paso meter un componente de NuGet install con la opción de restore seleccionada, para que restaure todas las dependencias que tengan los proyectos de .Net Framework de la solución, así como tenéis el Restore de .Net Core.
Con esto ya tendréis todo funcionando, dos tecnologías distintas conviviendo juntas en amor y compañía 😀
Tened en cuenta que en un tiempo indeterminado llamemosle X la información de este post estará obsoleta. En diciembre de 2016 la comunidad abrió un ticket en uno de los githubs de estos proyectos, y unos se lo han pasado a otros cerrando sus tickets, hasta que alguien ha decidido que lo que había que hacer era primero abrir un ticket para pensar lo que hay que hacer… Parece uno de esas chistosas historias paralelas que contaría la voz en off de «La guía del autoestopista galáctico», pero es real, podéis buscarlo vosotros mismos xD. Esperemos que ese X no sea demasiado largo, crucemos los dedos en forma de X.
Cualquier duda o pregunta, o si no se entiende algo, o si algo está mal… escribid un comentario o me contactáis por dónde sea. ¡A cuidarse!