Containerizzare un'app Java

Completato

In questa unità si containerizza un'applicazione Java.

Come accennato in precedenza, i contenitori vengono eseguiti direttamente sul sistema operativo host, sul kernel e sull'hardware come processi di sistema ordinari. I contenitori richiedono un minor numero di risorse di sistema, con un footprint inferiore, un sovraccarico inferiore e tempi di avvio delle applicazioni più veloci. Questi vantaggi sono ottimi casi d'uso per la scalabilità su richiesta.

Sono disponibili contenitori Windows e contenitori Linux. In questo modulo si usa il runtime Docker ampiamente usato per creare un'immagine di un container Linux. Si distribuisce quindi l'immagine del contenitore Linux sul sistema operativo host del computer locale. Infine, si distribuisce l'immagine del contenitore Linux in servizio Azure Kubernetes.

Panoramica di Docker

Il runtime Docker viene usato per creare, scaricare, eseguire e pubblicare immagini container, come illustrato nel diagramma seguente:

Diagramma che mostra i comandi Docker build, pull, push ed esecuzione che connettono il client, l'host Docker e il registry.

La tabella seguente descrive ogni comando Docker:

Comando Docker Descrizione
docker build Compila un'immagine del contenitore costituita dalle istruzioni o dai livelli necessari per Docker per creare un contenitore in esecuzione da un'immagine. Il risultato di questo comando è un'immagine.
docker pull Scarica un'immagine da un registro nell'archivio immagini del daemon Docker selezionato dal client. I livelli immagine esistenti possono essere riutilizzati. In questo modulo l'ambiente Docker locale usa le immagini dei registri durante lo sviluppo; successivamente, i nodi di AKS estraggono l'immagine dell'applicazione da Registro Azure Container per la distribuzione.
docker run Crea e avvia un nuovo contenitore da un'immagine. Il contenitore esegue il processo di avvio configurato, soggetto a override della riga di comando. I livelli immagine forniscono il file system del contenitore; Le istruzioni di compilazione dockerfile non vengono eseguite di nuovo all'avvio del contenitore.
docker push Registro Azure Container archivia le immagini in modo che siano facilmente disponibili e vicine dal punto di vista della rete per le distribuzioni e la scalabilità di Azure.

Clonare l'applicazione Java

Prima di tutto, clonare il repository Flight Booking System for Airline Reservations (Sistema di prenotazione dei voli per le prenotazioni aeree) e passare alla cartella del progetto dell'applicazione Web Airlines.

Annotazioni

Se la creazione del servizio Azure Kubernetes viene completata nella scheda dell'interfaccia della riga di comando, usare tale scheda. Se è ancora in esecuzione, aprire una nuova scheda e passare alla posizione in cui si preferisce clonare il sistema di prenotazione dei voli per le prenotazioni aeree.

Eseguire i comandi seguenti:

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

Facoltativamente, se è installato Java e Maven, è possibile eseguire il comando seguente nella console del terminale per ottenere un'idea dell'esperienza di compilazione dell'applicazione senza Docker. Se Java e Maven non sono installati, è possibile passare alla sezione successiva Costruire un file Docker. In questa sezione si usa Docker per scaricare Java e Maven per eseguire le compilazioni per conto dell'utente.

mvn clean package

Annotazioni

È stato usato il comando mvn clean package per illustrare le sfide operative del non utilizzare build multistage Docker, che tratteremo di seguito. Anche in questo caso, questo passaggio è facoltativo. In entrambi i casi, è possibile procedere in modo sicuro senza eseguire il comando Maven.

Se il processo ha avuto esito positivo, Maven ha generato correttamente l'artefatto Web Application Archive (WAR) del sistema di prenotazione dei voli per le prenotazioni aeree AirlinesReservationSample-0.0.1-SNAPSHOT.war, come illustrato dal seguente output cronologico. Il timestamp e la durata della compilazione possono variare:

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

Si supponga di essere uno sviluppatore Java e di aver appena creato AirlinesReservationSample-0.0.1-SNAPSHOT.war. Il passaggio successivo consiste probabilmente nell'collaborare con i tecnici delle operazioni per distribuire questo artefatto in un server locale o in una macchina virtuale. Affinché l'applicazione venga avviata ed eseguita correttamente, i server e le macchine virtuali devono essere disponibili e configurati con le dipendenze necessarie. Questo processo è complesso e richiede molto tempo, soprattutto all'occorrenza quando un aumento del carico interessa l'applicazione. Con i contenitori, queste sfide vengono risolte.

Creare un Dockerfile

