GlassFishのクラスター+ELBで高可用性Webサービスを構築する


# 予備知識

* DASとノードのOSの設定は極力合わせませしょう
* JDKのバージョンも合わせましょう
* OpenJDKは1.7系を使いましょう。8系だとGlassFish上でJSPが動作しないようです

# ノードとなるGlassFishが載るOSを用意する

Amazon Linuxが既に起動している前提で手順を進めます。  
ここでのポイントは、__GlassFishをインストールしない__ことです。

## ポート
* 4848
* 28080
* 28181

## OSの最新化
```zsh
sudo yum -y update
```

## OpenJDKのインストール
```zsh
sudo yum -y install java-1.7.0-openjdk-devel
```
## SSHの設定変更（rootログイン可、かつパスワード認証可に）
※この手順は、あまり望ましくないです。出来れば証明書によるSSHでクラスタ構築可能にしたい。  
が、現時点ではこの方法でしか動作確認が取れていないので、この手順をメモしておきます。

```zsh
sudo vi /etc/ssh/sshd_config
```

viで開いた```sshd_config```の以下の設定を書き換えます。

```
PermitRootLogin yes
PasswordAuthentication yes
```

sshdを再起動。

```zsh
sudo /etc/rc.d/init.d/sshd restart
```

## rootユーザのパスワードを設定
rootユーザのパスワードが空ではSSHでログイン出来ないため、パスワードを設定。
（もしかするとSSHの設定を変更すれば空パスワードでもログイン出来るようになるかもしれないが、未検証）

```zsh
sudo su -
passwd
```

任意のパスワードを設定して下さい。


## 動作確認

下記コマンドでrootユーザのパスワードを入力してログイン出来ればOK。

```zsh
ssh root@<ホスト>
```


# DAS（GlassFishのドメイン管理サーバ）を立てる

ここからはDASが載るサーバで作業します。ノードとなるサーバでは作業不要です。便利。

## Amazon LinuxにGlassFishをインストールする

```zsh
# ライブラリを最新に
sudo yum -y update

# JDKのインストール
sudo yum -y install java-1.7.0-openjdk-devel

# GlassFish-v4.1のインストール
sudo yum -y install wget
cd /tmp
wget -P /tmp http://dlc.sun.com.edgesuite.net/glassfish/4.1/release/glassfish-4.1-web.zip
unzip /tmp/glassfish-4.1-web.zip
sudo mv glassfish4/ /opt/
cd /opt
sudo mv glassfish4/ glassfish-4.1-web
```

* 現状ではopenjdk-1.7を利用します。1.8ではJSPが動きませんので（javacのパスが変わったため？）。


## GlassFishの自動起動設定を追加

```zsh
sudo touch /etc/init.d/glassfish
sudo chmod +x /etc/init.d/glassfish
sudo vi /etc/init.d/glassfish
```

開いたファイルの中を以下のように編集。

```zsh
#!/bin/bash
#
# glassfish    Startup script for the Apache HTTP Server
#
# chkconfig: - 85 15
# description: Startup script of Glassfish Application Server.
# processname: glassfish
#
export LANG=ja_JP.utf8

GLASSFISH_HOME=/opt/glassfish-4.1-web

case $1 in
start)
sh ${GLASSFISH_HOME}/bin/asadmin start-domain $2
;;
stop)
sh ${GLASSFISH_HOME}/bin/asadmin stop-domain $2
;;
restart)
sh ${GLASSFISH_HOME}/bin/asadmin restart-domain $2
;;
esac
exit 0
```

自動起動を登録。

```zsh
sudo chkconfig --add glassfish
sudo chkconfig glassfish on
sudo service glassfish restart
```

## GlassFishの管理コンソール設定を変更

リモートで操作可能になるように設定を変更します。

```zsh
cd glassfish-4.1-web/bin/
./asadmin start-domain
./asadmin change-admin-password
```

ここで任意のパスワードを入力。

入力したパスワードは、後のコマンド発行を楽にするためにファイルに出力しておきます。

```zsh
echo AS_ADMIN_PASSWORD=xxx >> pass.txt
```

リモート接続可能にするための最後のコマンド。

```zsh
./asadmin -W pass.txt enable-secure-admin
```

## クラスタを作成

```zsh
./asadmin -W pass.txt create-cluster <クラスタ名>
```

## ノードを作成

SSH接続する際のパスワードをパスワードファイルに設定しておきます。

```zsh
echo AS_ADMIN_SSHPASSWORD=yyy >> pass.txt
```

ノードを作成

```zsh
./asadmin -W pass.txt create-node-ssh --nodehost <nodehost> --install=true --sshuser root <ノード名>
```

## クラスタにインスタンスを追加

```zsh
./asadmin -W pass.txt create-instance --node <ノード名> --cluster <クラスタ名> <インスタンス名>
```

このコマンドはかなり時間がかかります。DASからノードにGlassFishのモジュールをコピーしてインストールするためです。
タイムアウトがしばしば発生し、下記のようなメッセージが表示されます。コマンドが失敗しているように見えます。

