Professionelle Containerisierung von Node.js-Anwendungen mit Docker

Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, ist ein DevOps-Ingenieur. Er sagt, er muss Docker benutzen. Insbesondere wird diese Containerverwaltungsplattform in verschiedenen Phasen des Lebenszyklus von Node.js-Anwendungen verwendet. Mit Docker, einer Technologie, die in letzter Zeit sehr beliebt war, können Sie den Entwicklungs- und Ausgabeprozess von Node.js Projekten in der Produktion optimieren.

Bild

Wir veröffentlichen jetzt eine Reihe von Artikeln über Docker, die für diejenigen gedacht sind, die diese Plattform für den Einsatz in verschiedenen Situationen erlernen möchten. Das gleiche Material konzentriert sich hauptsächlich auf den professionellen Einsatz von Docker in der Entwicklung von Node.j.

Was ist ein Docker?


Docker ist ein Programm zur Organisation der Virtualisierung auf Betriebssystemebene (Containerisierung). Das Herzstück von Containern sind geschichtete Bilder. Einfach ausgedrückt ist Docker ein Tool, mit dem Sie Anwendungen mithilfe von Containern erstellen, bereitstellen und ausführen können, unabhängig vom Betriebssystem, auf dem sie ausgeführt werden. Der Container enthält ein Image des Basisbetriebssystems, das für das Funktionieren der Anwendung erforderlich ist, die Bibliothek, von der diese Anwendung abhängt, und diese Anwendung selbst. Wenn mehrere Container auf demselben Computer ausgeführt werden, verwenden sie die Ressourcen dieses Computers zusammen. Docker-Container können Projekte packen, die mit einer Vielzahl von Technologien erstellt wurden. Wir sind an Projekten interessiert, die auf Node.js basieren.

Erstellen eines Node.js-Projekts


Bevor wir ein Node.js-Projekt in einen Docker-Container packen, müssen wir dieses Projekt erstellen. Lass es uns tun. Hier ist die Datei package.json dieses Projekts:

 { "name": "node-app", "version": "1.0.0", "description": "The best way to manage your Node app using Docker", "main": "index.js", "scripts": {   "start": "node index.js" }, "author": "Ankit Jain <ankitjain28may77@gmail.com>", "license": "ISC", "dependencies": {   "express": "^4.16.4" } } 

Führen Sie den Befehl npm install um die Projektabhängigkeiten zu npm install . Im Verlauf dieses Befehls wird unter anderem die Datei package-lock.json erstellt. Erstellen Sie nun die Datei index.js , die den Projektcode enthält:

 const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('The best way to manage your Node app using Docker\n'); }); app.listen(3000); console.log('Running on http://localhost:3000'); 

Wie Sie sehen können, haben wir hier einen einfachen Server beschrieben, der als Antwort auf Anforderungen Text zurückgibt.

Erstellen Sie eine Docker-Datei


Nachdem die Anwendung fertig ist, sprechen wir darüber, wie Sie sie in einen Docker-Container packen. Es geht nämlich darum, was der wichtigste Teil eines Docker-basierten Projekts ist, nämlich die Docker-Datei.

Eine Docker-Datei ist eine Textdatei, die Anweisungen zum Erstellen eines Docker-Images für eine Anwendung enthält. Die Anweisungen in dieser Datei beschreiben, wenn sie nicht auf Details eingehen, die Erstellung von Ebenen eines mehrstufigen Dateisystems, das alles enthält, was eine Anwendung zum Arbeiten benötigt. Die Docker-Plattform kann Bildebenen zwischenspeichern, was bei der Wiederverwendung von Ebenen, die sich bereits im Cache befinden, das Erstellen von Bildern beschleunigt.

In der objektorientierten Programmierung gibt es so etwas wie eine Klasse. Klassen werden zum Erstellen von Objekten verwendet. In Docker können Bilder mit Klassen verglichen werden, und Container können mit Instanzen von Bildern verglichen werden, dh mit Objekten. Betrachten Sie den Prozess des Generierens einer Docker-Datei, um dies herauszufinden.

