Contentorizar uma aplicação Java

Concluído

Nesta unidade, você conteineriza um aplicativo Java.

Como mencionado anteriormente, os contêineres são executados diretamente sobre o sistema operacional host, kernel e hardware como processos comuns do sistema. Os contêineres exigem menos recursos do sistema, resultando em menor espaço ocupado, menos sobrecarga e tempos de inicialização de aplicativos mais rápidos. Esses benefícios são ótimos casos de uso para dimensionamento sob demanda.

Existem contêineres Windows e contêineres Linux. Neste módulo, você usa o tempo de execução do Docker amplamente usado para criar uma imagem de contêiner do Linux. Em seguida, você implanta a imagem do contêiner Linux no sistema operacional host da sua máquina local. Finalmente, você implanta a imagem de contêiner do Linux no Serviço Kubernetes do Azure.

Visão geral do Docker

O tempo de execução do Docker é usado para criar, extrair, executar e enviar imagens de contêiner, conforme mostrado no diagrama a seguir:

Diagrama mostrando comandos do Docker.

A tabela a seguir descreve cada comando do Docker:

Comando Docker Descrição
docker build Cria uma imagem de contêiner que consiste nas instruções ou camadas necessárias para o Docker criar um contêiner em execução a partir de uma imagem. O resultado deste comando é uma imagem.
docker pull Os contêineres são inicializados a partir de imagens, que são extraídas de registros como o Registro de Contêiner do Azure. Este registro é de onde o Serviço Kubernetes do Azure extrai. O resultado deste comando é uma transferência de rede de uma imagem, que ocorre no Azure. Opcionalmente, você pode extrair imagens localmente. Essa opção é comum ao criar imagens que exigem dependências ou camadas que seu aplicativo pode precisar, como um servidor de aplicativos.
docker run Uma instância em execução de uma imagem é um contêiner e esse comando executa todas as camadas necessárias para executar e interagir com o aplicativo de contêiner em execução. O resultado desse comando é um processo de aplicativo em execução no sistema operacional host.
docker push O Registo de Contentores do Azure armazena as imagens para que estejam prontamente disponíveis e próximas à rede para implantações e dimensionamento no Azure.

Clone o aplicativo Java

Primeiro, clone o repositório do Sistema de Reservas de Voos para Reservas de Companhias Aéreas e navegue até à pasta do projeto da aplicação Web de Companhias Aéreas.

Observação

Se a criação do Serviço Kubernetes do Azure estiver concluída na guia CLI, use essa guia. Se ainda estiver em execução, abra uma nova guia e navegue até o local onde você prefere clonar o Sistema de Reservas de Voos para Reservas de Companhias Aéreas.

Execute os seguintes comandos:

git clone https://github.com/Azure-Samples/containerize-and-deploy-Java-app-to-Azure.git
cd containerize-and-deploy-Java-app-to-Azure/Project/Airlines

Opcionalmente, se você tiver o Java e o Maven instalados, poderá executar o seguinte comando no console do terminal para ter uma noção da experiência de criação do aplicativo sem o Docker. Se você não tiver o Java e o Maven instalados, poderá prosseguir com segurança para a próxima seção, Construir um arquivo do Docker. Nessa seção, você usa o Docker para puxar Java e Maven para executar as compilações em seu nome.

mvn clean package

Observação

Usamos o mvn clean package comando para ilustrar os desafios operacionais de não usar compilações de vários estágios do Docker, que abordaremos a seguir. Mais uma vez, este passo é opcional. De qualquer forma, você pode prosseguir com segurança sem executar o comando Maven.

Se o processo foi bem-sucedido, a Maven construiu com êxito o Flight Booking System for Airline Reservations Web Application Archive artifact AirlinesReservationSample-0.0.1-SNAPSHOT.war, conforme mostrado na saída a seguir:

[INFO] Building war: $PROJECT_PATH/containerize-and-deploy-Java-app-to-Azure/Project/Airlines/target/AirlinesReservationSample-0.0.1-SNAPSHOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.776 s
[INFO] Finished at: 2024-11-15T09:33:26+09:00
[INFO] ------------------------------------------------------------------------

Imagine que você é um desenvolvedor Java e acabou de desenvolver AirlinesReservationSample-0.0.1-SNAPSHOT.war. Sua próxima etapa provavelmente é trabalhar com os engenheiros de operação para implantar esse artefato em um servidor local ou em uma máquina virtual. Para que o aplicativo seja iniciado e executado com êxito, os servidores e máquinas virtuais devem estar disponíveis e configurados com as dependências necessárias. Esse processo é desafiador e demorado, especialmente sob demanda quando o aumento da carga está atingindo seu aplicativo. Com os contentores, estes desafios são aliviados.

