Noviembre de 2018

Volumen 33, número 11

.NET Core: opciones de publicación con .NET Core

Por Jamie Phillips | Noviembre de 2018

La llegada de .NET Core ofrece un nuevo paradigma de publicación de aplicaciones con la introducción de dos nuevas técnicas: implementaciones dependientes del marco (FDD) e implementaciones autocontenidas (SCD). Cada opción tiene ventajas e inconvenientes que deben tenerse en cuenta antes de la publicación. En este artículo, exploraré ambos enfoques. Para ello, crearé una aplicación de ejemplo, y explicaré las ventajas y desventajas de cada enfoque. A continuación, echaré un vistazo breve a una opción de implementación adicional, CoreRT, que se encuentra en las primeras fases de desarrollo.

La publicación con .NET Core ofrece una gran cantidad de información que se debe tener en cuenta. Tradicionalmente, la opción de implementación típica para una aplicación de consola o escritorio en .NET Framework era un ejecutable. Para una aplicación de ASP.NET, era un archivo DLL. Estas eran las únicas opciones disponibles y dependían del tipo de aplicación que se estaba compilando. Con el nuevo paradigma, lo importante es si .NET Core está instalado. La técnica FDD requiere que .NET Core esté instalado, mientras que la técnica SCD integra el tiempo de ejecución de .NET Core, lo que significa que se puede ejecutar en un equipo que no tenga .NET Core instalado. Ahora también se puede empaquetar una aplicación de ASP.NET como una aplicación de consola independiente, ya que aporta su propio servidor web (Kestrel) como ejecutable.

Para mi exploración de los dos enfoques, usaré .NET Core SDK 2.1, junto con una interfaz de línea de comandos. Puede encontrar instrucciones para instalar .NET Core 2.1 en microsoft.com/net/download. Estoy usando PowerShell casi exclusivamente para ejecutar todos los comandos que muestro en el artículo, pero el símbolo del sistema (cmd.exe) también debería funcionar bien y, de hecho, estos comandos se ejecutarán en los shells de otros sistemas operativos, incluidos macOS y Linux. Por último, para experimentar con las implementaciones SCD, el Subsistema Windows para Linux (WSL) tiene un valor incalculable para habilitar las pruebas directas de todos los ejecutables creados para Linux. Puede obtener más información sobre WSL en bit.ly/2Nj7FuQ.

Aplicación de ejemplo

Voy a crear una aplicación de ejemplo muy básica para probar los dos métodos de publicación con .NET Core 2.1. Será una aplicación de consola que imprimirá "Hello" junto con el nombre proporcionado. Puede usar cmd.exe o PowerShell para escribir el código siguiente:

> dotnet new console -o Publishing

Una vez creada la nueva aplicación de consola, abro Program.cs y escribo el código siguiente:

using System;
namespace Publishing
{
  class Program
  {
    static void Main(string[] args) => Console.WriteLine($"Hello {args[0]}!");
  }
}

Cuando ejecuto la aplicación, veo lo siguiente:

> dotnet run -- Jamie

Hello Jamie!

Como puede ver, funciona según lo previsto. A modo de apunte breve, la sintaxis de dos guiones (--) es el método para pasar parámetros a la aplicación cuando se usa el comando "dotnet run". Con mi aplicación de consola completa, puedo profundizar en los métodos de publicación.

Comando Publish

En primer lugar, echemos un vistazo a las opciones disponibles con el comando publish si se ejecuta con la opción "-h" (de Ayuda):

dotnet publish [<PROJECT>] [-c|--configuration] [-f|--framework] [--force]
                           [--manifest]
                           [--no-build] [--no-dependencies] [--no-restore]
                           [-o|--output]
                           [-r|--runtime] [--self-contained]
                           [-v|--verbosity] [--version-suffix]

Tenga en cuenta que dos de estas opciones son especialmente útiles cuando se publican aplicaciones de .NET Core: --runtime y --self-contained. Estas opciones se usan juntas; el tiempo de ejecución especifica el tiempo de ejecución o los tiempos de ejecución de destino al crear una implementación SCD, mientras que las implementaciones autocontenidas indican que se debe crear un paquete independiente con el tiempo de ejecución. Si se pasa la opción de tiempo de ejecución, se aplica automáticamente la opción autocontenida.

Otras opciones que se deben tener en cuenta son las opciones no-restore y no-build. La opción no-restore no ejecutará la restauración implícita cuando se ejecute el comando publish. La opción no-build no compilará el proyecto ni ejecutará el comando restore, de modo que el comando publish se ejecutará en la compilación existente. Esto es útil en escenarios de implementación continua e integración continua, ya que la compilación y la restauración no se desencadenarán varias veces. Por último, la opción framework permite especificar la versión de marco. Encontrará la documentación oficial en bit.ly/2pizcOL.

