著者は、 Open Internet / Free Speech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Kubernetes を使用すると、ユーザーは1つのコマンドで復元力のあるスケーラブルなサービスを作成できます。 良すぎると思われるものと同様に、問題があります。最初に適切な Docker イメージを準備し、徹底的にテストする必要があります。

継続的インテグレーション(CI)は、更新のたびにアプリケーションをテストする方法です。 これを手動で行うのは面倒でエラーが発生しやすいですが、CIプラットフォームはテストを実行し、エラーを早期にキャッチして、エラーが発生したポイントを特定します。 リリースと展開の手順は、多くの場合、複雑で時間がかかり、信頼性の高いビルド環境が必要です。 継続的デリバリー(CD)を使用すると、人間の介入なしに、更新ごとにアプリケーションを構築およびデプロイできます。

プロセス全体を自動化するには、継続的インテグレーションおよびデリバリー(CI / CD)プラットフォームであるセマフォを使用します。

このチュートリアルでは、Node.jsを使用してアドレスブックAPIサービスを構築します。 APIは、データベース内の人を作成、削除、および検索するための単純な RESTfulAPIインターフェースを公開します。 Git を使用して、コードをGitHubにプッシュします。 次に、セマフォを使用してアプリケーションをテストし、Dockerイメージをビルドして、 DigitalOceanKubernetesクラスターにデプロイします。 データベースの場合、 DigitalOcean ManagedDatabasesを使用してPostgreSQLクラスターを作成します。

前提条件

読み進める前に、次のものがあることを確認してください。

  • DigitalOceanアカウントとパーソナルアクセストークン。 パーソナルアクセストークンの作成に従って、アカウントにトークンを設定します。
  • DockerHubアカウント。
  • GitHubアカウント。
  • セマフォアカウント。 GitHubアカウントでサインアップできます。
  • プロジェクト用のaddressbookという新しいGitHubリポジトリ。 リポジトリを作成するときは、このリポジトリをREADME チェックボックスで初期化し、 Add.gitignoreメニューでNodeを選択します。 詳細については、GitHubのリポジトリの作成ヘルプページをフォローしてください。
  • Git がローカルマシンにインストールされ、がGitHubアカウントで動作するようにを設定します。 慣れていない場合や復習が必要な場合は、Gitリファレンスガイドの使用方法をお読みください。
  • curlがローカルマシンにインストールされています。
  • Node.jsがローカルマシンにインストールされています。 このチュートリアルでは、Node.jsバージョン10.16.0を使用します。

ステップ1—データベースとKubernetesクラスターを作成する

アプリケーションを強化するサービス(DigitalOceanデータベースクラスターとDigitalOcean Kubernetesクラスター)をプロビジョニングすることから始めます。

DigitalOceanアカウントにログインし、プロジェクトを作成します。 プロジェクトを使用すると、アプリケーションを構成するすべてのリソースを整理できます。 プロジェクトをaddressbookと呼びます。

次に、PostgreSQLクラスターを作成します。 PostgreSQLデータベースサービスはアプリケーションのデータを保持します。 利用可能な最新バージョンを選択できます。 サービスの準備が整うまでに数分かかるはずです。

PostgreSQLサービスの準備ができたら、データベースとユーザーを作成します。 データベース名をaddessbook_dbに設定し、ユーザー名をaddressbook_userに設定します。 新しいユーザー用に生成されたパスワードをメモします。 データベースは、PostgreSQLのデータを整理する方法です。 通常、各アプリケーションには独自のデータベースがありますが、これに関する厳格なルールはありません。 アプリケーションは、ユーザー名とパスワードを使用してデータベースにアクセスし、データを保存および取得できるようにします。

最後に、Kubernetesクラスターを作成します。 データベースが実行されているのと同じリージョンを選択します。 cluserにaddressbook-serverという名前を付け、ノード数を3に設定します。

ノードのプロビジョニング中に、アプリケーションの構築を開始できます。

ステップ2—アプリケーションの作成

デプロイするアドレス帳アプリケーションを作成しましょう。 まず、前提条件で作成したGitHubリポジトリのクローンを作成して、GitHubが作成した.gitignoreファイルのローカルコピーを作成します。手動で作成しなくても、アプリケーションコードをすばやくコミットできます。リポジトリ。 ブラウザを開き、新しいGitHubリポジトリに移動します。 クローンまたはダウンロードボタンをクリックして、提供されたURLをコピーします。 Gitを使用して、空のリポジトリをマシンに複製します。

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

プロジェクトディレクトリを入力します。

  1. cd addressbook

リポジトリのクローンを作成すると、アプリの作成を開始できます。 データベースと対話するモジュールとHTTPサービスを提供するモジュールの2つのコンポーネントを構築します。 データベースモジュールは、名簿データベースから人を保存および取得する方法を認識し、HTTPモジュールは要求を受信してそれに応じて応答します。

厳密には必須ではありませんが、コードの記述中にコードをテストすることをお勧めします。そのため、テストモジュールも作成します。 これは、アプリケーションの計画されたレイアウトです。

  • database.js:データベースモジュール。 データベース操作を処理します。
  • app.js:エンドユーザーモジュールとメインアプリケーション。 ユーザーが接続するためのHTTPサービスを提供します。
  • database.test.js:データベースモジュールをテストします。

