Novembro de 2018

Volume 33 – Número 11

.NET Core – Opções de publicação com o .NET Core

Por Jaime Phillips | Novembro de 2018

O advento do .NET Core traz um novo paradigma para a publicação de seus aplicativos com a introdução de duas novas técnicas: FDDs (implantações dependentes da estrutura) e SCDs (implantações autossuficientes). Cada opção tem vantagens e desvantagens que precisam ser consideradas antes da publicação. Neste artigo, vou explorar as duas abordagens criando um aplicativo de exemplo e discutindo os pontos fortes e fracos de cada uma. Depois, darei uma olhada rápida em outra opção de implantação, CoreRT, que está nos estágios iniciais de desenvolvimento.

A publicação com o .NET Core apresenta muitas informações para consideração. Tradicionalmente, a opção de implantação comum para um aplicativo da área de trabalho ou de console no .NET Framework é um executável. Para um aplicativo ASP.NET, é uma DLL. Essas eram as únicas opções disponíveis, e elas dependiam do tipo de aplicativo que está sendo criado. Com o novo paradigma, o aspecto importante é se o .NET Core está instalado ou não. A técnica de FDD exige a instalação do .NET Core, enquanto a técnica de SCD traz junto o tempo de execução do .NET Core, ou seja, ela pode ser executada em um computador que não tenha o .NET Core instalado. Agora, um aplicativo ASP.NET também pode ser empacotado como um aplicativo de console autônomo, pois traz seu próprio servidor Web (Kestrel) como executável.

Para a minha exploração das duas abordagens, usarei o SDK do .NET Core 2.1, juntamente com uma interface de linha de comando. Encontre instruções de instalação do o .NET Core 2.1 microsoft.com/net/download. Estou usando o PowerShell quase que exclusivamente para executar todos os comandos que mostro neste artigo, mas o prompt de comando (cmd.exe) devem funcionar também, e, na verdade, esses comandos serão executados nos shells de outros sistemas operacionais, incluindo macOS e Linux. Por fim, para experimentar os SCDs, o WSL ( Subsistema do Windows para Linux) é imprescindível para permitir testes iniciais de todos os executáveis criados para Linux. Saiba mais sobre WSL em bit.ly/2Nj7FuQ.

Aplicativo de exemplo

Vou criar um aplicativo de exemplo bem básico para testar os dois métodos de publicação usando o .NET Core 2.1. Será um aplicativo de console que imprime "Olá", junto com o nome fornecido. Você pode usar o cmd.exe ou o PowerShell para inserir o código a seguir:

> dotnet new console -o Publishing

Após a criação do novo aplicativo de console, eu abro Program.cs e insiro o código a seguir:

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

Após a execução do aplicativo, vejo isto:

> dotnet run -- Jamie

Hello Jamie!

Como você pode ver, funciona conforme o esperado. Só um detalhe rápido, a sintaxe de dois hifens (--) é como os parâmetros são passados para o aplicativo ao usar o comando "dotnet run". Agora que o meu aplicativo de console está completo, posso analisar os métodos de publicação.

O comando Publicar

Primeiro, vamos examinar as opções disponíveis com o comando publish, executando-o com a opção "-h" (ajuda):

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]

Observe que duas dessas opções são especialmente úteis ao publicar aplicativos .NET Core: --runtime e --self-contained. Essas opções são usadas juntas; runtime especifica quais tempos de execução devem ser direcionados durante a criação de um SCD, enquanto self-­contained indica a necessidade de criação de um pacote autossuficiente. Se a opção runtime for passada, a opção self-­contained será aplicada automaticamente.

Outras opções interessantes são no-restore e no-build. A opção no-restore não executará a restauração implícita ao executar o comando publish. A opção no-build não compilará o projeto ou executará o comando de restauração, assim o comando publish será executado na compilação existente. Isso é útil em cenários de implantação/integração contínua, pois a compilação e a restauração não serão disparadas várias vezes. Por último, a opção framework permite a especificação da versão da estrutura. A documentação oficial pode ser encontrada em bit.ly/2pizcOL.

Implantações dependentes da estrutura

