Erstellen und Bereitstellen einer Node.js-Anwendung für DigitalOcean Kubernetes mit Semaphore Continuous Integration and Delivery

Der Autor hat die Open Internet / Free Speech fund ausgewählt, um eine Spende als Teil der https://do.co/w4do-cta zu erhalten [Write for DOnations] program.

Einführung

Mit https://kubernetes.io [Kubernetes] können Benutzer ausfallsichere und skalierbare Dienste mit einem einzigen Befehl erstellen. Wie alles, was zu schön klingt, um wahr zu sein, hat es einen Haken: Sie müssen zuerst ein geeignetes https://www.docker.com [Docker] Image erstellen und es gründlich testen.

Continuous Integration (CI) ist das Testen der Anwendung bei jedem Update. Dies manuell zu tun ist mühsam und fehleranfällig, aber eine CI-Plattform führt die Tests für Sie durch, erkennt Fehler frühzeitig und lokalisiert den Punkt, an dem die Fehler eingeführt wurden. Release- und Bereitstellungsverfahren sind häufig kompliziert, zeitaufwändig und erfordern eine zuverlässige Build-Umgebung. Mit Continuous Delivery (CD) können Sie Ihre Anwendung bei jedem Update ohne menschliches Eingreifen erstellen und bereitstellen .

Um den gesamten Prozess zu automatisieren, verwenden Sie https://semaphoreci.com [Semaphore], eine CI / CD-Plattform (Continuous Integration and Delivery).

In diesem Lernprogramm erstellen Sie einen Adressbuch-API-Dienst mit https://nodejs.org [Node.js]. Die API verfügt über eine einfache RESTful API -Schnittstelle zum Erstellen, Löschen und Suchen von Personen in der Datenbank. Sie verwenden https://git-scm.com [Git], um den Code an https://github.com [GitHub] zu senden. Anschließend testen Sie die Anwendung mit Semaphore, erstellen ein Docker-Image und stellen es in einem DigitalOcean Kubernetes -Cluster bereit. Für die Datenbank erstellen Sie einen PostgreSQL-Cluster unter Verwendung von DigitalOcean Managed Databases.

Voraussetzungen

Stellen Sie vor dem Lesen Folgendes sicher:

  • Ein DigitalOcean-Konto und ein persönliches Zugriffstoken. Folgen Sie Create a Personal Access Token, um einen für Ihr Konto einzurichten.

  • Ein Docker Hub Konto.

  • Ein https://github.com [GitHub] Konto.

  • Ein https://semaphoreci.com [Semaphore] Konto; Sie können sich mit Ihrem GitHub-Konto anmelden.

  • Ein neues GitHub-Repository mit dem Namen "+ Adressbuch +" für das Projekt. Aktivieren Sie beim Erstellen des Repositorys das Kontrollkästchen * Dieses Repository mit einer README-Datei * initialisieren und wählen Sie im Menü * .gitignore hinzufügen * die Option * Knoten * aus. Folgen Sie der Create a Repo -Hilfeseite von GitHub, um weitere Informationen zu erhalten.

  • https://git-scm.com [Git] auf Ihrem lokalen Computer installiert und set up für die Arbeit mit Ihrem GitHub-Konto. Wenn Sie nicht vertraut sind oder eine Auffrischung benötigen, lesen Sie bitte das How to use-git -Referenzhandbuch.

  • curl auf Ihrem lokalen Computer installiert.

  • https://nodejs.org [Node.js] auf Ihrem lokalen Computer installiert. In diesem Tutorial verwenden Sie Node.js Version + 10.16.0 +.

Schritt 1 - Erstellen der Datenbank und des Kubernetes-Clusters

Stellen Sie zunächst die Dienste bereit, die die Anwendung mit Strom versorgen: den DigitalOcean-Datenbankcluster und den DigitalOcean-Kubernetes-Cluster.

Melden Sie sich bei Ihrem DigitalOcean-Konto und unter create a project an. In einem Projekt können Sie alle Ressourcen organisieren, aus denen die Anwendung besteht. Rufen Sie das Projekt "+ Adressbuch +" auf.

Erstellen Sie als Nächstes einen PostgreSQL -Cluster. Der PostgreSQL-Datenbankdienst speichert die Daten der Anwendung. Sie können die neueste verfügbare Version auswählen. Es sollte einige Minuten dauern, bis der Service fertig ist.

Sobald der PostgreSQL-Dienst verfügbar ist, können Sie unter https://www.digitalocean.com/docs/databases/how-to/clusters/add-users-and-databases/ eine Datenbank und einen Benutzer erstellen. Setzen Sie den Datenbanknamen auf "+ addessbook_db " und den Benutzernamen auf " addressbook_user +". Notieren Sie sich das Kennwort, das für Ihren neuen Benutzer generiert wurde. Datenbanken sind die Möglichkeit von PostgreSQL, Daten zu organisieren. Normalerweise verfügt jede Anwendung über eine eigene Datenbank, obwohl diesbezüglich keine strengen Regeln gelten. Die Anwendung verwendet den Benutzernamen und das Kennwort, um auf die Datenbank zuzugreifen und ihre Daten zu speichern und abzurufen.

Erstellen Sie schließlich einen Kubernetes Cluster. Wählen Sie dieselbe Region aus, in der die Datenbank ausgeführt wird. Nennen Sie den Cluster "+ Adressbuch-Server" und setzen Sie die Anzahl der Knoten auf "+ 3 +".

Während die Knoten bereitgestellt werden, können Sie mit dem Erstellen Ihrer Anwendung beginnen.

Schritt 2 - Schreiben der Anwendung

Erstellen wir die Adressbuchanwendung, die Sie bereitstellen möchten. Klonen Sie zunächst das GitHub-Repository, das Sie unter den Voraussetzungen erstellt haben, damit Sie eine lokale Kopie der GitHub-Datei "+ .gitignore +" haben, und Sie können Ihren Anwendungscode schnell festschreiben, ohne manuell ein Repository erstellen zu müssen . Öffnen Sie Ihren Browser und wechseln Sie zu Ihrem neuen GitHub-Repository. Klicken Sie auf die Schaltfläche * Clone or download * und kopieren Sie die angegebene URL. Verwenden Sie Git, um das leere Repository auf Ihrem Computer zu klonen:

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

Geben Sie das Projektverzeichnis ein:

cd addressbook

Wenn das Repository geklont ist, können Sie mit dem Schreiben der App beginnen. Sie erstellen zwei Komponenten: ein Modul, das mit der Datenbank interagiert, und ein Modul, das den HTTP-Dienst bereitstellt. Das Datenbankmodul weiß, wie Personen aus der Adressbuchdatenbank gespeichert und abgerufen werden, und das HTTP-Modul empfängt Anforderungen und antwortet entsprechend.

