AIイノベーションズ
Railway/Guides

プライベートネットワーキング

Railwayのプライベートネットワーキングについてすべて学びます。

著者: AIイノベーションズ 阿部隼也X / Twitter

Railwayはこちら

プライベートネットワーキング

プライベートネットワーキングは、サービス間のプライベートネットワークを持つことを可能にするRailway内の機能であり、APIのパブリックゲートウェイを持ちたいが、内部通信はプライベートに保ちたい場合に役立ちます。

デフォルトでは、すべてのプロジェクトでプライベートネットワーキングが有効になっており、サービスは railway.internal ドメインの下に新しいDNS名を取得します。このDNS名は、プロジェクト内のサービスの内部IPv6アドレスに解決されます。

プライベートネットワーク経由での通信

プライベートネットワーク経由で通信するには、成功するために知っておくべき特定のことがいくつかあります。

IPv6でリッスン

プライベートネットワークはIPv6のみのネットワークであるため、プライベートネットワーク経由でリクエストを受信するアプリケーションは、IPv6でリッスンするように設定する必要があります。ほとんどのWebフレームワークでは、ホスト :: にバインドすることでこれを行うことができます。

以下にいくつかの例を示します -

Node / Express

:: でリッスンして、IPv4とIPv6の両方にバインドします。

const port = process.env.PORT || 3000;

app.listen(port, '::', () => {
    console.log(`Server listening on [::]${port}`);
});
Node / Nest

:: でリッスンして、IPv4とIPv6の両方にバインドします。

const port = process.env.PORT || 3000;

async function bootstrap() {
  await app.listen(port, '::');
}
Node / Next

開始コマンドを更新して、IPv4とIPv6の両方にバインドします。

next start --hostname :: --port ${PORT-3000}

または、カスタムサーバーを使用している場合は、next() 関数に渡される構成オブジェクトで hostname:: に設定します。

const port = process.env.PORT || 3000;

const app = next({
  // ...
  hostname: '::',
  port: port
});

これらのオプションのいずれも実行できない場合は、HOSTNAME サービス変数を値 :: で設定して、IPv4とIPv6の両方でリッスンできます。

Python / Gunicorn

開始コマンドを更新して、IPv4とIPv6の両方にバインドします。

gunicorn app:app --bind [::]:${PORT-3000}
Python / Hypercorn

開始コマンドを更新して、IPv4とIPv6の両方にバインドします。

hypercorn app:app --bind [::]:${PORT-3000}
Python / Uvicorn

開始コマンドを更新して、IPv6にバインドします。

uvicorn app:app --host :: --port ${PORT-3000}

注: アプリケーションがプライベートネットワークとパブリックネットワークの両方でアクセスできる必要がある場合、アプリケーションサーバーはデュアルスタックバインディングをサポートする必要があります。ほとんどのサーバーは :: でリッスンするときにこれを自動的に処理しますが、Uvicornなどの一部は処理しません。

内部ホスト名とポートの使用

プライベートネットワーク経由でサービスにリクエストを行うアプリケーションでは、サービスの内部DNS名と、サービスがリッスンしている PORT を使用する必要があります。

たとえば、ポート3000でリッスンしている api というサービスがあり、別のサービスから通信したい場合は、ホスト名として api.railway.internal を使用し、ポートを指定します -

app.get('/fetch-secret', async (req, res) => {
    axios.get('http://api.railway.internal:3000/secret')
    .then(response => {
        res.json(response.data);
    })
})

アドレスに http を使用する必要があることに注意してください。

参照変数の使用

参照変数を使用すると、上記の例と同じ目的を達成できます。

api サービスと通信するためにフロントエンドサービスを設定しているとします。フロントエンドサービスで、次の変数を設定します -

BACKEND_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}

上記の api.PORT は、手動で設定する必要があるサービス変数を参照します。サービスがリッスンしているポートに自動的に解決されることも、実行時にサービスに挿入される PORT 環境変数に解決されることもありません。

次に、フロントエンドコードで、BACKEND_URL 環境変数を参照するだけです -

app.get('/fetch-secret', async (req, res) => {
    axios.get(`${process.env.BACKEND_URL}/secret`)
    .then(response => {
        res.json(response.data);
    })
})

プライベートネットワークコンテキスト

プライベートネットワークはプロジェクトと環境のコンテキストに存在し、パブリックインターネット経由ではアクセスできません。言い換えれば -

  • クライアント側のリクエストを行うWebアプリケーションは、プライベートネットワーク経由で別のサービスと通信できません
  • あるプロジェクト/環境のサービスは、プライベートネットワーク経由で別のプロジェクト/環境のサービスと通信できません

詳細については、FAQセクションを確認してください。