Os FDDs são o padrão ao executar o comando publish na CLI do .NET Core. O comando de publicação cria um aplicativo independente de plataforma que pode ser executado em qualquer tempo de execução do .NET Core que seja equivalente a, ou mais recente do que, a versão secundária usada para compilar o aplicativo (embora a versão 2.0 não execute algo direcionado para 1.x, e a 3.0 não execute algo direcionado para 2.x). Como o tempo de execução direcionado não vem com o aplicativo, essa opção gera o menor pacote. Se você for implantar vários aplicativos em um sistema, permitir o compartilhamento do tempo de execução entre os aplicativos reduz o uso geral da memória e do disco do sistema. Outra vantagem do tempo de execução compartilhado é que todas as atualizações de tempo de execução futuras serão aplicadas a todos os aplicativos.

Vamos conferir a publicação do aplicativo de exemplo como um FDD inserindo o seguinte na linha de comando:

> dotnet publish

Isso cria a saída na pasta bin\Debug\netcoreapp2.0, incluindo o arquivo publishing.dll. Essa DLL de 5 KB é o aplicativo. Em seguida, execute o aplicativo usando a CLI do dotnet, da seguinte forma:

> dotnet Publishing.dll Jamie

Hello Jamie!

E isso é tudo.

O FDD fornece vários benefícios, incluindo um pacote de implantação menor, um tempo de execução gerenciado pelo sistema e a capacidade de compartilhar o tempo de execução entre vários aplicativos, a fim de reduzir o uso da memória e do disco. O aplicativo também executará em qualquer plataforma que tenha um tempo de execução compatível instalado.

Os FDDs também têm desvantagens. As atualizações do tempo de execução de todo o sistema podem causar problemas, pois talvez sejam incompatíveis, e seu aplicativo pode ser executado apenas na versão da estrutura (ou mais recente) com a qual foi compilado. Eles também exigem que os usuários instalem essa versão do tempo de execução do .NET Core em seus computadores antes da execução do aplicativo.

Implantação autossuficiente

Agora, vamos examinar a segunda abordagem à publicação fornecida com a CLI do .NET Core — SCDs. Usarei o mesmo comando de publicação de antes, mas desta vez passarei um identificador de tempo de execução (RID) como opção para o comando publish. O RID informa à CLI do .NET Core para quais plataformas direcionar ao criar o aplicativo. Neste exemplo, criarei uma compilação do aplicativo para Windows 10 x64. Qualquer plataforma pode ser direcionada de qualquer sistema operacional, permitindo a criação de executáveis para outros sistemas operacionais a partir do Windows. Este é o comando usado para este exemplo:

> dotnet publish -r win10-x64

Observe que uma subpasta foi criada na pasta netcoreapp2.0 que recebeu o nome após a passagem do tempo de execução. Dentro da pasta win10-x64, você verá que agora há um executável em vez de uma DLL. Esse executável tem 77 KB, 72 KB a mais do que o aplicativo anterior. Posso executá-lo para mostrar que ele ainda funciona:

> .\Publishing.exe Jamie

Hello Jamie!

Agora que tenho um SCD para Windows 10 x64, compilarei um direcionado para o Ubuntu x64, da seguinte forma:

> dotnet publish -r ubuntu-x64

Assim, posso utilizar o WSL para testar se a versão do Ubuntu é executada conforme o desejado. Abro o WSL do Windows e transformo o aplicativo recém-criado em um executável, para que eu possa executá-lo a fim de garantir que esteja funcionando. Esta é a entrada, e a saída resultante:

> chmod +x Publishing
> ./Publishing Jamie

Hello Jamie!

A vantagem do SCD é que o tempo de execução do .NET Core é controlado e acompanha o seu aplicativo. Isso é conveniente para os usuários, pois eles não precisam instalar o tempo de execução, e as alterações no tempo de execução do sistema não afetarão seu aplicativo. Por outro lado, o SCD cria pacotes de implantação maiores, pois o tempo de execução é fornecido com o aplicativo. Você também precisa conhecer suas plataformas de destino antecipadamente para gerar os pacotes para cada direcionamento. Além disso, sempre que houver uma atualização do tempo de execução que inclua correções de bug ou de segurança, você deverá publicar novamente todos os aplicativos no computador para obter essas correções em vez de instalar uma atualização de tempo de execução para um computador único.

Antes, a abordagem de distribuição de aplicativos .NET era exigir a instalação do .NET Framework ou empacotá-lo com o instalador. Com o .NET Core e os SCDs, não há necessidade de criar um instalador especial para fornecer um aplicativo básico.

