Comment construire une application Node.js avec Docker

introduction

La plateforme Docker permet aux développeurs de créer des packages et d’exécuter des applications en tant que conteneurs. Un conteneur est un processus isolé qui s’exécute sur un système d’exploitation partagé, offrant une alternative plus légère aux machines virtuelles. Bien que les conteneurs ne soient pas nouveaux, ils offrent des avantages, notamment l’isolation des processus et la normalisation de l’environnement, qui prennent de plus en plus d’importance à mesure que de plus en plus de développeurs utilisent des architectures d’applications distribuées.

Lors de la création et de la mise à l’échelle d’une application avec Docker, le point de départ est généralement la création d’une image pour votre application, que vous pouvez ensuite exécuter dans un conteneur. L’image comprend votre code d’application, vos bibliothèques, vos fichiers de configuration, vos variables d’environnement et votre environnement d’exécution. L’utilisation d’une image garantit que l’environnement de votre conteneur est normalisé et ne contient que ce qui est nécessaire pour générer et exécuter votre application.

Dans ce didacticiel, vous allez créer une image d’application pour un site Web statique utilisant le cadre Express et Bootstrap. Vous allez ensuite créer un conteneur à l’aide de cette image et le transmettre à Docker Hub pour une utilisation ultérieure. Enfin, vous extrairez l’image stockée de votre référentiel Docker Hub et construisez un autre conteneur, en vous montrant comment vous pouvez recréer et mettre à l’échelle votre application.

Conditions préalables

Pour suivre ce tutoriel, vous aurez besoin de:

Étape 1 - Installation des dépendances de votre application

Pour créer votre image, vous devez d’abord créer vos fichiers d’application, que vous pouvez ensuite copier dans votre conteneur. Ces fichiers incluront le contenu statique, le code et les dépendances de votre application.

Commencez par créer un répertoire pour votre projet dans le répertoire de base de votre utilisateur non root. Nous appellerons notre ++, mais vous devriez vous sentir libre de le remplacer par autre chose:

mkdir

Naviguez vers ce répertoire:

cd

Ce sera le répertoire racine du projet.

Créez ensuite un fichier https://docs.npmjs.com/files/package.json [+ package.json +]] avec les dépendances de votre projet et d’autres informations d’identification. Ouvrez le fichier avec + nano + ou votre éditeur préféré:

nano package.json

Ajoutez les informations suivantes sur le projet, y compris son nom, son auteur, sa licence, son point d’entrée et ses dépendances. Assurez-vous de remplacer les informations sur l’auteur par votre propre nom et vos coordonnées:

~ / node_project / package.json

{
 "name": "",
 "version": "1.0.0",
 "description": "nodejs image demo",
 "author": "",
 "license": "MIT",
 "main": "app.js",
 "keywords": [
   "nodejs",
   "bootstrap",
   "express"
 ],
 "dependencies": {
   "express": "^4.16.4"
 }
}

Ce fichier comprend le nom du projet, l’auteur et la licence sous laquelle il est partagé. Npm recommends en rendant le nom de votre projet bref et descriptif, et en évitant les doublons dans le registre npm. Nous avons répertorié la licence MIT dans le champ de licence, permettant ainsi l’utilisation et la distribution gratuites du code de l’application.

De plus, le fichier spécifié:

  • " main ": Le point d’entrée de l’application, + app.js. Vous allez créer ce fichier ensuite.

  • " dependencies ": dépendances du projet - dans ce cas, Express 4.16.4 ou supérieur.

Bien que ce fichier ne répertorie pas de référentiel, vous pouvez en ajouter un en suivant ces instructions à l’adresse adding un référentiel à votre fichier + package.json +. C’est un bon ajout si vous utilisez un versioning de votre application.

Enregistrez et fermez le fichier une fois les modifications apportées.

Pour installer les dépendances de votre projet, exécutez la commande suivante:

npm install

Cela installera les paquetages que vous avez listés dans votre fichier + package.json + dans votre répertoire de projet.

Nous pouvons maintenant passer à la construction des fichiers d’application.

Étape 2 - Création des fichiers d’application

Nous allons créer un site Web offrant aux utilisateurs des informations sur les requins. Notre application aura un point d’entrée principal, + app.js, et un répertoire` + views` qui inclura les actifs statiques du projet. La page d’arrivée, + index.html, offrira aux utilisateurs des informations préliminaires et un lien vers une page contenant des informations plus détaillées sur les requins,` + sharks.html`. Dans le répertoire + views, nous allons créer à la fois la page d’arrivée et` + sharks.html`.

Tout d’abord, ouvrez + app.js + dans le répertoire principal du projet pour définir les routes du projet:

nano app.js

