nginxをリバースプロキシにしてGlassFishでクラスタリング

GlassFishクラスタ構成を作った時のメモです。

バージョンは、
OS: Scientific Linux 6.3 x86_64
nginx: 1.2.6
GlassFish: 3.1.2.2
Java: Java SE 7u11
いずれも今日時点の最新安定バージョンです。

アプリはJavaEE6で作ってます。
ステートフルなアプリなので、スティッキーセッションとセッションレプリケーションが必要です。

 

構成はこんな感じ。
DAS(ドメイン管理サーバ)がクラスタを管理します。
今回はGlassFishクラスタの話なので、「nginxとDASの冗長化は?」というツッコミは無しでお願いします:)

GlassFishのレプリケーション方式はインメモリレプリケーションです。
昔のGlassFishではHigh-Availability Database (HADB)という方式があったようですが、
もう無くなったみたいですね。

レプリケーションについてはこのページがわかりやすいです。
Clustering in GlassFish Version 3.1

では、クラスタ構成の手順です。
このブログエントリーを参考にさせて頂きました。ありがとうございます!
Glassfish 3.1の自己増殖クラスタを試す

1.ノード準備
※この手順だけはDASではなく、各ノードで行います。

  • glassfishユーザ作成(GlassFishはglassfishユーザで実行する事にします)
  • GlassFishインストールディレクトリ作成
  • GlassFishインストールディレクトリのオーナーをglassfishに変更
  • Javaインストール

{% codeblock lang:bash %} groupadd glassfish useradd -g glassfish glassfish mkdir /usr/local/glassfish3 chown glassfish:glassfish /usr/local/glassfish3 rpm -ivh /path/to/JDKのrpmファイル {% endcodeblock %}

2.クラスタ作成 {% codeblock lang:bash %} asadmin create-cluster cluster1 {% endcodeblock %}

※EC2環境で稼働させる場合、マルチキャストが使えないため以下のように明示的にノードを列挙してください。
詳しくはこのページを参照。
Discovering a Cluster When Multicast Transport Is Unavailable
@yoshioteradaさんに教えて頂きました。ありがとうございます!

※同一ノードにインスタンスを複数立ててクラスタを複数作る場合は、ポートが被らないように注意しましょう。
{% codeblock lang:bash %} asadmin create-cluster –properties GMS_DISCOVERY_URI_LIST=tcp’\:‘//glassfish01,tcp’\:‘//glassfish02,tcp’\:‘//glassfish03:GMS_LISTENER_PORT=9090 cluster1 {% endcodeblock %}

3.DASからノードへのssh接続設定
{% codeblock lang:bash %} echo "AS_ADMIN_SSHPASSWORD=ノードのglassfishユーザのパスワード" > /tmp/password asadmin –passwordfile=/tmp/password –interactive=false setup-ssh –sshuser glassfish –generatekey=true glassfish01 asadmin –passwordfile=/tmp/password –interactive=false setup-ssh –sshuser glassfish –generatekey=true glassfish02 asadmin –passwordfile=/tmp/password –interactive=false setup-ssh –sshuser glassfish –generatekey=true glassfish03 rm /tmp/password {% endcodeblock %}

4.DASからsshでノードのjavaコマンドが使えるか確認 {% codeblock lang:bash %} ssh glassfish01 java -version ssh glassfish02 java -version ssh glassfish03 java -version {% endcodeblock %}

5.ノードにGlassFishをインストール {% codeblock lang:bash %} asadmin install-node –installdir /usr/local/glassfish3 –sshuser glassfish –sshkeyfile ~/.ssh/id_rsa glassfish01 asadmin install-node –installdir /usr/local/glassfish3 –sshuser glassfish –sshkeyfile ~/.ssh/id_rsa glassfish02 asadmin install-node –installdir /usr/local/glassfish3 –sshuser glassfish –sshkeyfile ~/.ssh/id_rsa glassfish03 {% endcodeblock %}

6.DASにノードを追加 {% codeblock lang:bash %} asadmin create-node-ssh –nodehost glassfish01 –sshuser glassfish –sshkeyfile ~/.ssh/id_rsa –installdir /usr/local/glassfish3 glassfish01 asadmin create-node-ssh –nodehost glassfish02 –sshuser glassfish –sshkeyfile ~/.ssh/id_rsa –installdir /usr/local/glassfish3 glassfish02 asadmin create-node-ssh –nodehost glassfish03 –sshuser glassfish –sshkeyfile ~/.ssh/id_rsa –installdir /usr/local/glassfish3 glassfish03 {% endcodeblock %}

7.ノードにインスタンスを追加 {% codeblock lang:bash %} asadmin create-instance –node glassfish01 –cluster cluster1 instance1 asadmin create-instance –node glassfish02 –cluster cluster1 instance2 asadmin create-instance –node glassfish03 –cluster cluster1 instance3 {% endcodeblock %}

8.スティッキーセッションのためのプロパティ設定
この設定を行うと、Cookieの末尾にインスタンス識別子が付加され、
リクエストが振り分けられるインスタンスが固定されます。
{% codeblock lang:bash %} asadmin create-system-properties –target instance1 INSTANCE=i1 asadmin create-system-properties –target instance2 INSTANCE=i2 asadmin create-system-properties –target instance3 INSTANCE=i3 asadmin create-jvm-options –target cluster1 -DjvmRoute=\${INSTANCE} {% endcodeblock %}

9.クラスタを起動
※OS起動時のクラスタの自動起動がわかりませんでした。ご存知の方教えて下さい>< {% codeblock lang:bash %} asadmin start-cluster cluster1 {% endcodeblock %}

GlassFishの設定はここまで。
次はnginxのインストールと設定です。

