Já passou o tempo em que aplicações .NET eram grandes e precisavam de 1Gb de dependência, hoje com poucos megas você consegue rodar uma aplicação .NET core com todas as dependências, e no docker ficando abaixo dos 100Mb, com pequenas modificações e consumindo a mesma quantidade de memoria e CPU. Para isso vamos destrinchar um pouco as imagens docker disponibilizadas pela microsoft, publish e fazer pequenos testes.
Setup
Para exemplificar criei uma aplicação em ASP.NET Core 5.0, uma WEB API, e não modifiquei em nada, além disso criei um teste com Artillery fazendo algumas chamadas para vermos a memória utilizada, subi a imagem no docker local e executei os testes.
Esse teste foi executado na minha própria máquina, que não é o melhor ambiente para um benchmark completo, então é muito mais para curiosidade e pequenas diferenças devem ser desconsideradas.
Estrutura
- Verificação do tamanho da imagem
- Verificação da memória utilizada sem nenhuma chamada
- Verificação da memória máxima com o script de teste
Arquivo de teste
A ideia foi fazer 10 chamadas por 10 segundos, para Warmup, e logo em seguida aumentar até 30 durante 40 segundos, gerando aproximadamente 900 chamadas, segue o arquivo do Artillery utilizado:
Multi-stage build
Nos dockerfiles de exemplo usamos multi-stage build, onde temos uma imagem base que será utilizada, por fim fazemos o build dentro do próprio docker e repassamos os arquivos publicados para a imagem base, fazendo com que fique mais simples de entender o que está acontecendo, como a aplicação está sendo buildada ao invés de fazer o build local e copiar para a imagem docker.
Dockerfile padrão
Nesse dockerfile estamos usando a imagem padrão do asp na versão 5.0 com buster-slim, ela é baseada no Debian e tem o ASP.NET Core e os runtimes do .NET e é otimizada para rodar aplicações ASP.NET.
Esta é a imagem padrão quando adicionamos suporte a docker pelo Visual Studio.
Resultados
- Size: 205mb
- Memory Idle: 35mb
- Memory Max: 60mb
Runtime Deps alpine
Aqui temos algumas modificações, mudamos a imagem para a runtime-deps:alpine, que é baseada no Alpine e contém as dependências necessárias para rodar o .NET, ela é ideal para rodar aplicações self-contained, então alteramos o comando de publicação para gerar uma aplicação self-contained e para o runtime do linux.
Resultados
- Size: 104mb
- Memory Idle: 32mb
- Memory Max: 54mb
Com essas modificações conseguimos diminuir o tamanho total da imagem para 104mb, sem modificações no uso de memória.
Trimmed
Podemos adicionar no dotnet publish para remover as bibliotecas não usadas e deminuindo ainda mais o tamanho da nossa imagem.
Resultados
- Size: 61.6mb
- Memory Idle: 36mb
- Memory Max: 59mb
Agora temos uma imagem ainda menor, sem comprometer a memória consumida.
Single file
Podemos diminuir ainda mais a imagem colocando a opção no publish de SingleFile, que faz com que todos os arquivos e dependências fiquem dentro de um único arquivo.
Resultados
- Size: 50.2mb
- Memory Idle: 36mb
- Memory Max: 59mb
Agora temos uma imagem ainda menor, sem comprometer a memória consumida.
Resultados finais
Image | Size | Memory Idle | Memory Max |
---|---|---|---|
Base | 205Mb | 35Mb | 60Mb |
Runtime Deps | 104Mb | 32mb | 54mb |
Trimmed | 61.6Mb | 36Mb | 59mb |
Single file | 50.2Mb | 36Mb | 59mb |
Você pode explorar mais opções de Publish ou versões diferentes das imagens Docker.
Com isso podemos ter uma imagem com nossa aplicação, com 60mb de tamanho, consumindo 35mb de ram, e com mais segurança com imagens alpine e com um dos melhores desempenhos. :)