Obwohl dies nicht zwingend vorgeschrieben ist, empfiehlt es sich, den Code beim Schreiben zu testen, sodass Sie auch ein Testmodul erstellen. Dies ist das geplante Layout für die Anwendung:

  • + database.js +: Datenbankmodul. Es behandelt Datenbankoperationen.

  • + app.js +: das Endbenutzermodul und die Hauptanwendung. Es stellt den Benutzern einen HTTP-Dienst zur Verfügung, mit dem sie sich verbinden können.

  • + database.test.js +: Testet für das Datenbankmodul.

Außerdem benötigen Sie eine package.json -Datei für Ihr Projekt, die das Projekt und die erforderlichen Abhängigkeiten beschreibt. Sie können es entweder manuell mit Ihrem Editor oder interaktiv mit npm erstellen. Führen Sie den Befehl + npm init + aus, um die Datei interaktiv zu erstellen:

npm init

Der Befehl fordert Sie zur Eingabe einiger Informationen auf. Tragen Sie die Werte wie im Beispiel gezeigt ein. Wenn keine Antwort aufgeführt ist, lassen Sie das Feld leer. Dabei wird der Standardwert in Klammern verwendet:

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)

Jetzt können Sie den Code schreiben. Die Datenbank ist der Kern des Services, den Sie entwickeln. Vor dem Schreiben anderer Komponenten muss unbedingt ein gut gestaltetes Datenbankmodell vorhanden sein. Aus diesem Grund ist es sinnvoll, mit dem Datenbankcode zu beginnen.

Sie müssen nicht alle Bits der Anwendung codieren. Node.js verfügt über eine große Bibliothek wiederverwendbarer Module. Beispielsweise müssen Sie keine SQL-Abfragen schreiben, wenn Sie das Modul Sequelize ORM im Projekt haben. Dieses Modul bietet eine Schnittstelle, die Datenbanken als JavaScript-Objekte und -Methoden behandelt. Es kann auch Tabellen in Ihrer Datenbank erstellen. Sequelize benötigt das Modul pg, um mit PostgreSQL zu arbeiten.

Installieren Sie Module mit dem Befehl + npm install und der Option` + - save + , wodurch + npm + angewiesen wird, das Modul in + package.json` zu speichern. Führen Sie diesen Befehl aus, um sowohl + sequelize + als auch + pg + zu installieren:

npm install --save sequelize pg

Erstellen Sie eine neue JavaScript-Datei für den Datenbankcode:

nano database.js

Importieren Sie das Modul + sequelize +, indem Sie der Datei die folgende Zeile hinzufügen:

database.js

const Sequelize = require('sequelize');

. . .

Initialisieren Sie dann unterhalb dieser Zeile ein "+ sequelize " - Objekt mit den Datenbankverbindungsparametern, die Sie aus der Systemumgebung abrufen. Dadurch werden die Anmeldeinformationen nicht in Ihrem Code gespeichert, sodass Sie Ihre Anmeldeinformationen nicht versehentlich freigeben, wenn Sie Ihren Code an GitHub senden. Sie können " process.env " verwenden, um auf Umgebungsvariablen zuzugreifen, und den " || +" - Operator von JavaScripts, um Standardeinstellungen für undefinierte Variablen festzulegen:

database.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"
                                   }
                               });

. . .

Definieren Sie nun das Modell "+ Person ". Damit das Beispiel nicht zu komplex wird, erstellen Sie nur zwei Felder: " Vorname " und " Nachname +", in denen Zeichenfolgenwerte gespeichert werden. Fügen Sie den folgenden Code hinzu, um das Modell zu definieren:

database.js

. . .

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

. . .

Dies definiert die beiden Felder und macht + firstName + mit + allowNull: false + obligatorisch. Die model definition documentation von Sequelize zeigt die verfügbaren Datentypen und -optionen an.

Exportieren Sie abschließend das Objekt "+ sequelize " und das Modell " Person +", damit andere Module sie verwenden können:

database.js

. . .

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

Es ist praktisch, ein Skript zur Tabellenerstellung in einer separaten Datei zu haben, die Sie während der Entwicklung jederzeit aufrufen können. Diese Dateitypen werden Migrationen genannt. Erstellen Sie eine neue Datei, die diesen Code enthält:

nano migrate.js

Fügen Sie diese Zeilen zur Datei hinzu, um das von Ihnen definierte Datenbankmodell zu importieren, und rufen Sie die Funktion + sync () + auf, um die Datenbank zu initialisieren, die die Tabelle für Ihr Modell erstellt:

migrate.js

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

Die Anwendung sucht nach Datenbankverbindungsinformationen in Systemumgebungsvariablen. Erstellen Sie eine Datei mit dem Namen "+ .env +", die die Werte enthält, die Sie während der Entwicklung in die Umgebung laden:

nano .env

Fügen Sie der Datei die folgenden Variablendeklarationen hinzu. Stellen Sie sicher, dass Sie "+ DB_HOST ", " DB_PORT " und " DB_PASSWORD +" auf die Werte setzen, die Ihrem DigitalOcean PostgreSQL-Cluster zugeordnet sind:

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

Speicher die Datei.

Sie können die Datenbank jetzt initialisieren. Importieren Sie die Umgebungsdatei und führen Sie "+ migrate.js +" aus:

source ./.env
node migrate.js

Dadurch wird die Datenbanktabelle erstellt:

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;

Die Ausgabe zeigt zwei Befehle. Der erste erstellt die '+ People +' - Tabelle gemäß Ihrer Definition. Der zweite Befehl überprüft, ob die Tabelle tatsächlich erstellt wurde, indem er im PostgreSQL-Katalog nachgeschlagen wird.

Es wird empfohlen, Tests für Ihren Code zu erstellen. Mit Tests können Sie das Verhalten des Codes überprüfen. Sie können für jede Funktion, Methode oder jeden anderen Teil Ihres Systems eine Prüfung schreiben und sicherstellen, dass diese so funktioniert, wie Sie es erwarten, ohne die Dinge manuell testen zu müssen.

Das Testframework jest eignet sich hervorragend zum Schreiben von Tests für Node.js-Anwendungen. Jest durchsucht die Dateien im Projekt nach Testdateien und führt sie einzeln aus. Installieren Sie Jest mit der Option "+ - save-dev ", die " npm +" mitteilt, dass das Modul zum Ausführen des Programms nicht erforderlich ist, jedoch eine Abhängigkeit für die Entwicklung der Anwendung darstellt:

npm install --save-dev jest