Construir um Dockerfile

Agora você está pronto para construir um Dockerfile. Um Dockerfile é um documento de texto que contém todos os comandos que um usuário executaria na linha de comando para montar uma imagem de contêiner. Cada imagem é uma camada que pode ser armazenada em cache para eficiência. As camadas constroem-se umas sobre as outras.

Por exemplo, o Sistema de Reservas de Voos para Reservas de Companhias Aéreas precisa ser implantado e executado dentro de um servidor de aplicativos. Um servidor de aplicativos não é empacotado dentro do AirlinesReservationSample-0.0.1-SNAPSHOT.war. É uma dependência externa necessária para a execução do AirlinesReservationSample-0.0.1-SNAPSHOT.war, escutar e processar solicitações HTTP, gerir sessões de utilizadores e facilitar a reserva de voos. Se você usasse uma implantação tradicional, não conteinerizada, os engenheiros de operação instalariam e configurariam um servidor de aplicativos em algum servidor físico ou máquina virtual antes de implantar o AirlinesReservationSample-0.0.1-SNAPSHOT.war nele. Esses engenheiros de operação também precisariam garantir que o JDK que está sendo usado em sua máquina - que é o que mvn clean package usou para compilar o arquivo WAR - de fato corresponde ao mesmo JRE que está sendo usado pelo servidor de aplicativos. Gerenciar essas dependências é desafiador e demorado.

Com um Dockerfile, pode-se escrever as instruções ou camadas necessárias para alcançar esse objetivo automaticamente, organizando em camadas os passos necessários para garantir que o Sistema de Reserva de Voos para Reservas de Companhias Aéreas tenha todas as dependências necessárias para implementar no ambiente de execução do Docker. Esta solução é convincente quando se trabalha com escalabilidade sob demanda em intervalos não planeados. Cada camada usa o cache do Docker, que contém o estado da imagem do contêiner em cada etapa instrucional, otimizando o tempo de computação e a reutilização. Se uma camada não estiver mudando, as camadas armazenadas em cache serão usadas. Os casos de uso comuns para camadas em cache são o ambiente de execução Java, o servidor de aplicações e outras dependências para a aplicação Web de Reservas de Voos para Companhias Aéreas. Se e quando uma versão for alterada em uma camada armazenada anteriormente em cache, uma nova entrada armazenada em cache será criada.

O diagrama a seguir mostra as camadas de uma imagem de contêiner. Quando os comandos no Dockerfile são executados, as camadas são criadas. A camada superior é o Sistema de Leitura/Escrita de Reserva de Voo para a camada de aplicação web de Reservas de Companhias Aéreas. Essa camada é construída sobre as camadas de apenas leitura anteriores.

Diagrama mostrando as camadas do Docker.

O Docker tem o conceito de compilações de vários estágios, um recurso que permite criar uma imagem de contêiner menor com melhor cache e menor pegada de segurança, permitindo maior otimização e manutenção do Dockerfile ao longo do tempo. Por exemplo, você pode separar o estágio de compilação do contêiner para compilar e criar o aplicativo do estágio para executar o aplicativo. Esse recurso permite copiar apenas os artefatos gerados durante a compilação para a imagem do contêiner de produção, o que reduz o espaço ocupado. Como as imagens de contêiner são armazenadas em cache, se não houver alterações, as imagens armazenadas em cache podem ser reutilizadas, reduzindo o custo e o tempo de download da rede.

Os serviços expostos no ambiente de produção devem ser cuidadosamente gerenciados por segurança. Portanto, o ambiente de produção usa e opera uma imagem de contêiner segura. O exemplo usa a CBL-Mariner imagem fornecida pela Microsoft.

CBL-Mariner Linux é um sistema operacional leve, contendo apenas os pacotes necessários para um ambiente de nuvem. Você pode personalizá-lo através de pacotes e ferramentas personalizadas para atender aos requisitos do seu aplicativo. CBL-Mariner passa por testes de validação do Azure e é compatível com agentes do Azure. A Microsoft cria e testa CBL-Mariner para alimentar vários casos de uso, desde serviços do Azure até a alimentação da infraestrutura de IoT. É a distribuição Linux recomendada internamente para uso com serviços de nuvem da Microsoft e produtos relacionados.

Observação

