Docker è una piattaforma di containerizzazione che semplifica il confezionamento e l'esecuzione delle applicazioni. I container vengono eseguiti come processi isolati con il proprio filesystem ma condividono il kernel del proprio host. Docker è diventato famoso come metodo per implementare ambienti di sviluppo riproducibili e architetture di distribuzione distribuite.
Node.js è il principale runtime JavaScript per lo sviluppo di back-end. Per avviare correttamente un servizio Web Node.js è necessario disporre di un ambiente con il runtime installato, il codice dell'applicazione disponibile e un meccanismo che gestisca i riavvii automatici in caso di arresto anomalo.
In questa guida utilizzeremo Docker per containerizzare una semplice app Node.js creata con il popolare framework Web Express. Docker è un buon modo per distribuire i sistemi basati su nodi in quanto produce un ambiente coerente che include tutto il necessario per eseguire il servizio. Il demone Docker ha integrato il supporto per il riavvio dei container non riusciti quando il loro processo in primo piano si arresta in modo anomalo, risolvendo una delle sfide delle distribuzioni di Node.js.
Creazione del tuo progetto nodo
Salteremo i dettagli dell'implementazione della tua applicazione. Crea una directory per il tuo progetto e aggiungi del codice server al suo interno. Ecco un app.js
di base che ascolta sulla porta 8080 e risponde a ogni richiesta con una risposta codificata:
const express = require("express"); const app = express(); app.get("*", (req, res) => res.send("<p>It works!</p>")); app.listen(8080, () => console.log("Listening on 8080"));
Aggiungi Express al tuo progetto usando npm:
npm init npm install --save express
Avvia la tua app per verificarne il funzionamento:
node app.js
Dovresti essere in grado di visitare localhost:8080
nel tuo browser per vedere la risposta di esempio.
Scrivere un Dockerfile
Ora è il momento di iniziare a Dockerizzare il tuo progetto. Per prima cosa hai bisogno di un'immagine per la tua applicazione. Le immagini incapsulano il codice e le dipendenze come un unico pacchetto che usi per avviare le istanze del contenitore. Le istruzioni nel tuo Dockerfile definiscono lo stato del filesystem iniziale dei tuoi contenitori.
Ecco un Dockerfile che funziona per l'applicazione di esempio:
FROM node:16 WORKDIR /app COPY package.json . COPY package-lock.json . RUN npm ci COPY app.js . CMD ["app.js"]
Questo Dockerfile seleziona l'immagine Docker ufficiale di Node.js come base tramite il FROM
dichiarazione. L'immagine eredita tutto nella base, quindi aggiunge contenuto aggiuntivo tramite le seguenti istruzioni.
La directory di lavoro è impostata su /app
dal WORKDIR
linea. Il seguente COPY
gli estratti conto depositeranno i file nel /app
directory all'interno dell'immagine del contenitore.
Installazione delle dipendenze
La fase successiva consiste nell'aggiungere package.json
di npm ed esegui npm ci
. Questo installerà le dipendenze npm del tuo progetto, in questo caso Express, all'interno del filesystem del contenitore.
Non utilizzare COPY node_modules/ .
per copiare i node_modules
esistenti cartella nella directory del tuo progetto:ciò ti impedirebbe di riutilizzare Dockerfile in altri ambienti di build. Dockerfiles dovrebbe consentirti di creare build coerenti solo con il contenuto del tuo repository di controllo del codice sorgente. Se un file o una cartella è nel tuo .gitignore
, non dovrebbe essere referenziato in un Dockerfile COPY
istruzione.
Copia del codice dell'applicazione
Dopo npm ci
è stato eseguito, il codice dell'app viene copiato nell'immagine. Il posizionamento di questo COPY
dopo l'istruzione RUN
, separandolo dalle copie precedenti, è deliberato. Ogni istruzione crea un nuovo livello nella tua immagine; Il processo di build di Docker memorizza nella cache ogni livello per accelerare le build successive. Una volta modificato il contenuto di un livello, la cache di tutti i livelli successivi verrà invalidata.
Questo è il motivo per cui il codice dell'applicazione deve essere copiato dopo npm ci
è stato eseguito. Il codice di solito cambia molto più frequentemente rispetto al contenuto del file di blocco npm. Le ricostruzioni di immagini che implicano solo modifiche al codice salteranno effettivamente RUN npm ci
fase (e tutte le fasi precedenti), accelerando drasticamente il processo quando hai molte dipendenze.
Impostazione del comando dell'immagine
La fase finale di Dockerfile utilizza il CMD
istruzioni per eseguire automaticamente l'app all'avvio del contenitore. Funziona perché l'immagine di base di Node.js è configurata per utilizzare il node
processo come suo punto di ingresso. Il CMD
viene aggiunto all'entrypoint ereditato, risultando in node app.js
in esecuzione come processo in primo piano per la tua nuova immagine.
Costruire la tua immagine
Quindi devi costruire la tua immagine:
docker build -t node-app:latest .
Docker prenderà il Dockerfile
nella tua directory di lavoro, esegui le istruzioni al suo interno e tagga l'immagine risultante come node-app:latest
. Il .
(punto) specifica la tua directory di lavoro come contesto di creazione dell'immagine. Questo determina i percorsi che possono essere referenziati da COPY
istruzioni nel tuo Dockerfile.
Ottimizzazione build
Un modo per migliorare le prestazioni di compilazione consiste nell'aggiungere un .dockerignore
file nella radice del tuo progetto. Assegna al file il seguente contenuto:
node_modules/
Questo file definisce i percorsi nella tua directory di lavoro che non essere inclusi nel contesto di costruzione. Non sarai in grado di fare riferimento a loro all'interno del tuo Dockerfile. Nel caso di node_modules
, il contenuto di questa directory è irrilevante per la build poiché stiamo installando nuovamente le dipendenze tramite RUN npm ci
istruzione. Escludendo in particolare i node_modules
già presente nella tua directory di lavoro evita di dover copiare tutti quei file nella posizione del contesto di build temporaneo di Docker. Ciò aumenta l'efficienza e riduce il tempo impiegato per preparare la build.
Avvio di un container
A questo punto sei pronto per eseguire la tua applicazione utilizzando Docker:
docker run -d -p 8080:8080 --name my-app --restart on-failure node-app:latest
La docker run
Il comando viene utilizzato per avviare una nuova istanza del contenitore da un'immagine specificata. Vengono aggiunti alcuni flag aggiuntivi per configurare correttamente il contenitore per il caso d'uso previsto:
-d
– Stacca la tua shell dal processo in primo piano del contenitore, eseguendolo efficacemente come server in background.-p
– Associa la porta 8080 sull'host alla porta 8080 all'interno del contenitore (su cui è stata configurata l'app di esempio Express per l'ascolto). Ciò significa traffico versolocalhost:8080
passerà al porto container corrispondente. Puoi modificare il post host con un valore diverso modificando la prima parte della definizione di rilegatura, ad esempio8100:8080
per accedere al tuo container sulocalhost:8100
.--name
– Assegna al contenitore un nome descrittivo che puoi utilizzare per fare riferimento ad altri comandi Docker CLI.--restart
– Seleziona il criterio di riavvio da applicare al contenitore. Ilon-failure
l'impostazione significa che Docker riavvierà automaticamente il contenitore se esce con un codice di errore perché l'applicazione si è arrestata in modo anomalo.
L'immagine creata nel passaggio precedente viene referenziata come argomento finale per docker run
comando. L'ID del contenitore verrà emesso nella finestra del tuo terminale; dovresti essere in grado di accedere alla tua app Node.js visitando localhost:8080
ancora. Questa volta il server è in esecuzione all'interno del contenitore Docker, invece di utilizzare il node
processo installato sul tuo host.
Riepilogo
Docker ti aiuta a distribuire i servizi Web Node.js containerizzando l'intero ambiente dell'applicazione. Puoi avviare un contenitore dalla tua immagine con una singola docker run
comando su qualsiasi host con Docker installato. Ciò elimina la complessità della manutenzione delle versioni di Node.js, dell'installazione di moduli npm e del monitoraggio delle situazioni in cui è necessario riavviare il processo dell'applicazione.
Quando hai apportato modifiche al codice e desideri avviare l'aggiornamento, ricostruisci l'immagine Docker e rimuovi il vecchio contenitore con docker rm <container-name>
. È quindi possibile avviare un'istanza sostitutiva che utilizzi l'immagine modificata.
Potresti volere una routine leggermente diversa nella produzione. Sebbene tu possa utilizzare una normale installazione Docker con docker run
, questo tende a essere ingombrante per tutte le applicazioni tranne le più semplici. È più comune utilizzare uno strumento come Docker Compose o Kubernetes per definire la configurazione del contenitore in un file di cui è possibile eseguire la versione all'interno del repository.
Questi meccanismi eliminano la necessità di ripetere la tua docker run
flag ogni volta che si avvia un nuovo contenitore. Facilitano inoltre la replica dei container per scalare il servizio e fornire ridondanza. Se stai eseguendo la distribuzione su un host remoto, dovrai anche inviare la tua immagine a un registro Docker in modo che possa essere "estratta" dal tuo computer di produzione.
Un'altra considerazione specifica per la produzione riguarda il modo in cui indirizzerai il traffico ai tuoi container. I port binding possono essere sufficienti per cominciare, ma alla fine raggiungerai una situazione in cui desideri più container su un host, ciascuno in ascolto sulla stessa porta. In questo caso puoi distribuire un proxy inverso per instradare il traffico alle singole porte del container in base alle caratteristiche della richiesta come il nome di dominio e le intestazioni.