Esercitazione: Rendere persistenti i dati in un'app contenitore usando volumi in VS Code

In questa esercitazione si apprenderà come rendere persistenti i dati in un'applicazione contenitore. Quando vengono eseguiti o aggiornati, i dati sono ancora disponibili. Esistono due tipi principali di volumi usati per rendere persistenti i dati. Questa esercitazione è incentrata sui volumi denominati.

Verranno inoltre fornite informazioni sui montaggi di associazione, che controllano il punto di montaggio esatto nell'host. È possibile usare i montaggi di associazione per rendere persistenti i dati, ma può anche aggiungere altri dati ai contenitori. Quando si lavora su un'applicazione, è possibile usare un montaggio bind per montare il codice sorgente nel contenitore per visualizzare le modifiche al codice, rispondere e visualizzare immediatamente le modifiche.

Questa esercitazione presenta anche la suddivisione in livelli delle immagini, la memorizzazione nella cache dei livelli e le compilazioni a più fasi.

In questa esercitazione apprenderai a:

  • Informazioni sui dati tra contenitori.
  • Rendere persistenti i dati usando volumi denominati.
  • Usare i montaggi di associazione.
  • Visualizzare il livello immagine.
  • Dipendenze della cache.
  • Informazioni sulle compilazioni in più fasi.

Prerequisiti

Questa esercitazione continua l'esercitazione precedente, Creare e condividere un'app Docker con Visual Studio Code. Iniziare con quello, che include i prerequisiti.

Informazioni sui dati tra contenitori

In questa sezione si inizieranno due contenitori e si creerà un file in ognuno. I file creati in un contenitore non sono disponibili in un altro contenitore.

  1. Avviare un ubuntu contenitore usando questo comando:

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    Questo comando avvia il richiamo di due comandi tramite &&. La prima parte seleziona un singolo numero casuale e la scrive in /data.txt. Il secondo comando sta controllando un file per mantenere il contenitore in esecuzione.

  2. In VS Code, nell'area Docker fare clic con il pulsante destro del mouse sul contenitore ubuntu e scegliere Collega shell.

    Screenshot shows the Docker extension with a container selected and a context menu with Attach Shell selected.

    Viene aperto un terminale che esegue una shell nel contenitore Ubuntu.

  3. Eseguire il comando seguente per visualizzare il contenuto del /data.txt file.

    cat /data.txt
    

    Il terminale mostra un numero compreso tra 1 e 10000.

    Per usare la riga di comando per visualizzare questo risultato, ottenere l'ID contenitore usando il docker ps comando ed eseguire il comando seguente.

    docker exec <container-id> cat /data.txt
    
  4. Avviare un altro ubuntu contenitore.

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. Usare questo comando per esaminare il contenuto della cartella.

    docker run -it ubuntu ls /
    

    Non dovrebbe essere presente alcun data.txt file perché è stato scritto nello spazio scratch solo per il primo contenitore.

  6. Selezionare questi due contenitori Ubuntu. Fare clic con il pulsante destro del mouse e scegliere Rimuovi. Dalla riga di comando è possibile rimuoverli usando il docker rm -f comando .

Rendere persistenti i dati todo usando volumi denominati

Per impostazione predefinita, l'app todo archivia i dati in un database SQLite all'indirizzo /etc/todos/todo.db. Il database SQLite è un database relazionale che archivia i dati di un singolo file. Questo approccio funziona per progetti di piccole dimensioni.

È possibile rendere persistente il singolo file nell'host. Quando lo si rende disponibile per il contenitore successivo, l'applicazione può selezionare la posizione in cui è stata interrotta. Creando un volume e collegandoli o montandoli nella cartella in cui sono archiviati i dati, è possibile rendere persistenti i dati. Il contenitore scrive nel file todo.db e i dati vengono mantenuti nell'host nel volume.

