OpenAMとOpenDJをHAProxy + keepalivedで冗長化


## Requirements

- OpenDJ
- OpenAM
- HAProxy
- keepalived

## Overview

- サーバ2台
- それぞれOpenDJ、OpenAM、HAProxy、keepalivedをインストール
- HAProxyはkeepalivedでアクティブ-スタンバイにし冗長化
- OpenAM、OpenDJはHAProxyでバランシングし冗長化

## Install

### HAProxy

- haproxyをyumでインストール

```
yum install haproxy
```

SSL対応させる場合は開発版を入れる。

https://gist.github.com/ChrisMcKee/9cdb1bf9c5314f320ec5

```haproxy-install.sh
#!/bin/bash
 
### VARIABLES ###
PRE_PACK="openssl-devel pcre-devel make gcc"
VER="1.5.1"
 
 
# Setup Colours
black='\E[30;40m'
red='\E[31;40m'
green='\E[32;40m'
yellow='\E[33;40m'
blue='\E[34;40m'
magenta='\E[35;40m'
cyan='\E[36;40m'
white='\E[37;40m'
 
boldblack='\E[1;30;40m'
boldred='\E[1;31;40m'
boldgreen='\E[1;32;40m'
boldyellow='\E[1;33;40m'
boldblue='\E[1;34;40m'
boldmagenta='\E[1;35;40m'
boldcyan='\E[1;36;40m'
boldwhite='\E[1;37;40m'
 
Reset="tput sgr0"      #  Reset text attributes to normal
                       #+ without clearing screen.
 
cecho ()                     # Coloured-echo.
                             # Argument $1 = message
                             # Argument $2 = color
{
message=$1
color=$2
echo -e "$color$message" ; $Reset
return
}
 
clear
 
cecho "Installing/upgrading your web server..." $boldgreen
 
yum -y -q install $PRE_PACK > /dev/null
 
 
mkdir -p /src
cd /src
 
wget -q http://www.haproxy.org/download/1.5/src/haproxy-$VER.tar.gz
tar xzf haproxy-$VER.tar.gz && rm -f haproxy*.tar.gz;
cd haproxy-$VER
 
cecho "Configuring and Making HAPROXY" $boldgreen
 
make TARGET=linux2628 CPU=x86_64 USE_OPENSSL=1 USE_ZLIB=1 USE_PCRE=1     # compiles it with compression and ssl support; use CPU=x86_64 for CentOS x64 or i686 where approriate
 
cecho "Installing HAPROXY" $boldgreen
make install
 
 
cecho "Adding HAProxy User" $boldyellow
id -u haproxy &>/dev/null || useradd -s /usr/sbin/nologin -r haproxy
 
 
cecho "Creating config and Adding Daemon" $boldgreen
 
cp /usr/local/sbin/haproxy* /usr/sbin/     # copy binaries to /usr/sbin
cp /src/haproxy-$VER/examples/haproxy.init /etc/init.d/haproxy     # copy init script in /etc/init.d
chmod +x /etc/init.d/haproxy     # setting permission on init script
mkdir -p /etc/haproxy     # creating directory where the config file must reside
cp /src/haproxy-$VER/examples/examples.cfg /etc/haproxy/haproxy.cfg     # copy example config file
mkdir -p /var/lib/haproxy     # create directory for stats file
touch /var/lib/haproxy/stats     # creating stats file
 
 
cecho "checking config and starting service" $boldyellow
service haproxy check     # checking configuration file is valid
service haproxy start     # starting haproxy to verify it is working
chkconfig haproxy on     # setting haproxy to start with VM
 
exit 0
```

- socketファイル用のディレクトリを作成

```
mkdir /var/run/haproxy
```

- 証明書

```bash
# 鍵と証明書からpemファイルを作成
cat server.key server.crt > /etc/ssl/certs/server.pem
```

- haproxy.cfgを編集する

