セマフォの継続的な統合と配信を使用して、Node.jsアプリケーションを構築し、DigitalOcean Kubernetesにデプロイする方法

_作成者はhttps://www.brightfunds.org/funds/open-internet-free-speech[Open Internet / Free Speech Fund]を選択して、https://do.co/w4do-ctaの一部として寄付を受け取りました[寄付のために書く]プログラム。

前書き

https://kubernetes.io [Kubernetes]を使用すると、ユーザーは1つのコマンドで回復力と拡張性のあるサービスを作成できます。 あまりにも良いと思われるものと同様に、これには落とし穴があります。最初に適切なhttps://www.docker.com [Docker]イメージを準備し、徹底的にテストする必要があります。

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

プロセス全体を自動化するには、継続的インテグレーションと配信(CI / CD)プラットフォームであるhttps://semaphoreci.com [Semaphore]を使用します。

このチュートリアルでは、https://nodejs.org [Node.js]を使用してアドレス帳APIサービスを構築します。 APIは、データベース内のユーザーを作成、削除、検索するための単純なhttps://en.wikipedia.org/wiki/Representational_state_transfer[RESTful API]インターフェースを公開します。 https://git-scm.com [Git]を使用してコードをhttps://github.com [GitHub]にプッシュします。 次に、Semaphoreを使用してアプリケーションをテストし、Dockerイメージを構築し、https://www.digitalocean.com/products/kubernetes/ [DigitalOcean Kubernetes]クラスターに展開します。 データベースについては、https://www.digitalocean.com/products/managed-databases/ [DigitalOcean Managed Databases]を使用してPostgreSQLクラスターを作成します。

前提条件

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

  • DigitalOceanアカウントとパーソナルアクセストークン。 https://www.digitalocean.com/docs/api/create-personal-access-token/ [パーソナルアクセストークンの作成]に従って、アカウントにトークンを設定します。

  • Docker Hubアカウント。

  • https://github.com [GitHub]アカウント。

  • https://semaphoreci.com [Semaphore]アカウント。 GitHubアカウントでサインアップできます。

  • プロジェクトの `+ addressbook +`という新しいGitHubリポジトリ。 リポジトリを作成するときに、このリポジトリをREADME *で初期化するチェックボックスを選択し、。gitignore *メニューで*ノード*を選択します。 詳細については、GitHubのhttps://help.github.com/en/articles/create-a-repo [リポジトリの作成]ヘルプページをご覧ください。

  • ローカルマシンにインストールされたhttps://git-scm.com [Git]およびGitHubアカウントで動作するhttps://help.github.com/en/articles/set-up-git[set up] 慣れていない場合や復習が必要な場合は、https://www.digitalocean.com/community/tutorials/how-to-use-git-a-reference-guide [Gitの使用方法]リファレンスガイドを読むことを検討してください。

  • ローカルマシンにインストールされたhttps://curl.haxx.se/[curl]

  • ローカルマシンにインストールされたhttps://nodejs.org [Node.js]。 このチュートリアルでは、Node.jsバージョン「10.16.0」を使用します。

手順1-データベースとKubernetesクラスターの作成

まず、アプリケーションを駆動するサービスであるDigitalOcean Database ClusterとDigitalOcean Kubernetes Clusterのプロビジョニングから始めます。

DigitalOceanアカウントとhttps://www.digitalocean.com/docs/projects/how-to/create/ [プロジェクトの作成]にログインします。 プロジェクトを使用すると、アプリケーションを構成するすべてのリソースを整理できます。 プロジェクト `+ addressbook +`を呼び出します。

次に、https://www.digitalocean.com/docs/databases/how-to/clusters/create/ [PostgreSQL]クラスターを作成します。 PostgreSQLデータベースサービスは、アプリケーションのデータを保持します。 利用可能な最新バージョンを選択できます。 サービスの準備が整うまで数分かかります。

PostgreSQLサービスの準備ができたら、https://www.digitalocean.com/docs/databases/how-to/clusters/add-users-and-databases/ [データベースとユーザーを作成]。 データベース名を「+ addessbook_db 」に設定し、ユーザー名を「 addressbook_user +」に設定します。 新しいユーザー用に生成されたパスワードをメモします。 データベースは、PostgreSQLがデータを整理する方法です。 通常、各アプリケーションには独自のデータベースがありますが、これに関する厳密な規則はありません。 アプリケーションは、ユーザー名とパスワードを使用してデータベースにアクセスし、データを保存および取得できるようにします。

