Node.js ランタイム

概要

Node.js ランタイムは、アプリケーションのコードと依存関係をインストールして実行するソフトウェア スタックです。ランタイムはapp.yamlruntime: nodejsとして宣言されます。

runtime: nodejs
env: flex

フレキシブル環境のランタイムは、Docker を使用してビルドされています。Node.js ランタイムは Ubuntu 16.04 がベースとなっていて、Node.js ランタイムのソースコードは GitHub で一般公開されています。

パッケージ マネージャ

デプロイ時に、ランタイムは npm または yarn パッケージ マネージャーを使用し、依存関係をインストールしてアプリケーションを起動します。パッケージ マネージャーは次のロジックに基づいています。

  • デフォルトのパッケージマネージャは npm です。
  • yarn.lock ファイルがアプリケーションのルート ディレクトリに存在する場合は、ランタイムは代わりに yarn パッケージ マネージャーを使用します。
  • package-lock.jsonyarn.lock の両方が存在する場合、デプロイはエラーが発生して失敗します。両方のファイルが必要な場合は、app.yaml ファイルの skip_files セクションにどちらか一方を追加して、使用するパッケージ マネージャーを指定します。

エンジン

Node.js バージョン

デフォルトの Node.js エンジンは最新の LTS リリースにすることを方針としています。engines フィールドを使用して、アプリケーションの package.json ファイルで別の Node.js バージョンを指定できます。予期しない破損を回避するために、Node.js バージョンを指定するようにしてください。

次の例では、最新の Node 9 リリースを使用するようにランタイムを構成しています。

{
  "engines": {
    "node": "9.x"
  }
}

engines.node プロパティには semver 範囲を指定できます。これを指定すると、ランタイムによって、semver 範囲に一致する最新バージョンの Node.js がダウンロードおよびインストールされます。一致するものが見つからない場合は、アプリケーションのデプロイが失敗し、ランタイムからエラー メッセージが返されます。

パッケージ マネージャー バージョン

ランタイム イメージでは、最新の Node.js LTS リリースで提供されている最新の yarn リリースおよび npm のリリースを使用する方針としています。

engines フィールドを使用することによって、アプリケーションの package.json ファイルで 別のパッケージ マネージャー バージョンを指定できます。この場合、ランタイムは engines フィールドで指定したバージョンのパッケージ マネージャーをデプロイに使用します。

yarnnpm の両方のバージョンを指定した場合は、デプロイに使用されるパッケージ マネージャーのみが必要に応じて更新されます。アプリケーションのデプロイに実際に使用されていないカスタム バージョンのパッケージ マネージャーについては、デプロイ時間を短縮するためにインストールから除外されます。

次の例では、カスタム バージョンの npm を使用するようにランタイムを構成しています。

{
  "engines": {
    "npm": "5.x"
  }
}

次の例では、カスタム バージョンの yarn を使用するようにランタイムを構成しています。

{
  "engines": {
    "yarn": ">=1.0.0 <2.0.0"
  }
}

engines.npmengines.yarn のいずれのプロパティにも semver 範囲を指定できます。

依存関係

デプロイ時に、ランタイムは npm または yarn パッケージ マネージャーを使用し、npm install または yarn install を実行することで依存関係をインストールします。使用されるパッケージ マネージャーがランタイムでどのように選択されるかの詳細については、パッケージ マネージャーのセクションをご覧ください。

また、Google App Engine での Node.js パッケージの管理の詳細については、Node.js ライブラリの使用をご覧ください。

ネイティブ機能拡張を必要とする Node.js パッケージを使用できるように、次の Ubuntu パッケージが Docker イメージにプリインストールされています。

アプリケーションで追加のオペレーティング システムレベルの依存関係が必要な場合は、このランタイムに基づいてカスタム ランタイムを使用し、適切なパッケージをインストールする必要があります。

アプリケーションの起動

ランタイムは、package.jsonで指定されたコマンドを使用するnpm startを使用してアプリケーションを起動します。例:

"scripts": {
  "start": "node app.js"
}

起動スクリプトはウェブサーバーを起動し、それが PORT 環境変数で指定されたポート(一般的には 8080)で HTTP リクエストに応答します。

ランタイムの拡張

標準の Node.js ランタイムを使用して、カスタム ランタイムを作成できます。これを行うには、gcloud beta app gen-config --customを使用してベースDockerfileファイルと.dockerignoreファイルを作成します。そして、必要に応じて、このファイルをカスタマイズします。Dockerfile を作成する場合は、app.yamlファイルで runtime: nodejs の代わりに runtime: custom を指定する必要があります。

HTTPS プロキシと転送プロキシ

App Engine はロードバランサで HTTPS 接続を終了し、リクエストをアプリケーションに転送します。アプリケーションによっては、元のリクエスト IP とプロトコルを判断する必要があります。ユーザーの IP アドレスは、標準の X-Forwarded-For ヘッダーで確認できます。この情報を必要とするアプリケーションでは、プロキシを信頼するようにウェブ フレームワークを構成する必要があります。

Express.js では、次の trust proxy 設定を使用します。

app.set('trust proxy', true);

HTTPS 接続を強制する方法については、リクエストの処理方法をご覧ください。

環境変数

次の環境変数が、ランタイム環境によって設定されます。

環境変数 説明
GAE_INSTANCE 現在のインスタンスの名前。
GAE_MEMORY_MB アプリケーション プロセスで使用可能なメモリ量。
GAE_SERVICE アプリケーションの app.yaml ファイルで指定されたサービス名。サービス名が指定されていない場合は、default に設定されます。
GAE_VERSION 現在のアプリケーションのバージョン ラベル。
GOOGLE_CLOUD_PROJECT Google Cloud Console に表示されるアプリケーションに関連付けられたプロジェクト ID。
NODE_ENV アプリがデプロイされている場合、値は production です。
PORT HTTP リクエストを受信するポート。 8080 に設定します。

app.yaml で、追加の構成変数を設定できます。

メタデータ サーバー

アプリケーションのインスタンスは、ホスト名、外部 IP アドレス、インスタンス ID、カスタム メタデータ、サービス アカウント情報など、インスタンスに関する情報を Compute Engine メタデータ サーバーから取得します。App Engine では、インスタンスごとにカスタム メタデータを設定することはできませんが、プロジェクト単位のカスタム メタデータを設定して、App Engine インスタンスや Compute Engine インスタンスから読み取ることができます。

次のサンプル関数では、メタデータ サーバーからインスタンスの外部 IP アドレスを取得します。

const express = require('express');
const request = require('got');

const app = express();
app.enable('trust proxy');

const METADATA_NETWORK_INTERFACE_URL =
  'http://metadata/computeMetadata/v1/' +
  '/instance/network-interfaces/0/access-configs/0/external-ip';

const getExternalIp = async () => {
  const options = {
    headers: {
      'Metadata-Flavor': 'Google',
    },
    json: true,
  };

  try {
    const {body} = await request(METADATA_NETWORK_INTERFACE_URL, options);
    return body;
  } catch (err) {
    console.log('Error while talking to metadata server, assuming localhost');
    return 'localhost';
  }
};

app.get('/', async (req, res, next) => {
  try {
    const externalIp = await getExternalIp();
    res.status(200).send(`External IP: ${externalIp}`).end();
  } catch (err) {
    next(err);
  }
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
  console.log('Press Ctrl+C to quit.');
});