Containerizzare un'app Java

Completato

In questa unità verrà inserita in un contenitore un'app Java.

Come accennato in precedenza, i contenitori vengono eseguiti direttamente sul sistema operativo host, sul kernel e sull'hardware, essenzialmente come un altro processo di sistema. I contenitori richiedono meno risorse di sistema, hanno quindi footprint inferiore, un sovraccarico minore e tempi di avvio delle applicazioni ridotti. Questi sono ottimi casi d'uso per la scalabilità su richiesta.

Esistono contenitori Windows e contenitori Linux. In questo modulo verrà sfruttato il runtime Docker, ampiamente usato, per creare un'immagine del contenitore Linux. Quindi l'immagine del contenitore Linux verrà distribuita al sistema operativo host del computer locale. L'immagine del contenitore Linux verrà infine distribuita nel servizio Azure Kubernetes.

Panoramica di Docker

Il runtime Docker viene usato per la creazione, il pull, l'esecuzione e il push delle immagini del contenitore. L'immagine seguente illustra questi casi d'uso seguiti da una descrizione di ogni caso d'uso/comando Docker.

Diagram showing Docker commands.

Comando di Docker Descrizione
docker build Crea un'immagine del contenitore. Si tratta essenzialmente delle istruzioni o dei livelli necessari a Docker per creare un contenitore in esecuzione partendo da un'immagine. Il risultato di questo comando è un'immagine.
docker pull I contenitori vengono inizializzati partendo dalle immagini, di cui viene effettuato il pull dai registri, ad esempio Registro Azure Container, ed è da qui che il servizio Azure Kubernetes effettuerà il pull. Il risultato di questo comando è un pull di rete di un'immagine che si verificherà in Azure. Si noti che, facoltativamente, è possibile effettuare il pull delle immagini in locale. Questa operazione è comune quando si creano immagini che richiedono dipendenze/livelli di cui l'applicazione potrebbe avere bisogno, ad esempio un server applicazioni.
docker run Un'istanza in esecuzione di un'immagine è un contenitore e questo comando esegue tutti i livelli necessari per l'esecuzione e l'interazione con l'applicazione contenitore in esecuzione. Il risultato di questo comando è un processo dell'applicazione in esecuzione nel sistema operativo host.
docker push Registro Azure Container archivierà le immagini in modo che siano facilmente disponibili e in prossimità della rete per le distribuzioni e la scalabilità di Azure.

Clonare l'applicazione Java

Prima di tutto si clonerà il repository Flight Booking System for Airline Reservations, che verrà inserito nella cartella del progetto dell'applicazione Web Airlines.

Nota

Se la creazione del servizio Azure Kubernetes è stata completata correttamente nella scheda dell'interfaccia della riga di comando, usare la scheda. Se invece è ancora in esecuzione, aprire una nuova scheda e passare alla posizione in cui si preferisce clonare Flight Booking System for Airline Reservations.

Nell'interfaccia della riga di comando eseguire il comando seguente:

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

Nell'interfaccia della riga di comando eseguire il comando seguente:

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

Nota

Facoltativamente, se Java e Maven sono installati, è possibile eseguire i comandi seguenti nell'interfaccia della riga di comando per avere un'idea dell'esperienza di creazione dell'applicazione senza Docker. Se Java e Maven non sono installati, è possibile passare in modo sicuro alla sezione successiva Costruire un Dockerfile. In questa sezione si userà Docker per eseguire il pull di Java e Maven in modo che le compilazioni vengano eseguite per conto dell'utente.

Facoltativamente, se sono installati Maven e JDK(8) o versione successiva, è possibile eseguire il comando seguente nell'interfaccia della riga di comando:

mvn clean install

Nota

È stato usato il comando mvn clean install per illustrare le sfide operative del mancato utilizzo delle compilazioni Docker in più fasi, che verranno illustrate di seguito. Anche questo passaggio è facoltativo. È comunque possibile proseguire in modo sicuro senza eseguire il comando Maven.

