背景
Node.jsマイクロサービスをデプロイした直後、ログが悲鳴を上げているとします。ローカルマシンでは完璧に動作していたのに、本番環境では外部の決済ゲートウェイへのリクエストがすべて即座に失敗します。原因は?次のような不可解なネットワークエラーです。
Error: getaddrinfo ENOTFOUND api.paymentservice.com
簡単に言うと、ENOTFOUNDは、オペレーティングシステムのDNSリゾルバーがホスト名をIPアドレスにマッピングできなかったことを意味します。サーバーがダウンしているとは限りません。単にアプリケーションがデータの送信先を知らないだけなのです。これは、地図上に住所がない家に手紙を出そうとするようなものです。
デバッグプロセス
ターミナルがエラー(赤文字)で埋め尽くされたとき、私は体系的なチェックリストに従います。これにより、バグがコード内にあるのか、デプロイ環境にあるのか、あるいは内部ネットワークにあるのかを切り分けることができます。
1. URLの形式が正しくないか確認する
まずは基本から始めましょう。文字列を確認してください。よくある間違いは、Node.jsがホスト名のみを期待している箇所にフルURLを渡してしまうことです。ネイティブのhttpまたはhttpsモジュールを使用している場合、hostnameパラメータにhttps://api.example.comを指定すると、必ず失敗します。
// 誤り: プロトコルを含めている
const options = {
hostname: 'https://api.example.com',
path: '/v1/data',
method: 'GET'
};
// 正解: ドメインのみ
const options = {
hostname: 'api.example.com',
path: '/v1/data',
method: 'GET'
};
2. 外部接続を確認する
そのマシンは実際にインターネットにアクセスできていますか?私は通常、ターミナルでpingやdigを実行して、OSがホストを認識できるか確認します。
ping api.paymentservice.com
# または
nslookup api.paymentservice.com
これらのコマンドが「Non-existent domain(存在しないドメイン)」を返したり、5秒後にタイムアウトしたりする場合、問題はNode.jsのコードではありません。ネットワークインターフェースが停止しているか、ポート53でのアウトバウンドトラフィックをブロックする制限的なファイアウォールルールがある可能性があります。
3. 環境変数を精査する
設定ファイルは、目に見えないバグの温床です。コピーした.envファイルに、末尾のスペースや、ルックアップを妨げる不可視のASCII文字が含まれていることがあります。私は以前、ホスト文字列の末尾にあるたった1つのスペース(ASCII 32)を見つけるためだけに、デバッグに4時間を費やしたことがあります。
API_HOST=api.example.com# ここにスペースを入れない
API_HOST= api.example.com # この先頭のスペースが ENOTFOUND の原因になる
API_HOST="api.example.com" # パーサーによっては、文字列に引用符が含まれる場合がある
解決策
解決策A: Dockerのアイソレーションを修正する
コンテナはDNSのアイソレーション(分離)で有名です。コードがノートPCでは動作するのにDockerコンテナ内では失敗する場合、コンテナがホストのDNS設定を継承できていない可能性があります。Google (8.8.8.8) や Cloudflare (1.1.1.1) のような信頼できるパブリックリゾルバーを使用するようにコンテナに強制できます。
実行コマンドに--dnsフラグを追加してみてください。
docker run --dns 8.8.8.8 my-node-app
docker-compose.ymlファイルでは、以下のようになります。
services:
app:
image: my-node-app
dns:
- 8.8.8.8
- 1.1.1.1
解決策B: コーポレートプロキシを通過させる
企業環境では、すべてのトラフィックがZscalerやBlue Coatなどのプロキシを経由することがよくあります。ブラウザとは異なり、Node.jsはシステムのプロキシ設定を自動的には反映しません。外部環境にアクセスするには、リクエストを明示的にプロキシアージェント経由で渡す必要があります。
const HttpsProxyAgent = require('https-proxy-agent');
const axios = require('axios');
// 社内プロキシのURLを使用する
const agent = new HttpsProxyAgent('http://proxy.internal.company:8080');
axios.get('https://api.external.com', { httpsAgent: agent });
解決策C: Node.jsのDNSキャッシュを調整する
Node.jsはOSレベルのgetaddrinfoを使用しますが、これは内部のスレッドプールにおける同期操作です。秒間1,000リクエストのような高負荷下では、これがボトルネックになることがあります。DNSの制限に達したり、断続的にENOTFOUNDエラーが発生したりする場合は、ローカルキャッシュを実装してください。ここではdnscacheモジュールが非常に役立ちます。
require('dnscache')({
enable: true,
ttl: 300, // 5分間キャッシュする
cachesize: 1000
});
検証
修正を確認するには、隔離されたテストスクリプトを実行します。これにより、アプリケーション全体のロジックをバイパスして、生の接続性をテストできます。これをtest-dns.jsとして保存してください。
const dns = require('dns');
const host = 'api.paymentservice.com';
dns.lookup(host, (err, address, family) => {
if (err) {
console.error('DNS Lookup Failed:', err.code);
} else {
console.log(`Success! IP: ${address} (IPv${family})`);
}
});
node test-dns.jsで実行します。IPアドレスが表示されれば、環境は正常です。
学んだ教訓
ほとんどのENOTFOUNDエラーは、ロジックの欠陥ではなく設定ミスです。常に環境変数をサニタイズしてください。常にDockerのネットワークブリッジを確認してください。
クラウドインフラを構築する際、私はルーティングが可能であることを確認するためにCIDRブロックとサブネットを再確認します。VPCが誤って隔離されていないか確認するために、このサブネット計算機を使用しています。最後に、スタートアップスクリプトには必ず「プリフライト(事前)」チェックを含めるようにしています。起動時にコアAPIの名前解決ができない場合は、後で密かに失敗するのではなく、早期にクラッシュさせるべきです。