Erstellen Sie eine leere Docker-Datei:

 touch Dockerfile 

Da wir einen Container für die Anwendung Node.js erstellen, müssen wir zunächst das Basisknoten-Image in den Container einfügen, das sich auf dem Docker Hub befindet . Wir werden die LTS-Version von Node.js verwenden. Infolgedessen lautet die erste Anweisung unserer Docker-Datei wie folgt:

 FROM node:8 

Erstellen Sie danach ein Verzeichnis für unseren Code. Gleichzeitig können wir dank der hier verwendeten ARG Anweisung bei Bedarf den Namen des anderen Anwendungsverzeichnisses als /app während der Montage des Containers angeben. Details zu diesem Handbuch finden Sie hier .

 #   ARG APP_DIR=app RUN mkdir -p ${APP_DIR} WORKDIR ${APP_DIR} 

Da wir das Node-Image verwenden, sind die Plattformen Node.js und npm bereits darin installiert. Mit dem, was bereits im Image enthalten ist, können Sie die Installation von Projektabhängigkeiten organisieren. Wenn Sie das Flag --production verwenden (oder wenn die Umgebungsvariable NODE_ENV auf production ), installiert npm die im Abschnitt devDependencies Datei devDependencies aufgeführten Module nicht.

 #   COPY package*.json ./ RUN npm install #     # RUN npm install --production 

Hier kopieren wir die package*.json Datei in das Image, anstatt beispielsweise alle Projektdateien zu kopieren. Wir tun genau das, weil die Dockerfile-Anweisungen RUN , COPY und ADD zusätzliche Bildebenen erstellen, sodass Sie die Caching-Funktionen der Docker-Plattformebenen verwenden können. Mit diesem Ansatz wird Docker beim nächsten Sammeln eines ähnlichen Bilds herausfinden, ob es möglich ist, Bildebenen, die sich bereits im Cache befinden, wiederzuverwenden. In diesem Fall wird das bereits vorhandene Bild ausgenutzt, anstatt neue zu erstellen Schichten. Auf diese Weise können Sie beim Zusammenstellen von Ebenen bei der Arbeit an großen Projekten, die viele npm-Module enthalten, erheblich Zeit sparen.

Kopieren Sie nun die Projektdateien in das aktuelle Arbeitsverzeichnis. Hier verwenden wir nicht die Anweisung ADD , sondern die Anweisung COPY . In den meisten Fällen wird empfohlen, die COPY Anweisung zu bevorzugen.

Der ADD Befehl weist im Vergleich zu COPY einige Funktionen auf, die jedoch nicht immer benötigt werden. Zum Beispiel sprechen wir über Optionen zum Entpacken von .tar-Archiven und zum Herunterladen von Dateien per URL.

 #    COPY . . 

Docker-Container sind isolierte Umgebungen. Dies bedeutet, dass wir beim Starten der Anwendung im Container nicht direkt mit ihr interagieren können, ohne den Port zu öffnen, den diese Anwendung überwacht. Um Docker darüber zu informieren, dass sich in einem bestimmten Container eine Anwendung befindet, die einen bestimmten Port überwacht , können Sie die Anweisung EXPOSE verwenden .

 #   ,      EXPOSE 3000 

Bisher haben wir mithilfe der Docker-Datei das Image beschrieben, das die Anwendung enthalten wird, sowie alles, was zum erfolgreichen Start erforderlich ist. Fügen Sie nun der Datei die Anweisung hinzu, mit der Sie die Anwendung starten können. Dies ist eine CMD- Anweisung. Hier können Sie einen bestimmten Befehl mit Parametern angeben, die beim Start des Containers ausgeführt werden und bei Bedarf von der Befehlszeile überschrieben werden können.

 #   CMD ["npm", "start"] 