```zsh
No response from Domain Admin Server after 600 seconds.
The command is either taking too long to complete or the server has failed.
Please see the server log files for command status.
Command create-instance failed.
```

本当に失敗しているかどうかは分かりません。
経験的には、次のクラスタの起動がうまく行けば「実はうまくいっていたんだな」と判断しています。

## クラスタの起動

```zsh
./asadmin -W pass.txt start-cluster cluster01
```

# ELBの設定

## ポートのマッピング

80を28080にマッピングします。

![ELB_01.png](https://qiita-image-store.s3.amazonaws.com/0/10225/92b936c5-7325-3cfd-0ff4-9da92be06d15.png)

## ヘルスチェック設定

URLはお使いのアプリに合わせて適切に設定して下さい。
![ELB_02.png](https://qiita-image-store.s3.amazonaws.com/0/10225/ff74b4ce-3099-e690-49ee-0547728b5ea8.png)

## セキュリティグループ設定
通常はHTTP(80)とHTTPS(443)で充分でしょう。
![ELB_03.png](https://qiita-image-store.s3.amazonaws.com/0/10225/6e1b1636-77e3-014a-75ac-abd0f67ce612.png)

## Add EC2 Instances & Add Tags
Add EC2 InstancesではGlassFishクラスタのノードとなるEC2インスタンスを追加しましょう。

Add Tagsは運用時に役に立つタグを設定しておくといいでしょう。

## スティッキーセッションの設定
ELBを作成したら、もう１つやることが残っています。
同一セッション内のリクエストを常に同じノードに振るようにするために、スティッキーセッションの設定を行います。

ELBのStickinessのeditをクリックします。
![ELB_04.png](https://qiita-image-store.s3.amazonaws.com/0/10225/b99c59fd-28bd-ac9f-cc52-8dedb1ffd7bb.png)

```Enable Application Generated Cookie Stickiness```にチェックを入れます。
更に```Cookie Name```に```JSESSIONID```と入力します。
![ELB_05.png](https://qiita-image-store.s3.amazonaws.com/0/10225/69df7b97-04cd-b470-4fc9-cac21e36a908.png)

以上で設定完了です。

他にも作業は残っていて、例えば独自ドメインをELBのDNSに向ける設定を行う必要がありますが、この記事ではここまでにします。


### 余談・本来はGlassFishがセッション管理をしてくれるはず
ここでELBに施したスティッキーセッションの設定は、あまり良い手法ではありません。
```JSESSIONID```などという、アプリケーションサーバの実装詳細に依存したパラメータ設定は柔軟性を落とします。
また同一セッションのリクエストが常に同一ノードに振られるため、セッションの寿命によって各ノードの負荷に差が出ることが予想されます。
各ノードの負荷が出来るだけ平準になるようにリクエストを振ってくれた方が、高負荷時の対策がしやすそうです。

実は本来、セッションの共有はGlassFishのクラスタ機能がまかなってくれるようで、それを使いたかったのですが、動作させることが出来なかった、という背景があります。

かなり古い記述ですが、以下のページにインメモリセッションレプリケーションの仕組みについて記述がありました。
http://otn.oracle.co.jp/technology/global/jp/sdn/java/series/glassfish/200807.html

上記のページを見ると、LBがどのノードにリクエストを振っても、GlassFishのクラスタの中で適切にセッションを維持してくれる仕組みが働くようです。
GlassFish 4でもこの仕組みを踏襲してくれていることを期待しますし、実際、それらしき設定項目があるのですが、何故か設定変更することが出来ません。

![Web_Container_Availability.png](https://qiita-image-store.s3.amazonaws.com/0/10225/0ae7a253-c2b3-c36d-68e6-5e0b7c5da836.png)

しかしこの機能が有効になれば、LB側の設定が最小限で済む上、GlassFishの各ノードの負荷がうまいこと分散されることが期待出来ます。
また耐障害性も上がります（今回の構成だと、あるノードが落ちたら、そこに置かれているセッション情報は失われる）。
引き続き検証していきます。

# その他のメモ
## ノードに先にGlassFishをインストールしておいたら、時間短縮になるのでは？
結果としてこれは、うまくいきませんでした。原因は調査していません。
ノードを作成してクラスにインスタンスを追加するまでは出来たが、インスタンス起動が出来ませんでした。
また、期待したほど時間短縮にもなりませんでした。

## GlassFishクラスタを利用することの考察
### 利点と欠点
#### 利点
* ノードの管理が楽  
  * ノードを簡単に増やす事が出来ます。これはこの上なく楽です。
  * ノードの環境変数やアプリは、DASで設定するだけで全ノードに自動で反映されます。これもものすごく楽です。

#### 欠点
* DASが単一故障点になり得る？

### ノードとなるEC2インスタンスにElastic IPを振るべきか
結論は「振る必要なし」です。
ノードとなるEC2インスタンスが落ちたら、そのインスタンスは破棄し、新しいノードを作成して追加する運用が楽だと思います。
この運用だと、Elastic IPは不要です。