La première partie du fichier créera les objets Application Express et Router et définira le répertoire de base et le port sous forme de constantes:

~ / node_project / app.js

const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
const port = 8080;

La fonction + require charge le module` + express + , que nous utilisons ensuite pour créer les objets + app` et + router. L’objet + router + remplira la fonction de routage de l’application, et au fur et à mesure que nous définirons les itinéraires de méthode HTTP, nous les ajouterons à cet objet pour définir la manière dont notre application traitera les requêtes.

Cette section du fichier définit également deux constantes, + path et` + port`:

  • + path +: définit le répertoire de base, qui sera le sous-répertoire + views + dans le répertoire du projet actuel.

  • + port +: indique à l’application d’écouter et de se connecter au port + 8080 +.

Ensuite, définissez les routes pour l’application à l’aide de l’objet + router +:

~ / node_project / app.js

...

router.use(function (req,res,next) {
 console.log('/' + req.method);
 next();
});

router.get('/', function(req,res){
 res.sendFile(path + 'index.html');
});

router.get('/sharks', function(req,res){
 res.sendFile(path + 'sharks.html');
});

La fonction + router.use + charge une fonction middleware qui enregistre les demandes du routeur et les transmet aux itinéraires de l’application. Ceux-ci sont définis dans les fonctions suivantes, qui spécifient qu’une requête GET à l’URL du projet de base doit renvoyer la page + index.html +, tandis qu’une requête GET à la route + / sharks + doit renvoyer + sharks.html + .

Enfin, montez le middleware + routeur + et les actifs statiques de l’application et indiquez à l’application d’écouter sur le port + 8080 +:

~ / node_project / app.js

...

app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
 console.log('Example app listening on port 8080!')
})

Le fichier + app.js fini ressemblera à ceci:

~ / node_project / app.js

const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
const port = 8080;

router.use(function (req,res,next) {
 console.log('/' + req.method);
 next();
});

router.get('/', function(req,res){
 res.sendFile(path + 'index.html');
});

router.get('/sharks', function(req,res){
 res.sendFile(path + 'sharks.html');
});

app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
 console.log('Example app listening on port 8080!')
})

Enregistrez et fermez le fichier lorsque vous avez terminé.

Ensuite, ajoutons du contenu statique à l’application. Commencez par créer le répertoire + views +:

mkdir views

Ouvrez le fichier de page d’arrivée, + index.html:

nano views/index.html

Ajoutez le code suivant au fichier, qui importera Boostrap et créera un composant jumbotron avec un lien vers la page d’informations plus détaillée + sharks.html + :

~ / node_project / views / index.html

<!DOCTYPE html>
<html lang="en">

<head>
   <title>About Sharks</title>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
   <link href="css/styles.css" rel="stylesheet">
   <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
</head>

<body>
   <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md">
       <div class="container">
           <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
           </button> <a class="navbar-brand" href="#">Everything Sharks</a>
           <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
               <ul class="nav navbar-nav mr-auto">
                   <li class="active nav-item"><a href="/" class="nav-link">Home</a>
                   </li>
                   <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>
                   </li>
               </ul>
           </div>
       </div>
   </nav>
   <div class="jumbotron">
       <div class="container">
           <h1>Want to Learn About Sharks?</h1>
           <p>Are you ready to learn about sharks?</p>
           <br>
           <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>
           </p>
       </div>
   </div>
   <div class="container">
       <div class="row">
           <div class="col-lg-6">
               <h3>Not all sharks are alike</h3>
               <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.
               </p>
           </div>
           <div class="col-lg-6">
               <h3>Sharks are ancient</h3>
               <p>There is evidence to suggest that sharks lived up to 400 million years ago.
               </p>
           </div>
       </div>
   </div>
</body>

</html>

Le navbar permet aux utilisateurs de basculer entre les pages * Home * et * Sharks *. Dans le sous-composant + navbar-nav, nous utilisons la classe Bootstrap` + active` pour indiquer la page en cours à l’utilisateur. Nous avons également spécifié les itinéraires vers nos pages statiques, qui correspondent aux itinéraires définis dans + app.js +:

~ / node_project / views / index.html

...
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
  <ul class="nav navbar-nav mr-auto">
     <li class="active nav-item"><a href="/" class="nav-link">Home</a>
     </li>
     <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>
     </li>
  </ul>
</div>
...

De plus, nous avons créé un lien vers notre page d’information sur les requins dans le bouton de notre jumbotron:

~ / node_project / views / index.html

...
<div class="jumbotron">
  <div class="container">
     <h1>Want to Learn About Sharks?</h1>
     <p>Are you ready to learn about sharks?</p>
     <br>
     <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>
     </p>
  </div>