最後に、https://www.digitalocean.com/docs/kubernetes/quickstart/ [Kubernetes]クラスターを作成します。 データベースが実行されているのと同じリージョンを選択します。 クラスターに「+ address book-server」という名前を付け、ノードの数を「3」に設定します。

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

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

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

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

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

cd addressbook

リポジトリを複製したら、アプリの作成を開始できます。 データベースとやり取りするモジュールと、HTTPサービスを提供するモジュールの2つのコンポーネントを作成します。 データベースモジュールは、アドレス帳データベースから個人を保存および取得する方法を認識し、HTTPモジュールは要求を受信し、それに応じて応答します。

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

  • + database.js +:データベースモジュール。 データベース操作を処理します。

  • + app.js +:エンドユーザーモジュールとメインアプリケーション。 ユーザーが接続するためのHTTPサービスを提供します。

  • + database.test.js +:データベースモジュールのテスト。

さらに、プロジェクトとその必要な依存関係を説明するhttps://docs.npmjs.com/creating-a-package-json-file[package.json]ファイルがプロジェクト用に必要になります。 エディターで手動で作成するか、npmを使用してインタラクティブに作成できます。 `+ npm init +`コマンドを実行して、ファイルをインタラクティブに作成します。

npm init

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

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)

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

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

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

npm install --save sequelize pg

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

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 +`モデルを定義します。 サンプルが複雑になりすぎないように、2つのフィールドのみを作成します: `+ firstName +`と `+ lastName +、どちらも文字列値を保存します。 次のコードを追加して、モデルを定義します。

database.js

. . .

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

. . .

これは2つのフィールドを定義し、 `+ firstName `を ` allowNull:false +`で必須にします。 Sequelizeのhttps://docs.sequelizejs.com/manual/models-definition.html [モデル定義ドキュメント]には、使用可能なデータ型とオプションが示されています。

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

database.js

. . .

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

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

nano migrate.js

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

migrate.js

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

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

nano .env

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

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

ファイルを保存してください。

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

source ./.env
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つずつ実行します。 Jestを `-save-dev +`オプションでインストールします。これは、モジュールを実行するのにモジュールが必要ではないことを ` npm +`に伝えますが、アプリケーションの開発には依存関係です。

npm install --save-dev jest

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

`+ database.test.js +`ファイルを作成します:

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 `で行を取得します。 次に、 ` firstName `および ` lastName `の値を検証します。 もう一度、 ` async `と ` 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.');
});

. . .

最後に、データベースから人を削除するテストを行います。 `+ 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 +`に保存することもできます。 エディターでこのファイルを開きます。

nano package.json

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

. . .

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

. . .

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

テストを実行します。

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リクエストを処理するには、https://expressjs.com [Express] Webフレームワークを使用します。 Expressをインストールし、 `+ npm install`を使用して依存関係として保存します。

npm install --save express

また、https://www.npmjs.com/package/body-parser [+ body-parser +]モジュールも必要になります。このモジュールを使用して、HTTPリクエスト本文にアクセスします。 これも依存関係としてインストールします。

npm install --save body-parser

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

nano app.js

+ express、` + body-parser`、および `+ database in`モジュールをインポートします。 次に、「+ app 」という「 express 」モジュールのインスタンスを作成して、サービスを制御および設定します。 ミドルウェアなどの機能を追加するには、 ` app.use()`を使用します。 これを使用して ` body-parser +`モジュールを追加し、アプリケーションがhttps://en.wikipedia.org/wiki/Percent-encoding[url-encoded]文字列を読み取れるようにします。

app.js

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

. . .

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