Implementaciones dependientes del marco

Las implementaciones FDD son las predeterminadas cuando se ejecuta el comando publish en la CLI de .NET Core. El comando publish crea una aplicación independiente de la plataforma que se puede ejecutar en cualquier entorno en tiempo de ejecución de .NET Core equivalente a la versión menor (o más reciente) que se utiliza para compilar la aplicación (aunque la versión 2.0 no ejecutará nada con el destino 1.x y la versión 3.0 no ejecutará nada que tenga como destino 2.x). Dado que el entorno en tiempo de ejecución de destino no está empaquetado con la aplicación, esta opción da como resultado el paquete más pequeño. Si varias aplicaciones se van a implementar en un sistema, lo que permite compartir el tiempo de ejecución entre las aplicaciones, se reducirá la memoria total y el uso de disco en el sistema. Otra ventaja del tiempo de ejecución compartido es que las actualizaciones en tiempo de ejecución futuras se aplicarán a todas las aplicaciones.

Vamos a examinar la publicación de la aplicación de ejemplo como una implementación FDD, para lo que escribiremos lo siguiente en la línea de comandos:

> dotnet publish

Se crea una salida en la carpeta bin\Debug\netcoreapp2.0, que incluye el archivo publishing.dll. Este archivo DLL de 5 KB es la aplicación. A continuación, ejecutamos la aplicación con la CLI de dotnet de este modo:

> dotnet Publishing.dll Jamie

Hello Jamie!

Así de simple.

La técnica FDD ofrece varias ventajas, incluido un paquete de implementación más pequeño, un tiempo de ejecución administrado por el sistema y la capacidad de compartir el tiempo de ejecución entre varias aplicaciones para reducir el uso de disco y memoria. También se garantiza la ejecución de la aplicación en cualquier plataforma que tenga un tiempo de ejecución compatible instalado.

Las desventajas de las implementaciones FDD son que las actualizaciones en tiempo de ejecución de todo el sistema podrían provocar problemas por falta de compatibilidad y que la aplicación solo se puede ejecutar en la versión del marco (o posterior) con la que se compiló. También requiere que los usuarios tengan instalada esa versión del tiempo de ejecución de .NET Core en su equipo antes de ejecutar la aplicación.

Implementación autocontenida

Ahora veamos el segundo enfoque para la publicación con la CLI de .NET Core: SCD. Usaré el mismo comando de publicación que antes, pero esta vez pasaré un identificador en tiempo de ejecución (RID) como una opción para el comando publish. El RID indica a la CLI de .NET Core a qué plataforma o plataformas debe dirigirse al crear la aplicación. En este ejemplo, crearé una compilación de la aplicación para Windows 10 x64. Puede usar como destino cualquier plataforma desde cualquier sistema operativo, lo que permite crear ejecutables para otros sistemas operativos desde Windows. Este es el comando que uso para este ejemplo:

> dotnet publish -r win10-x64

Tenga en cuenta que hay una subcarpeta que se crea en la carpeta netcoreapp2.0, a la que se asigna un nombre una vez pasado el tiempo de ejecución de destino. Dentro de la carpeta win10-x64, verá que ahora hay un ejecutable en lugar de un archivo DLL. Ese ejecutable tiene un tamaño de 77 KB, 72 KB más que la aplicación anterior. Puedo ejecutarlo para mostrar que sigue funcionando:

> .\Publishing.exe Jamie

Hello Jamie!

Ahora que tengo una implementación SCD para Windows 10 x64, voy a crear una para Ubuntu x64, de la siguiente manera:

> dotnet publish -r ubuntu-x64

A continuación, puedo usar WSL para probar que la versión de Ubuntu se ejecuta de la manera deseada. Abro WSL desde Windows y hago que la aplicación recién creada sea ejecutable. La ejecuto para asegurarme de que funciona. Aquí tiene la entrada y la salida resultante:

> chmod +x Publishing
> ./Publishing Jamie

Hello Jamie!

La ventaja de la técnica SCD es que el tiempo de ejecución de .NET Core está controlado y se incluye con la aplicación. Esto resulta práctico para los usuarios, ya que no tienen que instalar el tiempo de ejecución y los cambios del tiempo de ejecución del sistema no afectarán a la aplicación. Por otro lado, la técnica SCD crea paquetes de implementación de mayor tamaño porque el tiempo de ejecución se incluye con la aplicación. También debe conocer de antemano las plataformas de destino para generar paquetes para cada destino. Además, cada vez que hay una actualización del tiempo de ejecución que incluye correcciones de seguridad o errores, debe volver a publicar todas las aplicaciones en el equipo para obtener estas correcciones, en lugar de instalar una sola actualización del tiempo de ejecución en todo el equipo.

