Criando imagens de contêiner Java com Jib
17 de agosto de 2021
0 minutos de leituraSe você trabalha com imagens de contêiner há algum tempo, provavelmente conhece os onipresentes documentos que descrevem, camada por camada, as etapas necessárias para criar uma imagem: os Dockerfiles. Você sabia que há cada vez mais ferramentas para criar imagens compatíveis com OCI sem Dockerfiles? Neste artigo, vamos conhecer o Jib, uma ferramenta 100% baseada em Java que cria imagens altamente otimizadas sem que você precise se preocupar em montar um Dockerfile corretamente.
Vou partir do princípio de que você já entende como as imagens são criadas e tem pelo menos uma noção básica de Dockerfiles. Mas, se ainda não conhece esse assunto, talvez queira conferir meu artigo sobre fluxos de trabalho orientados por desenvolvedores, no qual explico em detalhes como criar imagens de contêiner.
O problema
Como desenvolvedor Java, além da base de código do seu aplicativo e das dependências das bibliotecas, você provavelmente também está acostumado a acompanhar qual JDK e JVM está usando como alvo. Isso pode incluir a versão do servidor de aplicativos web sobre o qual você está desenvolvendo e talvez algumas configurações de execução, como ajustes do coletor de lixo e conexões com o banco de dados. O que talvez você não tenha precisado considerar são questões no nível do sistema operacional, como instalação de pacotes, permissões do sistema de arquivos, qual UID/GID é usado em tempo de execução e outros detalhes de configuração que, historicamente, ficam a cargo das equipes de operações e segurança responsáveis por fornecer as plataformas.
Com a adoção de ambientes de execução de contêineres e dos sistemas de orquestração que funcionam sobre eles, muitas dessas preocupações de nível mais baixo estão passando a fazer parte do escopo das equipes de desenvolvimento, na forma de arquivos de configuração de infraestrutura como código (IaC) que ficam nos repositórios de código dos aplicativos e serviços. Esses arquivos podem assumir vários formatos, como YAML do Kubernetes, HCL do Terraform, JSON do CloudFormation e, é claro, Dockerfiles, que definem a estrutura da imagem de contêiner onde seu aplicativo será executado.
Empacotar seu aplicativo em uma imagem e executá-lo em um contêiner geralmente não é muito difícil. Mas garantir que a imagem esteja bem formada, otimizada, segura e em conformidade com os padrões da sua organização pode ser um desafio. Agora, espera-se que os desenvolvedores aprendam a usar ferramentas de análise no nível do sistema operacional, mantenham padrões de rotulagem de imagens, conheçam as práticas recomendadas mais recentes para Dockerfiles e assumam outras responsabilidades. Ferramentas de lint e análise, como Hadolint, Dockle e nossa solução Snyk Container, podem ajudar nessas tarefas. Mas e se pudéssemos automatizar a maioria — ou até todos — desses novos requisitos e conteúdos padronizados?
Conheça o Jib: uma ferramenta 100% baseada em Java para criar imagens de contêiner
O Jib é uma ferramenta de código aberto, 100% baseada em Java, que cria imagens de contêiner compatíveis com OCI (Docker v2) sem Dockerfile ou mesmo sem um ambiente de execução de contêiner. O Jib pode ser usado como uma ferramenta de linha de comando independente, mas o mais comum é usá-lo como um plugin de etapa de build do Maven ou Gradle. Com o plugin, basta executar o mesmo comando de build mvn ou gradle que você já usa para criar seus artefatos .jar, .war etc. e também criar — e, opcionalmente, implantar — uma imagem OCI pronta para executar seu aplicativo em um contêiner.
Partindo de um aplicativo Spring Boot, adicionar o Jib é simples: basta inserir este trecho XML no arquivo pom.xml do Maven (a documentação completa está aqui) e executar novamente o comando mvn package. Há algumas opções que podemos definir aqui para escolher onde colocar o contêiner. Para simplificar, este exemplo implanta a imagem em um registro que usaremos mais tarde para executá-la, mas você também pode salvar a imagem em um arquivo **.**tar local ou, se tiver um daemon do Docker em execução e acessível, no cache local de imagens do Docker (como faria um build do Docker).
Observação: Vamos falar um pouco mais adiante sobre algumas linhas interessantes de “aviso” na saída acima.
Agora, em uma máquina com um ambiente de execução de contêiner disponível, basta executar o contêiner: docker run --rm -it -p 8080:8080 myimage:tag
Se você usa Gradle, confira a documentação oficial para ver todos os detalhes sobre como fazer o mesmo no arquivo build.gradle.
É só isso: não é preciso usar Dockerfile nem ferramentas extras para criar imagens. Basta usar as mesmas ferramentas de build que você já usa para criar seus artefatos Java. Isso não só facilita a vida dos desenvolvedores, como também torna o suporte a agentes de build de CI muito mais simples, já que não é preciso expor o socket do Docker nem gerenciar outras ferramentas de build.
Analisando a imagem em detalhes
Tudo bem, o processo de criação da imagem pode ser mais simples, mas, se você for como eu quando vi isso pela primeira vez, provavelmente tem perguntas como...
Como o Jib cria imagens?
Como qualquer ferramenta de criação de imagens, o Jib cria um conjunto de camadas do sistema de arquivos que contêm o ambiente de execução do Java, seu aplicativo e todas as dependências, além dos metadados que o mecanismo de contêiner usa para saber como iniciar a JVM.
Veja as informações das camadas deste exemplo, exibidas pelo comando docker image history:
Observe que as datas CREATED das primeiras quatro camadas mostram “51 years ago” (“há 51 anos”). Isso é consequência da estratégia de builds reproduzíveis do Jib, que busca criar exatamente os mesmos hashes de camada para builds da mesma base de código. Para saber mais, consulte as perguntas frequentes.
Como mostram os comentários das camadas, temos várias camadas da imagem base padrão do AdoptOpenJDK — falaremos mais sobre isso adiante — seguidas, nesta ordem, por:
Dependências de bibliotecas definidas nos arquivos Maven/Gradle
Arquivos de recursos
Arquivos de classe do código compilado do aplicativo
Arquivos de argumentos da JVM.
Vamos nos aprofundar um pouco mais e usar a ferramenta de código aberto dive para ver quais arquivos estão em cada uma dessas quatro camadas:
Dependências
Esta camada contém todos os arquivos .jar adicionados à pasta /app/libs.
Recursos
Aqui vemos /app/resources e todos os arquivos associados das nossas pastas de recursos.
Classes
Os arquivos .class gerados durante a fase de compilação do build estão nesta camada, em /app/classes.
Arquivos de argumentos da JVM
Por fim, temos alguns arquivos com os argumentos usados para iniciar a JVM no contêiner, na pasta de nível superior /app. Neste exemplo, se você inspecionar o conteúdo desses dois arquivos, encontrará o classpath de execução e as informações da classe principal.
Observação: Dependendo da estrutura do seu build Maven/Gradle, as imagens criadas pelo Jib podem ter mais camadas. Consulte as perguntas frequentes do Jib para saber mais sobre outras camadas possíveis.
Qual imagem base o Jib usa? E se eu precisar usar minha própria imagem?
Por padrão, o Jib usa a imagem base oficial adoptopenjdk:jre-8 do Docker Hub para builds de arquivos JAR ou a imagem base oficial jetty do Docker Hub para builds de arquivos WAR. É possível configurar isso nas opções do plugin. Consulte a documentação oficial para ver os detalhes, incluindo como definir um ENTRYPOINT, USER personalizado ou outras declarações, se necessário. A documentação também explica por que é uma boa ideia definir sua própria imagem base e usar um hash específico para garantir a reprodutibilidade dos builds. Muitas organizações têm imagens base internas “aprovadas”, que as equipes de desenvolvimento devem usar e que foram auditadas e reforçadas pelas equipes de segurança e operações. Veja um exemplo de como modificar o pom.xml do Maven para usar uma imagem desse tipo.
Também há suporte a configurações adicionais, como a padronização de rótulos de imagens. Por exemplo, suponha que sua organização exija que todas as imagens incluam os seguintes rótulos:
URL do Git do código-fonte
ID/hash do commit do Git
Versão do build do projeto Maven
Supondo que o pom.xml já tenha acesso a esses dados, adicionar os rótulos à configuração do plugin Jib é muito simples:
Ao inspecionar a imagem criada, vemos que os rótulos foram aplicados, como se tivessem sido adicionados por linhas LABELS do Dockerfile (estou usando a ótima ferramenta de linha de comando jq para extrair apenas esse array da resposta).
Agora imagine que toda a complexidade dessas configurações padronizadas esteja definida em um POM pai, por meio de <pluginManagement> e outras configurações comuns do Maven. Os desenvolvedores não precisam mais se preocupar com todo esse XML padronizado, nem sequer precisam examiná-lo. E os arquitetos podem aplicar e atualizar os padrões em toda a organização sem incomodar as equipes!
Como posso ter certeza de que tudo está seguro?
Um dos desafios das ferramentas mais genéricas para criar imagens, como o Dockerfile, é que você pode fazer praticamente qualquer coisa durante o build: instalar pacotes, usar ADD para baixar arquivos de servidores web aleatórios e executar inúmeras outras etapas que precisam ser analisadas e revisadas com cuidado. Com o Jib, muitas dessas escolhas são aplicadas automaticamente, e substituí-las exige alterações explícitas na configuração do Maven/Gradle, que ficam claramente visíveis na revisão de código, assim como mudanças em uma dependência ou em qualquer outra configuração de build.
Quanto à análise de segurança, um dos grandes recursos do Snyk Container é recomendar imagens base com menos vulnerabilidades. Desde hoje, esse recurso também funciona sem um Dockerfile que indique qual imagem base você está usando. Isso significa que imagens criadas pelo Jib — ou por qualquer ferramenta que não use Dockerfile — também podem aproveitar as mesmas recomendações.
Se quiser acompanhar e analisar seus próprios contêineres Java, crie uma conta gratuita e veja as instruções para instalar a ferramenta de análise da Snyk.
Neste exemplo, defini openjdk:8u121-jre como imagem base, executei mvn package e baixei a imagem para meu laptop. Agora, basta executar snyk container test nela.
Como você pode ver, a análise detectou automaticamente openjdk:8u181-jre-stretch como imagem base (uma tag de alias para openjdk:8u181-jre) e informou suas 410 vulnerabilidades. Também recomendou algumas opções para mudar a imagem base, desde uma atualização de versão secundária até a tag mais recente openjdk:8-jre — que tem cerca de metade dos problemas — e versões mais novas da JVM sem nenhuma vulnerabilidade.
Como você pode ver, não só conseguimos descobrir quais vulnerabilidades estão presentes na imagem que usamos, como também recebemos recomendações para corrigi-las usando uma imagem base mais recente, tudo isso sem escrever uma única linha de código em um Dockerfile.
Conclusão
Como vimos, o Jib pode facilitar muito para os desenvolvedores a criação de contêineres para aplicativos Java, eliminando a necessidade de aprender a sintaxe do Dockerfile ou instalar ferramentas desconhecidas. Ele também ajuda os arquitetos a gerenciar a padronização usando os recursos conhecidos de hierarquia de projetos do Maven ou Gradle. Como o Jib cria imagens compatíveis com OCI, você também pode usar ferramentas padrão do setor — como o scanner Snyk Container — para inspecionar, implantar e executar seu aplicativo.
Se você também dá suporte a projetos que não são Java, há outras ferramentas nesse espaço que vale a pena considerar, como Buildah, Bazel, Earthly e vários projetos relacionados ao BuildKit. Além disso, meu colega Pas Apicella publicou recentemente um artigo sobre Cloud Native Build Packs que vale muito a pena ler.
Não se esqueça de criar sua conta gratuita e começar hoje mesmo a analisar seus contêineres, dependências de código aberto e código IaC!
Proteja a infraestrutura desde a origem
A Snyk automatiza a segurança e a conformidade de IaC nos fluxos de trabalho e detecta recursos com configurações divergentes ou ausentes.