```/etc/haproxy/haproxy.cfg
global
    daemon
    maxconn 50000
    stats socket /var/run/haproxy/haproxy.sock mode 0600 level admin
defaults
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend sso_in
    # SSL対応
    #bind 192.168.34.100:443 ssl crt /etc/ssl/certs/example.com.pem
    bind 192.168.34.100:80
    maxconn 30000
    default_backend ju_sso

backend ju_sso
    mode http   
    # 強制https
    #redirect scheme https if !{ ssl_fc }
    # 強制的に/openam以下にリダイレクト
    # acl has_openam_uri path_beg -i /openam                                                                                                                                                                                                   
    # redirect location https://am-proxy.example.com/openam if !has_openam_uri
    cookie SERVERID insert nocache
    balance roundrobin
    server s1 192.168.34.10:8080 maxconn 30000 cookie 01 id 1001 check inter 2000 rise 2 fall 5
    server s2 192.168.34.11:8080 maxconn 30000 cookie 02 id 1002 check inter 2000 rise 2 fall 5
    option forwardfor

frontend ldap_in
    bind 192.168.34.100:3389
    maxconn 10000
    default_backend ju_ldap

backend ju_ldap
    mode tcp
    balance roundrobin
    server s1 192.168.34.10:389 maxconn 10000 check
    server s2 192.168.34.11:389 maxconn 10000 check

listen stats :8181        
   mode http
   stats enable
   stats realm Haproxy\ Statistics
   stats uri /
   stats auth haproxy:ju

```

バックエンドのポートと受け付けるポートが同じだとalready listenになるので別ポートにする。

- `server s1 192.168.34.10:8080 maxconn 30000 cookie 01 id 1001 check inter 2000 rise 2 fall 5`

stickysessionの設定。同一cookieは同じバックエンドに割り振る。

standby側はnon_local_ipにもbind出来るようにカーネルパラメータを変えて対応。master側もやっておく。

```bash
sysctl -w net.ipv4.ip_nonlocal_bind=1
```


- 管理ツールのhatopを入れておく

```
wget http://hatop.googlecode.com/files/hatop-0.7.7.tar.gz
tar xvf hatop-0.7.7.tar.gz
cd hatop-0.7.7
install -m 755 bin/hatop /usr/local/bin
install -m 644 man/hatop.1 /usr/local/share/man/man1
```

- テスト

```
hatop -s /var/run/haproxy/haproxy.sock
```


### keepalived

- インストール

```
yum install keepalived
```

- keepalived.confの設定

```/etc/keepalived/keepalived.conf 
! Configuration File for keepalived

global_defs {
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id LVS_DEVEL
}

vrrp_script chk_haproxy {
   script "killall -0 haproxy"   # verify the pid existance
   interval 2                    # check every 2 seconds
   weight 2                      # add 2 points of prio if OK
}
 
vrrp_instance VI_1 {
   interface eth1                # interface to monitor
   state MASTER
   virtual_router_id 51          # Assign one ID for this route
   priority 100                  # 101 on master, 100 on backup
   virtual_ipaddress {
       192.168.34.100            # the virtual IP
   }
   track_script {
       chk_haproxy
   }
}

```

`priority`はアクティブ側を101、バックアップ側を100とする。

#### Openstack上に構築する場合

```bash
sysctl -w net.ipv4.ip_nonlocal_bind=1
```

を実行しカーネルパラメータを変える。これをやらないと同時に同じIPが複数立ち上がる。

### OpenDJ

- Ansibleのインストール

```bash
yum install --enablerepo=epel ansible
```

- OpenDJ用roleのダウンロード

```
ansible-galaxy install warren.strange.opendj
```

- playbook作成

```opendj.yaml 
- hosts: localhost
  connection: local
  roles:
    - warren.strange.opendj
```

- playbook実行

```
ansible-playbook opendj.yaml
```

- ホスト名設定

  - **1台目**

    ```bash
    hostname openam.example.com
    sed -i -e "s/^HOSTNAME=.*/HOSTNAME=openam.example.com/g" /etc/sysconfig/network
    echo "192.168.34.10 openam.example.com" >> /etc/hosts
    echo "192.168.34.11 openam2.example.com" >> /etc/hosts
    echo "192.168.34.100 openam-proxy.example.com" >> /etc/hosts
    ```

  - **2台目**

    ```bash
    hostname openam2.example.com
    sed -i -e "s/^HOSTNAME=.*/HOSTNAME=openam2.example.com/g" /etc/sysconfig/network
    echo "192.168.34.10 openam.example.com" >> /etc/hosts
    echo "192.168.34.11 openam2.example.com" >> /etc/hosts
    echo "192.168.34.100 openam-proxy.example.com" >> /etc/hosts
    ```


