Conteinerizar um aplicativo Java

Concluído

Nesta unidade, você contêineriza um aplicativo Java.

Conforme mencionado anteriormente, os contêineres são executados diretamente na parte superior do sistema operacional host, kernel e hardware como processos comuns do sistema. Os contêineres exigem menos recursos do sistema, resultando em um volume menor, 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.

Há contêineres do Windows e contêineres do Linux. Neste módulo, você usa o runtime do Docker amplamente usado para criar uma imagem de contêiner do Linux. Em seguida, você implanta a imagem de contêiner do Linux no sistema operacional host do computador local. Por fim, você implanta a imagem de contêiner do Linux no Serviço de Kubernetes do Azure.

Visão geral do Docker

O runtime do Docker é usado para criar, fazer o pull, executar e fazer o push de 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 do 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 desse 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. Esse registro é de onde o Serviço de Kubernetes do Azure é extraído. O resultado desse comando é um pull de rede de uma imagem que ocorre no Azure. Opcionalmente, você pode baixar 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 Registro de Contêiner do Azure armazena as imagens para que elas estejam prontamente disponíveis e fechem a rede para implantações e escala do Azure.

Clonar o aplicativo Java

Primeiro, clone o repositório Flight Booking System for Airline Reservations e navegue até a pasta do projeto de aplicativo web da companhia aérea.

Observação

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

Execute os comandos a seguir:

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 Java e Maven instalados, poderá executar o comando a seguir 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 Java e 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 baixar Java e Maven e executar as compilações por você.

mvn clean package

Observação

Usamos o mvn clean package comando para ilustrar os desafios operacionais de não usar builds de vários estágios do Docker, que abordaremos em seguida. Novamente, essa etapa é opcional. De qualquer forma, você pode continuar com segurança sem executar o comando Maven.

Se o processo foi bem-sucedido, o Maven criou com sucesso o artefato de arquivo de aplicativo da Web Sistema de Reserva de Voo para Reservas Aéreas 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 criar AirlinesReservationSample-0.0.1-SNAPSHOT.war. Sua próxima etapa provavelmente é trabalhar com os engenheiros de operação para que esse artefato seja implantado em um servidor local ou em uma máquina virtual. Para que o aplicativo seja iniciado e executado com êxito, os servidores e as 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 contêineres, esses desafios são atenuados.

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 obter eficiência. As camadas se baseiam umas nas outras.

Por exemplo, o Sistema de Reservas de Voos de Companhias Aéreas precisa implementar e operar dentro de um servidor de aplicações. Um servidor de aplicativos não é empacotado dentro do AirlinesReservationSample-0.0.1-SNAPSHOT.war. É uma dependência externa necessária para que o AirlinesReservationSample-0.0.1-SNAPSHOT.war execute, ouça e processe solicitações HTTP, gerencie sessões de usuário e facilite as reservas de voo. Se você usou uma implantação tradicional e não em contêineres, 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 seu computador - que é o que mvn clean package é usado para compilar o arquivo WAR - de fato corresponda ao mesmo JRE que está sendo usado pelo servidor de aplicativos. O gerenciamento dessas dependências é desafiador e demorado.

Com um Dockerfile, você pode escrever as instruções ou as camadas necessárias para atingir essa meta automaticamente, usando as etapas necessárias para garantir que o Sistema de Reserva de Voo para Reservas Aéreas tenha todas as dependências necessárias para implantar no runtime de contêiner do Docker. Essa solução é atraente quando você trabalha com escalabilidade sob demanda em momentos inesperados. Cada camada usa o cache do Docker, que contém o estado da imagem do contêiner em cada marco de instrução, otimizando o tempo de computação e a reutilização. Se uma camada não estiver sendo alterada, as camadas armazenadas em cache serão usadas. Casos de uso comuns para camadas armazenadas em cache são o runtime do Java, o servidor de aplicativos e outras dependências para o aplicativo Web Flight Booking System for Airline Reservations. Se e quando uma versão for alterada em uma camada armazenada em cache anteriormente, uma nova entrada armazenada em cache será criada.

O diagrama a seguir ilustra 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 Reserva de Voo de leitura/gravação para a camada de aplicativo Web de Reservas Aéreas. Essa camada é criada sobre as camadas anteriores que são apenas de leitura.

Diagrama mostrando as camadas do Docker.

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