Sie schreiben Tests, um zu überprüfen, ob Sie Datensätze in Ihre Datenbank einfügen, daraus lesen und daraus löschen können. Diese Tests stellen sicher, dass Ihre Datenbankverbindung und Berechtigungen ordnungsgemäß konfiguriert sind, und bieten einige Tests, die Sie später in Ihrer CI / CD-Pipeline verwenden können.

Erstellen Sie die Datei + database.test.js +:

nano database.test.js

Fügen Sie den folgenden Inhalt hinzu. Importieren Sie zunächst den Datenbankcode:

database.test.js

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

. . .

Um sicherzustellen, dass die Datenbank einsatzbereit ist, rufen Sie + sync () + in der Funktion + beforeAll + auf:

database.test.js

. . .

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

. . .

Der erste Test erstellt einen Personendatensatz in der Datenbank. Die Bibliothek "+ sequelize " führt alle Abfragen asynchron aus, dh sie wartet nicht auf die Ergebnisse der Abfrage. Damit der Test auf Ergebnisse wartet, damit Sie sie überprüfen können, müssen Sie die Schlüsselwörter ` async ` und ` await ` verwenden. Dieser Test ruft die Methode ` create () ` auf, um eine neue Zeile in die Datenbank einzufügen. Verwenden Sie ` expect `, um die Spalte ` person.id ` mit ` 1 +` zu vergleichen. Der Test schlägt fehl, wenn Sie einen anderen Wert erhalten:

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);
});

. . .

Verwenden Sie im nächsten Test die Methode "+ findByPk () ", um die Zeile mit " id = 1 " abzurufen. Überprüfen Sie dann die Werte " firstName " und " lastName ". Verwenden Sie erneut " async " und " await +":

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.');
});

. . .

Testen Sie abschließend das Entfernen einer Person aus der Datenbank. Die Methode + destroy () + löscht die Person mit + id = 1 +. Um sicherzustellen, dass es funktioniert, versuchen Sie, die Person ein zweites Mal abzurufen und zu überprüfen, ob der zurückgegebene Wert "+ null +" ist:

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();
});

. . .

Fügen Sie schließlich diesen Code hinzu, um die Verbindung zur Datenbank mit + close () + zu schließen, sobald alle Tests abgeschlossen sind:

app.js

. . .

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

Speicher die Datei.

Der Befehl + jest + führt die Testsuite für Ihr Programm aus, Sie können jedoch auch Befehle in + package.json + speichern. Öffnen Sie diese Datei in Ihrem Editor:

nano package.json

Suchen Sie das Schlüsselwort "+ scripts" und ersetzen Sie die vorhandene "+ test" -Zeile (die nur ein Platzhalter war). Der Testbefehl lautet + jest +:

. . .

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

. . .

Jetzt können Sie + npm run test + aufrufen, um die Testsuite aufzurufen. Dies kann ein längerer Befehl sein, aber wenn Sie den Befehl "+ jest " später ändern müssen, müssen sich die externen Dienste nicht ändern. Sie können weiterhin " npm run test +" aufrufen.

Führen Sie die Tests durch:

npm run test

Überprüfen Sie dann die Ergebnisse:

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.

Mit dem getesteten Datenbankcode können Sie den API-Service erstellen, um die Personen im Adressbuch zu verwalten.

Um HTTP-Anfragen zu bearbeiten, verwenden Sie das https://expressjs.com [Express] Web-Framework. Installieren Sie Express und speichern Sie es als Abhängigkeit mit + npm install:

npm install --save express

Sie benötigen außerdem das https://www.npmjs.com/package/body-parser [+ body-parser +] -Modul, mit dem Sie auf den HTTP-Anforderungshauptteil zugreifen können. Installieren Sie dies auch als Abhängigkeit:

npm install --save body-parser

Erstellen Sie die Hauptanwendungsdatei + app.js:

nano app.js

Importieren Sie die Module "+ express", "+ body-parser" und "+ database". Erstellen Sie dann eine Instanz des Moduls "+ express " mit dem Namen " app ", um den Dienst zu steuern und zu konfigurieren. Sie verwenden " app.use () ", um Funktionen wie Middleware hinzuzufügen. Verwenden Sie dies, um das ` body-parser +` Modul hinzuzufügen, damit die Anwendung url-encoded Strings lesen kann:

app.js

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

. . .

Fügen Sie der Anwendung anschließend Routen hinzu. Routen ähneln Schaltflächen in einer App oder Website. Sie lösen eine Aktion in Ihrer Anwendung aus. Routen verknüpfen eindeutige URLs mit Aktionen in der Anwendung. Jede Route dient einem bestimmten Pfad und unterstützt einen anderen Vorgang.

Die erste Route, die Sie definieren, verarbeitet "+ GET " - Anforderungen für den " / person / $ ID " - Pfad, der den Datenbankeintrag für die Person mit der angegebenen ID anzeigt. Express setzt automatisch den Wert der angeforderten " $ ID " in die Variable " req.params.id +".

Die Anwendung muss mit den als JSON-Zeichenfolge codierten Personendaten antworten. Verwenden Sie wie in den Datenbanktests die Methode "+ findByPk () ", um die Person nach ID abzurufen und auf die Anforderung mit dem HTTP-Status " 200 +" (OK) zu antworten, und senden Sie den Personendatensatz als JSON. Fügen Sie den folgenden Code hinzu:

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));
       });
});

. . .

Fehler führen dazu, dass der Code in + catch () + ausgeführt wird. Wenn beispielsweise die Datenbank inaktiv ist, schlägt die Verbindung fehl und wird stattdessen ausgeführt. Setzen Sie bei Problemen den HTTP-Status auf "+ 500 +" (Internal Server Error) und senden Sie die Fehlermeldung an den Benutzer zurück:

Fügen Sie eine weitere Route hinzu, um eine Person in der Datenbank zu erstellen. Diese Route verarbeitet "+ PUT" -Anfragen und greift auf die Personendaten vom "+ req.body" zu. Verwenden Sie die Methode + create () +, um eine Zeile in die Datenbank einzufügen:

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));
       });
});

. . .

Fügen Sie eine weitere Route hinzu, um "+ DELETE " - Anforderungen zu verarbeiten, die Datensätze aus dem Adressbuch entfernen. Verwenden Sie zunächst die ID, um den Datensatz zu suchen, und entfernen Sie ihn dann mit der Methode " destroy +":

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));
       });
});

. . .

Fügen Sie der Einfachheit halber eine Route hinzu, die alle Personen in der Datenbank mit dem Pfad "+ / all +" abruft:

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));
       });
});

. . .

Ein letzter Weg links. Wenn die Anforderung mit keiner der vorherigen Routen übereinstimmt, senden Sie den Statuscode "+ 404 +" (nicht gefunden):

app.js

. . .

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

