Contentorizar uma aplicação Java

Concluído

Nesta unidade, você colocará em contêiner um aplicativo Java.

Como mencionado anteriormente, os contêineres são executados diretamente sobre o sistema operacional host, kernel e hardware como essencialmente apenas outro processo do sistema. Os contêineres exigem menos recursos do sistema, o que resulta em menor espaço ocupado, menos sobrecarga e tempos de inicialização de aplicativos mais rápidos. Estes são ótimos casos de uso para dimensionamento sob demanda.

Existem contêineres Windows e contêineres Linux. Neste módulo, você aproveitará o tempo de execução do Docker amplamente usado para criar uma imagem de contêiner do Linux. Em seguida, você implantará a imagem do contêiner Linux no sistema operacional host de sua máquina local. Finalmente, você implantará 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. A imagem a seguir mostra esses casos de uso, seguida por uma descrição de cada comando de caso de uso/Docker.

Diagrama mostrando comandos do Docker.

Comando Docker Descrição
docker build Constrói uma imagem de contêiner; essencialmente, as instruções/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 obtidas de registos como o Registo de Contêiner do Azure, e é daí que o Serviço Kubernetes do Azure as obtém. O resultado desse comando é um pull de rede de uma imagem que ocorrerá no Azure. Observe que, opcionalmente, você pode extrair imagens localmente; Isso é comum ao criar imagens que exigem dependências/camadas para as quais 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 Azure Container Registry armazenará as imagens para que elas estejam prontamente disponíveis e próximas em termos de rede para implantações e escalabilidade no Azure.

Clone o aplicativo Java

Primeiro, você clonará o repositório do Sistema de Reservas de Voos para Reservas de Companhias Aéreas e o cd para a pasta do projeto do aplicativo Web Companhias Aéreas.

Observação

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

Execute o seguinte comando na sua CLI:

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

Execute o seguinte comando na sua CLI:

cd containerize-and-deploy-Java-app-to-Azure/Project/Airlines

Observação

Opcionalmente, se você tiver o Java e o Maven instalados, poderá executar o(s) seguinte(s) comando(s) em sua CLI para ter uma noção da experiência na criação do aplicativo sem o Docker. Se você não tiver o Java e o Maven instalados, poderá avançar com segurança para a próxima seção, Construir um arquivo do Docker. Nessa seção, você usará o Docker para puxar Java e Maven para executar as compilações em seu nome.

Opcionalmente, se você tiver o Maven e um JDK(8) ou superior instalado, poderá executar o seguinte comando na CLI:

mvn clean install

Observação

Usamos o comando mvn clean install 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 se mover com segurança sem executar o comando Maven.

A Maven deve ter construído com sucesso o Flight Booking System for Airline Reservations Web Application Archive artifact FlightBookingSystemSample-0.0.-SNAPSHOT.war, como na saída a seguir:

[INFO] Building war: /mnt/c/Users/chtrembl/dev/git/containerize-and-deploy-Java-app-to-Azure/Project/FlightBookingSystemSample/target/FlightBookingSystemSample-0.0.1-SNAPSHOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  17.698 s
[INFO] Finished at: 2021-09-28T15:18:07-04:00
[INFO] ------------------------------------------------------------------------

Imagina que és um desenvolvedor Java e tens acabado de construir este FlightBookingSystemSample-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, isso requer que os servidores e máquinas virtuais estejam disponíveis e configurados com as dependências necessárias. Isto é desafiante e demorado, especialmente em situações de pico quando maior carga está a atingir a sua aplicação. Com os contentores, estes desafios são aliviados.

Construir um Dockerfile