Por mais positivo que o SCD possa ser, ainda há um problema ao implantar esses aplicativos em um sistema Linux. As dependências de tempo de execução para o .NET Core não estão incluídas com os SCDs, o que significa que os usuários não precisarão instalar o tempo de execução. No entanto, os usuários precisarão instalar seis dependências do gerenciador de pacote para a distribuição do Linux. Por exemplo, no Ubuntu, as seguintes dependências ainda precisarão ser instaladas:

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 esse problema, um desenvolvedor precisa empacotar o aplicativo SCD em um formato de pacote nativo para a distribuição, para que outras dependências possam ser definidas.

Embora isso possa ser um fator negativo, o lado positivo é que os usuários não precisarão adicionar qualquer outro repositório de pacote necessário se o tempo de execução do .NET Core precisar ser instalado.

O SCD é fascinante e merece mais discussão. Apesar de o funcionamento do SCD exigir várias partes, há dois componentes que realmente contribuem para isso como parte da instalação da CLI do .NET. O primeiro componente é o tempo de execução compartilhado, que é uma versão redistribuível do tempo de execução do .NET Core e é consumido pela CLI e pelos usuários finais. O segundo componente é o host compartilhado, que é responsável pelo consumo da DLL gerada como parte do processo de publicação. O host compartilhado é um apphost genérico que permite a execução de qualquer biblioteca do .NET Core (DLL) como um aplicativo. Quando "dotnet run my.dll" é executado, my.dll está sendo hospedado dentro deste host compartilhado. Quando o aplicativo de SCD é empacotado, o tempo de execução compartilhado, o host compartilhado e a DLL do aplicativo são colocados juntos em um pacote executável, um .exe para Windows ou um arquivo executável apropriado para Linux e macOS. A documentação real para esse design pode ser encontrada no repositório da CLI do .NET em bit.ly/2QCgZIp.

Identificadores de tempo de execução

Os RIDs indicam as plataformas com suporte para a criação de implantações autossuficientes para plataformas diferentes do Windows. Por exemplo, o Windows 10 dá suporte a x86, x64, ARM e ARM64. O suporte para ARM aborda os dispositivos Windows 10 IoT (Internet das Coisas). No Linux, há suporte apenas para x64, com as seguintes distribuições: Ubuntu, RedHat, CentOS, Fedora, Debian, Gentoo, OpenSuse, Oracle, Tizen e LinuxMint. No macOS, há suporte para as versões 10.10 a 10.13. Por fim, há suporte para Android no .NET Core 2.0 ou posterior, que também introduz um RID portátil que compilará todas as opções para um grupo de destino específico. Encontre mais informações no catálogo do RID, localizado em bit.ly/2PKXRXi.

Implantações de CoreRT

Além de FDD e SCD, há uma terceira opção em desenvolvimento na Microsoft, chamada de CoreRT, que oferece a capacidade de gerar binários nativos a partir do código baseado em .NET Core. O CoreRT executa a compilação AoT (ahead-of-time) usando o compilador JIT (Just-In-Time) do CoreCLR. Isso permite que o código do .NET Core produza arquivos e bibliotecas executáveis individuais que podem ser consumidas por outras linguagens como C++. O CoreRT permite que os desenvolvedores em .NET criem bibliotecas e executáveis nativos à plataforma de destino, fornecendo um alcance mais amplo para a plataforma .NET.

Começar a usar o CoreRT é tão simples quanto adicionar um pacote do NuGet a um projeto. Dentro do exemplo de projeto com o qual estou trabalhado, simplesmente executo o seguinte comando:

> dotnet new nuget

Com o arquivo nuget.config adicionado, chamo o feed MyGet do .NET usando as seguintes linhas:

<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"/>

Em seguida, adiciono o pacote NuGet do CoreRT, da seguinte forma:

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

E depois, executo publish, passando o RID da plataforma, conforme mostrado aqui:

> dotnet publish -r win-x64

Finalmente, posso testar o aplicativo como um aplicativo compilado nativamente. O aplicativo é criado em uma pasta chamada native e tem aproximadamente 6 KB, o que é próximo do tamanho do aplicativo FDD e não exige o envio de um tempo de execução, pois ele usaria SCD.