. . .

Fügen Sie abschließend die Methode + listen () + hinzu, mit der der Dienst gestartet wird. Wenn die Umgebungsvariable "+ PORT " definiert ist, lauscht der Dienst an diesem Port. Andernfalls wird standardmäßig der Port " 3000 +" verwendet:

app.js

. . .

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

Wie Sie gelernt haben, können Sie in der Datei + package.json verschiedene Befehle definieren, um Tests auszuführen, Ihre Apps zu starten und andere Aufgaben zu erledigen. Dadurch können Sie häufig Befehle ausführen, bei denen Sie weniger tippen müssen. Fügen Sie einen neuen Befehl auf "+ package.json +" hinzu, um die Anwendung zu starten. Bearbeiten Sie die Datei:

nano package.json

Fügen Sie den Befehl + start + hinzu, so dass er wie folgt aussieht:

package.json

. . .

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

 },

. . .

Vergessen Sie nicht, ein Komma in die vorherige Zeile einzufügen, da im Abschnitt "+ scripts +" die Einträge durch Kommas getrennt sein müssen.

Speichern Sie die Datei und starten Sie die Anwendung zum ersten Mal. Laden Sie zuerst die Umgebungsdatei mit + source +; Dadurch werden die Variablen in die Sitzung importiert und der Anwendung zur Verfügung gestellt. Dann starten Sie die Anwendung mit + npm run start +:

source ./.env
npm run start

Die App startet am Port "+ 3000 +":

Outputapp is running on port 3000

Öffnen Sie einen Browser und navigieren Sie zu "+ http: // localhost: 3000 / all ". Sie sehen eine Seite mit " [] +".

Wechseln Sie zurück zu Ihrem Terminal und drücken Sie "+ CTRL-C +", um die Anwendung zu stoppen.

Jetzt ist ein ausgezeichneter Zeitpunkt, um Codequalitätstests hinzuzufügen. Tools für die Codequalität, auch Linters genannt, durchsuchen das Projekt nach Problemen im Code. Schlechte Codierungsmethoden wie das Verlassen nicht verwendeter Variablen, das Nichtbeenden von Anweisungen mit einem Semikolon oder das Fehlen von geschweiften Klammern können zu Fehlern führen, die schwer zu finden sind.

Installieren Sie das Tool jshint, einen JavaScript-Linter, als Entwicklungsabhängigkeit:

npm install --save-dev jshint

Im Laufe der Jahre wurde JavaScript über Updates, Funktionen und Syntaxänderungen informiert. Die Sprache wurde von http://ecma-international.org [ECMA International] unter dem Namen „ECMAScript“ standardisiert. Ungefähr einmal im Jahr veröffentlicht ECMA eine neue Version von ECMAScript mit neuen Funktionen.

Standardmäßig geht + jshint + davon aus, dass Ihr Code mit ES6 (ECMAScript Version 6) kompatibel ist, und gibt einen Fehler aus, wenn Schlüsselwörter gefunden werden, die in dieser Version nicht unterstützt werden. Sie möchten die Version finden, die mit Ihrem Code kompatibel ist. Wenn Sie sich die feature table für alle aktuellen Versionen ansehen, werden Sie feststellen, dass die Schlüsselwörter "+ async / await +" erst mit ES8 eingeführt wurden. Sie haben beide Schlüsselwörter im Datenbanktestcode verwendet, sodass die minimale kompatible Version auf ES8 festgelegt ist.

Erstellen Sie eine Datei mit dem Namen "+ .jshintrc ", um " jshint +" die von Ihnen verwendete Version mitzuteilen:

nano .jshintrc

Geben Sie in der Datei "+ esversion " an. Die Datei ` jshintrc +` verwendet JSON. Erstellen Sie daher ein neues JSON-Objekt in der Datei:

jshintrc
{ "esversion": 8 }

Speichern Sie die Datei und beenden Sie den Editor.

Fügen Sie einen Befehl hinzu, um "+ jshint " auszuführen. Bearbeiten Sie ` package.json`:

nano package.json

Fügen Sie Ihrem Projekt im Abschnitt "+ scripts " von " package.json " einen " lint +" -Befehl hinzu. Der Befehl ruft das Lint-Tool für alle bisher erstellten JavaScript-Dateien auf:

package.json

. . .

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

 },

. . .

Jetzt können Sie den Linter ausführen, um Probleme zu finden:

npm run lint

Es sollte keine Fehlermeldung geben:

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

Wenn Fehler auftreten, zeigt "+ jshint +" die Zeile an, in der das Problem aufgetreten ist.

Sie haben das Projekt abgeschlossen und sichergestellt, dass es funktioniert. Fügen Sie die Dateien zum Repository hinzu, übertragen Sie sie und übertragen Sie die Änderungen:

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

Jetzt können Sie Semaphore zum Testen, Erstellen und Bereitstellen der Anwendung konfigurieren, indem Sie Semaphore mit Ihrem DigitalOcean Personal Access Token und Ihren Datenbankanmeldeinformationen konfigurieren.

Schritt 3 - Erstellen von Secrets in Semaphore

Es gibt einige Informationen, die nicht zu einem GitHub-Repository gehören. Passwörter und API-Token sind hierfür gute Beispiele. Sie haben diese vertraulichen Daten in einer separaten Datei gespeichert und in Ihre Umgebung geladen. Wenn Sie Semaphore verwenden, können Sie mit Secrets vertrauliche Daten speichern.

Es gibt drei Arten von Geheimnissen im Projekt:

  • Docker Hub: Der Benutzername und das Kennwort Ihres Docker Hub-Kontos.

  • DigitalOcean Personal Access Token: Zum Bereitstellen der Anwendung auf Ihrem Kubernetes-Cluster.

  • Umgebungsvariablen: für Datenbankbenutzername und Kennwortverbindungsparameter.

Öffnen Sie zum Erstellen des ersten Geheimnisses Ihren Browser und melden Sie sich bei der Website https://semaphoreci.com [Semaphore] an. Klicken Sie im linken Navigationsmenü unter der Überschrift * CONFIGURATION * auf * Secrets *. Klicken Sie auf die Schaltfläche * Create New Secret *.

Geben Sie als * Name des Geheimnisses * + dockerhub + ein. Erstellen Sie dann unter * Umgebungsvariablen * zwei Umgebungsvariablen:

  • + DOCKER_USERNAME: Ihr Docker Hub-Benutzername.

  • + DOCKER_PASSWORD +: Ihr DockerHub-Passwort.

Klicken Sie auf * Änderungen speichern *.