さらに、プロジェクトの package.json ファイルが必要になります。このファイルには、プロジェクトとその必要な依存関係が記述されています。 エディターを使用して手動で作成することも、npmを使用してインタラクティブに作成することもできます。 npm initコマンドを実行して、ファイルをインタラクティブに作成します。

  1. npm init

コマンドは、開始するためにいくつかの情報を要求します。 例に示すように値を入力します。 リストに回答が表示されない場合は、回答を空白のままにします。これは、括弧内のデフォルト値を使用します。

npm output
package name: (addressbook) addressbook version: (1.0.0) 1.0.0 description: Addressbook API and database entry point: (index.js) app.js test command: git repository: URL for your GitHub repository keywords: author: Sammy the Shark <[email protected]>" 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) yes

これで、コードの記述を開始できます。 データベースは、開発しているサービスの中核です。 他のコンポーネントを作成する前に、適切に設計されたデータベースモデルを用意することが不可欠です。 したがって、データベースコードから始めるのは理にかなっています。

アプリケーションのすべてのビットをコーディングする必要はありません。 Node.jsには、再利用可能なモジュールの大規模なライブラリがあります。 たとえば、プロジェクトに Sequelize ORM モジュールがある場合は、SQLクエリを作成する必要はありません。 このモジュールは、データベースをJavaScriptオブジェクトおよびメソッドとして処理するインターフェースを提供します。 また、データベースにテーブルを作成することもできます。 SequelizeがPostgreSQLで動作するには、pgモジュールが必要です。

npm installコマンドと--saveオプションを使用してモジュールをインストールします。これにより、npmにモジュールをpackage.jsonに保存するように指示されます。 次のコマンドを実行して、sequelizepgの両方をインストールします。

  1. npm install --save sequelize pg

データベースコードを保持する新しいJavaScriptファイルを作成します。

  1. nano database.js

次の行をファイルに追加して、sequelizeモジュールをインポートします。

database.js
const Sequelize = require('sequelize');

. . .

次に、その行の下で、システム環境から取得するデータベース接続パラメーターを使用してsequelizeオブジェクトを初期化します。 これにより、コードにクレデンシャルが含まれないため、コードをGitHubにプッシュするときに誤ってクレデンシャルを共有することがなくなります。 process.envを使用して環境変数にアクセスし、JavaScriptの||演算子を使用して未定義の変数のデフォルトを設定できます。

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

. . .

次に、Personモデルを定義します。 例が複雑になりすぎないように、作成するフィールドはfirstNamelastNameの2つだけで、どちらも文字列値を格納します。 次のコードを追加して、モデルを定義します。

database.js
. . .

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

. . .

これにより2つのフィールドが定義され、firstNameallowNull: falseで必須になります。 Sequelizeのモデル定義ドキュメントには、使用可能なデータ型とオプションが示されています。

最後に、sequelizeオブジェクトとPersonモデルをエクスポートして、他のモジュールがそれらを使用できるようにします。

database.js
. . .

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

開発中いつでも呼び出すことができる別のファイルにテーブル作成スクリプトがあると便利です。 これらのタイプのファイルは移行と呼ばれます。 このコードを保持する新しいファイルを作成します。

  1. nano migrate.js

これらの行をファイルに追加して、定義したデータベースモデルをインポートし、sync()関数を呼び出してデータベースを初期化します。これにより、モデルのテーブルが作成されます。

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

アプリケーションは、システム環境変数でデータベース接続情報を探しています。 .envというファイルを作成して、これらの値を保持します。これらの値は、開発中に環境にロードされます。

  1. nano .env

次の変数宣言をファイルに追加します。 DB_HOSTDB_PORT、およびDB_PASSWORDをDigitalOceanPostgreSQLクラスターに関連付けられているものに設定していることを確認してください。

.env
export DB_SCHEMA=addressbook_db
export DB_USER=addressbook_user
export DB_PASSWORD=your_db_user_password
export DB_HOST=your_db_cluster_host
export DB_PORT=your_db_cluster_port
export DB_SSL=true
export PORT=3000

ファイルを保存します。

警告:環境ファイルをソース管理にチェックインしないでください。 彼らは通常機密情報を持っています。

リポジトリの作成時にデフォルトの.gitignoreファイルを定義したため、このファイルはすでに無視されています。

これで、データベースを初期化する準備が整いました。 環境ファイルをインポートして、migrate.jsを実行します。

  1. source ./.env
  2. node migrate.js

これにより、データベーステーブルが作成されます。

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;

出力には2つのコマンドが表示されます。 最初のものは、定義に従ってPeopleテーブルを作成します。 2番目のコマンドは、PostgreSQLカタログでテーブルを検索して、テーブルが実際に作成されたことを確認します。

コードのテストを作成することをお勧めします。 テストを使用すると、コードの動作を検証できます。 システムの各関数、メソッド、またはその他の部分のチェックを記述し、手動でテストしなくても、期待どおりに機能することを確認できます。

jest テストフレームワークは、Node.jsアプリケーションに対するテストを作成するのに最適です。 Jestは、プロジェクト内のファイルをスキャンしてテストファイルを探し、それらを1つずつ実行します。 --save-devオプションを指定してJestをインストールします。これにより、npmに、モジュールがプログラムを実行する必要がないことを通知しますが、これはアプリケーション開発の依存関係です。

  1. npm install --save-dev jest