So sieht das fertige Dockerfile aus:

 FROM node:8 #   ARG APP_DIR=app RUN mkdir -p ${APP_DIR} WORKDIR ${APP_DIR} #   COPY package*.json ./ RUN npm install #     # RUN npm install --production #    COPY . . #   ,      EXPOSE 3000 #   CMD ["npm", "start"] 

Bildassemblierung


Wir haben eine Dockerfile-Datei vorbereitet, die Anweisungen zum Erstellen des Images enthält, auf deren Grundlage ein Container mit einer laufenden Anwendung erstellt wird. Stellen Sie das Bild zusammen, indem Sie einen Befehl der folgenden Form ausführen:

 docker build --build-arg <build arguments> -t <user-name>/<image-name>:<tag-name> /path/to/Dockerfile 

In unserem Fall sieht es so aus:

 docker build --build-arg APP_DIR=var/app -t ankitjain28may/node-app:V1 . 

Dockerfile verfügt über eine ARG Anweisung, die das Argument APP_DIR . Hier setzen wir seine Bedeutung. Wenn dies nicht getan wird, nimmt es den Wert an, der ihm in der Datei zugewiesen ist, app - app .

Überprüfen Sie nach dem Zusammenstellen des Bildes, ob Docker es sieht. Führen Sie dazu den folgenden Befehl aus:

 docker images 

Als Antwort auf diesen Befehl sollte ungefähr Folgendes ausgegeben werden.


Docker-Bilder

Bildstart


Nachdem wir das Docker-Image zusammengestellt haben, können wir es ausführen, dh eine Instanz davon erstellen, die durch einen Arbeitscontainer dargestellt wird. Verwenden Sie dazu einen Befehl dieser Art:

 docker run -p <External-port:exposed-port> -d --name <name of the container> <user-name>/<image-name>:<tag-name> 

In unserem Fall sieht es so aus:

 docker run -p 8000:3000 -d --name node-app ankitjain28may/node-app:V1 

Mit diesem Befehl werden wir das System um Informationen zu Arbeitscontainern bitten:

 docker ps 

Als Reaktion darauf sollte das System Folgendes ausgeben:


Docker-Container

Bisher läuft alles wie erwartet, obwohl wir noch nicht versucht haben, auf die im Container ausgeführte Anwendung zuzugreifen. Unser Container mit dem Namen node-app überwacht nämlich Port 8000 . Um zu versuchen, darauf zuzugreifen, können Sie einen Browser öffnen und unter localhost:8000 darauf zugreifen. Um den Zustand des Containers zu überprüfen, können Sie außerdem den folgenden Befehl verwenden:

 curl -i localhost:8000 

Wenn der Container wirklich funktioniert, wird als Antwort auf diesen Befehl etwas wie das in der folgenden Abbildung gezeigte zurückgegeben.


Ergebnis der Container-Integritätsprüfung

Auf der Grundlage des gleichen Bildes, beispielsweise auf der Grundlage der gerade erstellten, können viele Container erstellt werden. Darüber hinaus können Sie unser Image an die Docker Hub-Registrierung senden, damit andere Entwickler unser Image hochladen und die entsprechenden Container zu Hause starten können. Dieser Ansatz vereinfacht die Arbeit mit Projekten.

Empfehlungen


Hier sind einige Vorschläge, die Sie berücksichtigen sollten, um die Leistung von Docker zu nutzen und so kompakte Bilder wie möglich zu erstellen.

▍1. Erstellen Sie immer eine .dockerignore-Datei