A Microsoft fornece imagens de contentores em conjunto com o OpenJDK, incluindo imagens Ubuntu, CBL-Mariner e distroless. A distroless imagem tem o menor tamanho de imagem, mas executar o Tomcat nela é um desafio. Para obter um design leve, a imagem remove muitos comandos e ferramentas, incluindo o shell, o que significa que não se pode executar distroless para iniciar o Tomcat . A distroless imagem é adequada para executar JARs executáveis, como aqueles usados com Spring Boot ou Quarkus.

No exemplo a seguir, a mesma versão do Microsoft Build do OpenJDK é usada no estágio de compilação e no estágio final. Essa abordagem garante que você crie o código-fonte com a mesma versão do JDK usada pelo Tomcat na implantação do serviço, o que ajuda a evitar comportamentos inesperados devido a incompatibilidades de versão.

A imagem seguinte mostra a construção de vários estágios e o que está a ocorrer em cada estágio com base nos comandos especificados no Dockerfile.

Diagrama mostrando a compilação de vários estágios do Docker.

No estágio 0, o Tomcat é baixado e extraído em um diretório especificado por uma variável de ambiente em uma imagem do Ubuntu. A TOMCAT_VERSION variável especifica a versão do Tomcat a ser baixada. Se uma nova versão do Tomcat for lançada, você deve atualizar o número da versão, já que uma nova imagem só é buscada quando o número da versão é alterado. Caso contrário, a imagem armazenada em cache será usada. O Tomcat baixado é copiado para o ambiente de estágio final para uso.

No estágio 1, o Maven é instalado em uma imagem do Ubuntu, e o código-fonte criado e os arquivos de configuração são copiados antes de construir o projeto Maven. Cada camada é armazenada em cache, de modo que a imagem do sistema operacional e as camadas de imagem do Maven reutilizam o cache. Se os arquivos de configuração, os arquivos de código-fonte ou o diretório da Web forem atualizados, as camadas a partir das alterações serão reconstruídas. Se a compilação for concluída com êxito sem erros durante a compilação, um artefato chamado AirlinesReservationSample-0.0.1-SNAPSHOT.war será gerado no diretório de destino . Esse artefato é copiado para o ambiente final de estágio para utilização.

No estágio final, a imagem segura CBL-Mariner fornecida pela Microsoft é usada para copiar os artefatos de construção Tomcat e Java do estágio 0 e estágio 1, respectivamente. Um usuário chamado app possui todos os arquivos usados dentro do projeto, e o aplicativo também é executado como o app usuário em vez de ter root privilégios. Essa configuração garante que a imagem do contêiner possa ser operada com segurança sem conceder permissões desnecessárias. Finalmente, o número da porta 8080 é exposto e o script catalina.sh é executado para iniciar o Tomcat. Quando isso é executado em seu Docker Desktop local, você pode acessá-lo através do URL http://localhost:8080/AirlinesReservationSample.

Na pasta raiz do seu projeto, containerize-and-deploy-Java-app-to-Azure/Project/Airlines, use o seguinte comando para criar um arquivo chamado Dockerfile:

vi Dockerfile

Adicione o seguinte conteúdo ao seu Dockerfile, salve e saia. Para guardar e sair, prima ESC, escreva :wq! e, em seguida, prima Enter.

############################################
# Tomcat Intall stage
############################################
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS tomcat

ENV CATALINA_HOME=/usr/local/tomcat

# Configure Tomcat Version (Be sure to use the latest version)
ENV TOMCAT_VERSION=10.1.33

# Install Tomcat and required packages
RUN apt-get update ; \
    apt-get install -y curl ; \
    curl -O https://downloads.apache.org/tomcat/tomcat-10/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz ; \
    tar xzf apache-tomcat-${TOMCAT_VERSION}.tar.gz ; \
    mv apache-tomcat-${TOMCAT_VERSION} ${CATALINA_HOME} ; \
    rm apache-tomcat-${TOMCAT_VERSION}.tar.gz && \
    apt-get remove --purge -y curl && \
    apt-get autoremove -y && \
    apt-get clean

############################################
# Build stage (Compiles with Java 17)
############################################
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build

WORKDIR /build

# Install Maven
RUN apt-get update && apt-get install -y maven && mvn --version

# Copy source code
COPY pom.xml .
COPY src ./src
COPY web ./web

# Build the project
RUN mvn clean package

############################################
# Package final stage
############################################
FROM mcr.microsoft.com/openjdk/jdk:17-mariner

# Configure the location of the Tomcat installation
ENV CATALINA_HOME=/usr/local/tomcat
# Configure the path to the Tomcat binaries
ENV PATH=$CATALINA_HOME/bin:$PATH