データベースからレコードを挿入、読み取り、および削除できることを確認するためのテストを作成します。 これらのテストは、データベース接続とアクセス許可が適切に構成されていることを確認し、後でCI/CDパイプラインで使用できるいくつかのテストも提供します。

database.test.jsファイルを作成します。

  1. nano database.test.js

次のコンテンツを追加します。 データベースコードをインポートすることから始めます。

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

. . .

データベースを使用できるようにするには、beforeAll関数内でsync()を呼び出します。

database.test.js
. . .

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

. . .

最初のテストでは、データベースに個人レコードを作成します。 sequelizeライブラリは、すべてのクエリを非同期で実行します。つまり、クエリの結果を待機しません。 テストで結果を待機させて検証できるようにするには、asyncおよびawaitキーワードを使用する必要があります。 このテストでは、create()メソッドを呼び出して、データベースに新しい行を挿入します。 expectを使用して、person.id列を1と比較します。 別の値を取得すると、テストは失敗します。

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

. . .

次のテストでは、findByPk()メソッドを使用して、id=1で行を取得します。 次に、firstNamelastNameの値を検証します。 もう一度、asyncawaitを使用します。

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

. . .

最後に、データベースから人を削除することをテストします。 destroy()メソッドは、id=1を持つ人を削除します。 それが機能したことを確認するために、もう一度その人を取得して、戻り値が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();
});

. . .

最後に、このコードを追加して、すべてのテストが終了したら、close()を使用してデータベースへの接続を閉じます。

app.js
. . .

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

ファイルを保存します。

jestコマンドは、プログラムのテストスイートを実行しますが、コマンドをpackage.jsonに保存することもできます。 このファイルをエディターで開きます。

  1. nano package.json

scriptsキーワードを見つけて、既存のtest行(単なるプレースホルダー)を置き換えます。 テストコマンドはjestです。

. . .

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

. . .

これで、npm run testを呼び出してテストスイートを呼び出すことができます。 これは長いコマンドかもしれませんが、後でjestコマンドを変更する必要がある場合は、外部サービスを変更する必要はありません。 npm run testを引き続き呼び出すことができます。

テストを実行します。

  1. npm run test

次に、結果を確認します。

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.

テストされたデータベースコードを使用して、アドレスブックのユーザーを管理するためのAPIサービスを構築できます。

HTTPリクエストを処理するには、 ExpressWebフレームワークを使用します。 Expressをインストールし、npm installを使用して依存関係として保存します。

  1. npm install --save express

body-parser モジュールも必要です。これは、HTTPリクエストの本文にアクセスするために使用します。 これも依存関係としてインストールします。

  1. npm install --save body-parser

メインアプリケーションファイルapp.jsを作成します。

  1. nano app.js

expressbody-parser、およびdatabaseモジュールをインポートします。 次に、appというexpressモジュールのインスタンスを作成して、サービスを制御および構成します。 app.use()を使用して、ミドルウェアなどの機能を追加します。 これを使用してbody-parserモジュールを追加し、アプリケーションがurlエンコード文字列を読み取れるようにします。

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

. . .

次に、アプリケーションにルートを追加します。 ルートはアプリやウェブサイトのボタンに似ています。 これらは、アプリケーションで何らかのアクションをトリガーします。 ルートは、一意のURLをアプリケーションのアクションにリンクします。 各ルートは特定のパスを提供し、異なる操作をサポートします。

定義する最初のルートは、/person/$IDパスに対するGET要求を処理します。これにより、指定されたIDを持つ人のデータベースレコードが表示されます。 Expressは、要求された$IDの値をreq.params.id変数に自動的に設定します。

アプリケーションは、JSON文字列としてエンコードされた個人データで応答する必要があります。 データベーステストで行ったように、findByPk()メソッドを使用してIDで個人を取得し、HTTPステータス200(OK)でリクエストに応答し、個人レコードをJSONとして送信します。 次のコードを追加します。

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

. . .

エラーが発生すると、catch()のコードが実行されます。 たとえば、データベースがダウンしている場合、接続は失敗し、代わりにこれが実行されます。 問題が発生した場合は、HTTPステータスを500(内部サーバーエラー)に設定し、エラーメッセージをユーザーに返送してください。

別のルートを追加して、データベースに人を作成します。 このルートはPUTリクエストを処理し、req.bodyから個人のデータにアクセスします。 create()メソッドを使用して、データベースに行を挿入します。

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

. . .

DELETE要求を処理する別のルートを追加します。これにより、アドレス帳からレコードが削除されます。 まず、IDを使用してレコードを見つけ、次に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));
        });
});

. . .

また、便宜上、/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));
        });
});

. . .

最後のルートが1つ残っています。 リクエストが以前のルートのいずれにも一致しなかった場合は、ステータスコード404(見つかりません)を送信します。

app.js
. . .

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

. . .

最後に、サービスを起動するlisten()メソッドを追加します。 環境変数PORTが定義されている場合、サービスはそのポートでリッスンします。 それ以外の場合は、デフォルトでポート3000になります。

app.js
. . .

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