</div>
...

Il existe également un lien vers une feuille de style personnalisée dans l’en-tête:

~ / node_project / views / index.html

...
<link href="css/styles.css" rel="stylesheet">
...

Nous allons créer cette feuille de style à la fin de cette étape.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Avec la page d’arrivée de l’application en place, nous pouvons créer notre page d’informations sur les requins, + sharks.html +, qui offrira aux utilisateurs intéressés davantage d’informations sur les requins.

Ouvrez le fichier:

nano views/sharks.html

Ajoutez le code suivant, qui importe Bootstrap et la feuille de style personnalisée et offre aux utilisateurs des informations détaillées sur certains requins:

~ / node_project / views / sharks.html

<!DOCTYPE html>
<html lang="en">

<head>
   <title>About Sharks</title>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
   <link href="css/styles.css" rel="stylesheet">
   <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
</head>
<nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md">
   <div class="container">
       <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
       </button> <a class="navbar-brand" href="/">Everything Sharks</a>
       <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
           <ul class="nav navbar-nav mr-auto">
               <li class="nav-item"><a href="/" class="nav-link">Home</a>
               </li>
               <li class="active nav-item"><a href="/sharks" class="nav-link">Sharks</a>
               </li>
           </ul>
       </div>
   </div>
</nav>
<div class="jumbotron text-center">
   <h1>Shark Info</h1>
</div>
<div class="container">
   <div class="row">
       <div class="col-lg-6">
           <p>
               <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
               </div>
               <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
           </p>
       </div>
       <div class="col-lg-6">
           <p>
               <div class="caption">Other sharks are known to be friendly and welcoming!</div>
               <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
           </p>
       </div>
   </div>
</div>

</html>

Notez que dans ce fichier, nous utilisons à nouveau la classe + active + pour indiquer la page en cours.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Enfin, créez la feuille de style CSS personnalisée à laquelle vous êtes lié dans + index.html + et + sharks.html + en créant tout d’abord un dossier + css + dans le répertoire + views +:

mkdir views/css

Ouvrez la feuille de style:

nano views/css/styles.css

Ajoutez le code suivant, qui définira la couleur et la police souhaitées pour nos pages:

~ / node_project / views / css / styles.css

.navbar {
   margin-bottom: 0;
}

body {
   background: #020A1B;
   color: #ffffff;
   font-family: 'Merriweather', sans-serif;
}

h1,
h2 {
   font-weight: bold;
}

p {
   font-size: 16px;
   color: #ffffff;
}

.jumbotron {
   background: #0048CD;
   color: white;
   text-align: center;
}

.jumbotron p {
   color: white;
   font-size: 26px;
}

.btn-primary {
   color: #fff;
   text-color: #000000;
   border-color: white;
   margin-bottom: 5px;
}

img,
video,
audio {
   margin-top: 20px;
   max-width: 80%;
}

div.caption: {
   float: left;
   clear: both;
}

En plus de définir la police et la couleur, ce fichier limite également la taille des images en spécifiant un + max-width + de 80%. Cela les empêchera de prendre plus de place que ce que nous aimerions voir sur la page.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Avec les fichiers de l’application en place et les dépendances du projet installées, vous êtes prêt à démarrer l’application.

Si vous avez suivi le didacticiel de configuration initiale du serveur dans les conditions préalables, vous aurez un pare-feu actif n’autorisant que le trafic SSH. Pour autoriser le trafic sur le port + 8080 +, exécutez:

sudo ufw allow 8080

Pour démarrer l’application, assurez-vous d’être dans le répertoire racine de votre projet:

cd ~/

Démarrez l’application avec + node app.js:

node app.js

Accédez à votre navigateur pour + http: //: 8080 +. Vous verrez la page de destination suivante:

image: https: //assets.digitalocean.com/articles/docker_node_image/landing_page.png [Page de destination de l’application]

Cliquez sur le bouton * Get Shark Info *. Vous verrez la page d’informations suivante:

image: https: //assets.digitalocean.com/articles/docker_node_image/sharks.png [Page d’informations sur les requins]

Vous avez maintenant une application en marche. Lorsque vous êtes prêt, quittez le serveur en tapant + CTRL + C +. Nous pouvons maintenant passer à la création du fichier Dockerfile qui nous permettra de recréer et de redimensionner cette application à votre guise.

Étape 3 - Écriture du fichier docker

Votre fichier Docker indique ce qui sera inclus dans votre conteneur d’application lorsqu’il sera exécuté. L’utilisation d’un fichier Dockerfile vous permet de définir votre environnement de conteneur et d’éviter les divergences avec les dépendances ou les versions d’exécution.