Erstellen Sie ein zweites Geheimnis für Ihr DigitalOcean Personal Access Token. Klicken Sie im linken Navigationsmenü erneut auf * Secrets * und dann auf * Create New Secret *. Rufen Sie dieses geheime "+ do-access-token " auf und erstellen Sie einen Umgebungswert mit dem Namen " DO_ACCESS_TOKEN +" mit dem Wert, der für Ihr persönliches Zugriffstoken festgelegt wurde:

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

Bewahre das Geheimnis auf.

Für das nächste Geheimnis laden Sie die Datei "+ .env +" aus dem Stammverzeichnis des Projekts hoch, anstatt die Umgebungsvariablen direkt festzulegen.

Erstelle ein neues Geheimnis namens "+ env-production". Klicken Sie im Abschnitt * Files * auf den Link * Upload file *, um Ihre + .env + -Datei zu suchen und hochzuladen, und weisen Sie Semaphore an, sie unter + / home / semaphore / env-production + abzulegen.

Die Umgebungsvariablen sind alle konfiguriert. Jetzt können Sie mit der Einrichtung der kontinuierlichen Integration beginnen.

Schritt 4 - Hinzufügen Ihres Projekts zu Semaphore

In diesem Schritt fügen Sie Ihr Projekt zu Semaphore hinzu und starten die Continuous Integration (CI) -Pipeline.

Verknüpfen Sie zunächst Ihr GitHub-Repository mit Semaphore:

  1. Melden Sie sich bei Ihrem https://semaphoreci.com [Semaphore] -Konto an.

  2. Klicken Sie auf das Symbol * + * neben * PROJEKTE *.

  3. Klicken Sie auf die Schaltfläche * Repository hinzufügen * neben Ihrem Repository.

image: https: //assets.digitalocean.com/articles/semaphore_doks/eSZWvM9.png [Repository zu Semaphore hinzufügen]

Nachdem Semaphore verbunden ist, werden alle Änderungen im Repository automatisch übernommen.

Sie können jetzt die Continuous Integration-Pipeline für die Anwendung erstellen. Eine Pipeline definiert den Pfad, den der Code zurücklegen muss, um erstellt, getestet und bereitgestellt zu werden. Die Pipeline wird bei jeder Änderung im GitHub-Repository automatisch ausgeführt.

Stellen Sie zunächst sicher, dass Semaphore dieselbe Version von Node verwendet, die Sie während der Entwicklung verwendet haben. Sie können überprüfen, welche Version auf Ihrem Computer ausgeführt wird:

node -v
Outputv10.16.0

Sie können Semaphore mitteilen, welche Version von Node.js verwendet werden soll, indem Sie in Ihrem Repository eine Datei mit dem Namen "+ .nvmrc " erstellen. Intern verwendet Semaphore https://github.com/nvm-sh/nvm/blob/master/README.md[node version manager], um zwischen den Versionen von Node.js zu wechseln. Erstellen Sie die Datei " .nvmrc " und setzen Sie die Version auf " 10.16.0 +":

echo '10.16.0' > .nvmrc

Semaphor-Pipelines befinden sich im Verzeichnis "+ .semaphore +". Erstellen Sie das Verzeichnis:

mkdir .semaphore

Erstellen Sie eine neue Pipeline-Datei. Die anfängliche Pipeline heißt immer + semaphore.yml +. In dieser Datei definieren Sie alle Schritte, die zum Erstellen und Testen der Anwendung erforderlich sind.

nano .semaphore/semaphore.yml

In der ersten Zeile muss die Version der Semaphore-Datei festgelegt werden. der aktuelle Stable ist + v1.0 +. Außerdem benötigt die Pipeline einen Namen. Fügen Sie diese Zeilen zu Ihrer Datei hinzu:

semaphore/semaphore.yml
version:
name:

. . .

Semaphore stellt automatisch virtuelle Maschinen bereit, um die Aufgaben auszuführen. Es stehen verschiedene Maschinen zur Auswahl. Verwenden Sie für die Integrationsaufträge das "+ e1-standard-2 +" (2 CPUs, 4 GB RAM) zusammen mit einem Ubuntu 18.04-Betriebssystem. Fügen Sie der Datei die folgenden Zeilen hinzu:

semaphore/semaphore.yml
. . .

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

. . .

Semaphore verwendet blocks, um die Aufgaben zu organisieren. Jeder Block kann einen oder mehrere Jobs haben. Alle Jobs in einem Block werden parallel ausgeführt, jeder in einer isolierten Maschine. Semaphore wartet auf die Übergabe aller Jobs in einem Block, bevor der nächste gestartet wird.

Definieren Sie zunächst den ersten Block, der alle JavaScript-Abhängigkeiten installiert, um die Anwendung zu testen und auszuführen:

semaphore/semaphore.yml
. . .

blocks:
 - name: Install dependencies
   task:

. . .

Sie können Umgebungsvariablen definieren, die für alle Jobs gelten, z. B. "+ NODE_ENV " auf " test " setzen, damit Node.js weiß, dass dies eine Testumgebung ist. Füge diesen Code nach ` task +` ein:

semaphore/semaphore.yml
. . .
   task:




. . .

Befehle im Abschnitt prologue werden vor jedem Job im Block ausgeführt. Hier können Sie bequem Einrichtungsaufgaben definieren. Sie können checkout verwenden, um das GitHub-Repository zu klonen. Dann aktiviert + nvm use + die entsprechende Node.js-Version, die Sie in + .nvmrc + angegeben haben. Fügen Sie den Abschnitt "+ Prolog +" hinzu:

semaphore/semaphore.yml
   task:
. . .

     prologue:
       commands:
         - checkout
         - nvm use

. . .

Fügen Sie anschließend diesen Code hinzu, um die Abhängigkeiten des Projekts zu installieren. Um Aufträge zu beschleunigen, bietet Semaphore das Tool cache an. Sie können + cache store + ausführen, um das Verzeichnis + node_modules im Semaphores-Cache zu speichern. + cache + ermittelt automatisch, welche Dateien und Verzeichnisse gespeichert werden sollen. Bei der zweiten Ausführung des Jobs wird mit + cache restore + das Verzeichnis wiederhergestellt.

semaphore/semaphore.yml
. . .

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

. . .

Fügen Sie einen weiteren Block hinzu, der zwei Jobs ausführt. Eine zum Ausführen des Flusentests und eine zum Ausführen der Testsuite der Anwendung.

semaphore/semaphore.yml
. . .

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

. . .

Der + Prolog wiederholt die gleichen Befehle wie im vorherigen Block und stellt` + node_modules` aus dem Cache wieder her. Da dieser Block Tests ausführt, setzen Sie die Umgebungsvariable "+ NODE_ENV" auf "+ test".

Fügen Sie nun die Jobs hinzu. Der erste Job führt die Codequalitätsprüfung mit jshint durch:

semaphore/semaphore.yml
. . .

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

. . .

Der nächste Job führt die Komponententests aus. Sie benötigen eine Datenbank, um sie auszuführen, da Sie Ihre Produktionsdatenbank nicht verwenden möchten. Die sem-service von Semaphore kann eine lokale PostgreSQL-Datenbank in der Testumgebung starten, die vollständig isoliert ist. Die Datenbank wird zerstört, wenn der Job endet. Starten Sie diesen Dienst und führen Sie die Tests aus:

semaphore/semaphore.yml
. . .

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

Speichern Sie die Datei + .semaphore / semaphore.yml +.

Fügen Sie nun die Änderungen zum GitHub-Repository hinzu und übertragen Sie sie:

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

Sobald der Code an GitHub gesendet wird, startet Semaphore die CI-Pipeline:

image: https: //assets.digitalocean.com/articles/semaphore_doks/g7gd8f1.png [Laufender Workflow]

Sie können auf die Pipeline klicken, um die Blöcke und Jobs sowie deren Ausgabe anzuzeigen.

Als Nächstes erstellen Sie eine neue Pipeline, die ein Docker-Image für die Anwendung erstellt.

Schritt 5 - Erstellen von Docker-Images für die Anwendung

Ein Docker-Image ist die Basiseinheit einer Kubernetes-Bereitstellung. Das Image sollte alle Binärdateien, Bibliotheken und den Code enthalten, die zum Ausführen der Anwendung erforderlich sind. Ein Docker-Container ist keine schlanke virtuelle Maschine, verhält sich aber wie eine. Die Docker Hub-Registrierung enthält Hunderte von gebrauchsfertigen Images, aber wir werden unsere eigenen erstellen.

In diesem Schritt fügen Sie eine neue Pipeline hinzu, um ein benutzerdefiniertes Docker-Image für Ihre App zu erstellen und an Docker Hub zu übertragen.

Um ein benutzerdefiniertes Image zu erstellen, erstellen Sie ein + Dockerfile:

nano Dockerfile

Das + Dockerfile + ist ein Rezept zum Erstellen des Bildes. Sie können die Distribution official Node.js als Ausgangspunkt verwenden, anstatt von vorne zu beginnen. Fügen Sie dies zu Ihrer + Dockerfile + hinzu:

Dockerfile

FROM node:10.16.0-alpine

. . .

Fügen Sie dann einen Befehl hinzu, der "+ package.json" und "+ package-lock.json" kopiert, und installieren Sie dann die Knotenmodule im Image:

Dockerfile

. . .

COPY package*.json ./
RUN npm install

. . .

Das erstmalige Installieren der Abhängigkeiten beschleunigt nachfolgende Builds, da Docker diesen Schritt zwischenspeichert.

Fügen Sie nun diesen Befehl hinzu, der alle Anwendungsdateien im Projektstamm in das Image kopiert:

Dockerfile

. . .

COPY *.js ./

. . .

Schließlich gibt "+ EXPOSE " an, dass der Container auf Verbindungen an Port " 3000 " wartet, an dem die Anwendung empfangsbereit ist, und " CMD +" legt den Befehl fest, der ausgeführt werden soll, wenn der Container gestartet wird. Fügen Sie diese Zeilen zu Ihrer Datei hinzu:

Dockerfile

. . .

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

Speicher die Datei.

Mit dem Dockerfile können Sie eine neue Pipeline erstellen, sodass Semaphore das Image für Sie erstellen kann, wenn Sie Ihren Code an GitHub senden. Erstellen Sie eine neue Datei mit dem Namen "+ docker-build.yml +":

nano .semaphore/docker-build.yml

Starten Sie die Pipeline mit der gleichen Kesselplatte wie die CI-Pipeline, jedoch mit dem Namen "+ Docker build":

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

. . .

Diese Pipeline hat nur einen Block und einen Job. In Schritt 3 haben Sie ein Geheimnis mit dem Namen "+ dockerhub " mit Ihrem Docker Hub-Benutzernamen und -Kennwort erstellt. Hier importieren Sie diese Werte mit dem Schlüsselwort " secrets +". Füge diesen Code hinzu:

semaphore/docker-build.yml
. . .

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

. . .

Docker-Images werden in Repositorys gespeichert. Wir verwenden den offiziellen Docker Hub, der eine unbegrenzte Anzahl öffentlicher Bilder ermöglicht. Fügen Sie diese Zeilen hinzu, um den Code von GitHub auszuchecken, und verwenden Sie den Befehl + docker login, um sich bei Docker Hub zu authentifizieren.

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

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

. . .

Jedes Docker-Image wird durch die Kombination von Name und Tag vollständig identifiziert. Der Name entspricht normalerweise dem Produkt oder der Software, und das Tag entspricht der jeweiligen Version der Software. Zum Beispiel + node.10.16.0 +. Wenn kein Tag angegeben ist, verwendet Docker standardmäßig das spezielle "+ latest " - Tag. Daher wird empfohlen, das " latest +" - Tag zu verwenden, um auf das aktuellste Bild zu verweisen.

Fügen Sie den folgenden Code hinzu, um das Image zu erstellen und an Docker Hub zu übertragen:

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"

Wenn Docker das Image erstellt, werden Teile der vorhandenen Images wiederverwendet, um den Prozess zu beschleunigen. Der erste Befehl versucht, das "+ latest " - Image von Docker Hub abzurufen, damit es wiederverwendet werden kann. Semaphore stoppt die Pipeline, wenn einer der Befehle einen von Null verschiedenen Statuscode zurückgibt. Wenn das Repository beispielsweise kein " aktuelles " Image enthält, da dies beim ersten Versuch nicht der Fall ist, wird die Pipeline gestoppt. Sie können Semaphore zwingen, fehlgeschlagene Befehle zu ignorieren, indem Sie ` || anhängen true + `für den Befehl.

Der zweite Befehl erstellt das Bild. Um später auf dieses bestimmte Bild zu verweisen, können Sie es mit einer eindeutigen Zeichenfolge versehen. Semaphore bietet mehrere environment variables für Jobs an. Eines davon, "+ $ SEMAPHORE_WORKFLOW_ID +", ist eindeutig und wird von allen Pipelines im Workflow gemeinsam genutzt. Es ist praktisch, dieses Image später in der Bereitstellung zu referenzieren.

Mit dem dritten Befehl wird das Image an Docker Hub gesendet.

Die Build-Pipeline ist bereit, aber Semaphore startet sie erst, wenn Sie sie mit der Haupt-CI-Pipeline verbinden. Sie können mehrere Pipelines verketten, um komplexe Workflows mit mehreren Zweigen zu erstellen, indem Sie promotions verwenden.