学習したように、package.jsonファイルを使用すると、テストの実行、アプリの起動、その他のタスクを実行するためのさまざまなコマンドを定義できます。これにより、多くの場合、入力回数を大幅に減らして一般的なコマンドを実行できます。 package.jsonに新しいコマンドを追加して、アプリケーションを起動します。 ファイルを編集します。

  1. nano package.json

startコマンドを追加すると、次のようになります。

package.json
. . .

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

. . .

scriptsセクションではエントリをコンマで区切る必要があるため、前の行にコンマを追加することを忘れないでください。

ファイルを保存して、初めてアプリケーションを起動します。 まず、sourceを使用して環境ファイルをロードします。 これにより、変数がセッションにインポートされ、アプリケーションで使用できるようになります。 次に、npm run startでアプリケーションを起動します。

  1. source ./.env
  2. npm run start

アプリはポート3000で起動します。

Output
app is running on port 3000

ブラウザを開き、http://localhost:3000/allに移動します。 []を示すページが表示されます。

端末に戻り、CTRL-Cを押してアプリケーションを停止します。

今こそ、コード品質テストを追加する絶好の機会です。 リンターとも呼ばれるコード品質ツールは、プロジェクトをスキャンしてコードの問題を探します。 未使用の変数を残す、セミコロンでステートメントを終了しない、中括弧がないなどの不適切なコーディング方法は、見つけるのが難しいバグを引き起こす可能性があります。

開発依存関係として、JavaScriptリンターであるjshintツールをインストールします。

  1. npm install --save-dev jshint

何年にもわたって、JavaScriptは更新、機能、および構文の変更を受け取りました。 この言語は、 ECMAInternationalによって「ECMAScript」という名前で標準化されています。 約年に1回、ECMAは新しい機能を備えた新しいバージョンのECMAScriptをリリースします。

デフォルトでは、jshintは、コードがES6(ECMAScriptバージョン6)と互換性があると想定し、そのバージョンでサポートされていないキーワードが見つかるとエラーをスローします。 コードと互換性のあるバージョンを見つける必要があります。 最近のすべてのバージョンの機能テーブルを見ると、async/awaitキーワードはES8まで導入されていなかったことがわかります。 データベーステストコードで両方のキーワードを使用したため、互換性のある最小バージョンがES8に設定されます。

使用しているバージョンをjshintに伝えるには、.jshintrcというファイルを作成します。

  1. nano .jshintrc

ファイルにesversionを指定します。 jshintrcファイルはJSONを使用するため、ファイルに新しいJSONオブジェクトを作成します。

.jshintrc
{ "esversion": 8 }

ファイルを保存して、エディターを終了します。

jshintを実行するコマンドを追加します。 package.jsonを編集します:

  1. nano package.json

package.jsonscriptsセクションで、プロジェクトにlintコマンドを追加します。 このコマンドは、これまでに作成したすべてのJavaScriptファイルに対してlintツールを呼び出します。

package.json
. . .

  "scripts": {
    "test": "jest",
    "start": "node app.js",
    "lint": "jshint app.js database*.js migrate.js"
  },

. . .

これで、リンターを実行して問題を見つけることができます。

  1. npm run lint

エラーメッセージは表示されないはずです。

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

エラーがある場合は、jshintに問題のある行が表示されます。

プロジェクトを完了し、プロジェクトが機能することを確認しました。 ファイルをリポジトリに追加し、コミットして、変更をプッシュします。

  1. git add *.js
  2. git add package*.json
  3. git add .jshintrc
  4. git commit -m 'initial commit'
  5. git push origin master

これで、DigitalOceanパーソナルアクセストークンとデータベースクレデンシャルを使用してSemaphoreを構成することから始めて、アプリケーションをテスト、ビルド、およびデプロイするようにSemaphoreを構成できます。

ステップ3—セマフォでシークレットを作成する

GitHubリポジトリに属していない情報がいくつかあります。 パスワードとAPIトークンはこの良い例です。 この機密データを別のファイルに保存して環境にロードしました。セマフォを使用する場合は、シークレットを使用して機密データを保存できます。

プロジェクトには3種類の秘密があります。

  • Docker Hub:DockerHubアカウントのユーザー名とパスワード。
  • DigitalOceanパーソナルアクセストークン:アプリケーションをKubernetesクラスターにデプロイします。
  • 環境変数:データベースのユーザー名とパスワードの接続パラメーター用。

最初のシークレットを作成するには、ブラウザを開いてセマフォのWebサイトにログインします。 左側のナビゲーションメニューで、CONFIGURATION見出しの下にあるSecretsをクリックします。 新しいシークレットの作成ボタンをクリックします。

シークレットの名前dockerhubと入力します。 次に、環境変数の下に、2つの環境変数を作成します。

  • DOCKER_USERNAME:DockerHubのユーザー名。
  • DOCKER_PASSWORD:DockerHubのパスワード。

Docker Hub Secret

[変更を保存]をクリックします。

DigitalOceanパーソナルアクセストークンの2番目のシークレットを作成します。 もう一度、左側のナビゲーションメニューのシークレットをクリックしてから、新しいシークレットの作成をクリックします。 このシークレットをdo-access-tokenと呼び、パーソナルアクセストークンに設定された値を使用してDO_ACCESS_TOKENという環境値を作成します。

DigitalOcean Token Secret

秘密を保存します。

