Comment créer et déployer une application Node.js sur DigitalOcean Kubernetes à l’aide de l’intégration et de la fourniture continues de sémaphores

L’auteur a sélectionné le fonds Open Internet / Free Speech pour recevoir un don dans le cadre du https://do.co/w4do-cta Programme [Write for DOnations] .

introduction

https://kubernetes.io [Kubernetes] permet aux utilisateurs de créer des services résilients et évolutifs avec une seule commande. Comme tout ce qui semble trop beau pour être vrai, cela a un inconvénient: vous devez d’abord préparer une image https://www.docker.com [Docker] appropriée et la tester à fond.

Continuous Integration (CI) consiste à tester l’application à chaque mise à jour. Effectuer cette opération manuellement est fastidieux et source d’erreurs, mais une plate-forme CI exécute les tests pour vous, détecte les erreurs plus tôt et localise le point où les erreurs ont été introduites. Les procédures de publication et de déploiement sont souvent compliquées, prennent du temps et nécessitent un environnement de construction fiable. Avec Continuous Delivery (CD), vous pouvez créer et déployer votre application sur chaque mise à jour sans intervention humaine. .

Pour automatiser l’ensemble du processus, vous utiliserez https://semaphoreci.com [Semaphore], une plate-forme d’intégration et de distribution continues (CI / CD).

Dans ce didacticiel, vous allez créer un service API de carnet d’adresses avec https://nodejs.org [Node.js]. L’API présente une interface RESTful simple pour créer, supprimer et rechercher des personnes dans la base de données. Vous utiliserez https://git-scm.com [Git] pour transmettre le code à https://github.com [GitHub]. Vous utiliserez ensuite Semaphore pour tester l’application, créer une image Docker et la déployer sur un cluster DigitalOcean Kubernetes. Pour la base de données, vous allez créer un cluster PostgreSQL à l’aide de DigitalOcean Managed Databases.

Conditions préalables

Avant de continuer, assurez-vous de disposer des éléments suivants:

  • Un compte DigitalOcean et un jeton d’accès personnel. Suivez Create un jeton d’accès personnel pour en créer un pour votre compte.

  • Un compte Docker Hub.

  • Un compte https://github.com [GitHub].

  • Un compte https://semaphoreci.com [Semaphore]; vous pouvez vous inscrire avec votre compte GitHub.

  • Un nouveau référentiel GitHub appelé + addressbook + pour le projet. Lors de la création du référentiel, cochez la case * Initialiser ce référentiel avec une LISEZMOI * et sélectionnez * Nœud * dans le menu * Ajouter .gitignore *. Suivez la page d’aide Create a Repo de GitHub pour plus de détails.

  • https://git-scm.com [Git] installé sur votre ordinateur local et set up pour fonctionner avec votre compte GitHub. Si vous n’êtes pas familier ou si vous avez besoin d’un rappel, lisez le guide de référence Comment utiliser Git.

  • curl installé sur votre ordinateur local.

  • https://nodejs.org [Node.js] installé sur votre ordinateur local. Dans ce didacticiel, vous utiliserez la version Node.js + 10.16.0 +.

Étape 1 - Création de la base de données et du cluster Kubernetes

Commencez par configurer les services qui alimenteront l’application: le cluster de base de données DigitalOcean et le cluster Kubernetes DigitalOcean.

Connectez-vous à votre compte DigitalOcean et à create un projet. Un projet vous permet d’organiser toutes les ressources qui composent l’application. Appelez le projet + addressbook +.

Créez ensuite un cluster PostgreSQL. Le service de base de données PostgreSQL contiendra les données de l’application. Vous pouvez choisir la dernière version disponible. Cela devrait prendre quelques minutes avant que le service soit prêt.

Une fois que le service PostgreSQL est prêt, create une base de données et un utilisateur. Définissez le nom de la base de données sur + addessbook_db + et définissez le nom d’utilisateur sur + addressbook_user +. Prenez note du mot de passe généré pour votre nouvel utilisateur. Les bases de données sont la manière dont PostgreSQL organise les données. Habituellement, chaque application a sa propre base de données, bien qu’il n’y ait pas de règles strictes à ce sujet. L’application utilisera le nom d’utilisateur et le mot de passe pour accéder à la base de données afin de pouvoir enregistrer et récupérer ses données.

Enfin, créez un cluster Kubernetes. Choisissez la même région dans laquelle la base de données est en cours d’exécution. Nommez le cluster + address book-server et définissez le nombre de nœuds sur` + 3 + `.

Pendant que les noeuds approvisionnent, vous pouvez commencer à créer votre application.

Étape 2 - Écriture de l’application

Construisons l’application du carnet d’adresses que vous allez déployer. Pour commencer, clonez le référentiel GitHub que vous avez créé dans les conditions préalables pour disposer d’une copie locale du fichier + .gitignore + créé par GitHub, et vous pourrez ainsi valider rapidement le code de votre application sans avoir à créer manuellement un référentiel. . Ouvrez votre navigateur et accédez à votre nouveau référentiel GitHub. Cliquez sur le bouton * Cloner ou télécharger * et copiez l’URL fournie. Utilisez Git pour cloner le référentiel vide sur votre machine:

git clone https://github.com//addressbook

Entrez le répertoire du projet:

cd addressbook

Avec le référentiel cloné, vous pouvez commencer à écrire l’application. Vous construirez deux composants: un module qui interagit avec la base de données et un module qui fournit le service HTTP. Le module de base de données saura comment enregistrer et récupérer des personnes dans la base de données du carnet d’adresses, et le module HTTP recevra les demandes et y répondra en conséquence.

Bien que cela ne soit pas strictement obligatoire, il est recommandé de tester votre code pendant que vous l’écrivez. Vous créerez donc également un module de test. Voici la disposition prévue pour l’application:

  • + database.js +: module de base de données. Il gère les opérations de base de données.

  • + app.js +: le module utilisateur final et l’application principale. Il fournit un service HTTP pour que les utilisateurs se connectent.

  • + database.test.js +: teste le module de base de données.

En outre, vous souhaiterez un fichier package.json, qui décrit le projet et ses dépendances requises. Vous pouvez le créer manuellement avec votre éditeur ou de manière interactive en utilisant npm. Exécutez la commande + npm init + pour créer le fichier de manière interactive:

npm init

La commande vous demandera des informations pour commencer. Remplissez les valeurs comme indiqué dans l’exemple. Si vous ne voyez pas de réponse répertoriée, laissez la réponse en blanc, qui utilise la valeur par défaut entre parenthèses:

npm outputpackage name: (addressbook)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: "
license: (ISC)
About to write to package.json:

{
 "name": "addressbook",
 "version": "1.0.0",
 "description": "Addressbook API and database",
 "main": "app.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC"
}


Is this OK? (yes)

Vous pouvez maintenant commencer à écrire le code. La base de données est au cœur du service que vous développez. Il est essentiel d’avoir un modèle de base de données bien conçu avant d’écrire d’autres composants. Par conséquent, il est logique de commencer par le code de la base de données.