Bearbeiten Sie die Hauptpipeline-Datei + .semaphore / semaphore.yml +:

nano .semaphore/semaphore.yml

Fügen Sie am Ende der Datei die folgenden Zeilen hinzu:

semaphore/semaphore.yml
. . .

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

+ auto_promote_on + definiert die Bedingung zum Starten der + docker build-Pipeline. In diesem Fall wird es ausgeführt, wenn alle in der Datei + semaphore.yml + definierten Jobs bestanden wurden.

Um die neue Pipeline zu testen, müssen Sie alle geänderten Dateien zu GitHub hinzufügen, festschreiben und übertragen:

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

Nach Abschluss der CI-Pipeline wird die Docker-Build-Pipeline gestartet.

Wenn dies abgeschlossen ist, wird Ihr neues Image in Ihrem Docker Hub-Repository angezeigt.

Sie müssen den Build-Prozess testen und das Image erstellen. Jetzt erstellen Sie die endgültige Pipeline für die Bereitstellung der Anwendung auf Ihrem Kubernetes-Cluster.

Schritt 6 - Einrichten der kontinuierlichen Bereitstellung für Kubernetes

Der Baustein einer Kubernetes-Bereitstellung ist pod. Ein Pod ist eine Gruppe von Containern, die als einzelne Einheit verwaltet werden. Die Container in einem Pod werden gemeinsam gestartet und gestoppt und laufen immer auf demselben Computer, wobei die Ressourcen gemeinsam genutzt werden. Jeder Pod hat eine IP-Adresse. In diesem Fall haben die Hülsen nur einen Behälter.

Hülsen sind vergänglich; Sie werden häufig erstellt und zerstört. Sie können nicht sagen, welche IP-Adresse jedem Pod zugewiesen werden soll, bis er gestartet wird. Um dies zu lösen, verwenden Sie services, die feste öffentliche IP-Adressen haben, damit eingehende Verbindungen lastausgeglichen werden können an die Schoten weitergeleitet.

Sie können Pods direkt verwalten, aber Kubernetes sollte dies besser über deployment erledigen. In diesem Abschnitt erstellen Sie ein deklaratives Manifest, das den endgültigen gewünschten Status für Ihren Cluster beschreibt. Das Manifest verfügt über zwei Ressourcen:

  • Bereitstellung: Startet die Pods in den Clusterknoten nach Bedarf und protokolliert deren Status. Da in diesem Lernprogramm ein Cluster mit drei Knoten verwendet wird, werden drei Pods bereitgestellt.

  • Service: fungiert als Einstiegspunkt für unsere Nutzer. Überwacht den Datenverkehr auf dem Port "+ 80 +" (HTTP) und leitet die Verbindung an die Pods weiter.

Erstellen Sie eine Manifestdatei mit dem Namen "+ deployment.yml +":

nano deployment.yml

Starten Sie das Manifest mit der Ressource "+ Deployment +". Fügen Sie der neuen Datei den folgenden Inhalt hinzu, um die Bereitstellung zu definieren:

deployment.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"


. . .

Für jede Ressource im Manifest müssen Sie ein "+ apiVersion " festlegen. Verwenden Sie für Bereitstellungen " apiVersion: apps / v1 ", eine stabile Version. Dann teilen Sie Kubernetes mit, dass es sich bei dieser Ressource um ein Deployment mit dem Typ "" handelt: Deployment + ". Jede Definition sollte einen Namen haben, der in + metadata.name + definiert ist.

Im Abschnitt "+ spec " teilen Sie Kubernetes den gewünschten Endzustand mit. Diese Definition fordert Kubernetes auf, 3 Pods mit ` Replikaten zu erstellen: 3 +`.

Labels sind Schlüssel-Wert-Paare, die zum Organisieren und Querverweisen von Kubernetes-Ressourcen verwendet werden. Sie definieren Beschriftungen mit + metadata.labels +, und Sie können mit + selector.matchLabels + nach übereinstimmenden Beschriftungen suchen. So verbinden Sie Elemente miteinander.

Der Schlüssel "+ spec.template " definiert ein Modell, mit dem Kubernetes jeden Pod erstellt. In " spec.template.metadata.labels " legen Sie eine Bezeichnung für die Pods fest: " app: addressbook +".

Mit "+ spec.selector.matchLabels " können Sie die Bereitstellung dazu bringen, alle Pods mit der Bezeichnung " app: addressbook +" zu verwalten. In diesem Fall machen Sie diese Bereitstellung für alle Pods verantwortlich.

Zuletzt definieren Sie das Bild, das in den Pods ausgeführt wird. In + spec.template.spec.containers + legen Sie den Bildnamen fest. Kubernetes ruft das Image nach Bedarf aus der Registrierung ab. In diesem Fall wird vom Docker Hub gezogen. Sie können auch Umgebungsvariablen für die Container festlegen, was von Vorteil ist, da Sie für die Datenbankverbindung mehrere Werte angeben müssen.

Damit das Bereitstellungsmanifest flexibel bleibt, verlassen Sie sich auf Variablen. Da das YAML-Format keine Variablen zulässt, ist die Datei noch nicht gültig. Sie lösen dieses Problem, wenn Sie die Bereitstellungspipeline für Semaphore definieren.

Das war’s für den Einsatz. Dies definiert aber nur die Schoten. Sie benötigen weiterhin einen Dienst, über den der Datenverkehr zu Ihren Pods geleitet werden kann. Sie können eine weitere Kubernetes-Ressource in derselben Datei hinzufügen, solange Sie drei Bindestriche (+ --- +) als Trennzeichen verwenden.

Fügen Sie den folgenden Code hinzu, um einen Lastenausgleichsdienst zu definieren, der eine Verbindung zu Pods mit der Bezeichnung "+ Adressbuch +" herstellt:

deployment.yml

. . .

---

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

Der Load Balancer empfängt Verbindungen über den Port "+ 80 " und leitet sie an den Port " 3000 +" des Pods weiter, an dem die Anwendung empfangsbereit ist.

Speicher die Datei.

Erstellen Sie jetzt eine Bereitstellungspipeline für Semaphore, die die App mithilfe des Manifests bereitstellt. Erstellen Sie eine neue Datei im Verzeichnis "+ .semaphore +":

nano .semaphore/deploy-k8s.yml

Beginnen Sie die Pipeline wie gewohnt und geben Sie die Version, den Namen und das Bild an:

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

. . .

Diese Pipeline wird zwei Blöcke haben. Der erste Block stellt die Anwendung im Kubernetes-Cluster bereit.

Definieren Sie den Block und importieren Sie alle Geheimnisse:

semaphore/deploy-k8s.yml
. . .

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

. . .