次の秘密として、環境変数を直接設定する代わりに、プロジェクトのルートから.envファイルをアップロードします。

env-productionという新しいシークレットを作成します。 ファイルセクションで、ファイルのアップロードリンクを押して.envファイルを見つけてアップロードし、Semaphoreに/home/semaphore/env-productionに配置するように指示します。

Environment Secret

注:ファイルが非表示になっているため、コンピューターでファイルを見つけるのに問題が発生する可能性があります。 通常、CTRL+Hなどの隠しファイルを表示するためのメニュー項目またはキーの組み合わせがあります。 他のすべてが失敗した場合は、非表示になっていない名前でファイルをコピーしてみてください。

  1. cp .env env

次に、ファイルをアップロードして、名前を元に戻します。

  1. cp env .env

環境変数はすべて構成されています。 これで、継続的インテグレーションのセットアップを開始できます。

ステップ4—プロジェクトをSemaphoreに追加する

このステップでは、プロジェクトをSemaphoreに追加し、継続的インテグレーション(CI)パイプラインを開始します。

まず、GitHubリポジトリをSemaphoreにリンクします。

  1. セマフォアカウントにログインします。
  2. プロジェクトの横にある+アイコンをクリックします。
  3. リポジトリの横にあるリポジトリの追加ボタンをクリックします。

Add Repository to Semaphore

Semaphoreが接続されたので、リポジトリ内の変更を自動的に取得します。

これで、アプリケーションの継続的インテグレーションパイプラインを作成する準備が整いました。 パイプラインは、コードがビルド、テスト、およびデプロイされるために移動する必要のあるパスを定義します。 パイプラインは、GitHubリポジトリに変更があるたびに自動的に実行されます。

まず、Semaphoreが開発中に使用していたのと同じバージョンのNodeを使用していることを確認する必要があります。 マシンで実行されているバージョンを確認できます。

  1. node -v
Output
v10.16.0

リポジトリに.nvmrcというファイルを作成することで、使用するNode.jsのバージョンをSemaphoreに伝えることができます。 内部的には、Semaphoreはノードバージョンマネージャーを使用してNode.jsバージョンを切り替えます。 .nvmrcファイルを作成し、バージョンを10.16.0に設定します。

  1. echo '10.16.0' > .nvmrc

セマフォパイプラインは.semaphoreディレクトリに配置されます。 ディレクトリを作成します。

  1. mkdir .semaphore

新しいパイプラインファイルを作成します。 最初のパイプラインは常にsemaphore.ymlと呼ばれます。 このファイルでは、アプリケーションのビルドとテストに必要なすべてのステップを定義します。

  1. nano .semaphore/semaphore.yml

YAML形式でファイルを作成しています。 チュートリアルに示されているように、先頭のスペースを保持する必要があります。

最初の行は、セマフォファイルのバージョンを設定する必要があります。 現在の安定版はv1.0です。 また、パイプラインには名前が必要です。 次の行をファイルに追加します。

.semaphore / semaphore.yml
version: v1.0
name: Addressbook

. . .

セマフォは、タスクを実行するために仮想マシンを自動的にプロビジョニングします。 から選択できるさまざまなマシンがあります。 統合ジョブには、e1-standard-2(2 CPU 4 GB RAM)とUbuntu18.04OSを使用します。 次の行をファイルに追加します。

.semaphore / semaphore.yml
. . .

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

. . .

セマフォはブロックを使用してタスクを整理します。 各ブロックには、1つ以上のジョブを含めることができます。 ブロック内のすべてのジョブは並行して実行され、それぞれが分離されたマシンで実行されます。 セマフォは、ブロック内のすべてのジョブが通過するのを待ってから、次のジョブを開始します。

最初のブロックを定義することから始めます。これにより、アプリケーションをテストして実行するためのすべてのJavaScript依存関係がインストールされます。

.semaphore / semaphore.yml
. . .

blocks:
  - name: Install dependencies
    task:

. . .

NODE_ENVtestに設定するなど、すべてのジョブに共通の環境変数を定義できるため、Node.jsはこれがテスト環境であることを認識します。 taskの後に次のコードを追加します。

.semaphore / semaphore.yml
. . .
    task:
      env_vars:
        - name: NODE_ENV
          value: test

. . .

prologue セクションのコマンドは、ブロック内の各ジョブの前に実行されます。 セットアップタスクを定義するのに便利な場所です。 checkout を使用して、GitHubリポジトリのクローンを作成できます。 次に、nvm useは、.nvmrcで指定した適切なNode.jsバージョンをアクティブにします。 prologueセクションを追加します。

.semaphore / semaphore.yml
    task:
. . .

      prologue:
        commands:
          - checkout
          - nvm use

. . .

次に、このコードを追加して、プロジェクトの依存関係をインストールします。 ジョブを高速化するために、Semaphoreはcacheツールを提供しています。 cache storeを実行して、node_modulesディレクトリをセマフォのキャッシュに保存できます。 cacheは、どのファイルとディレクトリを保存するかを自動的に判断します。 2回目にジョブが実行されると、cache restoreはディレクトリを復元します。

.semaphore / semaphore.yml
. . .

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

. . .

2つのジョブを実行する別のブロックを追加します。 1つはlintテストを実行し、もう1つはアプリケーションのテストスイートを実行します。