È ora possibile creare un Dockerfile. Un Dockerfile è un documento di testo che contiene tutti i comandi che un utente eseguirà sulla riga di comando per assemblare un'immagine del container. Un'immagine del contenitore può contenere più livelli di file system di sola lettura. Docker può riutilizzare i risultati della compilazione memorizzati nella cache per i passaggi non modificati. I livelli si basano l'uno sull'altro.

Ad esempio, il sistema di prenotazione dei voli per le prenotazioni aeree deve essere distribuito ed eseguito all'interno di un application server. Un server di applicazioni non è incluso in AirlinesReservationSample-0.0.1-SNAPSHOT.war. Si tratta di una dipendenza esterna necessaria affinché AirlinesReservationSample-0.0.1-SNAPSHOT.war possa funzionare, ascoltare ed elaborare le richieste HTTP, gestire le sessioni utente e facilitare le prenotazioni dei voli. Se si fosse usato un deployment tradizionale, non containerizzato, gli ingegneri operativi avrebbero installato e configurato un server applicativo su un server fisico o una macchina virtuale prima di distribuirvi AirlinesReservationSample-0.0.1-SNAPSHOT.war. Questi ingegneri operativi devono anche assicurarsi che il JDK usato nel computer, ovvero quello mvn clean package usato per compilare il file WAR, corrisponda in effetti allo stesso JRE usato dal server applicazioni. La gestione di queste dipendenze è complessa e richiede molto tempo.

Con un Dockerfile, è possibile scrivere automaticamente le istruzioni o i layer necessari per raggiungere questo obiettivo, inserendo i passaggi necessari per garantire che il sistema di prenotazione dei voli per le prenotazioni aeree abbia tutte le dipendenze necessarie per essere distribuito nel runtime dei container Docker. Questa soluzione è interessante quando si lavora con scalabilità on-demand in momenti non pianificati. Ogni livello usa la cache Docker, che contiene lo stato dell'immagine del contenitore a ogni istruzione chiave del build, ottimizzando il tempo di calcolo e il riutilizzo. Se un livello non cambia, vengono usati livelli memorizzati nella cache. I casi d'uso comuni per i livelli memorizzati nella cache sono il runtime Java, il server applicazioni e 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.

Un'immagine del container contiene livelli di file system di sola lettura. In questo esempio, il Dockerfile copia Tomcat e il file WAR Flight Booking System nell'immagine in fase di compilazione. Docker aggiunge un livello scrivibile separato quando crea un contenitore dall'immagine. Il livello scrivibile di ogni contenitore registra le modifiche del proprio file system senza modificare i livelli dell'immagine.

Annotazioni

Per i file archiviati nel livello scrivibile del contenitore, le aggiunte, le modifiche e le eliminazioni influiscono sul contenitore anziché sull'immagine originale. La modifica dei file provenienti da un livello immagine usa il comportamento di copia su scrittura.

Docker ha il concetto di build multistadio, una funzionalità che consente di creare un'immagine del container più piccola con una memorizzazione nella cache migliore e un footprint di sicurezza inferiore, consentendo un'maggiore ottimizzazione e manutenzione del Dockerfile nel tempo. Ad esempio, è possibile separare la fase di build del container per la compilazione e la creazione dell'applicazione dalla fase per l'esecuzione dell'applicazione. Questa funzionalità consente di copiare solo gli artefatti generati durante la compilazione nell'immagine del contenitore di produzione, riducendo così il footprint. Poiché le immagini del contenitore vengono memorizzate nella cache, se non sono presenti modifiche, le immagini memorizzate nella cache possono essere riutilizzate, riducendo il costo e il tempo di download dalla rete.

I servizi esposti nell'ambiente di produzione devono essere gestiti con attenzione per la sicurezza. Pertanto, l'ambiente di produzione usa ed esegue un'immagine container sicura. L'esempio usa un'immagine Linux Azure fornita da Microsoft.

La fase finale usa Microsoft Build di OpenJDK 17 in Azure Linux 3.0. Il tag 17-azurelinux denomina esplicitamente questa base. Il tag più vecchio 17-mariner rimane un alias di compatibilità che rispecchia l'immagine Azure Linux; non deve essere confuso con le immagini 17-mariner-cm2 e 17-mariner-cm1 ritirate. Per altre informazioni, vedere immagini del contenitore Microsoft Build di OpenJDK.

Annotazioni

Microsoft fornisce immagini del contenitore in bundle con OpenJDK, tra cui Ubuntu, Azure Linux e distroless immagini. L'immagine distroless ha le dimensioni più piccole dell'immagine, ma l'esecuzione di Tomcat su di essa è complessa. Per ottenere una progettazione leggera, l'immagine distroless rimuove molti comandi e strumenti, inclusa la shell, il che significa che non è possibile chiamare catalina.sh per avviare Tomcat. L'immagine distroless è adatta per l'esecuzione di file JAR eseguibili, ad esempio quelli usati con Spring Boot o Quarkus.

