NodeJS によるインナーループ開発

1. 概要

このラボでは、コンテナ化された環境で NodeJS アプリケーションを開発するソフトウェア エンジニア向けに、開発ワークフローを効率化するための特長と機能を紹介します。一般的なコンテナ開発では、ユーザーがコンテナの詳細とコンテナのビルドプロセスを理解する必要があります。さらに、デベロッパーは通常、作業の中断を余儀なくされ、IDE から離れてリモート環境でアプリケーションのテストやデバッグを行う必要もあります。このチュートリアルで説明するツールとテクノロジーを使用すると、デベロッパーは IDE を離れることなく、コンテナ化されたアプリケーションを効果的に操作できます。

学習内容

このラボでは、GCP でコンテナを使用して開発するための次のような方法について学びます。

  • スターター Nodejs アプリケーションの作成
  • コンテナ開発用に Nodejs アプリケーションを構成する
  • 単純な CRUD REST サービスのコーディング
  • GKE へのデプロイ
  • エラー状態のデバッグ
  • ブレークポイント / ログの利用
  • 変更を GKE にホット デプロイして戻す
  • 省略可: バックエンドの永続性を確保するための Cloud SQL の統合

2. 設定と要件

セルフペース型の環境設定

  1. Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列で、いつでも更新できます。
  • プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud Console により一意の文字列が自動生成されます(通常は内容を意識する必要はありません)。ほとんどの Codelab では、プロジェクト ID を参照する必要があります(通常、プロジェクト ID は「PROJECT_ID」の形式です)。好みの文字列でない場合は、別のランダムな ID を生成するか、独自の ID を試用して利用可能であるかどうかを確認することができます。プロジェクトの作成後、ID は「フリーズ」されます。
  • 3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
  1. 次に、Cloud のリソースや API を使用するために、Cloud Console で課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルを終了した後に課金が発生しないようにリソースをシャットダウンするには、Codelab の最後にある「クリーンアップ」の手順を行います。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloudshell エディタを起動する

このラボは、Google Cloud Shell エディタで使用するように設計、テストされています。エディタにアクセスするには、

  1. https://console.cloud.google.com から Google プロジェクトにアクセスします。
  2. 右上にある Cloud Shell エディタのアイコンをクリックします。

8560cc8d45e8c112.png

  1. ウィンドウの下部に新しいペインが開きます
  2. [エディタを開く] ボタンをクリックします。

9e504cb98a6a8005.png

  1. エディタが開き、右側にエクスプローラ、中央部分にエディタが表示されます
  2. 画面の下部にターミナル ペインも表示されます。
  3. ターミナルが開いていない場合は、`Ctrl+` キーの組み合わせを使用して新しいターミナル ウィンドウを開きます。

gcloud を設定する

Cloud Shell で、プロジェクト ID とアプリケーションのデプロイ先のリージョンを設定します。これらを PROJECT_ID 変数と REGION 変数として保存します。

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

GKE クラスタとデータベースを設定する

  1. 設定スクリプトをダウンロードして、実行可能にします。
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/nodejs/setup.sh
chmod +x setup.sh

このラボで使用するインフラストラクチャをプロビジョニングする

このラボでは、GKE にコードをデプロイし、Spanner データベースに保存されているデータにアクセスします。以下の設定スクリプトは、このインフラストラクチャを準備します。

  1. setup.sh ファイルを開き、現在 CHANGEME に設定されているパスワードの値を編集します
  2. 設定スクリプトを実行して、このラボで使用する GKE クラスタと Cloud SQL データベースを立ち上げます。
./setup.sh
  1. Cloud Shell で、mynodejsapp という名前の新しいディレクトリを作成します。
mkdir mynodejsapp
  1. このディレクトリに移動し、ワークスペースとして開きます。これにより、新しく作成されたフォルダにワークスペース構成が作成され、エディタが再読み込みされます。
cd mynodejsapp && cloudshell workspace .
  1. NVM を使用してノードと NPM をインストールする。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
        
        # This loads nvm bash_completion
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  

nvm install stable

nvm alias default stable

3. 新しいスターター アプリケーションを作成する

  1. アプリケーションを初期化する

次のコマンドを実行して package.json ファイルを作成する