Maven dovrebbe aver creato correttamente l'artefatto di archivio FlightBookingSystemSample-0.0.-SNAPSHOT.war dell'applicazione Web Flight Booking System for Airline Reservations, come mostrato nell'output seguente:

[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] ------------------------------------------------------------------------

Si supponga di essere uno sviluppatore Java e di aver appena creato FlightBookingSystemSample-0.0.1-SNAPSHOT.war. Il passaggio successivo consisterà probabilmente nel collaborare con i tecnici operativi per distribuire questo artefatto, in un server locale o in una macchina virtuale. Affinché l'applicazione venga avviata ed eseguita correttamente, è necessario che i server e le macchine virtuali siano disponibili e configurati con le dipendenze necessarie. Si tratta di un'operazione complessa e dispendiosa in termini di tempo, in particolare su richiesta quando si raggiunge un aumento del carico nell'applicazione. Queste sfide vengono risolte con i contenitori.

Costruire un Dockerfile

A questo punto, si è pronti per costruire un Dockerfile. Un Dockerfile è un documento di testo contenente tutti i comandi che un utente potrebbe eseguire nella riga di comando per assemblare un immagine del contenitore. Tutti questi comandi sono livelli (che possono essere memorizzati nella cache per efficienza) basati l'uno sull'altro.

Ad esempio, Flight Booking System for Airline Reservations deve essere distribuito ed eseguito all'interno di un server applicazioni. Un server applicazioni non è incluso nel pacchetto FlightBookingSystemSample-0.0.1-SNAPSHOT.war, ma è una dipendenza esterna necessaria perché FlightBookingSystemSample-0.0.1-SNAPSHOT.war possa essere eseguito, ascoltare ed elaborare le richieste HTTP, gestire le sessioni utente e facilitare le prenotazioni di voli. Se si tratta di una distribuzione tradizionale non in contenitori, i tecnici operativi installano e configurano un server applicazioni in un server fisico e/o in una macchina virtuale, poi distribuiscono FlightBookingSystemSample-0.0.1-SNAPSHOT.war. Questi tecnici operativi devono anche assicurarsi che il JDK in uso nel computer (quello che mvn clean install stava usando per compilare il file war) corrisponda effettivamente allo stesso JRE usato dal server applicazioni. La gestione di queste dipendenze è complicata e richiede molto tempo.

Con un Dockerfile, è possibile scrivere le istruzioni (livelli) necessarie per eseguire questa operazione automaticamente, inserendo i passaggi necessari per garantire che Flight Booking System for Airline Reservations disponga di tutte le dipendenze necessarie per la distribuzione nel runtime del contenitore Docker. Questo è molto efficace quando si inizia a pensare alla scalabilità su richiesta a intervalli non pianificati. Vale la pena notare che ogni livello sfrutta la cache Docker, che contiene lo stato dell'immagine del contenitore in ogni attività cardine di istruzioni, ottimizzando così il tempo di calcolo e il riutilizzo. Se un livello non cambia, vengono usati i livelli memorizzati nella cache. I casi d'uso comuni per i livelli memorizzati nella cache sono elementi come runtime Java, server applicazioni e/o altre dipendenze per l'applicazione Web Flight Booking System for Airline Reservations. Se e quando una versione cambia in un livello memorizzato nella cache in precedenza, viene creata una nuova voce memorizzata nella cache.

L'immagine seguente illustra i livelli di un'immagine del contenitore. Si noterà che il livello superiore è il livello dell'applicazione Web Flight Booking System for Airline Reservations in lettura/scrittura, basato sui livelli di sola lettura precedenti, tutti risultanti dai comandi nel Dockerfile.

Diagram showing the Docker layers.