.semaphore / semaphore.yml
. . .

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

. . .

prologueは前のブロックと同じコマンドを繰り返し、キャッシュからnode_moduleを復元します。 このブロックはテストを実行するため、NODE_ENV環境変数をtestに設定します。

次に、ジョブを追加します。 最初のジョブは、jshintを使用してコード品質チェックを実行します。

.semaphore / semaphore.yml
. . .

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

. . .

次のジョブは単体テストを実行します。 本番データベースを使用したくないので、それらを実行するにはデータベースが必要です。 セマフォのsem-serviceは、完全に分離されたテスト環境でローカルPostgreSQLデータベースを起動できます。 ジョブが終了すると、データベースは破棄されます。 このサービスを開始し、テストを実行します。

.semaphore / semaphore.yml
. . .

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

.semaphore/semaphore.ymlファイルを保存します。

次に、変更をGitHubリポジトリに追加してコミットします。

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

コードがGitHubにプッシュされるとすぐに、SemaphoreはCIパイプラインを開始します。

Running Workflow

パイプラインをクリックして、ブロックとジョブ、およびそれらの出力を表示できます。

Integration Pipeline

次に、アプリケーションのDockerイメージを構築する新しいパイプラインを作成します。

ステップ5—アプリケーション用のDockerイメージを構築する

Dockerイメージは、Kubernetesデプロイメントの基本ユニットです。 イメージには、アプリケーションの実行に必要なすべてのバイナリ、ライブラリ、およびコードが含まれている必要があります。 Dockerコンテナーは軽量の仮想マシンではありませんが、そのように動作します。 Docker Hubレジストリには、すぐに使用できる数百のイメージが含まれていますが、独自のイメージを作成します。

このステップでは、新しいパイプラインを追加して、アプリ用のカスタムDockerイメージを構築し、DockerHubにプッシュします。

カスタムイメージを作成するには、Dockerfileを作成します。

  1. nano Dockerfile

Dockerfileは画像を作成するためのレシピです。 official Node.jsディストリビューションを最初から始める代わりに、開始点として使用できます。 これをDockerfileに追加します。

Dockerfile
FROM node:10.16.0-alpine

. . .

次に、package.jsonpackage-lock.jsonをコピーするコマンドを追加し、イメージ内にノードモジュールをインストールします。

Dockerfile
. . .

COPY package*.json ./
RUN npm install

. . .

Dockerがこのステップをキャッシュするため、最初に依存関係をインストールすると、後続のビルドが高速化されます。

次に、プロジェクトルート内のすべてのアプリケーションファイルをイメージにコピーする次のコマンドを追加します。

Dockerfile
. . .

COPY *.js ./

. . .

最後に、EXPOSEは、コンテナがアプリケーションがリッスンしているポート3000で接続をリッスンすることを指定し、CMDは、コンテナの起動時に実行するコマンドを設定します。 次の行をファイルに追加します。

Dockerfile
. . .

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

ファイルを保存します。

Dockerfileが完成したら、新しいパイプラインを作成して、コードをGitHubにプッシュするときにSemaphoreがイメージを構築できるようにします。 docker-build.ymlという名前の新しいファイルを作成します。

  1. nano .semaphore/docker-build.yml

CIパイプラインと同じボイラープレートでパイプラインを開始しますが、名前はDocker buildです。

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

. . .

このパイプラインには、1つのブロックと1つのジョブしかありません。 手順3では、DockerHubのユーザー名とパスワードを使用してdockerhubという名前のシークレットを作成しました。 ここでは、secretsキーワードを使用してこれらの値をインポートします。 このコードを追加します:

.semaphore / docker-build.yml
. . .

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

. . .

Dockerイメージはリポジトリに保存されます。 公開画像の数に制限がない公式のDockerHubを使用します。 これらの行を追加してGitHubからコードをチェックアウトし、docker loginコマンドを使用してDockerHubで認証します。

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

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

. . .

各Dockerイメージは、名前とタグの組み合わせによって完全に識別されます。 名前は通常、製品またはソフトウェアに対応し、タグはソフトウェアの特定のバージョンに対応します。 たとえば、node.10.16.0です。 タグが指定されていない場合、Dockerはデフォルトで特別なlatestタグになります。 したがって、latestタグを使用して最新の画像を参照することをお勧めします。

次のコードを追加してイメージをビルドし、DockerHubにプッシュします。

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

Dockerはイメージをビルドするときに、既存のイメージの一部を再利用してプロセスを高速化します。 最初のコマンドは、DockerHubからlatestイメージをプルして、再利用できるようにします。 コマンドのいずれかがゼロ以外のステータスコードを返す場合、セマフォはパイプラインを停止します。 たとえば、リポジトリにlatestイメージがない場合、最初の試行ではないため、パイプラインは停止します。 コマンドに|| trueを追加することにより、失敗したコマンドをセマフォに無視させることができます。

2番目のコマンドは、イメージをビルドします。 この特定の画像を後で参照するために、一意の文字列でタグ付けできます。 セマフォは、ジョブにいくつかの環境変数を提供します。 そのうちの1つ、$SEMAPHORE_WORKFLOW_IDは一意であり、ワークフロー内のすべてのパイプライン間で共有されます。 展開の後半でこのイメージを参照するのに便利です。