Per questa sezione, usare un volume denominato. Docker mantiene la posizione fisica del volume sul disco. Fare riferimento al nome del volume e Docker fornisce i dati corretti.

  1. Creare un volume usando il docker volume create comando .

    docker volume create todo-db
    
  2. In CONTENITORI selezionare Attività iniziali e fare clic con il pulsante destro del mouse. Selezionare Arresta per arrestare il contenitore dell'app.

    Per arrestare il contenitore dalla riga di comando, usare il docker stop comando .

  3. Avviare il contenitore introduttivo usando il comando seguente.

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    Il parametro volume specifica il volume da montare e la posizione , /etc/todos.

  4. Aggiornare il browser per ricaricare l'app. Se la finestra del browser è stata chiusa, passare a http://localhost:3000/. Aggiungere alcuni elementi all'elenco todo.

    Screenshot shows the sample app with several items added to the list.

  5. Rimuovere il contenitore introduttivo per l'app todo. Fare clic con il pulsante destro del mouse sul contenitore nell'area Docker e scegliere Rimuovi o usare i docker stop comandi e docker rm .

  6. Avviare un nuovo contenitore usando lo stesso comando:

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    Questo comando monta la stessa unità di prima. Aggiorna il browser. Gli elementi aggiunti sono ancora presenti nell'elenco.

  7. Rimuovere di nuovo il contenitore introduttivo .

I volumi denominati e i montaggi di associazione, descritti di seguito, sono i tipi principali di volumi supportati da un'installazione predefinita del motore Docker.

Proprietà Volumi denominati Associa montaggi
Posizione host Docker sceglie Si controlla
Esempio di montaggio (con -v) my-volume:/usr/local/data /path/to/data:/usr/local/data
Popola il nuovo volume con il contenuto del contenitore No
Supporta i driver di volume No

Sono disponibili molti plug-in driver di volume per supportare NFS, SFTP, NetApp e altro ancora. Questi plug-in sono particolarmente importanti per eseguire contenitori in più host in un ambiente cluster, ad esempio Swarm o Kubernetes.

Se ci si chiede dove Docker archivia effettivamente i dati, eseguire il comando seguente.

docker volume inspect todo-db

Esaminare l'output, simile a questo risultato.