Neste ponto, você está pronto para construir um Dockerfile. Um Dockerfile é um documento de texto que contém todos os comandos que um usuário pode executar na linha de comando para montar uma imagem de contêiner, cada um dos quais são camadas (que podem ser armazenadas em cache para eficiência) que se baseiam umas nas 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 FlightBookingSystemSample-0.0.1-SNAPSHOT.war; é uma dependência externa necessária para que o FlightBookingSystemSample-0.0.1-SNAPSHOT.war execute, ouça e processe solicitações HTTP, gerencie sessões de usuários e facilite reservas de voos. Se essa fosse uma implantação tradicional, não conteinerizada, os engenheiros de operação instalariam e configurariam um servidor de aplicativos em algum servidor físico e/ou máquina virtual antes de implantar o FlightBookingSystemSample-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 (o que mvn clean install estava usando para compilar o .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 (camadas) necessárias para realizar isso automaticamente, estruturando em camadas os passos essenciais para garantir que o Sistema de Reservas de Voos para Reservas de Companhias Aéreas disponha de todas as dependências necessárias para a implementação no ambiente de execução do contentor Docker. Isso é muito atraente quando você começa a pensar em escala sob demanda em intervalos não planejados. Vale a pena notar que cada camada está aproveitando o cache do Docker, que contém o estado da imagem do contêiner em cada etapa de instrução, 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. Casos de uso comuns para camadas armazenadas em cache são coisas como tempo de execução Java, servidor de aplicativos e/ou 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 anteriormente em cache, uma nova entrada armazenada em cache será criada.

A imagem a seguir mostra as camadas de uma imagem de contêiner. Você notará que a camada superior, o Sistema de Reserva de Voos de leitura/gravação, para a camada do aplicativo da Web de Reservas de Companhias Aéreas, é construída sobre as camadas somente leitura anteriores, todas resultantes dos comandos presentes no Dockerfile.

Diagrama mostrando as camadas do Docker.

O Docker também 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 uma pegada de segurança menor, permitindo uma maior otimização e manutenção do Dockerfile ao longo do tempo; Por exemplo, instruções que você pode usar para realizar tanto uma compilação do aplicativo (FlightBookingSystemSample-0.0.1-SNAPSHOT.war) quanto uma compilação da própria imagem do contêiner, deixando os restos da compilação FlightBookingSystemSample-0.0.1-SNAPSHOT.war para trás, resultando em uma pegada menor. A longo prazo, isso paga dividendos quando você começa a pensar nessas imagens viajando pela rede. Com compilações de vários estágios, você usa várias instruções FROM em seu Dockerfile. Cada instrução FROM pode usar uma base diferente, e cada uma dessas instruções começa do zero, removendo quaisquer ficheiros desnecessários na camada de cache que normalmente seriam armazenados em cache.

É imperativo garantir que o aplicativo seja construído pelo mesmo JDK correspondente ao mesmo JRE que será isolado na imagem do contêiner em tempo de execução. No exemplo a seguir, você terá um estágio de compilação que aproveita uma versão específica do Maven e uma versão específica do JDK para compilar o FlightBookingSystemSample-0.0.1-SNAPSHOT.war. Este estágio garante que qualquer runtime do Docker que execute este estágio obterá o código de byte gerado esperado que o autor do Dockerfile especificou (caso contrário, os engenheiros de operações teriam que fazer referência cruzada entre o runtime do Java e do servidor de aplicações com o do desenvolvedor). O estágio de Pacote utilizará uma versão específica do Tomcat e do JRE correspondente ao JDK no estágio de Construção. Novamente, isso é feito para garantir que todas as dependências (Java Development Kit JDK, Java Runtime Environment JRE, servidor de aplicativos) sejam controladas e isoladas para garantir o comportamento esperado em todas as máquinas nas quais essa imagem será executada.

Também vale a pena notar que, com esta construção de vários estágios, tecnicamente não há necessidade de Maven e Java serem instalados no sistema. O Docker irá puxá-los para baixo para uso com a criação do aplicativo, bem como o tempo de execução do aplicativo, evitando qualquer potencial conflito de versão e comportamento inesperado; a menos, é claro, que você esteja compilando código e criando artefatos fora do Docker.

A imagem seguinte mostra a construção de vários estágios e o que está ocorrendo em cada estágio com base nos comandos definidos no Dockerfile. Na Etapa 0, a Fase de Construção, o aplicativo da web Sistema de Reservas de Voos de Companhias Aéreas é compilado e FlightBookingSystemSample-0.0.1-SNAPSHOT.war é gerado. Esta etapa permite a consistência das versões Maven e Java usadas para compilar esta aplicação. Depois que o FlightBookingSystemSample-0.0.1-SNAPSHOT.war é criado, essa é a única camada necessária para o Estágio 1 (Estágio de Tempo de Execução), e todas as camadas anteriores podem ser descartadas. O Docker usará essa camada de FlightBookingSystemSample-0.0.1-SNAPSHOT.war do Estágio 0 para construir as camadas restantes necessárias para o tempo de execução; Nesse caso, configure o servidor de aplicativos e inicie o aplicativo.

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

Dentro da raiz do seu projeto, containerize-and-deploy-Java-app-to-Azure/Project/Airlines, crie um arquivo chamado Dockerfile:

vi Dockerfile

Adicione o seguinte conteúdo ao Dockerfile, salve e saia pressionando ESCe, em seguida, digitando :wq! e pressionando Enter:

#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
WORKDIR /build
COPY pom.xml .
COPY src ./src
COPY web ./web
RUN mvn clean package

#
# Package stage
#
FROM tomcat:8.5.72-jre11-openjdk-slim
COPY tomcat-users.xml /usr/local/tomcat/conf
COPY --from=build /build/target/*.war /usr/local/tomcat/webapps/FlightBookingSystemSample.war
EXPOSE 8080
CMD ["catalina.sh", "run"]

Observação

Opcionalmente, o Dockerfile_Solution na raiz do seu projeto contém o conteúdo necessário.

Este estágio de compilação do Dockerfile tem seis instruções:

Comando Docker Descrição
FROM FROM maven será a camada base a partir da qual esse FlightBookingSystemSample-0.0.1-SNAPSHOT.war é construído, uma versão específica do Maven e uma versão específica do JDK para garantir que a mesma compilação de código de byte ocorra em todas as máquinas que executam essa compilação.
WORKDIR WORKDIR é usado para definir o diretório de trabalho de um contêiner a qualquer momento; neste caso, onde os artefatos compilados residirão.
COPY COPY adiciona arquivos do diretório atual do cliente Docker. Configurando os arquivos necessários para o Maven compilar, pom.xml serão necessários para o contexto do Docker.
COPY Configura os arquivos necessários para o Maven compilar. O contexto do Docker precisará da pasta src que contém o aplicativo da Web Sistema de Reservas de Voos para Reservas de Companhias Aéreas.
COPY Configura os arquivos necessários para o Maven compilar. O contexto do Web Docket precisará da pasta que contém as dependências da aplicação web do Sistema de Reserva de Voos para Companhias Aéreas.
CORRER A instrução RUN mvn clean package é usada para executar qualquer comando na parte superior da imagem atual. Neste caso, RUN é usado para executar a compilação Maven, que compilará o FlightBookingSystemSample-0.0.1-SNAPSHOT.war.

Este estágio do pacote de arquivos do Docker tem cinco instruções:

Comando Docker Descrição
FROM FROM tomcat será a camada base sobre a qual a imagem do contêiner será construída. A imagem do contentor do Sistema de Reservas de Voos para Companhias Aéreas será uma imagem construída sobre a imagem do Tomcat. O tempo de execução do Docker tentará localizar a imagem do Tomcat localmente. Se ele não tiver essa versão, descarregará uma do registro. Se inspecionasse a imagem do tomcat que está sendo referenciada aqui, você descobriria que ela é construída usando muitas outras camadas, que a tornam reutilizável como uma imagem de contêiner do servidor de aplicativos empacotada para o mundo usar ao implantar seu aplicativo Java. Selecionamos e testamos tomcat:8.5.72-jre11-openjdk-slim para fins de módulo. Observe que todas as camadas anteriores do primeiro estágio de compilação desaparecerão quando o Docker reconhecer essa segunda instrução FROM.
COPY COPY tomcat-users.xml copiará o ficheiro de tomcat-users.xml que gere o Sistema de Reservas de Voos para utilizadores de Reservas de Companhias Aéreas (gerido dentro do controlo de código-fonte usando a identidade Tomcat; normalmente, isto ocorreria num sistema de gestão de identidades externo) para a imagem do contêiner Tomcat, garantindo que esteja presente toda vez que uma imagem de contêiner seja criada.
ADD ADD target/*.war /usr/local/tomcat/webapps/FlightBookingSystemSample.war copiará o FlightBookingSystemSample-0.0.1-SNAPSHOT.war compilado pelo Maven para a pasta webapps das imagens do Tomcat, garantindo que, quando o Tomcat for inicializado, encontrará o FlightBookingSystemSample-0.0.1-SNAPSHOT.war para ser instalado no servidor de aplicações.
EXPOSE EXPOSE 8080 é necessário porque o Tomcat está configurado para ouvir o tráfego na porta 8080. Isso garante que o processo Docker irá escutar nessa porta.
CMD A instrução CMD define um comando a ser executado ao executar o contêiner. Nesse caso, CMD ["catalina.sh", "run"] instrui o Docker a inicializar o servidor de aplicativos Tomcat.

Observação

Sem uma tag de versão na linha FROM tomcat, a versão mais recente será aplicada. Geralmente, você vai querer aproveitar uma tag de versão (lembre-se, o cache é aplicado, portanto, se as camadas estiverem mudando consistentemente, você incorrerá em largura de banda, latência, tempo de computação e/ou efeitos colaterais de compilações/camadas não testadas). Para este módulo, pré-selecionamos tags específicas Maven, Tomcat e Java JRE/JDK que são testadas para funcionar com FlightBookingSystemSample-0.0.1-SNAPSHOT.war em tempo de execução.

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