npm init
    Choose the entry point: (index.js) src/index.js and default values for the rest of the parameters. This will create the file with following contents
{
  "name": "mynodejsapp",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
  1. エントリ ポイントを追加する

このファイルを編集して、スクリプト "start": "node src/index.js", に起動コマンドを含めます。変更後のスクリプトは、次のコード スニペットのようになります。

"scripts": {
    "start": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  1. Express 依存関係を追加する

追加するコードでも express を使用しているため、この package.json ファイルにその依存関係を追加しましょう。すべての変更が完了すると、package.json ファイルは次のようになります。

​​{
  "name": "mynodejsapp",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Your Name",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.4"
  }
}
  1. index.js ファイルを作成する

src というソース ディレクトリを作成する

以下のコードを使用して src/index.js を作成します。

const express = require('express');
const app = express();
const PORT = 8080;

app.get('/', (req, res) => {
    var message="Greetings from Node";
    res.send({ message: message });
  });

app.listen(PORT, () => {
  console.log(`Server running at: http://localhost:${PORT}/`);

});

PORT が 8080 値に設定されていることに注目

マニフェストを生成する

Skaffold には、コンテナ開発を簡素化する統合ツールが用意されています。このステップでは、ベース Kubernetes YAML ファイルを自動的に作成する skaffold を初期化します。以下のコマンドを実行してプロセスを開始します。

ターミナルで次のコマンドを実行します。

skaffold init --generate-manifests

プロンプトが表示されたら、次の操作を行います。

  • ポートに「8080」と入力します。
  • y」と入力して構成を保存します。

ワークスペースのビジュアリゼーションに skaffold.yamldeployment.yaml の 2 つのファイルが追加されます。

アプリ名を更新

現在、構成に含まれるデフォルト値は、アプリケーションの名前と一致していません。デフォルト値ではなく、アプリケーション名を参照するようにファイルを更新します。

  1. Skaffold 構成のエントリを変更する
  • skaffold.yaml を開きます。
  • 現在 package-json-image として設定されているイメージ名を選択します
  • 右クリックして [すべてのオカレンスを変更] を選択します。
  • 新しい名前として「mynodejsapp」と入力します。
  1. Kubernetes 構成のエントリを変更する
  • deployment.yaml ファイルを開く
  • 現在 package-json-image として設定されているイメージ名を選択します
  • 右クリックして [すべてのオカレンスを変更] を選択します。
  • 新しい名前として「mynodejsapp」と入力します。

skaffold.yaml ファイルの build セクションで buildpacks を使用してアプリケーションをコンテナ化しています。このコードには Dockerfile がなく、デベロッパーはこのアプリケーションをコンテナ化するために Docker の知識は必要ありません。

また、この skaffold 構成により、エディタと実行中のコンテナの間でホット同期が自動的に有効になります。ホット同期を有効にするために、追加の構成は必要ありません。

4. 開発プロセスのチュートリアル

このセクションでは、Cloud Code プラグインを使用して基本的なプロセスを学習し、スターター アプリケーションの構成と設定を検証する手順をいくつか紹介します。

Cloud Code を skaffold と統合して開発プロセスを効率化できます。次の手順で GKE にデプロイすると、Cloud Code と Skaffold が自動的にコンテナ イメージをビルドして Container Registry に push し、アプリケーションを GKE にデプロイします。この処理はバックグラウンドで行われ、デベロッパー フローでは詳細が抽象化されます。また、Cloud Code は、コンテナベースの開発に従来のデバッグ機能とホットシンク機能を提供することで開発プロセスを強化します。

Kubernetes へのデプロイ

  1. Cloud Shell エディタの下部にあるペインで、[Cloud Code] を選択します。

fdc797a769040839.png

  1. 上部に表示されるパネルで、[Run on Kubernetes] を選択します。プロンプトが表示されたら、[はい] を選択して現在の Kubernetes コンテキストを使用します。

cfce0d11ef307087.png

  1. コマンドを初めて実行すると、画面の上部に現在の Kubernetes コンテキストが必要かどうかを尋ねるプロンプトが表示されます。[はい] を選択します。現在のコンテキストを受け入れて使用します。

817ee33b5b412ff8.png

  1. 次に、使用する Container Registry を尋ねるプロンプトが表示されます。Enter キーを押して、指定されたデフォルト値を受け入れます。

eb4469aed97a25f6.png

  1. 下部のペインで [Output] タブを選択すると、進行状況と通知が表示されます。

f95b620569ba96c5.png

  1. [Kubernetes: Run/Debug - Detailed] を選択します。右側のチャネル プルダウンから、追加の詳細情報や、コンテナからライブ ストリーミングされるログを確認できます。

94acdcdda6d2108.png

  1. [Kubernetes: Run/Debug] を選択すると、簡素化されたビューに戻ります。プルダウンから
  2. ビルドとテストが完了すると、[Output] タブに Resource deployment/mynodejsapp status completed successfully と表示され、URL として「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。
  3. Cloud Code ターミナルで、出力の URL(http://localhost:8080)にカーソルを合わせ、表示されたツールチップで [Open Web Preview] を選択します。

レスポンスは次のようになります。

{"message":"Greetings from Node"}

ホットリロード

  1. src/index.js に移動します。挨拶メッセージのコードを 'Hello from Node' に変更します。

すぐに、Output ウィンドウの Kubernetes: Run/Debug ビューで、ウォッチャーが更新されたファイルを Kubernetes のコンテナと同期します。

Update initiated
File sync started for 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
File sync succeeded for 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
Update succeeded
  1. Kubernetes: Run/Debug - Detailed ビューに切り替えると、ファイルの変更が認識され、ノードが再起動されます。
files modified: [src/index.js]
Copying files:map[src/index.js:[/workspace/src/index.js]]togcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
Syncing 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a
Watching for changes...
[mynodejsapp]
[mynodejsapp]> mynodejsapp@1.0.0 start /workspace
[mynodejsapp]> node src/index.js
[mynodejsapp]
[mynodejsapp]Server running at: http://localhost:8080/
  1. ブラウザを更新して、更新された結果を確認してください。

デバッグ

  1. デバッグビューに移動し、現在のスレッド 647213126d7a4c7b.png を停止します。
  2. 下部のメニューで Cloud Code をクリックし、Debug on Kubernetes を選択して、アプリを debug モードで実行します。
  • Output ウィンドウの Kubernetes Run/Debug - Detailed ビューで、skaffold がこのアプリケーションをデバッグモードでデプロイします。
  • アプリケーションのビルドとデプロイには数分かかります。今度はデバッガがアタッチされているのがわかります。
Port forwarding pod/mynodejsapp-6bbcf847cd-vqr6v in namespace default, remote port 9229 -> http://127.0.0.1:9229
[mynodejsapp]Debugger attached.
  1. 下部のステータスバーの色が青からオレンジ色に変わり、デバッグモードになっていることを示します。
  2. Kubernetes Run/Debug ビューで、Debuggable コンテナが開始されていることを確認します。
**************URLs*****************
Forwarded URL from service mynodejsapp-service: http://localhost:8080
Debuggable container started pod/mynodejsapp-deployment-6bc7598798-xl9kj:mynodejsapp (default)
Update succeeded
***********************************

ブレークポイントの活用

  1. src/index.js を開きます。
  2. var message="Greetings from Node";」というステートメントを見つけます。
  3. 行番号の左側にある空白スペースをクリックして、その行にブレークポイントを追加します。ブレークポイントが設定されたことを示す赤いインジケーターが表示される
  4. ブラウザを再読み込みすると、デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調査できるようになります。
  5. "message" 変数が見つかるまで、変数セクションをクリックします。
  6. Step over 7cfdee4fd6ef5c3a.png を押して行を実行します。
  7. "message" 変数の現在の値が "Greetings from Node" に変わることを確認します。
  8. 変数名「target」をダブルクリックします。ポップアップで、値を "Hello from Node" など別のものに変更します。
  9. デバッグ用コントロール パネルで [続行] ボタンをクリックします。
  10. ブラウザでレスポンスを確認します。入力した値が更新されています。
  11. 「Debug」と表示モードに切り替えるには停止ボタン 647213126d7a4c7b.png を押します。ブレークポイントをもう一度クリックすると、ブレークポイントを削除できます。

5. シンプルな CRUD REST サービスを開発する

これで、アプリケーションはコンテナ化された開発用に完全に構成され、Cloud Code での基本的な開発ワークフローはひととおり確認できました。以降のセクションでは、Google Cloud のマネージド データベースに接続する REST サービス エンドポイントを追加して、学習した内容を実践します。

依存関係を構成する

アプリケーション コードは、データベースを使用して残りのサービスデータを保持します。package.json ファイルに次の行を追加して、依存関係が利用可能であることを確認します。

  1. さらに 2 つの依存関係 pgsequelizepackage.json ファイルに追加して、CRUD アプリケーション Postgres を構築します。変更後は、依存関係セクションは次のようになります。
    "dependencies": {
    "express": "^4.16.4",
    "pg": "^8.7.3",
    "sequelize": "^6.17.0"
  }

REST サービスをコーディングする

  1. このアプリケーションに CRUD アプリケーション コードを追加します
wget -O app.zip https://github.com/GoogleCloudPlatform/container-developer-workshop/raw/main/labs/nodejs/app.zip

unzip app.zip

このコードは

  • item のエンティティ モデルを含む models フォルダ
  • CRUD オペレーションを行うコードを含む controllers フォルダ
  • routes フォルダ。特定の URL パターンを異なる呼び出しにルーティングします。
  • データベース接続の詳細を含む config フォルダ
  1. db.config.js ファイルのデータベース構成は、データベースに接続するために指定する必要がある環境変数を参照します。また、URL エンコード用に受信リクエストを解析する必要もあります。
  2. src/index.js に次のコード スニペットを追加して、app.listen(PORT, () => { で始まる最後のセクションの直前に、メインの JavaScript ファイルの CRUD コードに接続できるようにします。
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(
 bodyParser.urlencoded({
   extended: true,
 })
)
const db = require("../app/models");
db.sequelize.sync();
require("../app/routes/item.routes")(app);
  1. deployment.yaml ファイルの Deployment を編集して、データベースの接続情報を指定する環境変数を追加します。

次の定義と一致するように、ファイルの最後の spec エントリを更新します。

    spec:
      containers:
      - name: mynodejsapp
        image: mynodejsapp
        env:
        - name: DB_HOST
          value: ${DB_INSTANCE_IP}        
        - name: DB_PORT
          value: "5432"  
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: gke-cloud-sql-secrets
              key: username
        - name: DB_PASS
          valueFrom:
            secretKeyRef:
              name: gke-cloud-sql-secrets
              key: password
        - name: DB_NAME
          valueFrom:
            secretKeyRef:
              name: gke-cloud-sql-secrets
              key: database
  1. DB_HOST の値をデータベースのアドレスに置き換えます。
export DB_INSTANCE_IP=$(gcloud sql instances describe mytest-instance \
    --format=json | jq \
    --raw-output ".ipAddresses[].ipAddress")

envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml

アプリケーションのデプロイと検証

  1. Cloud Shell エディタの下部にあるペインで Cloud Code を選択し、画面上部の Debug on Kubernetes を選択します。
  2. ビルドとテストが完了すると、[Output] タブに Resource deployment/mynodejsapp status completed successfully と表示され、URL として「Forwarded URL from service mynodejsapp: http://localhost:8080」が表示されます。
  3. アイテムをいくつか追加します。

cloudshell ターミナルから、以下のコマンドを実行します。

URL=localhost:8080
curl -X POST $URL/items -d '{"itemName":"Body Spray", "itemPrice":3.2}' -H "Content-Type: application/json"
curl -X POST $URL/items -d '{"itemName":"Nail Cutter", "itemPrice":2.5}' -H "Content-Type: application/json"
  1. ブラウザで $URL/items を実行して、GET をテストします。コマンドラインから curl コマンドを実行することもできます。
curl -X GET $URL/items
  1. 削除をテストする: 次のコマンドを実行して、アイテムを削除してみます。必要に応じて、item-id の値を変更します。
curl -X DELETE $URL/items/1
    This throws an error message
{"message":"Could not delete Item with id=[object Object]"}

問題を特定して修正する

  1. デバッグモードでアプリを再起動して問題を見つけます。次のヒントを参考にしてください。
  • DELETE が意図した結果を返さないため、何か問題があることがわかります。そのため、ブレークポイントは itemcontroller.js->exports.delete メソッドで設定します。
  • ステップごとに実行し、各ステップの変数を監視して、左側のウィンドウでローカル変数の値を確認します。
  • request.params などの特定の値をモニタリングするには、この変数を [Watch] ウィンドウに追加します。
  1. id に割り当てられた値が undefined であることに注意してください。コードを変更して問題を解決してください。

固定コード スニペットは次のようになります。

// Delete a Item with the specified id in the request
exports.delete = (req, res) => {
    const id = req.params.id;
  1. アプリケーションが再起動したら、削除してもう一度テストします。
  2. デバッグ ツールバーの赤い正方形 647213126d7a4c7b.png をクリックして、デバッグ セッションを停止します

6. クリーンアップ

これで、このラボでは、新しい Nodejs アプリケーションをゼロから作成し、コンテナを使ってホット デプロイ モードで動作するように構成しました。その後、従来のアプリケーション スタックで見られるのと同じデベロッパー フローに従って、アプリケーションをリモート GKE クラスタにデプロイし、デバッグしました。

ラボの完了後にクリーンアップするには:

  1. ラボで使用したファイルを削除する
cd ~ && rm -rf mynodejsapp && rm -f setup.sh
  1. プロジェクトを削除して、関連するすべてのインフラストラクチャとリソースを削除する