Docker include anche il concetto di compilazioni a più livelli, una funzionalità che consente di creare un'immagine del contenitore più piccola con una memorizzazione nella cache migliore e un footprint di sicurezza più piccolo, permettendo quindi una maggiore ottimizzazione e manutenzione del Dockerfile nel tempo. Sono ad esempio disponibili istruzioni che è possibile usare per eseguire sia una compilazione dell'applicazione (FlightBookingSystemSample-0.0.1-SNAPSHOT.war) sia una compilazione dell'immagine contenitore stessa,tralasciando il resto della compilazione di FlightBookingSystemSample-0.0.1-SNAPSHOT.war, con conseguente riduzione del footprint. A lungo termine, questo offre vantaggi se si inizia a riflettere su queste immagini che viaggiano in rete. Con le build in più fasi, si usano più istruzioni FROM nel Dockerfile. Ogni istruzione FROM può usare una base diversa e ognuna di queste istruzioni inizia con uno slate pulito, rimuovendo nel livello di memorizzazione nella cache tutti i file non necessari che in genere potrebbero essere memorizzati nella cache.

È fondamentale assicurarsi che l'applicazione venga compilata dallo stesso JDK corrispondente allo stesso JRE che verrà isolato nell'immagine del contenitore al runtime. Nell'esempio seguente si vedrà una fase di compilazione che sfrutta una versione specifica di Maven e una versione specifica del JDK per compilare FlightBookingSystemSample-0.0.1-SNAPSHOT.war. È garantito che qualsiasi runtime Docker che esegue questa fase otterrà il codice di byte generato previsto che è stato specificato dall'autore del Dockerfile. In caso contrario, i tecnici operativi dovranno utilizzare un riferimento incrociato tra il runtime Java e del server applicazioni e quello degli sviluppatori. La fase di pacchetto userà quindi una versione specifica di Tomcat e il JRE corrispondente al JDK nella fase di compilazione. Anche in questo caso, questa operazione viene eseguita per garantire che tutte le dipendenze (Java Development Kit o JDK, Java Runtime Environment o JRE, server applicazioni) siano controllate e isolate in modo da assicurare il comportamento previsto in tutti i computer in cui verrà eseguita questa immagine.

Vale anche la pena di notare che con questa compilazione in più fasi non è tecnicamente necessario che nel sistema siano installati Maven e Java. Docker ne eseguirà il pull per l'uso sia con la compilazione che con il runtime dell'applicazione, evitando eventuali potenziali conflitti di versioni e comportamenti imprevisti, naturalmente a meno che non si stia compilando codice e si stiano creando artefatti all'esterno di Docker.

L'immagine seguente illustra la build in più fasi e ciò che si verifica in ogni fase in base ai comandi specificati nel Dockerfile. Nella fase 0, la fase di compilazione, viene compilata l'applicazione Web Flight Booking System for Airline Reservations e viene generato il file FlightBookingSystemSample-0.0.1-SNAPSHOT.war. Questa fase consente la coerenza delle versioni di Maven e Java usate per compilare l'applicazione. Dopo la creazione di FlightBookingSystemSample-0.0.1-SNAPSHOT.war, è l'unico livello necessario per la fase 1 (Runtime). Tutti i livelli precedenti possono essere rimossi. A questo punto Docker userà il livello FlightBookingSystemSample-0.0.1-SNAPSHOT.war della fase 0 per costruire i livelli rimanenti necessari per il runtime, in questo caso la configurazione del server applicazioni e l'avvio dell'applicazione.

Diagram showing the Docker multistage build.

All'interno della radice del progetto, inserire in un contenitore e distribuire l'app Java ad Azure/Project/Airlines, quindi creare un file denominato Dockerfile:

vi Dockerfile

Aggiungere il contenuto seguente a Dockerfile, quindi salvare e uscire premendo ESC, digitando :wq! e premendo INVIO:

#
# 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"]

Nota

Facoltativamente, ciò che serve è disponibile in Dockerfile_Solution nella radice del progetto.

Questa fase di compilazione di Dockerfile include sei istruzioni:

Comando di Docker Descrizione
FROM FROM maven sarà il livello di base da cui viene compilato questo file FlightBookingSystemSample-0.0.1-SNAPSHOT.war, una versione specifica di Maven e una versione specifica di JDK per garantire che in tutti i computer che eseguono questa build venga eseguita la stessa compilazione del codice di byte.
WORKDIR WORKDIR viene usato per definire la directory di lavoro di un contenitore in qualsiasi momento, in questo caso dove risiederanno gli artefatti compilati.
COPY COPY aggiunge i file dalla directory corrente del client Docker. Configurazione dei file necessari per la compilazione di Maven; per il contesto Docker servirà pom.xml.
COPY Configura i file necessari per la compilazione di Maven. Il contesto di Docker necessiterà della cartella src contenente l'applicazione Web Flight Booking System for Airline Reservations.
COPY Configura i file necessari per la compilazione di Maven. Il contesto di Docker Web necessiterà della cartella contenente l'applicazione Web Flight Booking System for Airline Reservations.
RUN L'istruzione RUN mvn clean package viene usata per eseguire qualsiasi comando sull'immagine corrente. In questo caso RUN viene usato per eseguire la build di Maven, che compilerà FlightBookingSystemSample-0.0.1-SNAPSHOT.war.

La fase di pacchetto del Dockerfile include cinque istruzioni:

Comando di Docker Descrizione
FROM FROM tomcat sarà il livello di base su cui verrà creata l'immagine del contenitore. L'immagine del contenitore di Flight Booking System for Airline Reservations sarà un'immagine basata sull'immagine tomcat. Il runtime Docker tenterà di individuare l'immagine tomcat in locale. Se non ha questa versione, effettuerà il pull di una dal registro. Se si esaminasse l'immagine tomcat a cui si fa riferimento qui, si noterebbe che è stata compilata usando molti altri livelli, che la rendono universalmente riutilizzabile come immagine del contenitore del server applicazioni in pacchetto da usare quando si distribuisce l'applicazione Java. Ai fini del modulo è stato selezionato e testato tomcat:8.5.72-jre11-openjdk-slim. Si noti che tutti i livelli precedenti della prima fase di compilazione vengono eliminati dopo che Docker riconosce questa seconda istruzione FROM.
COPY COPY tomcat-users.xml copierà il file tomcat-users.xml che gestisce gli utenti di Flight Booking System for Airline Reservations (gestito all'interno del controllo del codice sorgente usando l'identità Tomcat, generalmente in un sistema di gestione delle identità esterno) nell'immagine del contenitore tomcat, in modo che sia presente nell'immagine del contenitore ogni singola volta che ne viene creata una.
ADD ADD target/*.war /usr/local/tomcat/webapps/FlightBookingSystemSample.war copierà il file FlightBookingSystemSample-0.0.1-SNAPSHOT.war Maven compilato nella cartella delle app Web delle immagini tomcat per garantire che, al momento dell'inizializzazione, Tomcat trovi FlightBookingSystemSample-0.0.1-SNAPSHOT.war da installare nel server applicazioni.
EXPOSE EXPOSE 8080 è necessario perché Tomcat è configurato per ascoltare il traffico sulla porta 8080. In questo modo si garantisce che il processo Docker sia in ascolto in questa porta.
CMD L'istruzione CMD imposta un comando da eseguire durante l'esecuzione del contenitore. In questo caso CMD ["catalina.sh", "run"] indica a Docker di inizializzare il server applicazioni Tomcat.

Nota

Senza un tag di versione nella riga FROM tomcat, verrà applicata la versione più recente. In genere è consigliabile sfruttare un tag di versione. Tenere presente che viene applicata la memorizzazione nella cache, quindi, se i livelli cambiano in modo costante, si incorrerà in larghezza di banda, latenza, tempo di calcolo e/o effetti collaterali di build/livelli non verificati. Ai fini di questo modulo, sono stati preselezionati specifici tag Maven, Tomcat e Java JRE/JDK testati per l'uso con FlightBookingSystemSample-0.0.1-SNAPSHOT.war in fase di esecuzione.

Per altre informazioni sulla costruzione di Dockerfile, vedere Informazioni di riferimento su Dockerfile