3番目のコマンドは、イメージをDockerHubにプッシュします。

ビルドパイプラインの準備はできていますが、メインCIパイプラインに接続しない限り、Semaphoreはビルドパイプラインを開始しません。 Promotions を使用して、複数のパイプラインをチェーンし、複雑なマルチブランチワークフローを作成できます。

メインパイプラインファイル.semaphore/semaphore.ymlを編集します。

  1. nano .semaphore/semaphore.yml

ファイルの最後に次の行を追加します。

.semaphore / semaphore.yml
. . .

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

auto_promote_onは、docker buildパイプラインを開始するための条件を定義します。 この場合、semaphore.ymlファイルで定義されているすべてのジョブが通過したときに実行されます。

新しいパイプラインをテストするには、変更したすべてのファイルをGitHubに追加、コミット、プッシュする必要があります。

  1. git add Dockerfile
  2. git add .semaphore/docker-build.yml
  3. git add .semaphore/semaphore.yml
  4. git commit -m "docker build pipeline"
  5. git push origin master

CIパイプラインが完了すると、Dockerビルドパイプラインが開始されます。

Build Pipeline

完了すると、DockerHubリポジトリに新しいイメージが表示されます。

ビルドプロセスのテストとイメージの作成が完了しました。 次に、アプリケーションをKubernetesクラスターにデプロイするための最終的なパイプラインを作成します。

ステップ6—Kubernetesへの継続的デプロイの設定

Kubernetesデプロイメントの構成要素は、ポッドです。 ポッドは、単一のユニットとして管理されるコンテナーのグループです。 ポッド内のコンテナは同時に開始および停止し、常に同じマシン上で実行され、そのリソースを共有します。 各ポッドにはIPアドレスがあります。 この場合、ポッドにはコンテナが1つだけあります。

ポッドは一時的なものです。 それらは頻繁に作成および破棄されます。 開始するまで、各ポッドにどのIPアドレスが割り当てられるかはわかりません。 これを解決するには、 services を使用します。これは、パブリックIPアドレスが固定されているため、着信接続の負荷を分散してポッドに転送できます。

ポッドを直接管理することもできますが、デプロイメントを使用してKubernetesに処理させることをお勧めします。 このセクションでは、クラスターの最終的な望ましい状態を説明する宣言型マニフェストを作成します。 マニフェストには2つのリソースがあります。

  • デプロイメント:必要に応じてクラスターノードでポッドを開始し、そのステータスを追跡します。 このチュートリアルでは3ノードのクラスターを使用しているため、3つのポッドをデプロイします。
  • サービス:ユーザーのエントリポイントとして機能します。 ポート80(HTTP)でトラフィックをリッスンし、接続をポッドに転送します。

deployment.ymlというマニフェストファイルを作成します。

  1. nano deployment.yml

Deploymentリソースでマニフェストを開始します。 次の内容を新しいファイルに追加して、デプロイメントを定義します。

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


. . .

マニフェストのリソースごとに、apiVersionを設定する必要があります。 展開には、安定バージョンであるapiVersion: apps/v1を使用します。 次に、このリソースがkind: Deploymentを使用したデプロイであることをKubernetesに伝えます。 各定義には、metadata.nameで定義された名前が必要です。

specセクションで、Kubernetesに目的の最終状態を通知します。 この定義では、Kubernetesがreplicas: 3を使用して3つのポッドを作成する必要があります。

ラベルは、Kubernetesリソースを整理および相互参照するために使用されるキーと値のペアです。 metadata.labelsでラベルを定義し、selector.matchLabelsで一致するラベルを探すことができます。 これは、要素を相互に接続する方法です。

キーspec.templateは、Kubernetesが各ポッドを作成するために使用するモデルを定義します。 spec.template.metadata.labels内で、ポッドに1つのラベルapp: addressbookを設定します。

spec.selector.matchLabelsを使用すると、展開でapp: addressbookというラベルの付いたポッドを管理できます。 この場合、このデプロイメントにすべてのポッドの責任を負わせます。

最後に、ポッドで実行されるイメージを定義します。 spec.template.spec.containersで画像名を設定します。 Kubernetesは、必要に応じてレジストリからイメージをプルします。 この場合、Docker Hubからプルします)。 コンテナの環境変数を設定することもできます。これは、データベース接続にいくつかの値を指定する必要があるため、幸いです。

デプロイメントマニフェストを柔軟に保つために、変数に依存することになります。 ただし、YAML形式では変数が許可されていないため、ファイルはまだ有効ではありません。 Semaphoreのデプロイメントパイプラインを定義するときに、この問題を解決します。

展開は以上です。 しかし、これはポッドを定義するだけです。 トラフィックがポッドに流れるようにするサービスがまだ必要です。 区切り文字として3つのハイフン(---)を使用している限り、同じファイルに別のKubernetesリソースを追加できます。

次のコードを追加して、addressbookラベルの付いたポッドに接続するロードバランサーサービスを定義します。

deploy.yml
. . .

---

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

ロードバランサーはポート80で接続を受信し、アプリケーションがリッスンしているポッドのポート3000に転送します。

ファイルを保存します。

次に、マニフェストを使用してアプリをデプロイするSemaphoreのデプロイパイプラインを作成します。 .semaphoreディレクトリに新しいファイルを作成します。

  1. nano .semaphore/deploy-k8s.yml