In dem Projektordner, den Sie in den Container .dockerignore , müssen Sie immer eine .dockerignore Datei erstellen. Sie können Dateien und Ordner ignorieren, die beim Erstellen des Bildes nicht benötigt werden. Mit diesem Ansatz können wir den sogenannten Build-Kontext reduzieren, wodurch wir das Image schnell zusammenstellen und seine Größe reduzieren können. Diese Datei unterstützt Dateinamenvorlagen. In dieser Datei ähnelt sie einer .gitignore Datei. Es wird empfohlen, einen Befehl zu .dockerignore hinzuzufügen, aufgrund dessen Docker den Ordner /.git ignoriert, da dieser Ordner normalerweise große Materialien enthält (insbesondere während der Entwicklung eines Projekts) und das Hinzufügen zum Bild zu einer Vergrößerung führt. Darüber hinaus ist das Kopieren dieses Ordners in ein Bild wenig sinnvoll.

▍2. Verwenden Sie den mehrstufigen Bildassemblierungsprozess


Betrachten Sie das Beispiel, wenn wir ein Projekt für eine bestimmte Organisation sammeln. Dieses Projekt verwendet viele npm-Pakete, und jedes dieser Pakete kann zusätzliche Pakete installieren, von denen es abhängt. Das Ausführen all dieser Vorgänge führt zu zusätzlichem Zeitaufwand für das Zusammenstellen des Images (obwohl dies dank der Caching-Funktionen von Docker keine so große Sache ist). Schlimmer noch, das resultierende Bild, das die Abhängigkeiten eines bestimmten Projekts enthält, ist ziemlich groß. Wenn wir hier über Front-End-Projekte sprechen, können wir uns daran erinnern, dass solche Projekte normalerweise mit Bundlern wie Webpack verarbeitet werden, die es ermöglichen, alles, was eine Anwendung benötigt, bequem in ein Verkaufspaket zu packen. Daher sind npm-Paketdateien für ein solches Projekt nicht erforderlich. Dies bedeutet, dass wir solche Dateien entfernen können, nachdem wir das Projekt mit demselben Webpack erstellt haben.

Versuchen Sie mit dieser Idee Folgendes zu tun:

 #   COPY package*.json ./ RUN npm install --production # - COPY . . RUN npm run build:production #    npm- RUN rm -rf node_modules 

Ein solcher Ansatz wird uns jedoch nicht passen. Wie bereits erwähnt, erstellen die Anweisungen RUN , ADD und COPY von Docker zwischengespeicherte Ebenen. Daher müssen wir einen Weg finden, um die Installation von Abhängigkeiten zu handhaben, das Projekt zu erstellen und dann unnötige Dateien mit einem einzigen Befehl zu löschen. Zum Beispiel könnte es so aussehen:

 #      COPY . . #  ,      RUN npm install --production && npm run build:production && rm -rf node_module 

In diesem Beispiel gibt es nur eine RUN Anweisung, die die Abhängigkeiten installiert, node_modules Projekt erstellt und den Ordner node_modules löscht. Dies führt dazu, dass die Größe des Bildes nicht so groß ist wie die Größe des Bildes, das den Ordner node_modules . Wir verwenden die Dateien aus diesem Ordner nur während des Erstellungsprozesses des Projekts und löschen sie dann. Dieser Ansatz ist insofern schlecht, als die Installation von npm-Abhängigkeiten viel Zeit in Anspruch nimmt. Sie können diesen Nachteil mithilfe der Technologie der mehrstufigen Montage von Bildern beseitigen.

Stellen Sie sich vor, wir arbeiten an einem Frontend-Projekt mit vielen Abhängigkeiten und verwenden Webpack, um dieses Projekt zu erstellen. Mit diesem Ansatz können wir zur Reduzierung der Bildgröße die Funktionen von Docker für die mehrstufige Montage von Bildern nutzen .

 FROM node:8 As build #  RUN mkdir /app && mkdir /src WORKDIR /src #   COPY package*.json ./ RUN npm install #     # RUN npm install --production #       COPY . . RUN npm run build:production #    ,     FROM node:alpine #      build   app COPY --from=build ./src/build/* /app/ ENTRYPOINT ["/app"] CMD ["--help"] 