- レプリケーション設定

```bash
/opt/opendj/bin/dsreplication 
どの処理を実行しますか?

    1)  レプリケーションを有効にする
    2)  レプリケーションを無効にする
    3)  1 台のサーバーでレプリケーションを初期化
    4)  すべてのサーバーを初期化
    5)  外部初期化前
    6)  外部初期化後
    7)  レプリケーションの状態を表示
    8)  Purge Historical

    c)  取消し

選択肢: 1

>>>> 最初のサーバーのサーバー管理接続パラメータを指定します

ディレクトリサーバーのホスト名または IP アドレス [openam.example.com]: [Enter]

ディレクトリサーバーの管理ポート番号 [4444]: [Enter]

グローバル管理者のユーザー ID、またはグローバル管理者が定義されていない場合はバインド DN [admin]: cn=Directory Manager

ユーザー 'cn=Directory Manager' のパスワード: [password]
最初のサーバーのレプリケーションポート (未使用のポート) [8989]: [Enter] 

最初のサーバーのレプリケーションポート 8989 への接続時に、暗号化された通信を使ってレプリケーションしますか ? (yes / no) [no]: [Enter]



>>>> 2 番目のサーバーのサーバー管理接続パラメータを指定します

ディレクトリサーバーのホスト名または IP アドレス [openam.example.com]: openam2.example.com

ディレクトリサーバーの管理ポート番号 [4444]: [Enter]

グローバル管理者のユーザー ID、またはグローバル管理者が定義されていない場合はバインド DN [admin]: cn=Directory Manager

ユーザー 'cn=Directory Manager' のパスワード: [password]

サーバー証明書:

ユーザー DN: CN=localhost, O=Administration Connector Self-Signed Certificate
有効期間: 'Fri Oct 03 05:18:27 UTC 2014' から
             'Thu Sep 28 05:18:27 UTC 2034' まで
発行者: CN=localhost, O=Administration Connector Self-Signed Certificate


このサーバー証明書を信頼しますか ?

    1)  いいえ
    2)  はい、このセッションでのみ信頼します
    3)  はい、トラストストアにも追加します
    4)  証明書の詳細を表示します

選択肢 [2]: 2
2 番目のサーバーのレプリケーションポート (未使用のポート) [8989]: [Enter]

2 番目のサーバーのレプリケーションポート 8989 への接続時に、暗号化された通信を使ってレプリケーションしますか ? (yes / no) [no]: [Enter]

グローバル管理者を作成する必要があります。
レプリケート中のサーバーインスタンスの管理用に作成するグローバル管理者の資格を指定してください。
グローバル管理者のユーザー ID [admin]: [Enter]

グローバル管理者のパスワード: [password]

パスワードの確認: [password]


レプリケートするベース DN を 1 つ以上選択してください。
ベース DN dc=example,dc=com をレプリケートしますか ? (yes / no) [yes]: [Enter]
ベース DN dc=openam,dc=forgerock,dc=org をレプリケートしますか ? (yes / no) [yes]: [Enter] 

接続を確立しています ..... 完了。
登録情報を確認しています ..... 完了。
サーバー openam.example.com:4444 上のレプリケーションポートを構成しています ..... 完了。
サーバー openam2.example.com:4444 上のレプリケーションポートを構成しています ..... 完了。
サーバー openam.example.com:4444 上のベース DN dc=example,dc=com のレプリケーション構成を更新しています ..... 完了。
サーバー openam2.example.com:4444 上のベース DN dc=example,dc=com のレプリケーション構成を更新しています ..... 完了。
サーバー openam.example.com:4444 上のベース DN dc=openam,dc=forgerock,dc=org のレプリケーション構成を更新しています ..... 完了。
サーバー openam2.example.com:4444 上のベース DN dc=openam,dc=forgerock,dc=org のレプリケーション構成を更新しています ..... 完了。
サーバー openam.example.com:4444 上の登録構成を更新しています ..... 完了。
サーバー openam2.example.com:4444 上の登録構成を更新しています ..... 完了。
サーバー openam.example.com:4444 上のベース DN cn=schema のレプリケーション構成を更新しています ..... 完了。
サーバー openam2.example.com:4444 上のベース DN cn=schema のレプリケーション構成を更新しています ..... 完了。
サーバー openam2.example.com:4444 上の登録情報を、サーバー openam.example.com:4444 のコンテンツで初期化しています ..... 完了。
サーバー openam2.example.com:4444 上のスキーマを、サーバー openam.example.com:4444 のコンテンツで初期化しています ..... 完了。

レプリケーションが正常に有効になりました。  レプリケーションが機能するには、レプリケートするベース DN のコンテンツを初期化する必要があります (dsreplication initialize を使用して初期化)。

この操作の詳細なログについては /tmp/opendj-replication-4237327164984323237.log を参照してください。
```