定義する最初のルートは、指定されたIDを持つ人のデータベースレコードを表示する「+ / person / $ ID 」パスの「 GET 」リクエストを処理します。 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`に新しいコマンドを追加します。 ファイルを編集します。

nano package.json

`+ start +`コマンドを追加して、次のようにします。

package.json

. . .

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

 },

. . .

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

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

source ./.env
npm run start

アプリはポート `+ 3000 +`で起動します:

Outputapp is running on port 3000

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

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

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

JavaScriptリンターであるhttps://jshint.com/[jshint]ツールを開発の依存関係としてインストールします。

npm install --save-dev jshint

長年にわたり、JavaScriptは更新、機能、および構文の変更を受け取ってきました。 言語は、「ECMAScript」という名前でhttp://ecma-international.org [ECMA International]によって標準化されています。 約1年に1回、ECMAは新機能を備えたECMAScriptの新しいバージョンをリリースします。

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

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

nano .jshintrc

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

jshintrc
{ "esversion": 8 }

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

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

nano package.json

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

package.json

. . .

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

 },

. . .

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

npm run lint

エラーメッセージはありません。

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

エラーがある場合、 `+ jshint +`は問題のある行を表示します。

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

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

DigitalOcean Personal Access Tokenとデータベース資格情報を使用してSemaphoreを構成することから始めて、アプリケーションをテスト、ビルド、およびデプロイするようにSemaphoreを構成できます。

手順3-セマフォでの秘密の作成

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

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

  • Docker Hub:Docker Hubアカウントのユーザー名とパスワード。

  • DigitalOcean Personal Access Token:アプリケーションをKubernetesクラスターにデプロイします。

  • 環境変数:データベースのユーザー名とパスワードの接続パラメーター用。

最初のシークレットを作成するには、ブラウザーを開いてhttps://semaphoreci.com [Semaphore] Webサイトにログインします。 左側のナビゲーションメニューで、* CONFIGURATION 見出しの下の Secrets をクリックします。 [*新しいシークレットを作成]ボタンをクリックします。

秘密の名前*に、「+ dockerhub +」と入力します。 次に、 Environment Variables *で、2つの環境変数を作成します。

  • + DOCKER_USERNAME +:DockerHubユーザー名。

  • + DOCKER_PASSWORD +:DockerHubパスワード。

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

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

DigitalOcean Personal Access Tokenの2番目のシークレットを作成します。 もう一度、左側のナビゲーションメニューの[秘密]をクリックしてから、[新しい秘密の作成]をクリックします。 この秘密の + do-access-token`を呼び出し、個人アクセストークンに設定された値で + DO ACCESS_TOKEN`という環境値を作成します。

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

秘密を保存します。

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

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

image:https://assets.digitalocean.com/articles/semaphore_doks/IsIDPD2.png [環境の秘密]

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

ステップ4-プロジェクトをセマフォに追加する

この手順では、プロジェクトをSemaphoreに追加し、https://semaphoreci.com/blog/cicd-pipeline [Continuous Integration(CI)pipeline]を開始します。

まず、GitHubリポジトリをセマフォにリンクします。

  1. https://semaphoreci.com [Semaphore]アカウントにログインします。

  2. * PROJECTS の横にある + *アイコンをクリックします。

  3. リポジトリの横にある[*リポジトリの追加]ボタンをクリックします。

image:https://assets.digitalocean.com/articles/semaphore_doks/eSZWvM9.png [リポジトリをセマフォに追加]

セマフォが接続されたので、リポジトリ内のすべての変更を自動的に取得します。

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

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

node -v
Outputv10.16.0

リポジトリに `+ .nvmrc `というファイルを作成することで、使用するNode.jsのバージョンをSemaphoreに伝えることができます。 内部的に、Semaphoreはhttps://github.com/nvm-sh/nvm/blob/master/README.md[node version manager]を使用してNode.jsバージョンを切り替えます。 ` .nvmrc `ファイルを作成し、バージョンを ` 10.16.0 +`に設定します。

echo '10.16.0' > .nvmrc

セマフォパイプラインは、 `+ .semaphore +`ディレクトリに移動します。 ディレクトリを作成します。

mkdir .semaphore

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

nano .semaphore/semaphore.yml

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

semaphore/semaphore.yml
version:
name:

. . .

セマフォは、タスクを実行する仮想マシンを自動的にプロビジョニングします。 https://docs.semaphoreci.com/article/20-machine-types [から選択するさまざまなマシン]があります。 統合ジョブには、Ubuntu 18.04 OSとともに「+ e1-standard-2 +」(2 CPU 4 GB RAM)を使用します。 これらの行をファイルに追加します。

semaphore/semaphore.yml
. . .

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

. . .

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

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

semaphore/semaphore.yml
. . .

blocks:
 - name: Install dependencies
   task:

. . .

+ NODE_ENV`を + test ssoに設定するなど、すべてのジョブに共通の環境変数を定義できます。Node.jsはこれがテスト環境であることを認識しています。 `+ task +`の後にこのコードを追加してください:

semaphore/semaphore.yml
. . .
   task:




. . .

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

semaphore/semaphore.yml
   task:
. . .

     prologue:
       commands:
         - checkout
         - nvm use

. . .

次に、このコードを追加して、プロジェクトの依存関係をインストールします。 ジョブを高速化するために、Semaphoreはhttps://docs.semaphoreci.com/article/54-toolbox-reference#cache[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つはリントテストを実行し、もう1つはアプリケーションのテストスイートを実行します。

semaphore/semaphore.yml
. . .

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

. . .

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

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

semaphore/semaphore.yml
. . .

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

. . .

次のジョブは単体テストを実行します。 実稼働データベースを使用したくないため、それらを実行するにはデータベースが必要になります。 Semaphoreのhttps://docs.semaphoreci.com/article/132-sem-service-managing-databases-and-services-on-linux[sem-service]は、完全に分離されたテスト環境でローカルPostgreSQLデータベースを起動できます。 データベースは、ジョブが終了すると破棄されます。 このサービスを開始して、テストを実行します。

semaphore/semaphore.yml
. . .

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

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

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

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

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

image:https://assets.digitalocean.com/articles/semaphore_doks/g7gd8f1.png [ワークフローの実行]

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

image:https://assets.digitalocean.com/articles/semaphore_doks/WIFWWIu.png [統合パイプライン]

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

ステップ5-アプリケーションのDockerイメージの構築

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

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

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

nano Dockerfile

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

Dockerfile

FROM node:10.16.0-alpine

. . .

次に、 + package.json`と + package-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 +`という新しいファイルを作成します:

nano .semaphore/docker-build.yml

CIパイプラインと同じ定型文でパイプラインを開始しますが、名前は「+ Docker build」です:

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

. . .

このパイプラインには、1つのブロックと1つのジョブのみが含まれます。 ステップ3では、Docker Hubのユーザー名とパスワードを使用して、「+ dockerhub 」という名前のシークレットを作成しました。 ここでは、 ` secrets +`キーワードを使用してこれらの値をインポートします。 このコードを追加:

semaphore/docker-build.yml
. . .

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

. . .

Dockerイメージはリポジトリに保存されます。 公式のhttps://hub.docker.com/[Docker Hub]を使用して、無制限の数の公開画像を許可します。 これらの行を追加してGitHubからコードをチェックアウトし、 `+ docker login`コマンドを使用してDocker Hubで認証します。

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 +`タグを使用して最新の画像を参照することをお勧めします。

次のコードを追加してイメージを構築し、Docker Hubにプッシュします。

semaphore/docker-build.yml
. . .

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

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

2番目のコマンドは、イメージを構築します。 この特定の画像を後で参照するために、一意の文字列でタグ付けできます。 セマフォは、ジョブ用に複数のhttps://docs.semaphoreci.com/article/12-environment-variables [環境変数]を提供します。 その1つである「+ $ SEMAPHORE_WORKFLOW_ID +」は一意であり、ワークフロー内のすべてのパイプラインで共有されます。 展開の後半でこのイメージを参照するのに便利です。

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

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

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

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に追加、コミット、プッシュする必要があります。

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

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

image:https://assets.digitalocean.com/articles/semaphore_doks/XQfOwvK.png [パイプラインの構築]

終了すると、https://cloud.docker.com/repository [Docker Hubリポジトリ]に新しい画像が表示されます。

ビルドプロセスでイメージをテストおよび作成しました。 次に、アプリケーションをKubernetesクラスターにデプロイする最終パイプラインを作成します。

手順6-Kubernetesへの継続的な展開のセットアップ

Kubernetes展開のビルディングブロックは、https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes#pods [pod]です。 ポッドは、単一のユニットとして管理されるコンテナのグループです。 ポッド内のコンテナは同時に起動および停止し、常に同じマシンで実行され、リソースを共有します。 各ポッドにはIPアドレスがあります。 この場合、ポッドにはコンテナが1つしかありません。

ポッドは短命です。頻繁に作成および破棄されます。 各ポッドに割り当てられるIPアドレスは、開始されるまでわかりません。 これを解決するには、https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes#services [services]を使用します。このIPアドレスは、パブリックIPアドレスが固定されているため、着信接続の負荷分散とポッドに転送されます。

ポッドを直接管理できますが、https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes#deployments [deployment]を使用して、Kubernetesがそれを処理できるようにする方が適切です。 このセクションでは、クラスターの最終的な望ましい状態を記述する宣言マニフェストを作成します。 マニフェストには2つのリソースがあります。

  • 展開:必要に応じてクラスターノードでポッドを起動し、その状態を追跡します。 このチュートリアルでは3ノードのクラスターを使用しているため、3つのポッドをデプロイします。

  • サービス:ユーザーのエントリポイントとして機能します。 ポート + 80 +(HTTP)でトラフィックをリッスンし、接続をポッドに転送します。

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

nano deployment.yml

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

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"


. . .

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

`+ spec `セクションでは、Kubernetesに目的の最終状態を伝えます。 この定義は、Kubernetesが ` replicas:3 +`で3つのポッドを作成することを要求しています。

_Labels_は、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 +`ラベルでポッドに接続するロードバランサーサービスを定義します。