Bei diesem Ansatz ist das resultierende Bild viel kleiner als das vorherige Bild, und wir verwenden auch das node:alpine Bild, das selbst sehr klein ist. Und hier ist ein Vergleich eines Bildpaares, bei dem zu sehen ist, dass das Bild von node:alpine viel kleiner ist als das Bild von node:8 .


Vergleichen von Bildern aus dem Node-Repository

▍3. Verwenden Sie den Docker-Cache


Versuchen Sie, die Caching-Funktionen von Docker zum Erstellen Ihrer Bilder zu verwenden. Wir haben diese Funktion bereits bei der Arbeit mit einer Datei beachtet, auf die über das Namenspaket package*.json . Dies reduziert die Erstellungszeit des Bildes. Diese Gelegenheit sollte jedoch nicht vorschnell genutzt werden.

Angenommen, wir beschreiben in Dockerfile die Installation von Paketen in einem Image, das aus dem Basis- Ubuntu:16.04 Image erstellt wurde Ubuntu:16.04 :

 FROM ubuntu:16.04 RUN apt-get update && apt-get install -y \   curl \   package-1 \   .   . 

Wenn das System diese Datei verarbeitet und viele Pakete installiert sind, nehmen die Aktualisierungs- und Installationsvorgänge viel Zeit in Anspruch. Um die Situation zu verbessern, haben wir uns entschlossen, die Layer-Caching-Funktionen von Docker zu nutzen und die Docker-Datei wie folgt neu zu schreiben:

 FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y \   curl \   package-1 \   .   . 

Wenn Sie das Bild jetzt zum ersten Mal zusammenstellen, läuft alles so, wie es sollte, da der Cache noch nicht gebildet wurde. Stellen Sie sich jetzt vor, wir müssen ein anderes Paket installieren, package-2 . Dazu schreiben wir die Datei neu:

 FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y \   curl \   package-1 \   package-2 \   .   . 

Aufgrund eines solchen Befehls wird package-2 nicht installiert oder aktualisiert. Warum? Tatsache ist, dass Docker beim Ausführen der RUN apt-get update Anweisung RUN apt-get update keinen Unterschied zwischen dieser Anweisung und der zuvor ausgeführten Anweisung sieht, sodass Daten aus dem Cache entnommen werden. Und diese Daten sind bereits veraltet. Bei der Verarbeitung der RUN apt-get install Anweisung RUN apt-get install System sie aus, da sie nicht wie eine ähnliche Anweisung in der vorherigen Docker-Datei aussieht. Während der Installation können jedoch Fehler auftreten oder die alte Version von Paketen wird installiert. Infolgedessen stellt sich heraus, dass die update und install innerhalb derselben RUN Anweisung ausgeführt werden müssen, wie dies im ersten Beispiel der Fall ist. Caching ist eine großartige Funktion, aber die rücksichtslose Verwendung dieser Funktion kann zu Problemen führen.

▍4. Minimieren Sie die Anzahl der Bildebenen


Es wird empfohlen, nach Möglichkeit zu versuchen, die Anzahl der Bildebenen zu minimieren, da jede Ebene das Dateisystem des Docker-Bildes ist. Je kleiner das Ebenenbild, desto kompakter wird es. Bei Verwendung des mehrstufigen Prozesses zum Zusammensetzen von Bildern wird eine Verringerung der Anzahl von Schichten im Bild und eine Verringerung der Größe des Bildes erreicht.

Zusammenfassung


In diesem Artikel haben wir uns mit dem Verpacken von Node.js-Anwendungen in Docker-Containern und dem Arbeiten mit solchen Containern befasst. Darüber hinaus haben wir einige Empfehlungen abgegeben, die übrigens nicht nur beim Erstellen von Containern für Node.js-Projekte verwendet werden können.

Liebe Leser! Wenn Sie Docker bei der Arbeit mit Node.js-Projekten professionell verwenden, teilen Sie Anfängern Empfehlungen zur effektiven Verwendung dieses Systems mit.

Source: https://habr.com/ru/post/de440656/


All Articles