En el pasado, el enfoque para distribuir aplicaciones de .NET era exigir que .NET Framework estuviese instalado o empaquetarlo con el programa de instalación. Con .NET Core y las implementaciones SCD, ya no es necesario crear un instalador especial para entregar una aplicación básica.

Aún siendo tan positiva, la técnica presenta un problema inminente al implementar estas aplicaciones en un sistema Linux. Las dependencias del tiempo de ejecución de .NET Core no se incluyen con las implementaciones SCD, lo que significa que los usuarios no tendrán que instalar el tiempo de ejecución. Sin embargo, los usuarios deberán instalar seis dependencias desde el Administrador de paquetes para la distribución de Linux. Por ejemplo, en Ubuntu será necesario instalar las siguientes dependencias:

liblttng-ust0
libcurl3
libssl1.0.0
libkrb5-3
zlib1g
libicu52 (for 14.x)
libicu55 (for 16.x)
libicu57 (for 17.x)
libicu60 (for 18.x)

Para resolver este problema, un desarrollador debe empaquetar la aplicación SCD en un formato de paquete nativo para la distribución, de modo que se puedan definir dependencias adicionales.

Aunque este puede ser un punto negativo, lo positivo es que los usuarios no tienen que agregar ninguno de los repositorios de paquetes adicionales que serían necesarios en caso de tener que instalar el entorno en tiempo de ejecución de .NET Core.

SCD es fascinante y merece tratarse en profundidad. Aunque hay varios componentes que influyen en el funcionamiento de la técnica SCD, existen dos que contribuyen realmente a la instalación de la CLI de .NET. El primer componente es el tiempo de ejecución compartido, que es una versión redistribuible del tiempo de ejecución de .NET Core y que utilizan la CLI y los usuarios finales. El segundo componente es el host compartido, que es responsable del consumo del archivo DLL que se generó como parte del proceso de publicación. El host compartido es un host de aplicaciones genérico que permite que cualquier biblioteca de .NET Core (DLL) se ejecute como una aplicación. Cuando se ejecuta "dotnet run my.dll", my.dll se hospeda en este host compartido. Cuando se empaqueta la aplicación SCD, lo que sucede es que el tiempo de ejecución compartido, el host compartido y el archivo DLL de la aplicación se colocan juntos en un paquete ejecutable, un archivo .exe para Windows, o un archivo ejecutable adecuado para Linux y macOS. La documentación real de este diseño puede encontrarse en el repositorio de la CLI de .NET en bit.ly/2QCgZIp.

Identificadores de tiempo de ejecución

Los RID indican las plataformas compatibles para la creación de implementaciones autocontenidas para plataformas distintas de Windows. Por ejemplo, Windows 10 es compatible con x86, x 64, ARM y ARM64. La compatibilidad con ARM está dirigida a los dispositivos IoT (Internet de las cosas) de Windows 10. En Linux, solo se admite x64, con las siguientes distribuciones: Ubuntu, RedHat, CentOS, Fedora, Debian, Gentoo, OpenSuse, Oracle, Tizen y LinuxMint. En macOS, se admiten las versiones de 10.10 a 10.13. Por último, Android se admite en .NET Core 2.0 o posterior, que también presenta un RID portátil que compilará todas las opciones de un grupo de destino determinado. Puede encontrar más información en el catálogo de RID disponible en bit.ly/2PKXRXi.

Implementaciones CoreRT

Además de las implementaciones FDD y SCD, una tercera opción que se está desarrollando en Microsoft, denominada CoreRT, ofrece la capacidad para generar archivos binarios nativos a partir del código basado en .NET Core. CoreRT realiza una compilación Ahead Of Time (AOT) mediante el compilador Just-In-Time (JIT) de CoreCLR. Esto permite que el código de .NET Core genere ejecutables únicos y bibliotecas que otros lenguajes, como C++, puedan usar. CoreRT permite a los desarrolladores de .NET crear bibliotecas y ejecutables nativos para la plataforma de destino, y proporcionar así un alcance más amplio para la plataforma .NET.

Empezar a usar CoreRT es tan sencillo como agregar un paquete NuGet a un proyecto. Dentro del proyecto de ejemplo en el que he estado trabajando, solo ejecuto el siguiente comando:

> dotnet new nuget

Una vez agregado el archivo nuget.config, llamo a la fuente MyGet de .NET mediante las líneas siguientes:

<add key="dotnet-core"
  value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"/>
<add key="nuget.org"
  value="https://api.nuget.org/v3/index.json" protocolVersion="3"/>

A continuación, agrego el paquete NuGet de CoreRT de la siguiente manera:

> dotnet add package Microsoft.DotNet.ILCompiler -v 1.0.0-alpha-*

Luego, ejecuto publish y paso la plataforma RID, como se muestra aquí:

> dotnet publish -r win-x64

Por último, puedo probar la aplicación como una aplicación compilada de forma nativa. La aplicación se crea en una carpeta denominada native, de 6 KB de tamaño aproximadamente, que se aproxima al tamaño de la aplicación FDD y no requiere que se incluya ningún entorno en tiempo de ejecución, como sucedería con SCD.

Las ventajas de CoreRT son la compilación nativa de un único binario nativo, con las dependencias incluidas, para cada destino. La ejecución es más rápida, ya que no se requiere compilación de JIT, y la aplicación debería tener un rendimiento más rápido debido a optimizaciones del compilador para cada destino.

Los inconvenientes son que se debe compilar la aplicación para cada plataforma de destino, que debe seleccionarse de antemano. De momento, otro pequeño problema es que CoreRT está en versión preliminar.

Las aplicaciones de CoreRT funcionan de forma muy similar a las aplicaciones SCD. Existe un pequeño motor de ejecución nativo, el tiempo de ejecución nativo de CoreRT, que proporciona servicios de tiempo de ejecución, como la recolección de elementos no utilizados, que se compilan con la aplicación en un único paquete nativo. Además, existe una parte administrada de CoreRT escrita en C#. La aplicación de .NET Core se compila inicialmente con el compilador Roslyn y, a continuación, junto con CoreRT y CoreFX, se pasa por un compilador de lenguaje intermedio que analiza las dependencias y mueve el árbol, de modo que solo el número mínimo absoluto de bibliotecas se compilarán en código nativo utilizando un compilador basado en LLVM. Por último, se usa un enlazador para vincular el tiempo de ejecución nativo de CoreRT con la salida nativa compilada de la aplicación para generar el ejecutable nativo final. Matt Warren tiene una entrada de blog genial sobre el tema en bit.ly/2NRS5pj. Por supuesto, el repositorio de GitHub para CoreRT ofrece vínculos a elementos del diseño en github.com/dotnet/corert.

Resumen

SCD ofrece una gran ventaja sobre FDD: no requiere que el usuario que instale ningún software adicional para admitir una aplicación instalada. Estoy acostumbrado a compilar aplicaciones para Windows donde .NET Framework ya suele estar disponible. Si .NET Framework no está instalado o se ha instalado una versión incorrecta, puede dar lugar a malas experiencias para los usuarios.

.NET Core introducirá cambios, ya que requerirá que los usuarios instalen no solo el entorno en tiempo de ejecución, sino una versión específica del entorno en tiempo de ejecución que sea compatible con su aplicación. Las implementaciones SCD pueden producir aplicaciones más grandes (y, por lo tanto, descargas más grandes) porque el entorno en tiempo de ejecución se empaqueta con la aplicación, pero permiten al usuario instalar la aplicación sin tener que preocuparse por requisitos adicionales. Para los usuarios que no emplean Windows, como los de macOS y Linux, SCD es la experiencia común que los usuarios esperan y que les ayudará con la adopción. En entornos controlados por el desarrollador o la organización, este problema desaparece y FDD sería, probablemente, la mejor opción.

Las implementaciones de CoreRT (compilación a código nativo) están todavía en las primeras fases. Estas implementaciones ofrecerán muchas de las ventajas de FDD y SCD, sin necesidad de instalar el marco y archivos de aplicación compactos. Sin embargo, queda mucho camino por recorrer para que este enfoque sea funcional.


Jamie Phillips es ingeniero de desarrollo de software sénior de SentryOne, ubicado en el este de Tennessee. Trabaja con .NET desde 2007 y tiene un gran interés en DevOps y la nube. Puede encontrarlo en Twitter: @phillipsj73, en su blog en phillipsj.net y en GitHub como phillipsj.

Gracias a los siguientes expertos técnicos por revisar este artículo: Andrew Hall (Microsoft), Daniel Oliver y Cameron Presley (SentryOne)
Andrew es el administrador de programas principal para las herramientas de .NET, Azure App Service y web en Visual Studio. Tras graduarse en la universidad, escribió aplicaciones de línea de negocio antes de volver a estudiar este Máster en informática. Luego, se unió al equipo de diagnóstico de Visual Studio, donde trabajó en el desarrollo de herramientas de depuración, generación de perfiles y análisis de código. Ahora trabaja en el equipo de herramientas web y de .NET, donde lidera el equipo de administradores de programas responsable de las principales experiencias de desarrollo de .NET, que incluyen proyectos, IntelliSense, productividad y compatibilidad con las herramientas de Azure.

Cameron Presley es MVP de Microsoft y ayuda a los desarrolladores a mejorar día a día.

Daniel Oliver es un desarrollador de software de Tennessee que aprende y compila cosas nuevas con .NET y Azure.



Comente este artículo en el foro de MSDN Magazine