2012/04/16 追記:コンテナ間のEJB呼び出しのSASL認証設定に対応しました。
やること
JBoss AS 7.1では、複数ノードをシングルコンソールで管理する「ドメイン構成」が利用できます。
ここでは、ドメイン構成を利用して、2ノードでコンテナを起動した上で、EJB3.1のリモート呼び出しのサンプルを実行してみます。Jave EE6のEJB3.1では、手軽にEJBが取り扱えることが実感できます。
システム構成
全体構成はこんな感じ。
ドメイン管理のための通信は、管理ネットワークを通じて行います。
ドメイン構成では、各ノードに複数の「サーバ」(コンテナのこと)を起動して、複数ノードの複数サーバを任意にまとめた「サーバグループ」を作成します。アプリケーションのデプロイは、サーバグループ単位で行います。ここでは、「Web_Application_Group」と「EJB_Container_Group」を作成しています。
各ノードはOS(RHEL6.2)の導入まで終わっているものとします。簡単のために、iptablesは停止しています。SELinuxはtargeted modeで有効化しています。
JDKは、RHEL6.2に同梱のOpenJDKです。
# rpm -qa | grep openjdk java-1.6.0-openjdk-1.6.0.0-1.41.1.10.4.el6.x86_64 # java -version java version "1.6.0_22" OpenJDK Runtime Environment (IcedTea6 1.10.4) (rhel-1.41.1.10.4.el6-x86_64) OpenJDK 64-Bit Server VM (build 20.0-b11, mixed mode)
両ノードの名前解決は/etc/hostsで行なっています。
# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.122.41 node01 192.168.122.42 node02 192.168.3.41 node01m 192.168.3.42 node02m
JBoss AS 7.1の導入
「JBoss-7.1.1.Final Brontes.」を使用します。ダウンロードはこちらから。まずは、node01、node02に共通の作業です。
JBoss AS 7.1のtar.gzを展開します。
# cd /opt # tar -xvzf /root/work/jboss-as-7.1.1.Final.tar.gz # cd /root # ln -s /opt/jboss-as-7.1.1.Final jboss
デフォルトで用意されているサーバ、サーバグループの定義は消しておきます。
「~/jboss/domain/configuration/domain.xml」の下記の
<server-groups> <!-- ここから --> <server-group name="main-server-group" profile="full"> <jvm name="default"> <heap size="64m" max-size="512m"/> </jvm> <socket-binding-group ref="full-sockets"/> </server-group> <server-group name="other-server-group" profile="full-ha"> <jvm name="default"> <heap size="64m" max-size="512m"/> </jvm> <socket-binding-group ref="ha-sockets"/> </server-group> <!-- ここまで --> </server-groups>
「~/jboss/domain/configuration/host.xml」の下記の
<servers> <!-- ここから --> <server name="server-one" group="main-server-group"> <!-- Remote JPDA debugging for a specific server <jvm name="default"> <jvm-options> <option value="-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n"/> </jvm-options> </jvm> --> </server> <server name="server-two" group="main-server-group" auto-start="true"> <!-- server-two avoids port conflicts by incrementing the ports in the default socket-group declared in the server-group --> <socket-bindings port-offset="150"/> </server> <server name="server-three" group="other-server-group" auto-start="false"> <!-- server-three avoids port conflicts by incrementing the ports in the default socket-group declared in the server-group --> <socket-bindings port-offset="250"/> </server> <!-- ここまで --> </servers>
各ノードの「host名」を指定します。これは、OSのhostnameと異なっても構いません。(のはず。。。)
~/jboss/domain/configuration/host.xml
<host name="host01" xmlns="urn:jboss:domain:1.2">
「host01」の部分は各ノードにあわせて指定してください。ここでは、node01を「host01」、node02を「host02」として指定します。
「~/jboss/domain/configuration/host.xml」でListenするアドレスを指定します。
<interfaces> <interface name="management"> <inet-address value="${jboss.bind.address.management:node01m}"/> </interface> <interface name="public"> <inet-address value="${jboss.bind.address:0.0.0.0}"/> </interface> <interface name="unsecure"> <inet-address value="${jboss.bind.address.unsecure:0.0.0.0}"/> </interface> </interfaces>
"management"には、管理ネットワークに接続したNICを指定して、管理ネットワークからの接続のみを許可しています。「node01m」の部分は、各ノードにあわせて変えてください。(IPアドレスを直書きしてもかまいません。)
次は、node01のみでの作業です。管理コンソールに接続するユーザを登録します。
# ~/jboss/bin/add-user.sh What type of user do you wish to add? a) Management User (mgmt-users.properties) b) Application User (application-users.properties) (a): Enter the details of the new user to add. Realm (ManagementRealm) : Username : adminuser Password : Re-enter Password : About to add user 'adminuser' for realm 'ManagementRealm' Is this correct yes/no? yes Added user 'adminuser' to file '/opt/jboss-as-7.1.1.Final/standalone/configuration/mgmt-users.properties' Added user 'adminuser' to file '/opt/jboss-as-7.1.1.Final/domain/configuration/mgmt-users.properties' # ~/jboss/bin/add-user.sh What type of user do you wish to add? a) Management User (mgmt-users.properties) b) Application User (application-users.properties) (a): Enter the details of the new user to add. Realm (ManagementRealm) : Username : host02 Password : Re-enter Password : About to add user 'host02' for realm 'ManagementRealm' Is this correct yes/no? yes Added user 'host02' to file '/opt/jboss-as-7.1.1.Final/standalone/configuration/mgmt-users.properties' Added user 'host02' to file '/opt/jboss-as-7.1.1.Final/domain/configuration/mgmt-users.properties'
管理コンソールを使用するためのユーザ(adminuser)に加えて、node02の"host名"である「host02」もユーザとして登録しています。これは、node02がドメインに参加するために必要になります。
次は、node02のみでの作業です。ドメインのマスタノードを「node01」に指定して、管理接続のためのユーザ/パスワードを指定します。
~/jboss7/domain/configuration/host.xml
<host name="host02" xmlns="urn:jboss:domain:1.2"> <management> <security-realms> <security-realm name="ManagementRealm"> <server-identities> <secret value="aG9nZWhvZ2U="/> </server-identities> (snip) <domain-controller> <remote host="node01m" port="9999" security-realm="ManagementRealm"/> </domain-controller>
上記の「
ちなみに、BASE64エンコードは次のコマンドで可能です。
# echo -n "hogehoge" | base64 aG9nZWhvZ2U=
JBossの起動と構成
いよいよJBossを起動します。それぞれのノードで次を実行します。
# ~/jboss/bin/domain.sh
フォアグラウンドで起動ログがずらずら流れてきます。両ノードの起動が完了して、node01のログに次のメッセージが表示されれば、両ノードによるドメインが無事に構成されています。
[Host Controller] 14:27:47,081 INFO [org.jboss.as.domain] (domain-mgmt-handler-thread - 1) JBAS010918: Registered remote slave host "host02", JBoss AS 7.1.1.Final "Brontes"
Webブラウザで「http://192.168.3.41:9990/console」を開いて、先に設定した「adminuser」でログインするとドメインの管理コンソールが開きます。
はじめに、サーバグループを定義します。画面右上の「Profile」→画面左の「Server Groups/Group Configurations」を選択して、次の2種類のサーバグループを定義します。
Name: EJB_Container_Group Profile: default Socket Binding: standard-sockets Name: EJB_Container_Group Profile: default Socket Binding: standard-sockets
次に、サーバ(コンテナ)を定義します。画面右上の「Server」→画面左の「Server Configurations」を選択します。画面左上の「Host:」プルダウンから、host01を選択して、次のサーバを定義します。
Name: host01_server01 Server Group: Web_Application_Group Port Offset: 0 Auto Start?:「チェック」
同じく、host02を選択して、次のサーバを定義します。
Name: host02_server01 Server Group: EJB_Container_Group Port Offset: 0 Auto Start?:「チェック」
EJBサンプルアプリの作成
この内容は、こちらのドキュメントとこちらのディスカッションを参考にしています。
まず、EclipseでJavaプロジェクト「GreeterEJB」を作成して、プロジェクト・プロパティの「プロジェクト・ファセット」に「EJBモジュール」を追加します。クラスパスには「JREシステム・ライブラリー」と「JBoss 7.1 Runtime」あたりを追加しておきます。
EJB3.1で、EJBをつくるときは、最初にクライアントに見せるインターフェース(ビジネスインターフェース)を定義します。
/src/ejb/GreetingServiceBusiness.java
package ejb; public interface GreetingServiceBusiness { public String greet(String name); @javax.ejb.Remote public interface Remote extends GreetingServiceBusiness { } @javax.ejb.Local public interface Local extends GreetingServiceBusiness { } }
ローカルアクセス(同一のコンテナ内からの参照渡しでのアクセス)とリモートアクセス(異なるコンテナからの値渡しでのアクセス)用に2種類のインターフェースが必要ですが、ここでは、GreetingServiceBusinessインターフェースのインナーインターフェースとしてまとめています。「@javax.ejb.Remote」「@javax.ejb.Local」のアノテーションによって、それぞれ、リモートアクセス用とローカルアクセス用のビジネスインターフェースとして、コンテナから自動的に認識されます。
続いて、EJBの実装クラスを書きます。
/src/ejb/GreetingServiceBean.java
package ejb; import javax.annotation.Resource; import javax.ejb.SessionContext; import javax.ejb.Stateless; import ejb.GreetingServiceBusiness; @Stateless(name = "GreetEJB") public class GreetingServiceBean implements GreetingServiceBusiness.Local, GreetingServiceBusiness.Remote { @Resource private SessionContext context; @Override public String greet(String name) { System.out.println(context.getInvokedBusinessInterface().toString()); return "Hello, " + name + "!"; } }
先に定義した2種類のビジネスインターフェースを実装します。アノテーション「@Stateless(name = "GreetEJB")」により、コンテナから、Stateless Beanとして自動的に認識されます。ここで指定したnameは、JNDIでLookupする際のEJB名になります。ここではサンプルとして、@Resourceアノテーションにより、SessionContextのインジェクトも行なっています。
EJBの作成はこれだけです。xmlファイルを書かずに、アノテーションだけでEJBを構成することができました。Eclipseから「GreeterEJB.jar」として、Exportしておきます。
続いて、このEJBを利用するサーブレットを作成します。まず、Eclipseで動的Webプロジェクト「HelloWorld」を作成します。
EJBを利用するアプリケーションには、該当EJBのインターフェースクラスが必要です(実装クラスはもちろん不要)。先に作成した「/src/ejb/GreetingServiceBusiness.java」をプロジェクトにコピーします。
サーブレットのクラスを作成します。
src/web/HelloWorldServlet.java
package web; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import ejb.GreetingServiceBusiness; @WebServlet("/greeting") public class HelloWorldServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String JNDI_NAME = "ejb:/GreeterEJB/GreetEJB!ejb.GreetingServiceBusiness$Remote"; public HelloWorldServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { GreetingServiceBusiness greeter = null; String message = "Hello, World!"; String name = (String) request.getParameter("name"); if (name != null) { try { final Hashtable<String, String> jndiProperties = new Hashtable<String, String>(); jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); final Context context = new InitialContext(jndiProperties); greeter = (GreetingServiceBusiness) context.lookup(JNDI_NAME); } catch (NamingException e) { throw new RuntimeException(e); } message = greeter.greet(name); } ServletOutputStream out = response.getOutputStream(); out.println("<html><head><title>EJB Test</title></head>"); out.println("<body>"); out.println("<h1>" + message + "</h1>"); out.println("</body></html>"); } }
アノテーション「@WebServlet("/greeting")」により、コンテナからサーブレットとして認識されます。「HelloWorld/greeting」が対応するURLになります。
サーブレットの中身は単純で、nameパラメータが指定されている場合に、GreetEJBを呼び出して表示するメッセージを構成します。
EJBを呼び出すコードは、本質的には次の5行です。
private static final String JNDI_NAME = "ejb:/GreeterEJB/GreetEJB!ejb.GreetingServiceBusiness$Remote"; final Hashtable<String, String> jndiProperties = new Hashtable<String, String>(); jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); final Context context = new InitialContext(jndiProperties); greeter = (GreetingServiceBusiness) context.lookup(JNDI_NAME);
JNDI名に「ejb:/
jndiPropetiesで、JBoss環境に固有のURL_PKG_PREFIXESを使用しています。これをコードの外に出したい場合は、クラスパスの通った場所に下記のプロパティファイルを配置します。
jndi.properties
java.naming.factory.url.pkgs=org.jboss.ejb.client.namingこの場合は、下記のように、jndiPropertiesを指定せずにContextの取得が可能です。
// final Hashtable<String, String> jndiProperties = new Hashtable<String, String>(); // jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); // final Context context = new InitialContext(jndiProperties); final Context context = new InitialContext(); greeter = (GreetingServiceBusiness) context.lookup(JNDI_NAME);
EJBモジュールとサーブレットモジュールが同一コンテナにある場合は、これだけでOKです。作成した2つのプロジェクトをEclipse上でローカルのJBossサーバにデプロイすると、次のように動作確認ができます。
$ curl http://localhost:8080/HelloWorld/greeting <html><head><title>EJB Test</title></head> <body> <h1>Hello, World!</h1> </body></html> $ curl http://localhost:8080/HelloWorld/greeting?name=enakai <html><head><title>EJB Test</title></head> <body> <h1>Hello, enakai!</h1> </body></html>
これらのモジュールを異なるコンテナで実行する場合は、何らかの方法で、接続先のEJBコンテナを指定する必要があります。これには、サーブレットモジュール側の準備とコンテナ(JBoss)側の準備が必要です。
まず、サーブレットモジュールには、META-INFの下に次のxmlを用意します。
META-INF/jboss-ejb-client.xml
<?xml version="1.0" encoding="UTF-8"?> <jboss-ejb-client xmlns="urn:jboss:ejb-client:1.0"> <client-context> <ejb-receivers> <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection"/> </ejb-receivers> </client-context> </jboss-ejb-client>
これは、コンテナ(JBoss)上で定義された「remote-ejb-connection」で指定されるEJBコンテナにアクセスするという指定です。(これを作成すると、必要な設定がなされていない、ローカルのJBossではサーブレットが実行できなくなるので注意してください。)これでサーブレット・モジュールも完成です。Eclipseから「HelloWorld.war」として、Exportしておきます。
続いて、JBoss側の設定です。CLIを利用すると動的変更もできるようですが、ここでは、一旦、両ノードのJBossをCtrl+Cで停止して、設定ファイルを直接変更します。
まず、ドメインマスタのdomain.xmlに次の設定を追加します。
domain.xml
<socket-binding-groups> <socket-binding-group name="standard-sockets" default-interface="public"> (snip) <outbound-socket-binding name="mail-smtp"> <remote-destination host="localhost" port="25"/> </outbound-socket-binding> <!-- ここから --> <outbound-socket-binding name="remote-ejb"> <remote-destination host="node02" port="4447"/> </outbound-socket-binding> <!-- ここまで --> </socket-binding-group>
これは、リモートのEJBコンテナのホストネームとポートを「remote-ejb」という名前で定義しています。「socket-binding-group」には、「name」アトリビュートが異なるいくつか種類があります。サーバグループを定義する際に指定した「Socket Binding: standard-sockets」に対応しますので、サーバグループで指定したもの(この例では「standard-sockets」)に対して設定を追加します。
さらに使用する設定プロファイルの「
<subsystem xmlns="urn:jboss:domain:pojo:1.0"/> <subsystem xmlns="urn:jboss:domain:remoting:1.1"> <connector name="remoting-connector" socket-binding="remoting" security-realm="ApplicationRealm"/> <!-- ここから --> <outbound-connections> <remote-outbound-connection name="remote-ejb-connection" outbound-socket-binding-ref="remote-ejb" security-realm="ejb-security-realm" username="ejbuser"> <properties> <property name="SASL_POLICY_NOANONYMOUS" value="false"/> <property name="SSL_ENABLED" value="false"/> </properties> </remote-outbound-connection> </outbound-connections> <!-- ここまで --> </subsystem> <subsystem xmlns="urn:jboss:domain:resource-adapters:1.0"/>
設定プロファイルは、サーバグループを定義する際に指定した「Profile: default」の部分に相当します。サーブレット側で指定した「remote-ejb-connection」という名前がこれによって、先の「remote-ejb」と紐付きます。
この時、上記の設定に2種類の「security-realm」が登場している事に注意します。
したがって、接続先EJBコンテナ(node02)の「ApplicationRealm」にユーザ「ejbuser」とパスワードを登録して、そのパスワードを接続元コンテナ(node01)の「ejb-security-realm」の認証情報として埋め込んでおく必要があります。
具体的には次の手順になります。
まず、node02で、ApplicationRealmにejbuserを登録します。
# ./jboss/bin/add-user.sh What type of user do you wish to add? a) Management User (mgmt-users.properties) b) Application User (application-users.properties) (a): b Enter the details of the new user to add. Realm (ApplicationRealm) : Username : ejbuser Password : Re-enter Password : What roles do you want this user to belong to? (Please enter a comma separated list, or leave blank for none) : About to add user 'ejbuser' for realm 'ApplicationRealm' Is this correct yes/no? yes Added user 'ejbuser' to file '/opt/jboss-as-7.1.1.Final/standalone/configuration/application-users.properties' Added user 'ejbuser' to file '/opt/jboss-as-7.1.1.Final/domain/configuration/application-users.properties'
この時セットしたパスワードをBASE64に変換したものを、node01のhost.xmlに下記のように仕込みます。
<management> <!-- ここから --> <security-realms> <security-realm name="ejb-security-realm"> <server-identities> <secret value="YXNkZmhvZ2U="/> </server-identities> </security-realm> <!-- ここまで --> <security-realm name="ManagementRealm">
モジュールのデプロイ
作成した「GreetingEJB.jar」と「HelloWorld.war」をデプロイします。
各ノードのdomain.shを起動した後に、JBossの管理コンソールを開きなおして、画面右上の「Runtime」→画面左の「Deployments/Manage Deployments」を選択します。ここで、画面上の「Deployment Content」タブから、それぞれのモジュールをアップロードします。
アップロードしたモジュールの「Add to Groups」ボタンで、それぞれのモジュールをサーバグループに割り当てます。ここでは、「GreetingEJB.jar」を「EJB_Container_Group」に、「HelloWorld.war」を「Web_Application_Group」にそれぞれ割り当てます。これで、node01でサーブレットが起動して、node02でEJBが起動する状態になります。
次のように動作確認ができます。
$ curl http://192.168.122.41:8080/HelloWorld/greeting <html><head><title>EJB Test</title></head> <body> <h1>Hello, World!</h1> </body></html> $ curl http://192.168.122.41:8080/HelloWorld/greeting?name="E.Nakai" <html><head><title>EJB Test</title></head> <body> <h1>Hello, E.Nakai!</h1> </body></html>