As vantagens do CoreRT incluem a compilação nativa para um único binário nativo, com dependências incluídas, para cada destino. A execução é mais rápida, pois a compilação JIT não é necessária, e o aplicativo deve apresentar uma taxa de transferência mais rápida devido a otimizações do compilador para cada destino.

As desvantagens são que o aplicativo deve ser compilado para cada plataforma de destino, que devem ser selecionadas com antecedência. Há também, por enquanto, a questão do CoreRT estar na fase de pré-lançamento.

Aplicativos do CoreRT funcionam de um jeito bem parecido com os aplicativos de SCD. Há um mecanismo de execução nativo pequeno, o tempo de execução nativo do CoreRT, que fornece serviços de tempo de execução, como coleta de lixo compilada com o aplicativo em um único pacote nativo. E há uma parte gerenciada do CoreRT que é escrita em C#. O aplicativo .NET Core é compilado primeiro com o Compilador Roslyn. Depois, o aplicativo, o CoreRT e o CoreFX passam por um compilador de linguagem intermediário que analisa as dependências e reduz ao mínimo as bibliotecas que serão compiladas em código nativo usando um compilador baseado em LLVM. Por fim, um vinculador é usado para vincular o tempo de execução nativo de CoreRT com a saída nativa compilada do aplicativo para produzir o executável nativo final. Matt Warren tem uma postagem de blog excelente sobre o assunto em bit.ly/2NRS5pj e, logicamente, o repositório do GitHub para CoreRT tem links para partes do design em github.com/dotnet/corert.

Conclusão

O SCD oferece uma grande vantagem sobre o FDD, ele não exige que o usuário instale softwares adicionais para dar suporte a um aplicativo instalado. Estou acostumado a simplesmente compilar aplicativos para Windows, onde o .NET Framework normalmente já está disponível. Mas, quando o .NET Framework não está instalado, ou a versão incorreta está instalada, os usuários podem ter experiências ruins.

O .NET Core promete mudar isso, pois exigirá que os usuários instalem não apenas o tempo de execução, mas uma versão específica do tempo de execução para dar suporte ao seu aplicativo. Os SCDs podem produzir aplicativos maiores (e, portanto, downloads maiores) pois o tempo de execução é empacotado com o aplicativo, mas isso permite que o usuário instale o aplicativo sem precisar se preocupar com requisitos adicionais. Para usuários fora do Windows, como os de macOS e Linux, o SCD é a experiência comum esperada, e ajudará com a adoção. Em ambientes controlados pelo desenvolvedor ou pela organização, esse problema diminui, e o FDD provavelmente teria vantagem.

As implantações de CoreRT, compilado para nativo, ainda estão em estágios iniciais. Essas implantações oferecerão muitas das vantagens do FDD e do SCD, sem exigir a instalação da estrutura e com arquivos de aplicativo compactos. No entanto, ainda há muito caminho para tornar essa abordagem funcional.


Jamie Phillipsé engenheiro de desenvolvimento de software sênior da SentryOne, localizado no leste do Tennessee. Ele trabalha com o .NET desde 2007 e tem um grande interesse em DevOps e na nuvem. Encontre-o no Twitter: @phillipsj73, em seu blog em phillipsj.net e no GitHub como phillipsj.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Andrew Hall (Microsoft), Daniel Oliver, Cameron Presley (SentryOne)
Andrew é o gerente principal de programação em .NET, Web e ferramentas do Serviço de Aplicativo do Azure no Visual Studio. Depois de se formar na universidade, ele criou aplicativos de negócios antes de voltar a estudar para conquistar seu mestrado em ciência da computação. Em seguida, ele entrou para a equipe de diagnóstico do Visual Studio, trabalhando em ferramentas de depuração, criação de perfil e análise de código. Agora, ele trabalha na equipe de ferramentas .NET e Web, onde lidera a equipe de gerentes de programas responsáveis pelas experiências de desenvolvimento em .NET Core, incluindo projetos, IntelliSense, produtividade e suporte às ferramentas do Azure.

Cameron Presley é MVP da Microsoft, ajudando no aprimoramento diário dos desenvolvedores.

Daniel Oliver é desenvolvedor de software no Tennessee, onde aprende e cria novas coisas usando .NET e o Azure



Discuta esse artigo no fórum do MSDN Magazine