バージョン、名前、イメージを指定して、通常どおりパイプラインを開始します。

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

. . .

このパイプラインには2つのブロックがあります。 最初のブロックは、アプリケーションをKubernetesクラスターにデプロイします。

ブロックを定義し、すべてのシークレットをインポートします。

.semaphore / deploy-k8s.yml
. . .

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

. . .

DigitalOcean Kubernetesクラスター名を環境変数に保存して、後で参照できるようにします。

.semaphore / deploy-k8s.yml
. . .

      env_vars:
        - name: CLUSTER_NAME
          value: addressbook-server

. . .

DigitalOcean Kubernetesクラスターは、kubectldoctlの2つのプログラムの組み合わせで管理されます。 前者はすでにセマフォのイメージに含まれていますが、後者は含まれていないため、インストールする必要があります。 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

. . .

最初のコマンドは、doctl公式リリースwgetをダウンロードします。 2番目のコマンドは、tarを使用して解凍し、ローカルパスにコピーします。 doctlをインストールすると、DigitalOcean APIで認証し、クラスターのKubernetes構成ファイルをリクエストするために使用できます。 コードをチェックアウトすると、prologueが完了します。

次は、パイプラインの最後の部分であるクラスターへのデプロイです。

deployment.ymlにはいくつかの環境変数があり、YAMLではそれが許可されていないことに注意してください。 その結果、現在の状態のdeployment.ymlは機能しません。 これを回避するには、環境ファイルをsourceして変数をロードし、envsubstコマンドを使用して変数を実際の値で所定の位置に展開します。 結果として、deploy.ymlというファイルは、値が挿入された完全に有効なYAMLです。 ファイルを配置したら、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

. . .

2番目のブロックは、DockerHubのイメージにlatestタグを追加して、これがデプロイされている最新バージョンであることを示します。 Dockerログイン手順を繰り返してから、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"

ファイルを保存します。

このパイプラインはデプロイを実行しますが、Dockerイメージが正常に生成されてDockerHubにプッシュされた場合にのみ開始できます。 そのため、ビルドパイプラインとデプロイメントパイプラインをプロモーションに接続する必要があります。 Dockerビルドパイプラインを編集して追加します。

  1. nano .semaphore/docker-build.yml

ファイルの最後にプロモーションを追加します。

.semaphore / docker-build.yml
. . .

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

CI/CDワークフローの設定が完了しました。

残っているのは、変更されたファイルをプッシュし、Semaphoreに作業を任せることだけです。 リポジトリの変更を追加、コミット、およびプッシュします。

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

展開が完了するまでに数分かかります。

Deploy Pipeline

次に、アプリケーションをテストしてみましょう。

ステップ7—アプリケーションのテスト

この時点で、アプリケーションは稼働しています。 このステップでは、curlを使用してAPIエンドポイントをテストします。

DigitalOceanがクラスターに与えたパブリックIPを知る必要があります。 次の手順に従って見つけてください。

  1. DigitalOceanアカウントにログインします。
  2. 名簿プロジェクトを選択します
  3. ネットワークに移動します。
  4. ロードバランサーをクリックします。
  5. IPアドレスが表示されます。 IPアドレスをコピーします。

Load Balancer IP

curlを使用して/allルートを確認しましょう。

  1. curl -w "\n" YOUR_CLUSTER_IP/all

-w "\n"オプションを使用して、curlがすべての行を印刷するようにすることができます。

データベースにはまだレコードがないため、結果として空のJSON配列が取得されます。

Output
[]

/personエンドポイントにPUTリクエストを送信して、新しい個人レコードを作成します。

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

APIは、個人のJSONオブジェクトを返します。

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

2人目の人を作成します。

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

出力は、2人目の人物が作成されたことを示しています。

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

次に、GETリクエストを作成して、2idを持つ人を取得します。

  1. curl -w "\n" YOUR_CLUSTER_IP/person/2

サーバーは、要求されたデータで応答します。

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

個人を削除するには、DELETEリクエストを送信します。

  1. curl -w "\n" -X DELETE YOUR_CLUSTER_IP/person/2

このコマンドでは出力は返されません。

データベースには、1idを持つ人を1人だけ含める必要があります。 /allをもう一度取得してみてください。

  1. curl -w "\n" YOUR_CLUSTER_IP/all

サーバーは、1つのレコードのみを含む一連の人物で応答します。

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

この時点で、データベースに残っているのは1人だけです。

これで、アプリケーション内のすべてのエンドポイントのテストが完了し、チュートリアルの終わりを示します。

結論

このチュートリアルでは、DigitalOceanのマネージドPostgreSQLデータベースサービスを使用して、完全なNode.jsアプリケーションを最初から作成しました。 次に、SemaphoreのCI / CDパイプラインを使用して、コンテナーイメージをテストおよび構築し、Docker Hubにアップロードして、DigitalOceanKubernetesにデプロイするワークフローを完全に自動化しました。

Kubernetesの詳細については、Kubernetesの概要およびその他のDigitalOceanのKubernetesチュートリアルをご覧ください。

アプリケーションがデプロイされたので、ドメイン名の追加データベースクラスターの保護、またはデータベースのアラートの設定を検討できます。