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プレミアムサポートスタッフのみなさんありがとうございましたー