プライベートネットワーキング
Railwayのプライベートネットワーキングについてすべて学びます。
著者: AIイノベーションズ 阿部隼也(X / Twitter)プライベートネットワーキング
プライベートネットワーキングは、サービス間のプライベートネットワークを持つことを可能にする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();
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);
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,
},
});
Go Fiber
fiber
はGo用のWebフレームワークです。Fiberアプリを構成するときは、ネットワークフィールドを tcp
に設定して、IPv4だけでなくIPv6でもリッスンするようにする必要があります。
app := fiber.New(fiber.Config{
Network: "tcp",
ServerHeader: "Fiber",
AppName: "Test App v1.0.1",
})
DNSのサービス名の変更
サービス設定内で、参照するサービス名を変更できます(例:api-1.railway.internal
-> api-2.railway.internal
)。
ドメインのルート railway.internal
は静的であり、変更できません。
注意事項
機能開発プロセス中に、注意すべきいくつかの注意点が見つかりました。
- プライベートネットワーキングは、ビルドフェーズでは利用できません。
- プライベートネットワークでトラフィックを受信するには、IPv6ポートにバインドする必要があります。
- IPv4プライベートネットワーキングはサポートしていません
FAQ
クライアントサイドアプリ、サーバーサイドアプリとは何ですか?また、私が実行しているアプリはどの種類ですか?
プライベートネットワーキングのコンテキストでは、クライアントサイドとサーバーサイドの主な違いは、リクエストがどこから行われるかです。
- クライアントサイドアプリケーションでは、他のリソース(他のRailwayサービスなど)へのリクエストは、プライベートネットワークの外部のパブリックネットワークに存在するブラウザから行われます。
- サーバーサイドアプリケーションでは、他のリソースへのリクエストは、アプリケーションをホストしているサーバーから行われます。これは、(アプリをホストしているサーバーがRailwayにあると仮定して)プライベートネットワーク内に存在します。
アプリケーションがクライアントサイドまたはサーバーサイドのリクエストを行っているかどうかを判断する1つの方法は、DevToolsのネットワークタブでリクエストを検査することです。RequestURLがリクエストが行われているリソース(バックエンドサーバーなど)である場合、これはブラウザ自体がリクエストを行っている(クライアントサイド)ことを示す良い兆候です。
Vercelからサーバーサイドでリクエストを行っている場合はどうなりますか?
VercelでホストされているアプリケーションはRailwayのプライベートネットワークの外部に存在するため、Vercelサーバーからのリクエストはプライベートネットワーク経由では行えません。
PR