Apache TomcatからELBにアクセスする際に気をつけたい事 sun.net.inetaddr.ttl=-1

それは予告なく突然起こった

TomcatからELBを経由したEC2インスタンスが見つからない。Management Consoleで確認するとちゃんと起動している。なぜ?分からない。ELBの障害でも無さそうだ。ELBへHTTPリクエストしているTomcat側の問題か?謎が深まるばかり。

ELBのIPアドレスは変わることがあるらしい

いろいろ調べていると分かったことがある。ELBのIPアドレスは変わることがあるらしいと。たしかに、各種ドキュメントにはELBはドメイン名を使うように至る所で注意書きがあった。確かに、ELBは単一のサーバーではなく、ELBというサービスだからIPが変わるのは理解できる。でも今回は、ELBはちゃんとドメイン名で書いているはずだぞ。全て正しく設定されているはずなのにTomcatがConnection Refusedとエラーを吐いている。

TomcatがDNSキャッシュをしていたかも

いろいろ調べていると、Javaのセキュリティポリシーによって、DNSキャッシュの生存期間が無限に設定されることもあるらしいと。セキュリティマネージャをONにしているとそうなる。OFFの場合は30秒のTTL。値が設定されている場所は、java.securityというセキュリティポリシーを設定するファイルだそうな。

# The Java-level namelookup cache policy for successful lookups:
#
# any negative value: caching forever
# any positive value: the number of seconds to cache an address for
# zero: do not cache
#
# default value is forever (FOREVER). For security reasons, this
# caching is made forever when a security manager is set. When a security
# manager is not set, the default behavior is to cache for 30 seconds.
#
# NOTE: setting this to anything other than the default value can have
#       serious security implications. Do not set it unless 
#       you are sure you are not exposed to DNS spoofing attack.
#
# networkaddress.cache.ttl=-1 

この時点で私がインストールしたJavaVMではnetworkaddress.cache.ttlをコメントアウトで未設定にしていることが分かりました。

Tomcatのセキュリティマネージャは有効か?

次にTomcatのセキュリティマネージャが有効になっているか調べてみましょう。JSPで以下のように記述すると簡単に分かります。TTLも取得できます。

<%@ page import="java.net.*,java.security.*,sun.net.*" %>
<%
	InetAddress inetAddress = InetAddress.getByName("www.akari7.net");
	byte[] address = inetAddress.getAddress();
	out.println("Domain Name : "+inetAddress.getHostName() + "<br>");
	out.println("IP Address : "+inetAddress.getHostAddress() + "<br>"); 
	out.println("Cache Policy : "+InetAddressCachePolicy.get() + "<br>"); 	
%>

結果は以下の通り。私のローカル環境でのCachePolicyは未設定により30秒でした。

どうやらセキュリティマネージャは設定されていないようです。

セキュリティマネージャをONにしてみる

それでは、セキュリティマネージャONにすることでどのような挙動になるか確認してみましょう。セキュリティマネージャをONにするには、Tomcat起動時に引数に-securityを付けるだけです。

$ ./startup.sh -security
Using CATALINA_BASE:   /Users/satoshi/dev/apache-tomcat-6.0.33
Using CATALINA_HOME:   /Users/satoshi/dev/apache-tomcat-6.0.33
Using CATALINA_TMPDIR: /Users/satoshi/dev/apache-tomcat-6.0.33/temp
Using JRE_HOME:        /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
Using CLASSPATH:       /Users/satoshi/dev/apache-tomcat-6.0.33/bin/bootstrap.jar
Using Security Manager

最後の行にUsing Security Managerと表示されていますね。ブラウザから先ほどのJSPを実行したときにどうなるか確認してみます。結果、セキュリティ違反のエラーとなりました。この例では、SocketPermissionについてAccessControlExceptionが出ました。

また、試しにSystem.exit(0)と記述したJSPを実行したところ、RuntimePermissionについてAccessControlExceptionが出ました。

TTLをJVM起動オプションで指定する

そろそろ答えですが、DNSのTTL指定には、Javaの起動オプションで指定できます。Tomcatの場合は、CATALINA_OPTSを指定することでJVM起動オプションとなります。

$ export CATALINA_OPTS=-Dsun.net.inetaddr.ttl=100
$ ./startup.sh

これでOK!

まとめ

Javaはセキュリティマネージャを設定している場合にDNSのTTLを指定していない場合、無期限で設定してしまいます。そのため、Tomcatを再起動しない限り同じIPを見てしまい、ドメインに対するIPが新しくなっても古いIPを見てしまい、Connection Refusedが起こっていました。java.securityのデフォルトではTTLを指定していないため、問題が発生するまで気付かなかったと思います。そこで、JVM起動オプションによってDNSのTTLを明示的に指定する方法で回避できていることが分かりました。ELBは同じドメインでもIPが変わる可能性があることから、このような問題がたまたま起こったのではないでしょうか。JavaやTomcatの特性を理解してELBを使いこなそう!

後日談

ELBのIPアドレスが実際に変わったかどうか調べるには、AWS内部のログを調べる必要があります。これは、外から分からないので、AWSさんに聞く必要があります。こういった質問に答えてくれるのが、AWSプレミアムサポートです。当社は入っていまして、実際に確認したところIPアドレスが変わっていたのでした。さらに、Tomcat側のセキュリティマネージャはOFFだったのですが、ttl=-1だったのです。ということで、予想が的中!問題解決できました~。AWSプレミアムサポートスタッフのみなさんありがとうございましたー