Os serviços expostos no ambiente de produção devem ser cuidadosamente gerenciados para 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 por meio de pacotes personalizados e ferramentas 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 os serviços do Azure até a alimentação da infraestrutura de IoT. É a distribuição do Linux recomendada internamente para uso com os serviços de nuvem da Microsoft e produtos relacionados.

Observação

A Microsoft fornece imagens de contêiner agrupadas com OpenJDK, incluindo as imagens Ubuntu, CBL-Mariner e distroless. A distroless imagem tem o menor tamanho de imagem, mas executar o Tomcat nela é desafiador. Para obter um design leve, a distroless imagem remove muitos comandos e ferramentas, incluindo o shell, o que significa que você não pode chamar catalina.sh 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 tanto no estágio de build quanto no estágio final. Essa abordagem garante que você crie o código-fonte com a mesma versão do JDK que o Tomcat de implantação de serviço usa, o que ajuda a evitar comportamentos inesperados devido a incompatibilidades de versão.

A imagem a seguir ilustra o build de vários estágios e o que está ocorrendo em cada estágio com base nos comandos especificados no Dockerfile:

Diagrama mostrando o build 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ê deverá atualizar o número de versão, pois uma nova imagem só será buscada quando o número de versão for 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 os arquivos de configuração e código-fonte criados são copiados antes de criar o projeto Maven. Cada camada é armazenada em cache, de modo que a imagem do sistema operacional e as camadas de imagem do Maven reutilizem o cache. Se arquivos de configuração, arquivos de código-fonte ou o diretório Web forem atualizados, as camadas das alterações em diante serão recriadas. Se o build for concluído 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 de estágio final para uso.

No estágio final, a imagem segura CBL-Mariner fornecida pela Microsoft é usada para copiar os artefatos de build Tomcat e Java do estágio 0 e estágio 1, respectivamente. Um usuário nomeado app possui todos os arquivos usados no 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 de contêiner possa ser operada com segurança sem conceder permissões desnecessárias. Por fim, o número da porta 8080 é exposto e o script catalina.sh é executado para iniciar o Tomcat. Quando isso é executado no Docker Desktop local, você pode acessá-lo por meio da URL http://localhost:8080/AirlinesReservationSample.

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

vi Dockerfile

Adicione o conteúdo a seguir ao Dockerfile e salve e saia. Para salvar e sair, pressione ESC, digite :wq!e pressione 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 projeto, que contém o conteúdo necessário.

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

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

    Comando do Docker Descrição
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS tomcat define a imagem base para o Microsoft Build do OpenJDK 17 no Ubuntu e nomeia esse 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 curl, instala, baixa 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.
  • O estágio build, que é compilado com o Java 17:

    Comando do Docker Descrição
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build define a imagem base para o Microsoft Build do OpenJDK 17 no Ubuntu e nomeia esse estágio build. Esse estágio é usado para compilar o aplicativo Java.
    WORKDIR WORKDIR /build define o diretório de trabalho dentro do contêiner para /build, em que 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 build usada para projetos Java e verifica sua instalação.
    COPY COPY pom.xml . copia o arquivo de configuração do Maven no diretório de trabalho. Esse arquivo é essencial para a criaçã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 do aplicativo Java.
    COPY COPY web ./web copia o diretório de recursos da Web para o contêiner. Isso inclui os recursos de aplicativo Web necessários para o build.
    RUN RUN mvn clean package executa o processo de build do Maven, que compila o aplicativo Java e o empacota em um arquivo WAR.
  • O estágio final do pacote:

    Comando do Docker Descrição
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-mariner define a imagem base para o Microsoft Build do OpenJDK 17 em 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 Tomcat ao sistema PATH, permitindo que os comandos Tomcat sejam executados facilmente.
    USER USER app especifica o usuário sob o qual o processo do Tomcat é executado, aprimorando a segurança ao não ser executado como usuário raiz.
    COPY COPY --chown=app:app --from=tomcat ${CATALINA_HOME} ${CATALINA_HOME} copia a instalação do Tomcat na etapa tomcat, definindo como proprietário o usuário app.
    COPY COPY --chown=app:app tomcat-users.xml ${CATALINA_HOME}/conf copia o arquivo de configuração do usuário Tomcat no 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 arquivo WAR compilado do build estágio para o diretório webapps do Tomcat, definindo a propriedade para o app usuário.
    EXPOSE EXPOSE 8080 expõe a porta 8080, a porta padrão do Tomcat, permitindo o acesso externo ao aplicativo.
    CMD CMD ["catalina.sh", "run"] especifica o comando para iniciar o Tomcat quando o contêiner for executado.

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