# This is the user that runs the Tomcat process
USER app

# Copy the Tomcat installation from the Tomcat stage
COPY --chown=app:app --from=tomcat ${CATALINA_HOME} ${CATALINA_HOME}
# Copy the Tomcat configuration files
COPY --chown=app:app tomcat-users.xml ${CATALINA_HOME}/conf
# Copy the compiled WAR file from the build stage
COPY --chown=app:app --from=build /build/target/*.war ${CATALINA_HOME}/webapps/AirlinesReservationSample.war

# Expose the default Tomcat port
EXPOSE 8080
# Start Tomcat
CMD ["catalina.sh", "run"]

Observação

Opcionalmente, você pode usar o arquivo Dockerfile_Solution na raiz do seu projeto, que contém o conteúdo que você precisa.

O Dockerfile é dividido em três estágios, que são descritos nas tabelas a seguir:

  • O estágio de instalação do Tomcat:

    Comando Docker Descrição
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS tomcat define a imagem base como Microsoft Build do OpenJDK 17 no Ubuntu e nomeia este estágio tomcat. É aqui que o Tomcat está instalado.
    ENV ENV CATALINA_HOME=/usr/local/tomcat define uma variável de ambiente para o diretório de instalação do Tomcat.
    ENV ENV TOMCAT_VERSION=10.1.33 define a versão do Tomcat a ser instalada. Isso deve ser atualizado para a versão mais recente, conforme necessário.
    RUN O RUN comando atualiza a lista de pacotes, instala, curlbaixa a versão especificada do Tomcat, extrai-a, move-a para o diretório especificado e limpa arquivos e pacotes desnecessários. Isso garante que a imagem permaneça leve.
  • A etapa de build, que compila utilizando Java 17:

    Comando Docker Descrição
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build define a imagem base como Microsoft Build do OpenJDK 17 no Ubuntu e nomeia este estágio build. Esta etapa é usada para compilar a aplicação Java.
    WORKDIR WORKDIR /build Define o diretório de trabalho dentro do contêiner como /build, onde o código-fonte é copiado e compilado.
    RUN RUN apt-get update && apt-get install -y maven && mvn --version instala o Maven, uma ferramenta de automação de construção usada para projetos Java, e verifica sua instalação.
    COPY COPY pom.xml . copia o arquivo de configuração do Maven para o diretório de trabalho. Este arquivo é essencial para a construção do projeto.
    COPY COPY src ./src Copia o diretório do código-fonte para o contêiner. É aqui que reside o código da aplicação Java.
    COPY COPY web ./web Copia o diretório Recursos da Web para o contêiner. Isso inclui recursos de aplicativos Web necessários para a compilação.
    RUN RUN mvn clean package executa o processo de compilação Maven, que compila o aplicativo Java e o empacota em um arquivo WAR.
  • A fase final do pacote:

    Comando Docker Descrição
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-mariner define a imagem base como Microsoft Build do OpenJDK 17 no CBL-Mariner, que é usado para a implantação final do aplicativo.
    ENV ENV CATALINA_HOME=/usr/local/tomcat define a variável de ambiente para o diretório de instalação do Tomcat, semelhante ao estágio de instalação.
    ENV ENV PATH=$CATALINA_HOME/bin:$PATH adiciona o diretório bin do Tomcat ao sistema PATH, permitindo que os comandos do Tomcat sejam executados facilmente.
    USER USER app especifica o usuário sob o qual o processo Tomcat é executado, aumentando a segurança ao não ser executado como o usuário raiz.
    COPY COPY --chown=app:app --from=tomcat ${CATALINA_HOME} ${CATALINA_HOME} copia a instalação do Tomcat da tomcat etapa, definindo a propriedade para o app utilizador.
    COPY COPY --chown=app:app tomcat-users.xml ${CATALINA_HOME}/conf copia o arquivo de configuração do usuário do Tomcat para o contêiner, definindo a propriedade para o app usuário.
    COPY COPY --chown=app:app --from=build /build/target/*.war ${CATALINA_HOME}/webapps/AirlinesReservationSample.war copia o ficheiro WAR compilado da build etapa para o diretório webapps do Tomcat, atribuindo a propriedade ao utilizador app.
    EXPOSE EXPOSE 8080 expõe a porta 8080, a porta padrão do Tomcat, permitindo acesso externo ao aplicativo.
    CMD CMD ["catalina.sh", "run"] especifica o comando para iniciar o Tomcat quando o contêiner é executado.

Para obter mais informações sobre a construção do Dockerfile, consulte a referência do Dockerfile.