nginxは標準でスティッキーセッションの機能がありませんが、
モジュールを作ってくれている方がいらっしゃるので、ありがたく使わせて頂きます。

1.nginxインストール

ソースからインストールするので、ユーザを作ります。 {% codeblock lang:bash %} groupadd nginx useradd -g nginx nginx {% endcodeblock %}

依存ライブラリのインストール {% codeblock lang:bash %} yum install pcre-devel {% endcodeblock %}

ソースと追加モジュールのダウンロード {% codeblock lang:bash %} cd /usr/local/src wget http://nginx.org/download/nginx-1.2.6.tar.gz svn checkout http://nginx-sticky-module.googlecode.com/svn/trunk/ nginx-sticky-module-read-only {% endcodeblock %}

インストール ※各種パスとモジュールの指定は適宜読み替えてください。 {% codeblock lang:bash %} tar xvf nginx-1.2.6.tar.gz cd nginx-1.2.6 ./configure
–prefix=/var/www
–user=nginx
–group=nginx
–sbin-path=/usr/local/sbin/nginx
–conf-path=/etc/nginx/nginx.conf
–error-log-path=/var/log/nginx/error.log
–http-log-path=/var/log/nginx/access.log
–pid-path=/var/run/nginx/nginx.pid
–lock-path=/var/lock/subsys/nginx.lock
–with-http_ssl_module
–with-http_realip_module
–with-http_gzip_static_module
–with-http_stub_status_module
–add-module=../nginx-sticky-module-read-only make make install {% endcodeblock %}

2.設定ファイル修正
nginxのconfigureの時に–conf-pathで指定したパスに設定ファイルが出来ています。
以下、設定例です。
※スティッキーセッションに必要な設定は、upstreamのstickyだけです。
{% codeblock lang:javascript %} user nginx; worker_processes 1;

error_log /var/log/nginx/error.log debug;

pid /var/run/nginx/nginx.pid;

events { worker_connections 1024; }

http { include mime.types; default_type application/octet-stream;

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" '
                  '$upstream_response_time';

access_log  /var/log/nginx/access.log  main;

sendfile        on;

tcp_nopush      off;

keepalive_timeout  65;

gzip  off;

server_tokens off;

upstream cluster1 {
    sticky;
    server glassfish01:28080 max_fails=2 fail_timeout=10m;
    server glassfish02:28080 max_fails=2 fail_timeout=10m;
    server glassfish03:28080 max_fails=2 fail_timeout=10m;
}

server {
    listen       80;

    proxy_set_header Host               $host;
    proxy_set_header X-Real-IP          $remote_addr;
    proxy_set_header X-Forwarded-Host   $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;

    error_page  404              /404.html;
    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }

    location /favicon.ico {
        root   /var/www/html;
    }

    location /warファイルコンテキスト名/ {
        proxy_pass    http://cluster1;
    }
}

} {% endcodeblock %}

以上で環境は出来ました。
動作確認してみます。

1.確認用ソース
以下をsession.jspという名前で保存して、warファイルを作成します。
{% codeblock lang:java %} <%@ page language=“java” contentType=“text/html; charset=UTF-8” pageEncoding=“UTF-8” %> <%@ page import=“java.net.InetAddress” %> <%@ page import=“java.text.SimpleDateFormat” %> <%@ page import=“java.util.Date” %> <%@ page import=“javax.servlet.http.Cookie” %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Session Info

Server <%= InetAddress.getLocalHost().getHostName() %>
Session ID <%= session.getId() %>
Created on <% SimpleDateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss.SSS”); out.print(sdf.format(new Date(session.getCreationTime()))); %>
Request Count <% if (session.getAttribute(“count”) == null) { session.setAttribute(“count”, new Integer(0)); } int count = ((Integer)session.getAttribute(“count”)).intValue() + 1; session.setAttribute(“count”, new Integer(count)); out.print(count); %>


Cookie Info

<% Cookie[] cookies=request.getCookies(); if (cookies != null) { for (int i=0; i” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); out.print(”” + “\r\n”); } } %>
Name Value Domain Path MaxAge Comment Secure Version
” + cookie.getName() + “” + cookie.getValue() + “” + cookie.getDomain() + “” + cookie.getPath() + “” + cookie.getMaxAge() + “” + cookie.getComment() + “” + cookie.getSecure() + “” + cookie.getVersion() + “

{% endcodeblock %}

2.デプロイ Jenkinsを使ってビルドからデプロイまで自動化するのが良いと思います。 {% codeblock lang:bash %} asadmin –host=glassfish00 –port=4848 –passwordfile=/path/to/.glassfishpasswd deploy –target=cluster1 –contextroot=warファイルコンテキスト名 –force=true –property java-web-start-enabled=false:preserveAppScopedResources=true /path/to/warファイル {% endcodeblock %}

3.ブラウザで確認
http://nginxホスト名/warファイルコンテキスト名/session.jsp
を表示して、リクエストの度に「Request Count」の値がカウントアップされる事、
「JSESSIONID」の値が変わらない事を確認します。

4.GlassFishのWeb管理コンソールから、テスト中のセッションを処理しているノードを停止します。

5.もう一度ブラウザで確認
http://nginxホスト名/warファイルコンテキスト名/session.jsp
を再度表示して、「Request Count」の値が初期化されずにカウントアップされる事、
「JSESSIONID」の値はインスタンス識別子の部分だけ変わる事を確認します。

以上で完了です。

 

最後に
nginx + GlassFishの組み合わせは情報が少なかったです。
昔からApacheを使うのが当たり前ではありますが、(マニュアルにも載ってるし)
個人的には、nginxの方が設定がわかりやすくパフォーマンスも優れていると思います。

 
comments powered by Disqus