[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint è la posizione effettiva in cui vengono archiviati i dati. Nella maggior parte dei computer è necessario l'accesso radice per accedere a questa directory dall'host.

Usare i montaggi di binding

Con i montaggi di associazione, è possibile controllare il punto di montaggio esatto nell'host. Questo approccio rende persistenti i dati, ma viene spesso usato per fornire più dati nei contenitori. È possibile usare un montaggio di associazione per montare il codice sorgente nel contenitore per visualizzare le modifiche al codice, rispondere e visualizzare immediatamente le modifiche.

Per eseguire il contenitore per supportare un flusso di lavoro di sviluppo, seguire questa procedura:

  1. Rimuovere tutti i getting-started contenitori.

  2. app Nella cartella eseguire il comando seguente.

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:20-alpine sh -c "yarn install && yarn run dev"
    

    Questo comando contiene i parametri seguenti.

    • -dp 3000:3000 Uguale a prima. Eseguire in modalità scollegata e creare un mapping delle porte.
    • -w /app Directory di lavoro all'interno del contenitore.
    • -v ${PWD}:/app" Eseguire il montaggio della directory corrente dall'host nel contenitore nella /app directory .
    • node:20-alpine Immagine da usare. Questa immagine è l'immagine di base per l'app dal Dockerfile.
    • sh -c "yarn install && yarn run dev" Comando . Avvia una shell usando sh ed esegue yarn install per installare tutte le dipendenze. Esegue quindi yarn run dev. Se si cerca in package.json, lo dev script sta avviando nodemon.
  3. È possibile controllare i log usando docker logs.

    docker logs -f <container-id>
    
    $ nodemon src/index.js
    [nodemon] 2.0.20
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching path(s): *.*
    [nodemon] watching extensions: js,mjs,json
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    Quando viene visualizzata la voce finale in questo elenco, l'app è in esecuzione.

    Al termine dell'osservazione dei log, selezionare qualsiasi tasto nella finestra del terminale o premere CTRL+C in una finestra esterna.

  4. In VS Code aprire src/static/js/app.js. Modificare il testo del pulsante Aggiungi elemento alla riga 109.

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    Salvare la modifica.

  5. Aggiorna il browser. Verrà visualizzata la modifica.

    Screenshot shows the sample app with the new text on the button.

Visualizzare i livelli immagine

È possibile esaminare i livelli che costituiscono un'immagine. Eseguire il docker image history comando per visualizzare il comando usato per creare ogni livello all'interno di un'immagine.

  1. Usare docker image history per visualizzare i livelli nell'immagine introduttiva creata in precedenza nell'esercitazione.

    docker image history getting-started
    

    Il risultato dovrebbe essere simile a questo output.

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    Ognuna delle linee rappresenta un livello nell'immagine. L'output mostra la base nella parte inferiore con il livello più recente nella parte superiore. Usando queste informazioni, è possibile visualizzare le dimensioni di ogni livello, consentendo di diagnosticare immagini di grandi dimensioni.

  2. Diverse righe vengono troncate. Se si aggiunge il --no-trunc parametro , si otterrà l'output completo.

    docker image history --no-trunc getting-started
    

Dipendenze della cache

Una volta modificato un livello, è necessario ricreare anche tutti i livelli downstream. Ecco di nuovo il Dockerfile :

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Ogni comando nel Dockerfile diventa un nuovo livello nell'immagine. Per ridurre al minimo il numero di livelli, è possibile ristrutturare il Dockerfile per supportare la memorizzazione nella cache delle dipendenze. Per le applicazioni basate su Node, tali dipendenze vengono definite nel package.json file .

L'approccio consiste nel copiare solo il file in primo luogo, installare le dipendenze e quindi copiare tutto il resto. Il processo ricrea le dipendenze yarn solo se è stata apportata una modifica a package.json.

  1. Aggiornare il Dockerfile per copiare le dipendenze nel package.json primo, installare le dipendenze e quindi copiare tutto il resto. Ecco il nuovo file:

    FROM node:20-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. Compilare una nuova immagine usando docker build.

    docker build -t getting-started .
    

    Verrà visualizzato un output simile ai risultati seguenti:

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    Tutti i livelli sono stati ricostruiti. Questo risultato è previsto perché è stato modificato il Dockerfile.

  3. Apportare una modifica al src/static/index.html. Ad esempio, modificare il titolo in modo da indicare "The Awesome Todo App".

  4. Compilare ora l'immagine Docker usando docker build di nuovo. Questa volta, l'output dovrebbe avere un aspetto leggermente diverso.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    Poiché si usa la cache di compilazione, dovrebbe essere molto più veloce.

Compilazioni in più fasi

Le compilazioni a più fasi sono uno strumento incredibilmente potente che consente di usare più fasi per creare un'immagine. Esistono diversi vantaggi:

  • Separare le dipendenze in fase di compilazione dalle dipendenze di runtime
  • Ridurre le dimensioni complessive dell'immagine in base alla spedizione solo di ciò che l'app deve eseguire

In questa sezione vengono forniti brevi esempi.

Esempio di Maven/Tomcat

Quando si compilano applicazioni basate su Java, è necessario un JDK per compilare il codice sorgente in bytecode Java. JDK non è necessario nell'ambiente di produzione. È possibile usare strumenti come Maven o Gradle per facilitare la compilazione dell'app. Questi strumenti non sono necessari anche nell'immagine finale.

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

Questo esempio usa una fase, build, per eseguire la compilazione Java effettiva usando Maven. La seconda fase, a partire da "FROM tomcat", copia nei file dalla build fase. L'immagine finale è solo l'ultima fase da creare, che può essere sottoposta a override usando il --target parametro .

Esempio di React

Quando si compilano applicazioni React, è necessario un ambiente Node per compilare il codice JavaScript, i fogli di stile SASS e altro ancora in HTML statico, JavaScript e CSS. Se non si esegue il rendering sul lato server, non è necessario nemmeno un ambiente Node per la compilazione di produzione.

FROM node:20-alpine AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

In questo esempio viene usata un'immagine node:20 per eseguire la compilazione, che ottimizza la memorizzazione nella cache dei livelli e quindi copia l'output in un contenitore nginx .

Pulire le risorse

Mantenere tutto ciò che è stato fatto finora per continuare questa serie di esercitazioni.

Passaggi successivi

Sono state illustrate le opzioni per rendere persistenti i dati per le app contenitore.

Cosa vuoi fare?