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インストール
groupadd glassfish
useradd -g glassfish glassfish
mkdir /usr/local/glassfish3
chown glassfish:glassfish /usr/local/glassfish3
rpm -ivh /path/to/JDKのrpmファイル
2.クラスタ作成
asadmin create-cluster cluster1
※EC2環境で稼働させる場合、マルチキャストが使えないため以下のように明示的にノードを列挙してください。
詳しくはこのページを参照。
Discovering a Cluster When Multicast Transport Is Unavailable
@yoshioteradaさんに教えて頂きました。ありがとうございます!
※同一ノードにインスタンスを複数立ててクラスタを複数作る場合は、ポートが被らないように注意しましょう。
asadmin create-cluster --properties GMS_DISCOVERY_URI_LIST=tcp'\\:'//glassfish01,tcp'\\:'//glassfish02,tcp'\\:'//glassfish03:GMS_LISTENER_PORT=9090 cluster1
3.DASからノードへのssh接続設定
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
4.DASからsshでノードのjavaコマンドが使えるか確認
ssh glassfish01 java -version
ssh glassfish02 java -version
ssh glassfish03 java -version
5.ノードにGlassFishをインストール
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
6.DASにノードを追加
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
7.ノードにインスタンスを追加
asadmin create-instance --node glassfish01 --cluster cluster1 instance1
asadmin create-instance --node glassfish02 --cluster cluster1 instance2
asadmin create-instance --node glassfish03 --cluster cluster1 instance3
8.スティッキーセッションのためのプロパティ設定
この設定を行うと、Cookieの末尾にインスタンス識別子が付加され、
リクエストが振り分けられるインスタンスが固定されます。
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}
9.クラスタを起動
※OS起動時のクラスタの自動起動がわかりませんでした。ご存知の方教えて下さい><
asadmin start-cluster cluster1
GlassFishの設定はここまで。
次はnginxのインストールと設定です。
nginxは標準でスティッキーセッションの機能がありませんが、
モジュールを作ってくれている方がいらっしゃるので、ありがたく使わせて頂きます。
1.nginxインストール
ソースからインストールするので、ユーザを作ります。
groupadd nginx
useradd -g nginx nginx
依存ライブラリのインストール
yum install pcre-devel
ソースと追加モジュールのダウンロード
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
インストール ※各種パスとモジュールの指定は適宜読み替えてください。
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
2.設定ファイル修正
nginxのconfigureの時に–conf-pathで指定したパスに設定ファイルが出来ています。
以下、設定例です。
※スティッキーセッションに必要な設定は、upstreamのstickyだけです。
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;
}
}
}
以上で環境は出来ました。
動作確認してみます。
1.確認用ソース
以下をsession.jspという名前で保存して、warファイルを作成します。
<%@ 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">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
<!--
body
{
font-family: 'MS ゴシック';
font-size: 9pt;
}
table
{
border-collapse: collapse;
border-left: 1px;
border-right: 1px;
border-top: 1px;
border-bottom: 1px;
border-style: solid;
border-color: #000000;
}
table.border
{
border-left: 0px;
border-right: 0px;
border-top: medium solid;
border-bottom: 0px;
border-color: #000000;
}
td
{
border-left: 0px;
border-right: 1px;
border-top: 0px;
border-bottom: 1px;
border-style: solid;
border-color: #000000;
margin: 2px;
padding: 2px;
}
td.title-left
{
background-color: #a3bacd;
font-weight: bold;
text-align: left;
}
td.title-center
{
background-color: #a3bacd;
font-weight: bold;
text-align: center;
}
-->
</style>
</head>
<body>
<h1><font color="red">Session Info</font></h1>
<table align="center" border="0" width="800">
<tr>
<td class="title-left" width="120">Server</td>
<td><%= InetAddress.getLocalHost().getHostName() %></td>
</tr>
<tr>
<td class="title-left" width="120">Session ID</td>
<td><%= session.getId() %></td>
</tr>
<tr>
<td class="title-left" width="120">Created on</td>
<td>
<%
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
out.print(sdf.format(new Date(session.getCreationTime())));
%>
</td>
</tr>
<tr>
<td class="title-left" width="120">Request Count</td>
<td>
<%
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);
%>
</td>
</tr>
</table>
<br/>
<table border="0" width="100%" class="border">
<tr>
<td width="80%"></td>
</tr>
</table>
<h1><font color="red">Cookie Info</font></h1>
<table align="center" border="0" width="800">
<tr>
<td class="title-center">Name</td>
<td class="title-center">Value</td>
<td class="title-center">Domain</td>
<td class="title-center">Path</td>
<td class="title-center">MaxAge</td>
<td class="title-center">Comment</td>
<td class="title-center">Secure</td>
<td class="title-center">Version</td>
</tr>
<%
Cookie[] cookies=request.getCookies();
if (cookies != null) {
for (int i=0; i<cookies.length; i++) {
Cookie cookie = cookies[i];
out.print("<tr>" + "\r\n");
out.print("<td>" + cookie.getName() + "</td>" + "\r\n");
out.print("<td>" + cookie.getValue() + "</td>" + "\r\n");
out.print("<td>" + cookie.getDomain() + "</td>" + "\r\n");
out.print("<td>" + cookie.getPath() + "</td>" + "\r\n");
out.print("<td>" + cookie.getMaxAge() + "</td>" + "\r\n");
out.print("<td>" + cookie.getComment() + "</td>" + "\r\n");
out.print("<td>" + cookie.getSecure() + "</td>" + "\r\n");
out.print("<td>" + cookie.getVersion() + "</td>" + "\r\n");
out.print("</tr>" + "\r\n");
}
}
%>
</table>
</body>
</html>
2.デプロイ Jenkinsを使ってビルドからデプロイまで自動化するのが良いと思います。
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ファイル
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の方が設定がわかりやすくパフォーマンスも優れていると思います。