En suivant de ces directives sur la construction de conteneurs optimisés, nous allons rendre notre image aussi efficace que possible en réduisant au minimum le nombre de couches d’image et limiter la fonction de l’image à un seul objectif - recréer nos fichiers d’application et notre contenu statique.

Dans le répertoire racine de votre projet, créez le fichier Dockerfile:

nano Dockerfile

Les images Docker sont créées à l’aide d’une succession d’images superposées qui se construisent les unes sur les autres. Notre première étape consistera à ajouter l’image de base de votre application qui constituera le point de départ de la création de l’application.

Utilisons la https://hub.docker.com//node/ [+ node: + image], puisqu’au moment de la rédaction de cette version, il s’agissait de la recommended LTS de Node.js. L’image + alpine + est dérivée du projet Alpine Linux et nous aidera à réduire la taille de notre image. Pour plus d’informations sur le choix de l’image + alpine + pour votre projet, consultez la discussion complète dans la section * Variantes de l’image * du site https://hub.docker.com//node/ [ Page d’image du nœud Docker Hub].

Ajoutez l’instruction + FROM + suivante pour définir l’image de base de l’application:

~ / node_project / Dockerfile

FROM node:

Cette image comprend Node.js et npm. Chaque fichier Docker doit commencer par une instruction + FROM +.

Par défaut, l’image du nœud Docker inclut un utilisateur non-root * node * que vous pouvez utiliser pour éviter d’exécuter votre conteneur d’applications en tant que * root *. Il est recommandé de ne pas exécuter les conteneurs en tant que * root * et de restrict dans le conteneur uniquement à ceux requis pour exécuter ses processus. Nous allons donc utiliser le répertoire de base de l’utilisateur * node * en tant que répertoire de travail pour notre application et les définir en tant qu’utilisateur dans le conteneur. Pour plus d’informations sur les meilleures pratiques lors de l’utilisation de l’image de nœud Docker, voir le présent guide des pratiques https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md.

Pour affiner les autorisations sur le code de notre application dans le conteneur, créons le sous-répertoire + node_modules + dans + / home / node + avec le répertoire + app +. La création de ces répertoires garantira qu’ils disposent des autorisations souhaitées, ce qui sera important lorsque nous créerons des modules de nœud locaux dans le conteneur avec + npm install +. En plus de la création de ces répertoires, nous allons en définir la propriété pour notre utilisateur * node *:

~ / node_project / Dockerfile

...
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

Pour plus d’informations sur l’utilité de la consolidation des instructions `+ RUN + ', consultez la discussion sur la manière de gérer les couches de conteneur.

Ensuite, définissez le répertoire de travail de l’application sur + / home / node / app:

~ / node_project / Dockerfile

...
WORKDIR /home/node/app

Si un paramètre + WORKDIR + n’est pas défini, Docker en créera un par défaut. Il est donc judicieux de le définir explicitement.

Ensuite, copiez les fichiers + package.json et` + package-lock.json` (pour NPM 5+):

~ / node_project / Dockerfile

...
COPY package*.json ./

Ajouter cette instruction + COPY + avant d’exécuter + npm install + ou copier le code de l’application nous permet de tirer parti du mécanisme de mise en cache de Docker. À chaque étape de la construction, Docker vérifiera si une couche est mise en cache pour cette instruction particulière. Si nous changeons + package.json +, cette couche sera reconstruite, mais si nous ne le faisons pas, cette instruction permettra à Docker d’utiliser la couche d’image existante et d’éviter de réinstaller nos modules de noeuds.

Pour vous assurer que tous les fichiers de l’application appartiennent à l’utilisateur * root * non-root, y compris le contenu du répertoire + node_modules +, basculez l’utilisateur sur * node * avant d’exécuter + npm install +:

~ / node_project / Dockerfile

...
USER node

Après avoir copié les dépendances du projet et changé d’utilisateur, nous pouvons exécuter + npm install:

~ / node_project / Dockerfile

...
RUN npm install

Ensuite, copiez le code de votre application avec les autorisations appropriées dans le répertoire de l’application sur le conteneur:

~ / node_project / Dockerfile

...
COPY --chown=node:node . .

Cela garantira que les fichiers de l’application appartiennent à l’utilisateur non-root * node *.

Enfin, exposez le port + 8080 + sur le conteneur et démarrez l’application:

~ / node_project / Dockerfile

...
EXPOSE 8080

CMD [ "node", "app.js" ]

+ EXPOSE + ne publie pas le port, mais sert plutôt à documenter les ports du conteneur qui seront publiés au moment de l’exécution. + CMD + exécute la commande pour démarrer l’application - dans ce cas, +node app.js + ` . Notez qu’il ne doit y avoir qu’une seule instruction `+ CMD +