IPv6の既知の構成要件

一部のライブラリとコンポーネントでは、IPv6経由でリッスンまたは接続を確立するときに明示的に指定する必要があります。

ioredis

ioredis はnode.js用のRedisクライアントであり、nodeアプリケーションからRedisに接続するためによく使用されます。

ioredis を使用してRedisクライアントを初期化する場合、IPv6とIPv4の両方のエンドポイントへの接続をサポートするために、接続文字列に family=0 を指定する必要があります。

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL + '?family=0');

const ping = await redis.ping();

ioredis docs

bullmq

bullmq はnode.js用のメッセージキューおよびバッチ処理ライブラリであり、キュー内のジョブを処理するためによく使用されます。

bullmqクライアントを初期化する場合、IPv6とIPv4の両方のRedisエンドポイントへの接続をサポートするために、接続オブジェクトに family: 0 を指定する必要があります。

import { Queue } from "bullmq";

const redisURL = new URL(process.env.REDIS_URL);

const queue = new Queue("Queue", {
    connection: {
        family: 0,
        host: redisURL.hostname,
        port: redisURL.port,
        username: redisURL.username,
        password: redisURL.password
    }
});

const jobs = await queue.getJobs();

console.log(jobs);

bullmq docs

Mongo Dockerイメージ

Docker Hubの公式Mongo Dockerイメージを使用してサービスを作成し、プライベートネットワーク経由で接続したい場合は、MongoインスタンスにIPv6でリッスンするように指示するいくつかのオプションを指定してコンテナを起動する必要があります。たとえば、これは開始コマンドで設定されます。

docker-entrypoint.sh mongod --ipv6 --bind_ip ::,0.0.0.0

Railwayが提供する公式テンプレートは、既にこの開始コマンドでデプロイされていることに注意してください。

hot-shots

hot-shots はnode.js用のStatsDクライアントであり、たとえばDataDogエージェントにメトリックを送信するために使用できます。hot-shots を使用してStatsDクライアントを初期化する場合、IPv6経由で接続するように指定する必要があります。

const StatsD = require('hot-shots');

const statsdClient = new StatsD({
  host: process.env.AGENT_HOST,
  port: process.env.AGENT_PORT,
  protocol: 'udp',
  cacheDns: true,
  udpSocketOptions: {
    type: 'udp6',
    reuseAddr: true,
    ipv6Only: true,
  },
});

hot-shots docs

Go Fiber

fiber はGo用のWebフレームワークです。Fiberアプリを構成するときは、ネットワークフィールドを tcp に設定して、IPv4だけでなくIPv6でもリッスンするようにする必要があります。

app := fiber.New(fiber.Config{
    Network:       "tcp",
    ServerHeader:  "Fiber",
    AppName: "Test App v1.0.1",
})

Fiber docs

DNSのサービス名の変更

サービス設定内で、参照するサービス名を変更できます(例:api-1.railway.internal -> api-2.railway.internal)。

ドメインのルート railway.internal は静的であり、変更できません

注意事項

機能開発プロセス中に、注意すべきいくつかの注意点が見つかりました。

  • プライベートネットワーキングは、ビルドフェーズでは利用できません。
  • プライベートネットワークでトラフィックを受信するには、IPv6ポートにバインドする必要があります。
  • IPv4プライベートネットワーキングはサポートしていません

FAQ

クライアントサイドアプリ、サーバーサイドアプリとは何ですか?また、私が実行しているアプリはどの種類ですか?

プライベートネットワーキングのコンテキストでは、クライアントサイドとサーバーサイドの主な違いは、リクエストがどこから行われるかです。

  • クライアントサイドアプリケーションでは、他のリソース(他のRailwayサービスなど)へのリクエストは、プライベートネットワークの外部のパブリックネットワークに存在するブラウザから行われます。
  • サーバーサイドアプリケーションでは、他のリソースへのリクエストは、アプリケーションをホストしているサーバーから行われます。これは、(アプリをホストしているサーバーがRailwayにあると仮定して)プライベートネットワーク内に存在します。

アプリケーションがクライアントサイドまたはサーバーサイドのリクエストを行っているかどうかを判断する1つの方法は、DevToolsのネットワークタブでリクエストを検査することです。RequestURLがリクエストが行われているリソース(バックエンドサーバーなど)である場合、これはブラウザ自体がリクエストを行っている(クライアントサイド)ことを示す良い兆候です。

Vercelからサーバーサイドでリクエストを行っている場合はどうなりますか?

VercelでホストされているアプリケーションはRailwayのプライベートネットワークの外部に存在するため、Vercelサーバーからのリクエストはプライベートネットワーク経由では行えません。

Railwayはこちら

PR