Nell'esempio seguente viene usata la stessa versione di Microsoft Build di OpenJDK sia nella fase di compilazione che nella fase finale. Questo approccio garantisce la compilazione del codice sorgente con la stessa versione di JDK usato per la distribuzione del servizio, il che consente di evitare comportamenti imprevisti dovuti a differenze di versione.

L'immagine seguente illustra la compilazione a più fasi e le operazioni eseguite in ogni fase in base ai comandi specificati nel Dockerfile:

Diagramma che mostra le istruzioni dockerfile per la creazione di livelli di immagine nelle fasi di installazione, compilazione del codice e runtime di Tomcat.

Nella fase 0, Tomcat viene scaricato da un archivio Apache specifico della versione, verificato con un checksum SHA-512 ed estratto in una directory specificata da una variabile di ambiente in un'immagine Ubuntu. La variabile TOMCAT_VERSION specifica la release e l'argomento di compilazione TOMCAT_SHA512 specifica il checksum previsto. Aggiornare entrambi i valori insieme quando si seleziona una versione supportata più recente. Docker può riutilizzare il livello di installazione memorizzato nella cache finché le istruzioni e gli input rimangono invariati. L'installazione di Tomcat viene copiata nell'ambiente di fase finale da usare.

Nella fase 1, Maven viene installato in un'immagine Ubuntu e i file di configurazione e il codice sorgente creati vengono copiati prima di compilare il progetto Maven. Ogni livello viene memorizzato nella cache, quindi i livelli immagine del sistema operativo e immagine Maven riutilizzano la cache. Se i file di configurazione, i file del codice sorgente o la directory Web vengono aggiornati, i layer dalle modifiche in poi vengono ricostruiti. Se la compilazione viene completata correttamente senza errori durante la compilazione, viene generato un artefatto denominato AirlinesReservationSample-0.0.1-SNAPSHOT.war nella directory target. Questo artefatto viene copiato nell'ambiente di fase finale da usare.

Nella fase finale, gli elementi di compilazione Tomcat e Java rispettivamente dalla fase 0 e dalla fase 1 vengono copiati in un'immagine Microsoft Build di OpenJDK 17 in Azure Linux 3.0. Un utente denominato app è proprietario di tutti i file usati all'interno del progetto e l'applicazione viene eseguita anche come app utente anziché avere root privilegi. Questa configurazione garantisce che l'immagine del contenitore possa essere gestita in modo sicuro senza concedere autorizzazioni non necessarie. Infine, viene esposto il numero di porta 8080 e lo script catalina.sh viene eseguito per avviare Tomcat. Quando questa operazione viene eseguita nel desktop Docker locale, è possibile accedervi tramite l'URL http://localhost:8080/AirlinesReservationSample.

In containerize-and-deploy-Java-app-to-Azure/Project/Airlines, usare un editor di testo installato per creare un file denominato Dockerfile. Aggiungere il contenuto seguente e salvare il file:

Importante

Prima di creare un'immagine che verrà esposta pubblicamente, controllare la versione di Tomcat 10.1.x supportata corrente nella pagina di download di Apache Tomcat ed esaminare le note sulla sicurezza. Aggiornare TOMCAT_VERSION e TOMCAT_SHA512 insieme utilizzando le informazioni di rilascio pubblicate di Apache. Un artefatto rimanente disponibile nell'archivio Apache non significa che la sua versione sia corrente o consigliata.

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

ENV CATALINA_HOME=/usr/local/tomcat

# Configure the reviewed Tomcat version and checksum
ENV TOMCAT_VERSION=10.1.61
ARG TOMCAT_SHA512=35120a2b1182bac3d55438fbec0dc581d03b0f1c16ce692a930f3bb6a1097cf896b94b389152257071a87cb2928ac8c15f1bad08387c14acc7c17dbc1bb598fb

# Install Tomcat and required packages
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl ca-certificates && \
    curl -fSL \
      "https://archive.apache.org/dist/tomcat/tomcat-10/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz" \
      -o /tmp/tomcat.tar.gz && \
    echo "${TOMCAT_SHA512}  /tmp/tomcat.tar.gz" | sha512sum -c - && \
    mkdir -p "${CATALINA_HOME}" && \
    tar -xzf /tmp/tomcat.tar.gz --strip-components=1 -C "${CATALINA_HOME}" && \
    rm /tmp/tomcat.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-azurelinux

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

Annotazioni