deployment.yml

. . .

---

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

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

ファイルを保存してください。

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

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:

. . .

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

. . .

最初のコマンドは、公式のhttps://github.com/digitalocean/doctl/releases[release]と `+ wget `を使用して、 ` doctl `をダウンロードします。 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番目のブロックは、 `+ latest +`タグをDocker Hubのイメージに追加して、これがデプロイされた最新バージョンであることを示します。 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イメージが正常に生成され、Docker Hubにプッシュされた場合にのみ開始できます。 そのため、ビルドパイプラインと展開パイプラインをプロモーションに接続する必要があります。 Dockerビルドパイプラインを編集して追加します。

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に作業を任せることだけです。 リポジトリの変更を追加、コミット、プッシュします。

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

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

image:https://assets.digitalocean.com/articles/semaphore_doks/Ee7VRw7.png [パイプラインの展開]

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

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

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

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

  1. DigitalOceanアカウントにログインします。

  2. アドレス帳プロジェクトを選択

  3. *ネットワーク*に移動します。

  4. * Load Balancers *をクリックします。

  5. * IPアドレス*が表示されます。 IPアドレスをコピーします。

image:https://assets.digitalocean.com/articles/semaphore_doks/S6nLLmg.png [ロードバランサーIP]

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

curl -w "\n" /all

`+ -w" \ n "`オプションを使用して、 ` curl +`がすべての行を出力するようにします。

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

Output[]

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

curl -w "\n" -X PUT \
 -d "firstName=Sammy&lastName=the Shark" /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"
}

二人目を作成します:

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

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

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

ここで、「+ id 」が「+2」の人を取得するために「+ GET +」リクエストを作成します。

curl -w "\n" /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`リクエストを送信します:

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

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

データベースには、「+ 1+」の「+ id 」を持つ人が1人だけいるはずです。 もう一度 ` / all +`を取得してください:

curl -w "\n" /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アプリケーションをゼロから作成しました。 次に、セマフォのCI / CDパイプラインを使用して、コンテナイメージをテストおよび構築し、Docker Hubにアップロードし、DigitalOcean Kubernetesに展開するワークフローを完全に自動化しました。

Kubernetesの詳細については、https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes [Kubernetesの概要]およびDigitalOceanのhttps://www.digitalocean.comの残りの部分を参照してください。 / community / tags / kubernetes [Kubernetesチュートリアル]。

アプリケーションがデプロイされたので、https://www.digitalocean.com/docs/networking/dns/how-to/add-domains [ドメイン名の追加]、https://www.digitalocean.com/ docs / databases / how-to / clusters / secure-clusters [データベースクラスターの確保]、またはhttps://www.digitalocean.com/docs/databases/how-to/clusters/set-up-alerts[alertsデータベース用]。

Related