シナリオ
それは大抵、予期せぬタイミングで発生します。Spring Bootのマイクロサービスが順調に稼働している最中、ログが突然真っ赤なテキストで埋め尽くされます。APIのレスポンスが正常に返る代わりに、次のようなエラーが表示されます。
Exception in thread "main" java.net.UnknownHostException: api.service-provider.com
at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:229)
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.base/java.net.Socket.connect(Socket.java:609)
このエラーは、Java仮想マシン(JVM)が迷子になっていることを意味します。ホスト名は把握していますが、データを送信するための対応するIPアドレスを見つけることができません。IPアドレスがなければ、接続を開始することすらできません。
なぜルックアップに失敗したのか?
診断は解決への道のりの半分を占めます。JavaにおけるDNSエラーのほとんどは、次の3つのカテゴリに分類されます。
- **インフラストラクチャ:** DNSサーバーに到達できない、またはサーバーのインターネット接続が切断されている。
- **設定:** 開発者のタイポ(入力ミス)、またはプライベートな内部ホストがパブリックDNSレコードに登録されていない。
- **JVMキャッシュ:** リモートサーバーのIPアドレスが変更されたが、JVMが頑固に古くて無効なレコードを保持し続けている。
ステップ1:サニティチェック(基本確認)
常に基本から始めましょう。アプリケーションが実行されているマシンのターミナルから、手動でルックアップを実行します。nslookup または dig を使用します:
nslookup api.service-provider.com
このコマンドでエラーが返される場合、問題はJavaコードではなく、システムレベルのネットワークの問題です。application.yml 内の単純なタイポに注意してください。よくあるミスは、末尾のスペースや、api.servcie-provider.com のような文字の入れ替わりです。これらは毎回例外を引き起こします。
ステップ2:ローカルテストでのhostsファイルの活用
まだパブリックDNSエントリがない開発サーバーに接続する必要がある場合があります。このような場合、OSのhostsファイルでマッピングを強制的に指定できます。
LinuxまたはmacOSの場合: ルート権限で /etc/hosts を開きます。
sudo nano /etc/hosts
# 手動エントリを追加:
1.2.3.4 api.service-provider.com
Windowsの場合: 管理者として C:\Windows\System32\drivers\etc\hosts を編集します。
この方法はDNSサーバーを完全にバイパスします。OSに対し、その特定の名前を即座に特定のIPに解決するように指示します。
ステップ3:DockerとKubernetesのトラブルシューティング
コンテナ化された環境では、ネットワークに複雑さが加わります。アプリがコンテナ内にある場合、UnknownHostException は隔離されたネットワークに起因することがよくあります。
Dockerネットワーキング
同じホスト上の別のコンテナに接続しようとしている場合は、それらが同じDockerネットワークを共有していることを確認してください。また、コンテナにGoogle(8.8.8.8)やCloudflare(1.1.1.1)などの信頼できるDNSプロバイダーを使用させることもできます:
docker run --dns 8.8.8.8 my-java-app
Kubernetes CoreDNS
クラスター内では、短縮名は同じネームスペース内でのみ機能します。別のネームスペースにあるサービスにアクセスする必要がある場合は、完全修飾ドメイン名(FQDN)を使用する必要があります:
# 標準フォーマット: service-name.namespace.svc.cluster.local
auth-service.production.svc.cluster.local
ステップ4:JVM DNSキャッシュの制御
最も見落とされがちな原因は、JVMの内部キャッシュです。デフォルトでは、セキュリティマネージャーが有効な場合、JVMは成功したDNSルックアップを永続的にキャッシュします(TTL = -1)。クラウドプロバイダーがロードバランサーを新しいIPに移行した場合、アプリケーションは存在しない古い接続先にアクセスし続けることになります。
起動引数に以下を追加することで、JVMに60秒ごとにキャッシュをリフレッシュさせることができます:
-Dsun.net.inetaddr.ttl=60
起動スクリプトを変更できない場合は、main メソッドの冒頭でプログラム的に設定します:
java.security.Security.setProperty("networkaddress.cache.ttl" , "60");
検証:解決のテスト
JVMがホストを認識できるか確認するために、この小さなスニペットを実行してください。これにより、アプリケーションフレームワーク全体の複雑さを排除してテストできます:
import java.net.InetAddress;
public class DNSCheck {
public static void main(String[] args) {
try {
String host = "api.service-provider.com";
System.out.println("Resolving: " + host);
System.out.println("IP: " + InetAddress.getByName(host).getHostAddress());
} catch (Exception e) {
e.printStackTrace();
}
}
}
予防戦略
ネットワークの信頼性は、分散システムの核心部分です。ネットワークレイアウトを計画する際は、この サブネット計算機(Subnet Calculator) のようなツールを使用してCIDRブロックを設計してください。これにより、DNSエラーの原因となるルーティングループを引き起こしがちなサブネットの重複を防ぐことができます。
最後に、常にエクスポネンシャルバックオフ(指数関数的バックオフ)を用いたリトライを実装してください。DNSは本質的に不安定なものです。50ミリ秒後の1回のリトライだけで、一時的な不具合を解消し、アプリケーションをスムーズに動かし続けられることもあります。