Usare il Dockerfile illustrato in questa unità per questo esercizio. L'applicazione deve essere copiata in webapps/AirlinesReservationSample.war in modo che gli URL nelle unità seguenti usino il /AirlinesReservationSample/ contesto. Un nome file WAR diverso modifica il contesto dell'applicazione Tomcat predefinito.

Il Dockerfile è suddiviso in tre fasi, descritte nelle tabelle seguenti:

  • Fase di installazione di Tomcat:

    Comando Docker Descrizione
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS tomcat imposta l'immagine di base su Microsoft Build di OpenJDK 17 in Ubuntu e denomina questa fase tomcat. Qui è dove viene installato Tomcat.
    ENV ENV CATALINA_HOME=/usr/local/tomcat imposta una variabile di ambiente per la directory di installazione di Tomcat.
    ENV ENV TOMCAT_VERSION=10.1.61 imposta la versione di Tomcat bloccata per questo esempio. Esaminare le versioni supportate e aggiornarla insieme a TOMCAT_SHA512, se necessario.
    ARG ARG TOMCAT_SHA512 fornisce il checksum SHA-512 pubblicato di Apache per l'archivio selezionato come argomento di compilazione.
    RUN Installa curl e i certificati CA, quindi usa curl -fSL per scaricare l'archivio Apache specifico della versione. -f fa fallire in caso di errori HTTP; -L segue i reindirizzamenti. L'archivio viene verificato con sha512sum -c - prima dell'estrazione in CATALINA_HOME con --strip-components=1. La && catena si arresta in caso di errore invece di mascherare gli errori durante la pulizia. Infine, rimuove l'archivio e i pacchetti non necessari e pulisce la cache dei pacchetti.
  • La fase di build, che viene compilata con Java 17:

    Comando Docker Descrizione
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build imposta l'immagine di base su Microsoft Build di OpenJDK 17 in Ubuntu e denomina questa fase build. Questa fase viene usata per compilare l'applicazione Java.
    WORKDIR WORKDIR /build imposta la directory di lavoro all'interno del contenitore su /build, in cui il codice sorgente viene copiato e compilato.
    RUN RUN apt-get update && apt-get install -y maven && mvn --version installa Maven, uno strumento di automazione della compilazione usato per i progetti Java e ne verifica l'installazione.
    COPY COPY pom.xml . copia il file di configurazione Maven nella directory di lavoro. Questo file è essenziale per la compilazione del progetto.
    COPY COPY src ./src copia la directory del codice sorgente nel contenitore. Qui risiede il codice dell'applicazione Java.
    COPY COPY web ./web copia la directory delle risorse Web nel contenitore. Sono incluse le risorse dell'applicazione Web necessarie per la compilazione.
    RUN RUN mvn clean package esegue il processo di build Maven, che compila l'applicazione Java e la impacchetta in un file WAR.
  • Fase finale del pacchetto:

    Comando Docker Descrizione
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-azurelinuximposta l'immagine di base su Microsoft Build di OpenJDK 17 in Azure Linux 3.0, che viene usata per la distribuzione finale dell'applicazione.
    ENV ENV CATALINA_HOME=/usr/local/tomcat imposta la variabile di ambiente per la directory di installazione di Tomcat, come nella fase di installazione.
    ENV ENV PATH=$CATALINA_HOME/bin:$PATH aggiunge la directory bin di Tomcat al PATH di sistema, consentendo l'esecuzione semplice dei comandi Tomcat.
    USER USER app specifica l'utente con cui viene eseguito il processo Tomcat, migliorando la sicurezza non eseguendo come utente radice.
    COPY COPY --chown=app:app --from=tomcat ${CATALINA_HOME} ${CATALINA_HOME} copia l'installazione di Tomcat dalla tomcat fase, assegnando la proprietà all'utente app.
    COPY COPY --chown=app:app tomcat-users.xml ${CATALINA_HOME}/conf copia il file di configurazione dell'utente Tomcat nel contenitore, impostando il proprietario su app.
    COPY COPY --chown=app:app --from=build /build/target/*.war ${CATALINA_HOME}/webapps/AirlinesReservationSample.war copia il file WAR compilato dalla build fase nella directory webapps di Tomcat, impostando la proprietà del file all'utente app.
    EXPOSE EXPOSE 8080 indica che l'applicazione deve restare in ascolto sulla porta TCP 8080. Non pubblica una porta host. L'unità successiva espone la porta con l'opzione -p 8080:8080 su docker run.
    CMD CMD ["catalina.sh", "run"] specifica il comando per avviare Tomcat quando viene eseguito il contenitore.

Per ulteriori informazioni sulla creazione di Dockerfile, vedere il riferimento di Dockerfile.