Speichern Sie den Namen Ihres DigitalOcean Kubernetes-Clusters in einer Umgebungsvariablen, damit Sie später darauf verweisen können:

semaphore/deploy-k8s.yml
. . .

     env_vars:
       - name: CLUSTER_NAME
         value:

. . .

DigitalOcean Kubernetes-Cluster werden mit einer Kombination aus zwei Programmen verwaltet: + kubectl + und + doctl +. Ersteres ist bereits im Image von Semaphore enthalten, letzteres jedoch nicht. Daher müssen Sie es installieren. Sie können den Abschnitt "+ Prolog +" verwenden, um dies zu tun.

Füge diesen Prologabschnitt hinzu:

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

. . .

Der erste Befehl lädt das offizielle release mit + wget + herunter. Der zweite Befehl dekomprimiert es mit + tar + und kopiert es in den lokalen Pfad. Sobald + doctl + installiert ist, kann es verwendet werden, um sich bei der DigitalOcean-API zu authentifizieren und die Kubernetes-Konfigurationsdatei für unseren Cluster anzufordern. Nachdem wir unseren Code ausgecheckt haben, sind wir mit dem Prolog fertig:

Als Nächstes folgt der letzte Teil unserer Pipeline: die Bereitstellung im Cluster.

Denken Sie daran, dass in + deployment.yml + einige Umgebungsvariablen vorhanden waren und YAML dies nicht zulässt. Infolgedessen funktioniert "+ deployment.yml " im aktuellen Status nicht. Um dies zu umgehen, geben Sie in der Umgebungsdatei "" ein, um die Variablen zu laden, und verwenden Sie dann den Befehl "+ envsubst", um die Variablen direkt mit den tatsächlichen Werten zu erweitern. Das Ergebnis, eine Datei mit dem Namen "+ deploy.yml ", ist eine vollständig gültige YAML mit den eingefügten Werten. Wenn die Datei vorhanden ist, können Sie die Bereitstellung mit ` kubectl apply +` starten:

semaphore/deploy-k8s.yml
. . .

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

. . .

Der zweite Block fügt das "+ latest +" - Tag zum Image in Docker Hub hinzu, um anzuzeigen, dass dies die aktuellste bereitgestellte Version ist. Wiederholen Sie die Docker-Anmeldeschritte, ziehen Sie, markieren Sie sie erneut und drücken Sie sie auf 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"

Speicher die Datei.

Diese Pipeline führt die Bereitstellung durch, kann jedoch nur gestartet werden, wenn das Docker-Image erfolgreich generiert und an Docker Hub übertragen wurde. Daher müssen Sie die Erstellungs- und Bereitstellungs-Pipelines mit einer Heraufstufung verbinden. Bearbeiten Sie die Docker-Build-Pipeline, um sie hinzuzufügen:

nano .semaphore/docker-build.yml

Fügen Sie die Promotion am Ende der Datei hinzu:

semaphore/docker-build.yml
. . .

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

Sie haben den CI / CD-Workflow eingerichtet.

Alles, was bleibt, ist die geänderten Dateien zu pushen und Semaphore die Arbeit machen zu lassen. Fügen Sie die Änderungen Ihres Repository hinzu, übertragen Sie sie und übertragen Sie sie:

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

Es dauert einige Minuten, bis die Bereitstellung abgeschlossen ist.

Lassen Sie uns als Nächstes die Anwendung testen.

Schritt 7 - Testen der Anwendung

Zu diesem Zeitpunkt ist die Anwendung aktiv. In diesem Schritt verwenden Sie "+ curl +", um den API-Endpunkt zu testen.

Sie müssen die öffentliche IP-Adresse kennen, die DigitalOcean Ihrem Cluster zugewiesen hat. Befolgen Sie diese Schritte, um es zu finden:

  1. Melden Sie sich bei Ihrem DigitalOcean-Konto an.

  2. Wählen Sie das Adressbuchprojekt aus

  3. Gehen Sie zu * Networking *.

  4. Klicken Sie auf * Load Balancers *.

  5. Die * IP-Adresse * wird angezeigt. Kopieren Sie die IP-Adresse.

Lassen Sie uns die "+ / all" -Route mit "+ curl" überprüfen:

curl -w "\n" /all

Sie können die Option "+ -w" \ n "" verwenden, um sicherzustellen, dass " curl +" alle Zeilen druckt:

Da sich noch keine Datensätze in der Datenbank befinden, erhalten Sie als Ergebnis ein leeres JSON-Array:

Output[]

Erstellen Sie einen neuen Personendatensatz, indem Sie eine "+ PUT" -Anforderung an den "+ / person" -Endpunkt senden:

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

Die API gibt das JSON-Objekt für die Person zurück:

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

Erstellen Sie eine zweite Person:

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

Die Ausgabe gibt an, dass eine zweite Person erstellt wurde:

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

Stellen Sie nun eine "+ GET " -Anforderung, um die Person mit der " id " von " 2 +" zu erhalten:

curl -w "\n" /person/2

Der Server antwortet mit den von Ihnen angeforderten Daten:

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

Um die Person zu löschen, senden Sie eine "+ DELETE" -Anforderung:

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

Dieser Befehl gibt keine Ausgabe zurück.

Sie sollten nur eine Person in Ihrer Datenbank haben, die mit der "+ id " von " 1 ". Versuchen Sie erneut, " / all +" zu erhalten:

curl -w "\n" /all

Der Server antwortet mit einem Array von Personen, die nur einen Datensatz enthalten:

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

Zu diesem Zeitpunkt befindet sich nur noch eine Person in der Datenbank.

Damit sind die Tests für alle Endpunkte in unserer Anwendung abgeschlossen, und das Lernprogramm ist beendet.

Fazit

In diesem Tutorial haben Sie eine vollständige Node.js-Anwendung von Grund auf neu geschrieben, die den von DigitalOcean verwalteten PostgreSQL-Datenbankdienst verwendet. Anschließend haben Sie mithilfe der CI / CD-Pipelines von Semaphore einen Workflow vollständig automatisiert, in dem ein Container-Image getestet und erstellt, auf Docker Hub hochgeladen und auf DigitalOcean Kubernetes bereitgestellt wurde.

Weitere Informationen zu Kubernetes finden Sie unter An Introduction to Kubernetes und im übrigen https://www.digitalocean.com von DigitalOcean / community / tags / kubernetes [Kubernetes Tutorials].

Nachdem Ihre Anwendung bereitgestellt wurde, können Sie adding a domain name, https://www.digitalocean.com/ in Betracht ziehen. docs / databases / how-to / clusters / secure-clusters [Sichern Ihres Datenbankclusters] oder Einrichten von alerts für Ihre Datenbank.