Vous n’êtes pas obligé de coder tous les bits de l’application; Node.js a une grande bibliothèque de modules réutilisables. Par exemple, vous n’avez pas à écrire de requête SQL si vous avez le module Sequelize ORM dans le projet. Ce module fournit une interface qui gère les bases de données en tant qu’objets et méthodes JavaScript. Il peut également créer des tables dans votre base de données. Sequelize a besoin du module pg pour fonctionner avec PostgreSQL.

Installez les modules en utilisant la commande + npm install avec l’option` + - save + , qui indique + npm + de sauvegarder le module dans + package.json`. Exécutez cette commande pour installer à la fois + sequelize + et + pg +:

npm install --save sequelize pg

Créez un nouveau fichier JavaScript pour contenir le code de la base de données:

nano database.js

Importez le module + sequelize + en ajoutant cette ligne au fichier:

base de données.js

const Sequelize = require('sequelize');

. . .

Ensuite, en dessous de cette ligne, initialisez un objet + sequelize + avec les paramètres de connexion à la base de données, que vous récupérerez à partir de l’environnement système. Ainsi, les informations d’identité restent en dehors de votre code afin que vous ne partagiez pas accidentellement vos informations d’identité lorsque vous envoyez votre code à GitHub. Vous pouvez utiliser + process.env + pour accéder aux variables d’environnement et l’opérateur + || + de JavaScripts pour définir les valeurs par défaut des variables non définies:

base de données.js

. . .

const sequelize = new Sequelize(process.env.DB_SCHEMA || 'postgres',
                               process.env.DB_USER || 'postgres',
                               process.env.DB_PASSWORD || '',
                               {
                                   host: process.env.DB_HOST || 'localhost',
                                   port: process.env.DB_PORT || 5432,
                                   dialect: 'postgres',
                                   dialectOptions: {
                                       ssl: process.env.DB_SSL == "true"
                                   }
                               });

. . .

Définissons maintenant le modèle + Person +. Pour éviter que l’exemple ne devienne trop complexe, vous ne créerez que deux champs: + firstName + et + lastName +, tous deux stockant des valeurs de chaîne. Ajoutez le code suivant pour définir le modèle:

base de données.js

. . .

const Person = sequelize.define('Person', {
   firstName: {
       type: Sequelize.STRING,
       allowNull: false
   },
   lastName: {
       type: Sequelize.STRING,
       allowNull: true
   },
});

. . .

Ceci définit les deux champs, rendant + firstName + obligatoire avec + allowNull: false +. La documentation de définition de modèle de Sequelize https://docs.sequelizejs.com/manual/models-definition.html] montre les types de données et les options disponibles.

Enfin, exportez l’objet + sequelize + et le modèle + Person + pour que les autres modules puissent les utiliser:

base de données.js

. . .

module.exports = {
   sequelize: sequelize,
   Person: Person
};

Il est pratique d’avoir un script de création de table dans un fichier séparé que vous pouvez appeler à tout moment du développement. Ces types de fichiers s’appellent migrations. Créez un nouveau fichier pour contenir ce code:

nano migrate.js

Ajoutez ces lignes au fichier pour importer le modèle de base de données que vous avez défini et appelez la fonction + sync () + pour initialiser la base de données, ce qui crée la table pour votre modèle:

migrate.js

var db = require('./database.js');
db.sequelize.sync();

L’application recherche des informations de connexion à la base de données dans les variables d’environnement du système. Créez un fichier appelé + .env + pour contenir ces valeurs, que vous allez charger dans l’environnement pendant le développement:

nano .env

Ajoutez les déclarations de variable suivantes au fichier. Assurez-vous que vous définissez + DB_HOST +, + DB_PORT + et + DB_PASSWORD + pour ceux associés à votre cluster DigitalOcean PostgreSQL:

env
export DB_SCHEMA=
export DB_USER=
export DB_PASSWORD=
export DB_HOST=
export DB_PORT=
export DB_SSL=true
export PORT=3000

Enregistrez le fichier.

Vous êtes prêt à initialiser la base de données. Importez le fichier d’environnement et exécutez + migrate.js:

source ./.env
node migrate.js

Cela crée la table de base de données:

Output
Executing (default): CREATE TABLE IF NOT EXISTS "People" ("id"   SERIAL , "firstName" VARCHAR(255) NOT NULL, "lastName" VARCHAR(255), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'People' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;

La sortie montre deux commandes. Le premier crée la table + Personnes + selon votre définition. La deuxième commande vérifie que la table a bien été créée en la cherchant dans le catalogue PostgreSQL.

Il est recommandé de créer des tests pour votre code. Avec les tests, vous pouvez valider le comportement du code. Vous pouvez écrire un chèque pour chaque fonction, méthode ou toute autre partie de votre système et vérifier que cela fonctionne comme prévu, sans avoir à tester les choses manuellement.

Le cadre de test jest convient parfaitement à la rédaction de tests sur des applications Node.js. Jest analyse les fichiers du projet à la recherche de fichiers de test et les exécute une à la fois. Installez Jest avec l’option + - save-dev +, qui indique + npm + que le module n’est pas requis pour exécuter le programme, mais qu’il s’agit d’une dépendance pour le développement de l’application:

npm install --save-dev jest

Vous écrivez des tests pour vérifier que vous pouvez insérer, lire et supprimer des enregistrements de votre base de données. Ces tests vérifieront que votre connexion à la base de données et vos autorisations sont correctement configurées, et fourniront également certains tests que vous pourrez utiliser ultérieurement dans votre pipeline CI / CD.

Créez le fichier + database.test.js +:

nano database.test.js

Ajoutez le contenu suivant. Commencez par importer le code de la base de données:

database.test.js

const db = require('./database');

. . .

Pour vous assurer que la base de données est prête à être utilisée, appelez + sync () + dans la fonction + beforeAll +:

database.test.js

. . .

beforeAll(async () => {
   await db.sequelize.sync();
});

. . .

Le premier test crée un enregistrement de personne dans la base de données. La bibliothèque + sequelize + exécute toutes les requêtes de manière asynchrone, ce qui signifie qu’elle n’attend pas les résultats de la requête. Pour que le test attende les résultats afin que vous puissiez les vérifier, vous devez utiliser les mots-clés + async + et + wait +. Ce test appelle la méthode + create () + pour insérer une nouvelle ligne dans la base de données. Utilisez + expect + pour comparer la colonne + person.id + avec + 1 +. Le test échouera si vous obtenez une valeur différente:

database.test.js

. . .

test('create person', async () => {
   expect.assertions(1);
   const person = await db.Person.create({
       id: 1,
       firstName: 'Sammy',
       lastName: 'Davis Jr.',
       email: '[email protected]'
   });
   expect(person.id).toEqual(1);
});

. . .

Lors du prochain test, utilisez la méthode + findByPk () + pour récupérer la ligne avec + id = 1 +. Ensuite, validez les valeurs + prénom + + et + nom + . Encore une fois, utilisez ` async ` et ` wait +`:

database.test.js

. . .

test('get person', async () => {
   expect.assertions(2);
   const person = await db.Person.findByPk(1);
   expect(person.firstName).toEqual('Sammy');
   expect(person.lastName).toEqual('Davis Jr.');
});

. . .

Enfin, testez le retrait d’une personne de la base de données. La méthode + destroy () + supprime la personne avec + id = 1 +. Pour vous assurer que cela fonctionne, essayez de récupérer la personne une seconde fois et vérifiez que la valeur renvoyée est + null +:

database.test.js

. . .

test('delete person', async () => {
   expect.assertions(1);
   await db.Person.destroy({
       where: {
           id: 1
       }
   });
   const person = await db.Person.findByPk(1);
   expect(person).toBeNull();
});

. . .

Enfin, ajoutez ce code pour fermer la connexion à la base de données avec + close () + une fois tous les tests terminés:

app.js

. . .

afterAll(async () => {
   await db.sequelize.close();
});

Enregistrez le fichier.

La commande + jest + exécute la suite de tests pour votre programme, mais vous pouvez également stocker des commandes dans + package.json +. Ouvrez ce fichier dans votre éditeur:

nano package.json

Localisez le mot clé + scripts et remplacez la ligne` + test` existante (qui n’était qu’un espace réservé). La commande de test est + jest +:

. . .

 "scripts": {
   "test": "jest"
 },

. . .

Vous pouvez maintenant appeler + npm run test pour appeler la suite de tests. Cela peut être une commande plus longue, mais si vous devez modifier la commande + jest + plus tard, les services externes n’auront pas à changer; ils peuvent continuer à appeler + npm run test +.

Exécutez les tests:

npm run test

Ensuite, vérifiez les résultats:

Output  console.log node_modules/sequelize/lib/sequelize.js:1176
   Executing (default): CREATE TABLE IF NOT EXISTS "People" ("id"   SERIAL , "firstName" VARCHAR(255) NOT NULL, "lastName" VARCHAR(255), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id"));

 console.log node_modules/sequelize/lib/sequelize.js:1176
   Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'People' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;

 console.log node_modules/sequelize/lib/sequelize.js:1176
   Executing (default): INSERT INTO "People" ("id","firstName","lastName","createdAt","updatedAt") VALUES ($1,$2,$3,$4,$5) RETURNING *;

 console.log node_modules/sequelize/lib/sequelize.js:1176
   Executing (default): SELECT "id", "firstName", "lastName", "createdAt", "updatedAt" FROM "People" AS "Person" WHERE "Person"."id" = 1;

 console.log node_modules/sequelize/lib/sequelize.js:1176
   Executing (default): DELETE FROM "People" WHERE "id" = 1

 console.log node_modules/sequelize/lib/sequelize.js:1176
   Executing (default): SELECT "id", "firstName", "lastName", "createdAt", "updatedAt" FROM "People" AS "Person" WHERE "Person"."id" = 1;

PASS  ./database.test.js
 ✓ create person (344ms)
 ✓ get person (173ms)
 ✓ delete person (323ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        5.315s
Ran all test suites.

Avec le code de base de données testé, vous pouvez créer le service API pour gérer les personnes du carnet d’adresses.

Pour répondre aux demandes HTTP, vous utiliserez la structure Web https://expressjs.com [Express]. Installez Express et enregistrez-le en tant que dépendance en utilisant + npm install:

npm install --save express

Vous aurez également besoin du module https://www.npmjs.com/package/body-parser [+ body-parser +], que vous utiliserez pour accéder au corps de la requête HTTP. Installez-le également comme dépendance:

npm install --save body-parser

Créez le fichier d’application principal + app.js:

nano app.js

Importez les modules + express,` + body-parser` et + database in. Créez ensuite une instance du module + express + appelée + app + pour contrôler et configurer le service. Vous utilisez + app.use () + pour ajouter des fonctionnalités telles que le middleware. Utilisez-le pour ajouter le module + body-parser + afin que l’application puisse lire les chaînes url-encoded:

app.js

var express = require('express');
var bodyParser = require('body-parser');
var db = require('./database');
var app = express();
app.use(bodyParser.urlencoded({ extended: true }));

. . .

Ensuite, ajoutez des itinéraires à l’application. Les itinéraires sont similaires aux boutons d’une application ou d’un site Web. ils déclenchent une action dans votre application. Les itinéraires lient des URL uniques aux actions de l’application. Chaque route servira un chemin spécifique et supportera une opération différente.

Le premier itinéraire que vous définissez gère les requêtes + GET + pour le chemin + / person / $ ID +, qui affichera l’enregistrement de la base de données de la personne avec l’ID spécifié. Express définit automatiquement la valeur du + $ ID + demandé dans la variable + req.params.id +.

L’application doit répondre avec les données personnelles codées sous forme de chaîne JSON. Comme vous l’avez fait dans les tests de base de données, utilisez la méthode + findByPk () + pour récupérer la personne par identifiant et répondre à la requête avec le statut HTTP + 200 + (OK) et envoyer l’enregistrement de la personne au format JSON. Ajoutez le code suivant:

app.js

. . .

app.get("/person/:id", function(req, res) {
   db.Person.findByPk(req.params.id)
       .then( person => {
           res.status(200).send(JSON.stringify(person));
       })
       .catch( err => {
           res.status(500).send(JSON.stringify(err));
       });
});

. . .

Les erreurs entraînent l’exécution du code dans + catch () +. Par exemple, si la base de données est en panne, la connexion échouera et sera exécutée à la place. En cas de problème, définissez le statut HTTP sur «+ 500 +» (erreur interne du serveur) et renvoyez le message d’erreur à l’utilisateur:

Ajoutez un autre itinéraire pour créer une personne dans la base de données. Cet itinéraire gérera les requêtes + PUT et accédera aux données de la personne à partir du` req.body`. Utilisez la méthode + create () + pour insérer une ligne dans la base de données:

app.js

. . .

app.put("/person", function(req, res) {
   db.Person.create({
       firstName: req.body.firstName,
       lastName: req.body.lastName,
       id: req.body.id
   })
       .then( person => {
           res.status(200).send(JSON.stringify(person));
       })
       .catch( err => {
           res.status(500).send(JSON.stringify(err));
       });
});

. . .

Ajoutez une autre route pour gérer les requêtes + DELETE +, ce qui supprimera les enregistrements du carnet d’adresses. Tout d’abord, utilisez l’ID pour localiser l’enregistrement, puis utilisez la méthode + destroy + pour le supprimer:

app.js

. . .

app.delete("/person/:id", function(req, res) {
   db.Person.destroy({
       where: {
           id: req.params.id
       }
   })
       .then( () => {
           res.status(200).send();
       })
       .catch( err => {
           res.status(500).send(JSON.stringify(err));
       });
});

. . .

Et pour plus de commodité, ajoutez une route qui récupère toutes les personnes de la base de données à l’aide du chemin + / all +:

app.js

. . .

app.get("/all", function(req, res) {
   db.Person.findAll()
       .then( persons => {
           res.status(200).send(JSON.stringify(persons));
       })
       .catch( err => {
           res.status(500).send(JSON.stringify(err));
       });
});

. . .

Une dernière route à gauche. Si la demande ne correspond à aucune des routes précédentes, envoyez le code d’état + 404 + (non trouvé):

app.js

. . .

app.use(function(req, res) {
   res.status(404).send("404 - Not Found");
});

. . .

Enfin, ajoutez la méthode + listen () +, qui démarre le service. Si la variable d’environnement + PORT + est définie, le service écoute ce port; sinon, par défaut, le port + 3000 +:

app.js

. . .

var server = app.listen(process.env.PORT || 3000, function() {
   console.log("app is running on port", server.address().port);
});

Comme vous l’avez appris, le fichier + package.json + vous permet de définir diverses commandes permettant de lancer des tests, de lancer vos applications et d’autres tâches, ce qui vous permet souvent d’exécuter des commandes courantes avec beaucoup moins de saisie. Ajoutez une nouvelle commande sur + package.json pour démarrer l’application. Editez le fichier:

nano package.json

Ajoutez la commande + start +, ainsi elle ressemble à ceci:

package.json

. . .

 "scripts": {
   "test": "jest"

 },

. . .

N’oubliez pas d’ajouter une virgule à la ligne précédente, car la section + scripts + a besoin de ses entrées séparées par des virgules.

Enregistrez le fichier et démarrez l’application pour la première fois. Commencez par charger le fichier d’environnement avec + source +; cela importe les variables dans la session et les met à la disposition de l’application. Ensuite, démarrez l’application avec + npm run start +:

source ./.env
npm run start

L’application démarre sur le port + 3000 +:

Outputapp is running on port 3000

Ouvrez un navigateur et accédez à + ​​http: // localhost: 3000 / all +. Vous verrez une page affichant + [] +.

Revenez à votre terminal et appuyez sur + CTRL-C + pour arrêter l’application.

C’est le moment idéal pour ajouter des tests de qualité du code. Les outils de qualité du code, également connus sous le nom de linters, analysent le projet à la recherche de problèmes dans le code. De mauvaises pratiques de codage telles que laisser des variables non utilisées, ne pas terminer les instructions par un point-virgule ou des accolades manquantes peuvent être à l’origine de bogues difficiles à trouver.

Installez l’outil jshint, une linterface JavaScript, en tant que dépendance de développement:

npm install --save-dev jshint

Au fil des ans, JavaScript a reçu des mises à jour, des fonctionnalités et des modifications de syntaxe. Le langage a été normalisé par http://ecma-international.org [ECMA International] sous le nom de «ECMAScript». Environ une fois par an, ECMA publie une nouvelle version d’ECMAScript avec de nouvelles fonctionnalités.

Par défaut, + jshint + suppose que votre code est compatible avec ES6 (ECMAScript version 6) et génère une erreur s’il trouve des mots-clés non pris en charge dans cette version. Vous voudrez trouver la version compatible avec votre code. Si vous consultez le tableau feature pour toutes les versions récentes, vous constaterez que les mots-clés + async / wait + n’ont pas été introduits jusqu’à ES8. Vous avez utilisé les deux mots-clés dans le code de test de la base de données, de sorte que la version compatible minimale est définie sur ES8.

Pour indiquer + jshint + la version que vous utilisez, créez un fichier nommé + .jshintrc +:

nano .jshintrc

Dans le fichier, spécifiez + esversion +. Le fichier + jshintrc + utilise JSON, créez donc un nouvel objet JSON dans le fichier:

jshintrc
{ "esversion": 8 }

Enregistrez le fichier et quittez l’éditeur.

Ajoutez une commande pour exécuter + jshint +. Éditez + package.json:

nano package.json

Ajoutez une commande + lint + à votre projet dans la section + scripts + de + package.json +. La commande appelle l’outil Lint contre tous les fichiers JavaScript que vous avez créés jusqu’à présent:

package.json

. . .

 "scripts": {
   "test": "jest",
   "start": "node app.js"

 },

. . .

Maintenant, vous pouvez lancer le linter pour rechercher les problèmes éventuels:

npm run lint

Il ne devrait y avoir aucun message d’erreur:

Output> jshint app.js database*.js migrate.js

S’il y a des erreurs, + jshint + affichera la ligne qui a le problème.

Vous avez terminé le projet et vous êtes assuré qu’il fonctionne. Ajoutez les fichiers au référentiel, validez et appliquez les modifications:

git add *.js
git add package*.json
git add .jshintrc
git commit -m 'initial commit'
git push origin master

Vous pouvez maintenant configurer Semaphore pour tester, créer et déployer l’application, en commençant par la configuration de Semaphore avec votre jeton d’accès DigitalOcean Personal et vos informations d’identification de base de données.

Étape 3 - Créer des secrets dans un sémaphore

Certaines informations n’appartiennent pas à un référentiel GitHub. Les mots de passe et les jetons d’API en sont de bons exemples. Vous avez stocké ces données sensibles dans un fichier séparé et les avez chargé dans votre environnement. Lorsque vous utilisez Semaphore, vous pouvez utiliser Secrets pour stocker des données sensibles.

Il y a trois sortes de secrets dans le projet:

  • Docker Hub: nom d’utilisateur et mot de passe de votre compte Docker Hub.

  • Jeton d’accès personnel DigitalOcean: pour déployer l’application sur votre cluster Kubernetes.

  • Variables d’environnement: pour les paramètres de connexion du nom d’utilisateur et du mot de passe de la base de données.

Pour créer le premier secret, ouvrez votre navigateur et connectez-vous au site Web https://semaphoreci.com [Semaphore]. Dans le menu de navigation de gauche, cliquez sur * Secrets * sous le titre * CONFIGURATION *. Cliquez sur le bouton * Create New Secret *.

Pour * Nom du secret *, entrez + dockerhub +. Ensuite, sous * Variables d’environnement *, créez deux variables d’environnement:

  • + DOCKER_USERNAME: votre nom d’utilisateur Docker Hub.

  • + DOCKER_PASSWORD +: votre mot de passe DockerHub.

image: https: //assets.digitalocean.com/articles/semaphore_doks/jnOY5HR.png [Secret de Docker Hub]

Cliquez sur * Enregistrer les modifications *.

Créez un second secret pour votre jeton d’accès personnel DigitalOcean. Encore une fois, cliquez sur * Secrets * dans le menu de navigation de gauche, puis sur * Create New Secret *. Appelez ce secret + do-access-token et créez une valeur d’environnement appelée` + DO ACCESS_TOKEN` avec la valeur définie pour votre jeton d’accès personnel:

image: https: //assets.digitalocean.com/articles/semaphore_doks/VDj90jI.png [Secret de Token DigitalOcean]

Sauvez le secret.

Pour le prochain secret, au lieu de définir directement les variables d’environnement, vous allez télécharger le fichier + .env + à partir de la racine du projet.

Créez un nouveau secret appelé + env-production. Dans la section * Fichiers *, cliquez sur le lien * Télécharger le fichier * pour localiser et télécharger votre fichier + .env +, puis demandez à Semaphore de le placer dans + / home / sémaphore / env-production +.

image: https: //assets.digitalocean.com/articles/semaphore_doks/IsIDPD2.png [Secret de l’environnement]

Les variables d’environnement sont toutes configurées. Vous pouvez maintenant commencer la configuration de l’intégration continue.

Étape 4 - Ajout de votre projet à Semaphore

Dans cette étape, vous allez ajouter votre projet à Semaphore et démarrer le pipeline Continuous Integration (CI).

Commencez par lier votre référentiel GitHub à Semaphore:

  1. Connectez-vous à votre compte https://semaphoreci.com [Semaphore].

  2. Cliquez sur l’icône * + * à côté de * PROJECTS *.

  3. Cliquez sur le bouton * Ajouter un référentiel * à côté de votre référentiel.

image: https: //assets.digitalocean.com/articles/semaphore_doks/eSZWvM9.png [Ajouter un référentiel à un sémaphore]

Maintenant que Semaphore est connecté, il enregistrera automatiquement les modifications apportées au référentiel.

Vous êtes maintenant prêt à créer le pipeline d’intégration continue pour l’application. Un pipeline définit le chemin que doit suivre votre code pour être généré, testé et déployé. Le pipeline est automatiquement exécuté chaque fois qu’il y a une modification dans le référentiel GitHub.

Tout d’abord, vous devez vous assurer que Semaphore utilise la même version de Node que vous avez utilisée pendant le développement. Vous pouvez vérifier quelle version est en cours d’exécution sur votre ordinateur:

node -v
Outputv10.16.0

Vous pouvez indiquer à Semaphore quelle version de Node.js utiliser en créant un fichier nommé + .nvmrc + dans votre référentiel. Semaphore utilise en interne node version manager pour basculer entre les versions de Node.js. Créez le fichier + .nvmrc + et définissez la version sur + 10.16.0 +:

echo '10.16.0' > .nvmrc

Les pipelines de sémaphore vont dans le répertoire + .semaphore +. Créez le répertoire:

mkdir .semaphore

Créez un nouveau fichier de pipeline. Le pipeline initial s’appelle toujours + semaphore.yml +. Dans ce fichier, vous définissez toutes les étapes nécessaires à la création et au test de l’application.

nano .semaphore/semaphore.yml

La première ligne doit définir la version du fichier sémaphore; l’écurie actuelle est + v1.0 +. En outre, le pipeline a besoin d’un nom. Ajoutez ces lignes à votre fichier:

semaphore/semaphore.yml
version:
name:

. . .

Semaphore configure automatiquement les machines virtuelles pour exécuter les tâches. Vous pouvez choisir parmi différentes machines. Pour les travaux d’intégration, utilisez le + e1-standard-2 + (2 processeurs de 4 Go de RAM) avec un système d’exploitation Ubuntu 18.04. Ajoutez ces lignes au fichier:

semaphore/semaphore.yml
. . .

agent:
 machine:
   type: e1-standard-2
   os_image: ubuntu1804

. . .

Semaphore utilise blocks pour organiser les tâches. Chaque bloc peut avoir un ou plusieurs jobs. Tous les travaux d’un bloc sont exécutés en parallèle, chacun sur une machine isolée. Sémaphore attend que tous les travaux d’un bloc passent avant de lancer le suivant.

Commencez par définir le premier bloc, qui installe toutes les dépendances JavaScript pour tester et exécuter l’application:

semaphore/semaphore.yml
. . .

blocks:
 - name: Install dependencies
   task:

. . .

Vous pouvez définir des variables d’environnement communes à tous les travaux, par exemple, si vous définissez + NODE_ENV sur` + test sso Node.js sait qu’il s’agit d’un environnement de test. Ajoutez ce code après + task +:

semaphore/semaphore.yml
. . .
   task:




. . .

Les commandes de la section prologue sont exécutées avant chaque travail du bloc. C’est un endroit pratique pour définir les tâches de configuration. Vous pouvez utiliser checkout pour cloner le référentiel GitHub. Ensuite, + nvm use + active la version appropriée de Node.js que vous avez spécifiée dans + .nvmrc +. Ajoutez la section + prologue +:

semaphore/semaphore.yml
   task:
. . .

     prologue:
       commands:
         - checkout
         - nvm use

. . .

Ajoutez ensuite ce code pour installer les dépendances du projet. Pour accélérer les travaux, Semaphore fournit l’outil cache. Vous pouvez exécuter + cache store + pour sauvegarder le répertoire + node_modules dans le cache de Semaphores. + cache + détermine automatiquement quels fichiers et répertoires doivent être stockés. La deuxième fois que le travail est exécuté, + cache restore + restaure le répertoire.

semaphore/semaphore.yml
. . .

     jobs:
       - name: npm install and cache
         commands:
           - cache restore
           - npm install
           - cache store

. . .

Ajoutez un autre bloc qui exécutera deux travaux. Un pour exécuter le test anti-peluche et un autre pour exécuter la suite de tests de l’application.

semaphore/semaphore.yml
. . .

 - name: Tests
   task:
     env_vars:
       - name: NODE_ENV
         value: test
     prologue:
       commands:
         - checkout
         - nvm use
         - cache restore

. . .

Le + prologue répète les mêmes commandes que dans le bloc précédent et restaure` + node_modules` à partir du cache. Comme ce bloc exécute des tests, vous définissez la variable d’environnement + NODE_ENV sur` + test`.

Maintenant, ajoutez les emplois. Le premier travail effectue la vérification de la qualité du code avec jshint:

semaphore/semaphore.yml
. . .

     jobs:
       - name: Static test
         commands:
           - npm run lint

. . .

Le travail suivant exécute les tests unitaires. Vous aurez besoin d’une base de données pour les exécuter, car vous ne voulez pas utiliser votre base de données de production. Https://docs.semaphoreci.com/article/132-sem-service-managing-databases-and-services-on-linux[sem-service] de Semaphore peut démarrer une base de données PostgreSQL locale dans un environnement de test complètement isolé. La base de données est détruite à la fin du travail. Démarrez ce service et lancez les tests:

semaphore/semaphore.yml
. . .

       - name: Unit test
         commands:
           - sem-service start postgres
           - npm run test

Enregistrez le fichier + .semaphore / semaphore.yml +.

Maintenant, ajoutez et validez les modifications dans le référentiel GitHub:

git add .nvmrc
git add .semaphore/semaphore.yml
git commit -m "continuous integration pipeline"
git push origin master

Dès que le code est poussé dans GitHub, Semaphore lance le pipeline CI:

image: https: //assets.digitalocean.com/articles/semaphore_doks/g7gd8f1.png [Flux de travail en cours]

Vous pouvez cliquer sur le pipeline pour afficher les blocs, les travaux et leur sortie.

image: https: //assets.digitalocean.com/articles/semaphore_doks/WIFWWII.png [Pipeline d’intégration]

Vous allez ensuite créer un nouveau pipeline qui crée une image Docker pour l’application.

Étape 5 - Création d’images de menu fixe pour l’application

Une image Docker est l’unité de base d’un déploiement Kubernetes. L’image doit contenir tous les fichiers binaires, bibliothèques et codes nécessaires à l’exécution de l’application. Un conteneur Docker n’est pas une machine virtuelle légère, mais il se comporte comme tel. Le registre Docker Hub contient des centaines d’images prêtes à l’emploi, mais nous allons construire la nôtre.

Au cours de cette étape, vous allez ajouter un nouveau pipeline pour créer une image Docker personnalisée pour votre application et la transmettre vers Docker Hub.

Pour construire une image personnalisée, créez un + Dockerfile:

nano Dockerfile

Le + Dockerfile + est une recette pour créer l’image. Vous pouvez utiliser la distribution official Node.js comme point de départ au lieu de partir de zéro. Ajoutez ceci à votre + Dockerfile +:

Dockerfile

FROM node:10.16.0-alpine

. . .

Ajoutez ensuite une commande qui copie + package.json et` + package-lock.json`, puis installez les modules de nœud à l’intérieur de l’image:

Dockerfile

. . .

COPY package*.json ./
RUN npm install

. . .

L’installation préalable des dépendances accélérera les générations suivantes, car Docker mettra cette étape en cache.

Ajoutez maintenant cette commande qui copie tous les fichiers d’application de la racine du projet dans l’image:

Dockerfile

. . .

COPY *.js ./

. . .

Enfin, + EXPOSE + spécifie que le conteneur écoute les connexions sur le port + 3000 +, où l’application est à l’écoute, et + CMD + définit la commande à exécuter au démarrage du conteneur. Ajoutez ces lignes à votre fichier:

Dockerfile

. . .

EXPOSE 3000
CMD [ "npm", "run", "start" ]

Enregistrez le fichier.

Une fois le fichier Dockerfile terminé, vous pouvez créer un nouveau pipeline afin que Semaphore puisse créer l’image pour vous lorsque vous transmettez votre code à GitHub. Créez un nouveau fichier nommé + docker-build.yml +:

nano .semaphore/docker-build.yml

Démarrez le pipeline avec le même modèle que le pipeline CI, mais avec le nom + Docker build:

semaphore/docker-build.yml
version: v1.0
name:
agent:
 machine:
   type: e1-standard-2
   os_image: ubuntu1804

. . .

Ce pipeline aura seulement un bloc et un travail. À l’étape 3, vous avez créé un secret nommé + dockerhub + avec votre nom d’utilisateur et votre mot de passe Docker Hub. Ici, vous importerez ces valeurs en utilisant le mot-clé + secrets +. Ajoutez ce code:

semaphore/docker-build.yml
. . .

blocks:
 - name: Build
   task:
     secrets:
       - name: dockerhub

. . .

Les images Docker sont stockées dans des référentiels. Nous allons utiliser le site officiel Docker Hub, qui permet un nombre illimité d’images publiques. Ajoutez ces lignes pour extraire le code de GitHub et utilisez la commande + docker login pour vous authentifier avec Docker Hub.

semaphore/docker-build.yml
   task:
. . .

     prologue:
       commands:
         - checkout
         - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin

. . .

Chaque image Docker est entièrement identifiée par la combinaison du nom et de la balise. Le nom correspond généralement au produit ou au logiciel, et l’étiquette correspond à la version du logiciel. Par exemple, + node.10.16.0 +. Lorsqu’aucune balise n’est fournie, Docker utilise par défaut la balise spéciale + latest +. Par conséquent, il est considéré comme une bonne pratique d’utiliser la balise + latest + pour faire référence à l’image la plus récente.

Ajoutez le code suivant pour générer l’image et la transmettre à Docker Hub:

semaphore/docker-build.yml
. . .

     jobs:
     - name: Docker build
       commands:
         - docker pull "${DOCKER_USERNAME}/addressbook:latest" || true
         - docker build --cache-from "${DOCKER_USERNAME}/addressbook:latest" -t "${DOCKER_USERNAME}/addressbook:$SEMAPHORE_WORKFLOW_ID" .
         - docker push "${DOCKER_USERNAME}/addressbook:$SEMAPHORE_WORKFLOW_ID"

Lorsque Docker construit l’image, il réutilise des parties des images existantes pour accélérer le processus. La première commande essaie d’extraire l’image + latest + de Docker Hub afin qu’elle puisse être réutilisée. Sémaphore arrête le pipeline si l’une des commandes renvoie un code d’état différent de zéro. Par exemple, si le référentiel n’a pas d’image + latest +, comme il ne le sera pas au premier essai, le pipeline s’arrêtera. Vous pouvez forcer Semaphore à ignorer les commandes ayant échoué en ajoutant `+ || true + `à la commande.

La deuxième commande construit l’image. Pour référencer cette image particulière plus tard, vous pouvez la marquer avec une chaîne unique. Semaphore fournit plusieurs environment variables pour les travaux. L’un d’eux, + $ SEMAPHORE_WORKFLOW_ID + est unique et partagé entre tous les pipelines du flux de travaux. Il est pratique de référencer cette image plus tard dans le déploiement.

La troisième commande pousse l’image vers Docker Hub.

Le pipeline de construction est prêt, mais Semaphore ne le démarrera pas à moins que vous ne le connectiez au pipeline CI principal. Vous pouvez chaîner plusieurs pipelines pour créer des flux de travail complexes comportant plusieurs branches à l’aide de promotions.

Editez le fichier de pipeline principal + .semaphore / semaphore.yml +:

nano .semaphore/semaphore.yml

Ajoutez les lignes suivantes à la fin du fichier:

semaphore/semaphore.yml
. . .

promotions:
 - name: Dockerize
   pipeline_file: docker-build.yml
   auto_promote_on:
     - result: passed

+ auto_promote_on + définit la condition pour démarrer le pipeline + docker build. Dans ce cas, il s’exécute lorsque tous les travaux définis dans le fichier + semaphore.yml + sont passés.

Pour tester le nouveau pipeline, vous devez ajouter, valider et transmettre tous les fichiers modifiés à GitHub:

git add Dockerfile
git add .semaphore/docker-build.yml
git add .semaphore/semaphore.yml
git commit -m "docker build pipeline"
git push origin master

Une fois le pipeline de configuration terminé, le pipeline de génération de Docker démarre.

image: https: //assets.digitalocean.com/articles/semaphore_doks/XQfOwvK.png [Construire le pipeline]

Une fois l’opération terminée, vous verrez votre nouvelle image dans votre référentiel Docker Hub.

Votre processus de construction a été testé et la création de l’image a été testée. Vous allez maintenant créer le dernier pipeline pour déployer l’application sur votre cluster Kubernetes.

Étape 6 - Configuration du déploiement continu sur Kubernetes

Le composant de base d’un déploiement de Kubernetes est le pod. Un pod est un groupe de conteneurs gérés comme une seule unité. Les conteneurs d’un pod démarrent et s’arrêtent à l’unisson et s’exécutent toujours sur le même ordinateur, partageant ses ressources. Chaque pod a une adresse IP. Dans ce cas, les pods n’auront qu’un conteneur.

Les pods sont éphémères; ils sont créés et détruits fréquemment. Vous ne pouvez pas savoir quelle adresse IP va être assignée à chaque pod avant son démarrage. Pour résoudre ce problème, vous utiliserez services[services, qui ont des adresses IP publiques fixes afin que les connexions entrantes puissent être équilibrées et transmis aux pods.

Vous pouvez gérer les pods directement, mais il est préférable de laisser Kubernetes le faire en utilisant un https://www.digitalocean.com/community/tutorials/an-inintroduction-to-kubernetes#deployments&deployment]. Dans cette section, vous allez créer un manifeste déclaratif décrivant l’état final souhaité pour votre cluster. Le manifeste a deux ressources:

  • Déploiement: démarre les pods dans les nœuds du cluster selon les besoins et assure le suivi de leur statut. Dans ce didacticiel, nous utilisons un cluster à 3 nœuds, nous allons donc déployer 3 modules.

  • Service: sert de point d’entrée pour nos utilisateurs. Écoute le trafic sur le port + 80 + (HTTP) et transmet la connexion aux pods.

Créez un fichier manifeste appelé + deployment.yml +:

nano deployment.yml

Démarrez le manifeste avec la ressource + Deployment +. Ajoutez le contenu suivant au nouveau fichier pour définir le déploiement:

déploiement.yml

apiVersion: apps/v1
kind: Deployment
metadata:
 name: addressbook
spec:
 replicas: 3
 selector:
   matchLabels:
     app: addressbook
 template:
   metadata:
     labels:
       app: addressbook
   spec:
     containers:
       - name: addressbook
         image: ${DOCKER_USERNAME}/addressbook:${SEMAPHORE_WORKFLOW_ID}
         env:
           - name: NODE_ENV
             value: "production"
           - name: PORT
             value: "$PORT"
           - name: DB_SCHEMA
             value: "$DB_SCHEMA"
           - name: DB_USER
             value: "$DB_USER"
           - name: DB_PASSWORD
             value: "$DB_PASSWORD"
           - name: DB_HOST
             value: "$DB_HOST"
           - name: DB_PORT
             value: "$DB_PORT"
           - name: DB_SSL
             value: "$DB_SSL"


. . .

Pour chaque ressource du manifeste, vous devez définir un + apiVersion +. Pour les déploiements, utilisez + apiVersion: apps / v1 +, une version stable. Ensuite, indiquez à Kubernetes que cette ressource est un déploiement avec + kind: Deployment +. Chaque définition doit avoir un nom défini dans + metadata.name +.

Dans la section + spec + vous indiquez à Kubernetes quel est l’état final souhaité. Cette définition demande à Kubernetes de créer 3 pods avec + réplicas: 3 +.

Les étiquettes sont des paires clé-valeur utilisées pour organiser et référencer les ressources Kubernetes. Vous définissez les étiquettes avec + metadata.labels +, et vous pouvez rechercher les étiquettes correspondantes avec + selector.matchLabels +. C’est comment vous connectez des éléments ensemble.

La clé + spec.template + définit un modèle que Kubernetes utilisera pour créer chaque pod. Dans + spec.template.metadata.labels +, vous définissez une étiquette pour les pods: + app: addressbook +.

Avec + spec.selector.matchLabels +, vous faites en sorte que le déploiement gère les pods portant le libellé + app: addressbook +. Dans ce cas, vous rendez ce déploiement responsable de tous les pods.

Enfin, vous définissez l’image qui s’exécute dans les modules. Dans + spec.template.spec.containers +, vous définissez le nom de l’image. Kubernetes extraira l’image du registre selon les besoins. Dans ce cas, il tirera de Docker Hub). Vous pouvez également définir des variables d’environnement pour les conteneurs, ce qui est une chance, car vous devez fournir plusieurs valeurs pour la connexion à la base de données.

Pour que le manifeste de déploiement reste flexible, vous utiliserez des variables. Le format YAML, cependant, n’autorise pas les variables et le fichier n’est donc pas encore valide. Vous allez résoudre ce problème lorsque vous définissez le pipeline de déploiement pour Semaphore.

C’est tout pour le déploiement. Mais cela ne définit que les pods. Vous avez toujours besoin d’un service permettant au trafic de circuler dans vos pods. Vous pouvez ajouter une autre ressource Kubernetes dans le même fichier tant que vous utilisez trois traits d’union (+ --- +) comme séparateur.

Ajoutez le code suivant pour définir un service d’équilibrage de charge qui se connecte aux pods avec le libellé + addressbook +:

déploiement.yml

. . .

---

apiVersion: v1
kind: Service
metadata:
 name: addressbook-lb
spec:
 selector:
   app: addressbook
 type: LoadBalancer
 ports:
   - port: 80
     targetPort: 3000

L’équilibreur de charge recevra les connexions sur le port + 80 + et les transmettra au port + 3000 + des pods, où l’application est en cours d’écoute.

Enregistrez le fichier.

Créez maintenant un pipeline de déploiement pour Semaphore qui déploiera l’application à l’aide du manifeste. Créez un nouveau fichier dans le répertoire + .semaphore +:

nano .semaphore/deploy-k8s.yml

Commencez le pipeline comme d’habitude, en spécifiant la version, le nom et l’image:

semaphore/deploy-k8s.yml
version: v1.0
name: Deploy to Kubernetes
agent:
 machine:
   type: e1-standard-2
   os_image: ubuntu1804

. . .

Ce pipeline aura deux blocs. Le premier bloc déploie l’application sur le cluster Kubernetes.

Définissez le bloc et importez tous les secrets:

semaphore/deploy-k8s.yml
. . .

blocks:
 - name: Deploy to Kubernetes
   task:
     secrets:
       - name: dockerhub
       - name: do-access-token
       - name: env-production

. . .

Stockez votre nom de cluster DigitalOcean Kubernetes dans une variable d’environnement afin de pouvoir le référencer ultérieurement:

semaphore/deploy-k8s.yml
. . .

     env_vars:
       - name: CLUSTER_NAME
         value:

. . .

Les grappes DigitalOcean Kubernetes sont gérées avec une combinaison de deux programmes: + kubectl + et + doctl +. Le premier est déjà inclus dans l’image de Sémaphore, mais le dernier ne l’est pas, vous devez donc l’installer. Vous pouvez utiliser la section + prologue + pour le faire.

Ajoutez cette section de prologue:

semaphore/deploy-k8s.yml
. . .

     prologue:
       commands:
         - wget https://github.com/digitalocean/doctl/releases/download/v1.20.0/doctl-1.20.0-linux-amd64.tar.gz
         - tar xf doctl-1.20.0-linux-amd64.tar.gz
         - sudo cp doctl /usr/local/bin
         - doctl auth init --access-token $DO_ACCESS_TOKEN
         - doctl kubernetes cluster kubeconfig save "${CLUSTER_NAME}"
         - checkout

. . .

La première commande télécharge le fichier + doctl + officiel release avec + wget +. La deuxième commande le décompresse avec + tar + et le copie dans le chemin local. Une fois que + doctl + est installé, il peut être utilisé pour s’authentifier auprès de l’API DigitalOcean et demander le fichier de configuration Kubernetes pour notre cluster. Après vérification de notre code, nous en avons terminé avec le + prologue +:

Vient ensuite le dernier élément de notre pipeline: le déploiement sur le cluster.

N’oubliez pas qu’il y avait des variables d’environnement dans + deployment.yml +, et YAML ne le permet pas. Par conséquent, + deployment.yml + dans son état actuel ne fonctionnera pas. Pour contourner ce problème, + source le fichier d’environnement pour charger les variables, puis utilisez la commande` + envsubst` pour développer les variables sur place avec les valeurs réelles. Le résultat, un fichier nommé + deploy.yml +, est entièrement valide en YAML avec les valeurs insérées. Avec le fichier en place, vous pouvez démarrer le déploiement avec + kubectl apply +:

semaphore/deploy-k8s.yml
. . .

     jobs:
     - name: Deploy
       commands:
         - source $HOME/env-production
         - envsubst < deployment.yml | tee deploy.yml
         - kubectl apply -f deploy.yml

. . .

Le deuxième bloc ajoute la balise + latest + à l’image sur Docker Hub pour indiquer qu’il s’agit de la version la plus récente déployée. Répétez les étapes de connexion à Docker, puis tirez, retagez et appuyez sur Docker Hub:

semaphore/deploy-k8s.yml
. . .

 - name: Tag latest release
   task:
     secrets:
       - name: dockerhub
     prologue:
       commands:
         - checkout
         - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
         - checkout
     jobs:
     - name: docker tag latest
       commands:
         - docker pull "${DOCKER_USERNAME}/addressbook:$SEMAPHORE_WORKFLOW_ID"
         - docker tag "${DOCKER_USERNAME}/addressbook:$SEMAPHORE_WORKFLOW_ID" "${DOCKER_USERNAME}/addressbook:latest"
         - docker push "${DOCKER_USERNAME}/addressbook:latest"

Enregistrez le fichier.

Ce pipeline effectue le déploiement, mais il ne peut démarrer que si l’image Docker a été générée avec succès et transmise à Docker Hub. Par conséquent, vous devez connecter les pipelines de construction et de déploiement à une promotion. Modifiez le pipeline de génération de Docker pour l’ajouter:

nano .semaphore/docker-build.yml

Ajoutez la promotion à la fin du fichier:

semaphore/docker-build.yml
. . .

promotions:
 - name: Deploy to Kubernetes
   pipeline_file: deploy-k8s.yml
   auto_promote_on:
     - result: passed

Vous avez terminé la configuration du flux de travail CI / CD.

Il ne reste plus qu’à pousser les fichiers modifiés et laisser Semaphore faire le travail. Ajoutez, validez et envoyez les modifications de votre référentiel:

git add .semaphore/deploy-k8s.yml
git add .semaphore/docker-build.yml
git add deployment.yml
git commit -m "kubernetes deploy pipeline"
git push origin master

Cela prendra quelques minutes pour que le déploiement se termine.

image: https: //assets.digitalocean.com/articles/semaphore_doks/Ee7VRw7.png [Déployer le pipeline]

Testons ensuite l’application.

Étape 7 - Test de l’application

À ce stade, l’application est opérationnelle. Dans cette étape, vous utiliserez + curl + pour tester le point de terminaison de l’API.

Vous devez connaître l’adresse IP publique que DigitalOcean a attribuée à votre cluster. Suivez ces étapes pour le trouver:

  1. Connectez-vous à votre compte DigitalOcean.

  2. Sélectionnez le projet de carnet d’adresses

  3. Allez à * Networking *.

  4. Cliquez sur * Load Balancers *.

  5. L’adresse * IP * est affichée. Copiez l’adresse IP.

image: https: //assets.digitalocean.com/articles/semaphore_doks/S6nLLmg.png [IP de l’équilibreur de charge]

Vérifions la route + / all en utilisant` + curl`:

curl -w "\n" /all

Vous pouvez utiliser l’option + -w" \ n "+ pour vous assurer que + curl + affiche toutes les lignes:

Comme il n’y a pas encore d’enregistrements dans la base de données, vous obtenez un tableau JSON vide:

Output[]

Créez un nouvel enregistrement de personne en envoyant une requête + PUT au noeud final` + / person`:

curl -w "\n" -X PUT \
 -d "firstName=Sammy&lastName=the Shark" /person

L’API renvoie l’objet JSON pour la personne:

Output{
   "id": 1,
   "firstName": "Sammy",
   "lastName": "the Shark",
   "updatedAt": "2019-07-04T23:51:00.548Z",
   "createdAt": "2019-07-04T23:51:00.548Z"
}

Créer une deuxième personne:

curl -w "\n" -X PUT \
 -d "firstName=Tommy&lastName=the Octopus" /person

La sortie indique qu’une deuxième personne a été créée:

Output{
   "id": 2,
   "firstName": "Tommy",
   "lastName": "the Octopus",
   "updatedAt": "2019-07-04T23:52:08.724Z",
   "createdAt": "2019-07-04T23:52:08.724Z"
}

Maintenant, faites une requête + GET + pour obtenir la personne avec le + id + de + 2 +:

curl -w "\n" /person/2

Le serveur répond avec les données demandées:

Output{
   "id": 2,
   "firstName": "Tommy",
   "lastName": "the Octopus",
   "createdAt": "2019-07-04T23:52:08.724Z",
   "updatedAt": "2019-07-04T23:52:08.724Z"
}

Pour supprimer la personne, envoyez une demande + DELETE:

curl -w "\n" -X DELETE /person/2

Aucune sortie n’est renvoyée par cette commande.

Vous ne devriez avoir qu’une personne dans votre base de données, celle avec le + id + de + 1 +. Essayez d’obtenir + / all + à nouveau:

curl -w "\n" /all

Le serveur répond avec un tableau de personnes contenant un seul enregistrement:

Output[
   {
       "id": 1,
       "firstName": "Sammy",
       "lastName": "the Shark",
       "createdAt": "2019-07-04T23:51:00.548Z",
       "updatedAt": "2019-07-04T23:51:00.548Z"
   }
]

À ce stade, il ne reste plus qu’une personne dans la base de données.

Ceci termine les tests pour tous les points de terminaison de notre application et marque la fin du tutoriel.

Conclusion

Dans ce tutoriel, vous avez écrit une application complète Node.js utilisant le service de base de données PostgreSQL géré par DigitalOcean. Vous avez ensuite utilisé les pipelines CI / CD de Semaphore pour automatiser entièrement un flux de travail qui a testé et construit une image de conteneur, puis chargé celle-ci sur Docker Hub, puis déployée sur DigitalOcean Kubernetes.

Pour en savoir plus sur Kubernetes, vous pouvez lire An Introduction to Kubernetes et le reste du site Web de DigitalOcean https://www.digitalocean.com. / community / tags / kubernetes [tutoriels sur Kubernetes].

Maintenant que votre application est déployée, vous pouvez envisager adding un nom de domaine, https://www.digitalocean.com/ docs / database / procédures / clusters / secure-clusters [sécuriser votre cluster de base de données], ou configurer alerts pour votre base de données.