Conteinerizar um aplicativo Java
Nesta unidade, você vai conteinerizar um aplicativo Java.
Conforme mencionado anteriormente, os contêineres são executados diretamente sobre o sistema operacional do host, o kernel e o hardware, como essencialmente apenas outro processo do sistema. Os contêineres requerem menos recursos do sistema, o que resulta em um volume menor, menos sobrecarga e tempos de inicialização do aplicativo mais rápidos. Esses são ótimos casos de uso para escala sob demanda.
Há contêineres do Windows e do Linux. Neste módulo, você aproveitará o runtime do Docker amplamente usado para criar uma imagem de contêiner do Linux. Em seguida, você implantará a imagem de contêiner do Linux no sistema operacional host do computador local. Para terminar, você vai implantar 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 compilar, extrair, executar e enviar imagens de contêiner. A imagem a seguir ilustra esses casos de uso seguidos por uma descrição de cada comando de caso de uso/Docker.
Comando do Docker | Descrição |
---|---|
docker build |
Cria uma imagem de contêiner; essencialmente, as instruções/camadas necessárias para que o Docker, em última análise, crie 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, e é desse local que o Serviço de Kubernetes do Azure irá extrair. O resultado desse comando é um pull de rede de uma imagem que ocorrerá no Azure. Observe que você pode, opcionalmente, extrair imagens localmente, algo comum quando você cria imagens que requerem dependências/camadas de que seu aplicativo pode precisar, como, por exemplo, um servidor de aplicativos. |
docker run |
A instância de uma imagem em execução é 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 do host. |
docker push |
O Registro de Contêiner do Azure armazenará as imagens para que elas estejam prontamente disponíveis e a rede esteja próxima para implantações e escala do Azure. |
Clonar o aplicativo Java
Primeiro, você clona o repositório do Sistema de Reservas de Voos para Reservas de Companhias Aéreas e usa o comando cd para mudar para a pasta do projeto do aplicativo Web de Companhias Aéreas.
Observação
Se a criação do Serviço de Kubernetes do Azure tiver sido concluída com êxito em sua guia da CLI, use essa guia. Caso contrário, se ela ainda estiver em execução, abra uma nova guia e use o comando cd para mudar para o local em que você prefere clonar o Sistema de Reservas de Voos para Reservas de 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 tiver o Java e o Maven instalados, você poderá executar os comandos a seguir na sua CLI para ter uma noção da experiência de compilação do aplicativo sem o Docker. Se não tiver o Java e o Maven instalados, você 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 efetuar pull de Java e Maven e executar as compilações em seu nome.
Opcionalmente, se você tiver o Maven e um JDK(8) ou superior instalados, execute o seguinte comando na sua CLI:
mvn clean install
Observação
Usamos o comando mvn clean install
para ilustrar os desafios operacionais de não usar as compilações multiestágio do Docker, que vamos abordar em seguida. Novamente, essa etapa é opcional. De qualquer forma, você pode avançar com segurança sem executar o comando Maven.
O Maven deve ter compilado com sucesso o artefato de Arquivamento do Aplicativo Web do Sistema de Reservas de Voos para Reservas de Companhias Aéreas FlightBookingSystemSample-0.0.-SNAPSHOT.war, conforme indicado no seguinte resultado:
[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] ------------------------------------------------------------------------
Imagine que você é um desenvolvedor de Java e acabou de criar esse FlightBookingSystemSample-0.0.1-SNAPSHOT.war
. A próxima etapa provavelmente é trabalhar com os engenheiros de operação para que o artefato seja implantado em um servidor local ou em uma máquina virtual. Para que o aplicativo seja iniciado e executado com êxito, é necessário que os servidores e as máquinas virtuais estejam disponíveis e configurados com as dependências necessárias. Isso é 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
Neste ponto, você já pode construir um Dockerfile. Um Dockerfile é um documento de texto que contém todos os comandos que um usuário poderia executar na linha de comando para montar uma imagem de contêiner, cada qual uma camada (que pode ser armazenada em cache para maior eficiência) criada como resultado da anterior.
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 fica empacotado dentro da FlightBookingSystemSample-0.0.1-SNAPSHOT.war
; trata-se, na verdade, de uma dependência externa necessária para que a FlightBookingSystemSample-0.0.1-SNAPSHOT.war
seja executada, escute e processe solicitações HTTP, gerencie sessões de usuário e facilite reservas de voo. Se essa fosse uma implantação tradicional, não conteinerizada, os engenheiros de operação instalariam e configurariam um servidor de aplicativos em um servidor físico e/ou em uma máquina virtual antes de implantar FlightBookingSystemSample-0.0.1-SNAPSHOT.war
nele. Esses engenheiros de operação também precisariam garantir que o JDK usado no seu computador (que mvn clean install
estava usando para compilar o arquivo .war) corresponde ao mesmo JRE sendo usado pelo servidor de aplicativos. Gerenciar essas dependências é desafiador e demorado.
Com um Dockerfile, você pode escrever as instruções (camadas) necessárias para fazer isso automaticamente, colocando em camadas as etapas necessárias para garantir que o Sistema de Reservas de Voos para Reservas de Companhias Aéreas tenha todas as dependências necessárias para a implantação no runtime de contêiner do Docker. Isso é muito atraente quando você começa a pensar na escala sob demanda em intervalos não planejados. Vale observar que cada camada está aproveitando o cache do Docker, que contém o estado da imagem de contêiner em cada etapa de instrução, otimizando o tempo de computação e a reutilização. Se uma camada não se destinar a ser alterada, uma camada armazenada em cache será usada. Casos de uso comuns para camadas armazenadas em cache são coisas como o runtime do Java, o servidor de aplicativos e/ou outras dependências para o aplicativo Web do Sistema de Reservas de Voos para Reservas de Companhias Aéreas. Se/quando uma versão for modificada em uma camada anteriormente armazenada em cache, uma nova inserção armazenada em cache será criada.
A imagem a seguir ilustra as camadas de uma imagem de contêiner. Você observará que toda a camada superior é a camada do aplicativo Web de leitura/gravação do Sistema de Reservas de Voos para Reservas de Companhias Aéreas, criada com base nas camadas somente leitura anteriores, todas elas resultantes dos comandos no Dockerfile.
O Docker também tem o conceito de compilações multiestágio, 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 melhor 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 de contêiner, deixando os restos da compilação de FlightBookingSystemSample-0.0.1-SNAPSHOT.war
para trás e resultando em um volume menor. No longo prazo, isso traz dividendos quando você começa a pensar nessas imagens viajando pela rede. Com as compilações multiestágio, você deve usar várias instruções FROM no seu Dockerfile. Cada instrução FROM pode usar uma base diferente, e cada uma dessas instruções começa com uma imagem fixa limpa, removendo todos os arquivos desnecessários na camada de cache, que normalmente podem estar armazenados em cache.
É fundamental garantir que o aplicativo seja criado pelo mesmo JDK correspondente ao mesmo JRE que será isolado na imagem de contêiner em runtime. No exemplo a seguir, você terá um estágio de Build 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
. Esse estágio garante que qualquer runtime do Docker que execute esse estágio receberá o código de byte gerado esperado que o autor do Dockerfile especificou (caso contrário, os engenheiros de operação teriam que fazer referência cruzada do Java e do runtime do servidor de aplicativos com o do desenvolvedor). O estágio do pacote usará então uma versão específica do Tomcat e do JRE correspondente ao JDK no estágio de criaçã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 todos os computadores em que essa imagem será executada.
Também vale observar que, com essas compilações multiestágio, tecnicamente não é necessário que o Maven e o Java sejam instalados no sistema. O Docker efetuará pull desses recursos para usá-los tanto na compilação quanto no runtime do aplicativo, evitando qualquer possibilidade de conflito de versões e comportamento inesperado, a menos, é claro, que você esteja compilando código e criando artefatos fora do Docker.
A imagem a seguir ilustra a compilação multiestágio e o que está ocorrendo em cada estágio com base nos comandos especificados no Dockerfile. No Estágio 0, a Fase de Build, o aplicativo Web do Sistema de Reservas de Voos para Reservas de Companhias Aéreas é compilado e FlightBookingSystemSample-0.0.1-SNAPSHOT.war
é gerado. Esse estágio possibilita a consistência das versões do Maven e do Java usadas para compilar esse aplicativo. Quando FlightBookingSystemSample-0.0.1-SNAPSHOT.war
é criado, essa é a única camada necessária para o Estágio 1 (Estágio de Runtime) e todas as camadas anteriores podem ser descartadas. Em seguida, 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 runtime; nesse caso, configurar o servidor de aplicativos e iniciar o aplicativo.
Na raiz do seu projeto, conteinerizar-e-implantar-um-aplicativo-Java-no-Azure/Projeto/CompanhiaAerea, crie um arquivo chamado Dockerfile:
vi Dockerfile
Adicione o conteúdo a seguir ao Dockerfile e, a seguir, salve e saia pressionando ESC e, 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, Dockerfile_Solution na raiz do seu projeto contém o conteúdo necessário.
Este estágio de Build do Dockerfile tem seis instruções:
Comando do Docker | Descrição |
---|---|
FROM |
FlightBookingSystemSample-0.0.1-SNAPSHOT.war será a camada de base a partir da qual esse FROM maven é criado, 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 todos os computadores que executam esse build. |
WORKDIR |
WORKDIR é usado para definir o diretório de trabalho de um contêiner em determinado momento; nesse caso, o local onde os artefatos compilados irão residir. |
COPY |
COPY adiciona arquivos do diretório atual do seu cliente do Docker. Configurar os arquivos necessários para o Maven compilar pom.xml será necessário pelo contexto do Docker. |
COPY |
Configura os arquivos necessários para que o Maven compile. O contexto do Docker vai precisar da pasta src que contém o aplicativo web do Sistema de Reservas de Voos para Reservas de Companhias Aéreas. |
COPY |
Configura os arquivos necessários para que o Maven compile. O contexto web do Docker vai precisar da pasta que contém as dependências do aplicativo web do Sistema de Reservas de Voos para Reservas de Companhias Aéreas. |
EXECUTAR | A instrução RUN mvn clean package é usada para executar qualquer comando com base na imagem atual. Nesse caso, RUN é usado para executar o build do Maven, que compilará o FlightBookingSystemSample-0.0.1-SNAPSHOT.war . |
Este estágio do Pacote do arquivo do Docker tem cinco instruções:
Comando do Docker | Descrição |
---|---|
FROM |
FROM tomcat será a camada de base sobre a qual essa imagem de contêiner será criada. A imagem de contêiner do Sistema de reservas de voos para reservas de companhias aéreas será uma imagem criada com base na imagem do tomcat. O runtime do Docker tentará localizar a imagem tomcat localmente. Se não tiver essa versão, ele efetuará pull de uma do registro. Se você inspecionasse a imagem do Tomcat que está sendo mencionada aqui, veria que ela foi criada usando muitas outras camadas que a tornam reutilizável como uma imagem de contêiner do servidor de aplicativo empacotado para o mundo usar ao implantar o aplicativo Java. Nós selecionamos e testamos tomcat:8.5.72-jre11-openjdk-slim para este módulo. Observe que todas as camadas anteriores do primeiro estágio de criação se vão depois que o Docker reconhece essa segunda instrução FROM. |
COPY |
COPY tomcat-users.xml copiará o arquivo tomcat-users.xml que gerencia os usuários do Sistema de Reservas de Voos para Reservas de Companhias Aéreas (gerenciado no controle do código-fonte usando a identidade do Tomcat que, tipicamente, deveria estar em um sistema de gerenciamento de identidade externo) para a imagem de contêiner do Tomcat para estar presente na imagem do contêiner a cada vez que uma imagem de contêiner for criada. |
ADD |
ADD target/*.war /usr/local/tomcat/webapps/FlightBookingSystemSample.war copiará a FlightBookingSystemSample-0.0.1-SNAPSHOT.war compilada pelo Maven para a pasta tomcat images webapps para garantir que, quando for inicializado, o Tomcat encontrará a FlightBookingSystemSample-0.0.1-SNAPSHOT.war a ser instalada no servidor de aplicativos. |
EXPOSE |
EXPOSE 8080 é necessário porque o Tomcat está configurado para escutar o tráfego na porta 8080. Isso garante que o processo do Docker escutará nessa porta. |
CMD |
A instrução CMD configura 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. De modo geral, você vai querer aproveitar uma tag de versão (lembre-se de que o armazenamento em cache foi aplicado e, portanto, se as camadas estiverem sendo alteradas de forma consistente, você incorrerá em largura de banda, latência, tempo de computação e/ou efeitos colaterais de camadas/builds não testados). Para este módulo, selecionamos marcas específicas do Maven, Tomcat, Java JRE/JDK que são testadas para funcionar com FlightBookingSystemSample-0.0.1-SNAPSHOT.war
no runtime.
Para obter mais informações sobre a construção do Dockerfile, confira a Referência do Dockerfile.