### OpenAM

1台目と2台目で微妙にインストール時の手順が異なるので注意。

#### 1台目

- [公式サイト](https://backstage.forgerock.com/#!/downloads)よりOpenAMのwarファイルをダウンロード。

- 依存パッケージのインストール

```
yum install java-1.6.0-openjdk
yum install tomcat6 tomcat6-webapps tomcat6-admin-webapps
```

tomcat7の場合はmanagerのアップロード容量制限があるのでwebから上げる場合は制限をゆるくする

```bash
vi /usr/share/tomcat/webapps/manager/WEB-INF/web.xml
<max-file-size>52428800</max-file-size> 
<max-request-size>52428800</max-request-size>       
```

- hostnameの設定

※ここと後述のOpenAM設定のhostnameが一致していないとエラーでインストール出来ないので注意。

```bash
$ cat /etc/sysconfig/networkk

openam-test.example.com
```

- SELinuxをDisabled

```
cat /etc/sysconfig/selinux
SELINUX=disabled
```

- tomcatのmanagerユーザ追加

```
vim /etc/tomcat6/tomcat-users.xml

# 以下を追加
<role rolename="manager"/>
  <user username="admin" password="password" roles="manager"/>
```

- 設定ファイルの書き込みが出来るようにする。

```
chown -R root:tomcat /usr/share/tomcat6
```

- example.com:8080にアクセス。

- カスタム設定から設定をしていく

- デフォルトユーザ
pass: password

- ポリシーエージェント
pass: agentpassword

```
設定ストアの詳細 編集...
SSL が有効 
ホスト名 
待機ポート 
ルートサフィックス 
ユーザー名 
ディレクトリ名	いいえ 
localhost 
50389 
dc=example,dc=com 
cn=Directory Manager
/usr/share/tomcat6/OpenAM-11.0.0
ユーザーストアの詳細 編集...
SSL が有効
ホスト名
openam-proxy.example.com
待機ポート
3389
ルートサフィックス
dc=example,dc=com
ユーザー名
cn=Directory Manager
ユーザーデータストアタイプ	いいえ
172.16.42.120
ポート
389
dc=example,dc=com
cn=Directory Manager
OpenDJ
サイト設定の詳細 編集...
このインスタンスは、ロードバランサの背後には設定されません。

```

設定ストアはOpenAMでユーザデータストアはOpenDJにする。OpenDJのホストとポートはロードバランサのホストとポートにする。

エラーが出たら設定用のフォルダが既に出来ているので一度削除してから再度入れなおす

#### 2台目

1台目と途中までは同じで「既存の配備に追加する」を選択する。
既存の配備に追加で既に存在するOpenAMのURLを入力する。

```
サイト名: test
ロードバランサのURL: http://openam-proxy.example.com:80/openam
```


自動的に設定が埋まるので「次へ」を押す。
サイト名、ロードバランサのURLを入力しインストールを進める。ここで失敗が間違っていてもログイン後「設定」→「サーバーおよびサイト」より設定を直せるので問題ない。
インストールが終了したら各インスタンスへログインし「設定」→「サーバーおよびサイト」より親サイトの箇所が空欄になっているホストの親ホストを設定したサイトに変更する。

## 検証

### HAProxyが落ちた場合

```bash

# 落とす前のip
openam1 $ ip -f inet a
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 192.168.34.10/24 brd 192.168.34.255 scope global eth1
    inet 192.168.34.100/32 scope global eth1

# HAProxyを落としてみる
openam1 $ service haproxy stop

# VIPがfailoverされているのが分かる
openam1 $ ip -f inet a
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 192.168.34.10/24 brd 192.168.34.255 scope global eth1

openam2 $ ip -f inet a
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 192.168.34.11/24 brd 192.168.34.255 scope global eth1
    inet 192.168.34.100/32 scope global eth1

```

VIPはスタンバイ側に引き継がれる。

```
# 再度HAProxyを起動させると元に戻る。
openam1 $ service haproxy start
openam1 $ ip -f inet a 
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 192.168.34.10/24 brd 192.168.34.255 scope global eth1
    inet 192.168.34.100/32 scope global eth1

```

### OpenDJが落ちた場合

```
# 手動で停止
service opendj stop
```

- OpenAMにアクセス。ー＞問題なし。 
- OpenAMでユーザ情報に変更を加えるー＞問題なし。
- ユーザ情報変更後落ちていた方のOpenDJを起動。

```bash
# レプリケーション状態確認
$ /opt/opendj/bin/dsreplication status -h localhost -p 4444 -I admin -w password -X
Suffix DN                     : Server                   : Entries : Replication enabled : DS ID : RS ID : RS Port (1) : M.C. (2) : A.O.M.C. (3) : Security (4)
------------------------------:--------------------------:---------:---------------------:-------:-------:-------------:----------:--------------:-------------
dc=example,dc=com             : openam.example.com:4444  : 26      : true                : 29535 : 29036 : 8989        : 0        :              : false
dc=example,dc=com             : openam2.example.com:4444 : 22      : true                : 4242  : 16196 : 8989        : 1        :              : false
dc=openam,dc=forgerock,dc=org : openam.example.com:4444  : 0       : true                : 10822 : 29036 : 8989        : 0        :              : false
dc=openam,dc=forgerock,dc=org : openam2.example.com:4444 : 0       : true                : 24071 : 16196 : 8989        : 0        :              : false

[1] The port used to communicate between the servers whose contents are being replicated.
[2] The number of changes that are still missing on this server (and that have been applied to at least one of the other servers).
[3] Age of oldest missing change: the date on which the oldest change that has not arrived on this server was generated.
[4] Whether the replication communication through the replication port is encrypted or not.

```

落ちていた方がM.C(Missed Change)が溜まっているのが分かる。

- レプリケーションは勝手には始まらない。

- 起動直後は差分が発生している。ー＞ OpenAMからユーザ一覧を見ると更新後の値が常に表示される。
- 更新ログが進んでいる方のLDAPに常にクエリ（更新も参照も）が行くようになる。
- dsreplicationで遅れている方を進んでいるサーバで初期化

```bash
 /opt/opendj/bin/dsreplication initialize -h openam.example.com -p 4444 -O openam2.example.com --portDestination 4444 -X -I admin -w password
```

- ログが追いついた。
- 再度OpenAM上からユーザ情報更新 ー＞ 同期されているのでマルチマスタ状態でどちらを更新しても同期される。

- **結論**: **OpenDJが落ちたら進んでる方のデータで初期化させる**


### OpenAMが落ちた場合

```
# 手動で落とす
service tomcat6 stop
```

- ロードバランサにアクセス -> 問題なし。

```
# 再起動
service tomcat6 start
```

- バランシング確認 -> 問題なし。

- 停止中に片側で設定変更した場合dsreplicationの初期化が必要な気がする。

### ホストが落ちた場合

- リクエストが全て片側に寄る
- ホスト再起動後全てのサービスを起動で問題なし

### Session Failoverさせる

#### 設定

「設定」-> 「グローバル」 -> 「セッション」より作成済みのサイトに対してSession Failoverを設定する。

#### Session Failover手順

1. バランサから外す
2. Session Failoverが有効になっていれば同期されているCTSを参照してSessionがFailoverされる

トークンはOpenAM内蔵のコンフィグ用のOpenDJディレクトリのRoot DNの`ou=tokens`以下に格納されている。