諸事情により、前回から非常に時間が空いてしまってご迷惑をおかけしました。
前回 は、OpenLDAPのsyncrepl機能を使って、LDAPサーバの二重化、つまり冗長化を実現することができました。ただしいくつかの制限がありましたので、改めてその内容をリストアップしておきます。
通常はプロバイダのみが検索、更新処理を行い、コンシューマ側はスタンバイ状態となっている(クライアント側の設定次第では、コンシューマ側の検索機能を活用することも可能)
コンシューマは検索結果を提供することはできるが、更新要求を直接受け付けることはできない
プロバイダがダウンした場合、すべての要求はコンシューマに委任されるが、この際更新要求が発生するとクライアントにエラーが通知される
負荷分散は実現されていなかった(クライアント側の設定切り替え、またはロードバランサの導入により対応可能)
このように、一見不完全に思われる部分もありますが、今まで何度も紹介してきたように、LDAPサーバのデータ更新頻度はそれほど高くない場合が多いため、障害が発生してプロバイダが数時間停止してしまったとしても、なるべく早く復旧作業を正しく行えば、それほど大きな影響を及ぼすものではありません。
しかし、「 比較的データの更新頻度が高い場合」 、または「社外の顧客などが更新処理を行う(数分のダウンタイムも許されない) 」といった場合、このダウンタイムを極力ゼロに近づける必要があります。今回は、このようなシチュエーションを考慮した冗長設計を考えていくことにしましょう。
Active/ActiveとActive/Standby
LDAPに限らず、どのサービスについても言えることですが、冗長化対策を行う上で覚えておきたいのがこの2つの用語です。
前者では、2台以上のサーバがすべて稼働し、全ノードのCPUを効率的に活用することで 冗長化+負荷分散(並列処理)という効果を期待することができます。一方、後者の場合はサービスを提供するのは常時1台のノードであり、そのノードに障害が発生して初めて別のノードへの切り替わりが発生します。スタンバイ側のCPUは通常眠ったままですので、一見Active/Activeのほうがハードウェア資源を効率的に使用できるように思えますが、アプリケーション側での排他制御などが必要となるため、Active/Active方式はすべてのアプリケーションで利用できる機能ではありません。
また、仮にActive/Active構成の場合で両方のCPU資源をフルに活用した場合、片方の障害によって、全てのアクセスがもう一方に集中してしまうことになります。サーバがそのアクセスに耐えうるスペックを持っていれば問題ないのですが、もしそうでない場合、1サーバだけでは高負荷に耐えることができず、1台の障害が結局全体の障害になってしまう可能性も考慮しておかなければなりません。
前回の設定でも、「 検索」のみに着目した場合はActive/Active方式を実現できていたわけですが、更新処理に関しては冗長化対策を行っていませんでした。
OpenLDAPのMirrorMode
OpenLDAP-2.4系で実装された「ミラーモード」という機能を用いると、いわゆるマルチマスターレプリケーションと呼ばれる、更新処理をもサポートしたActive/Active型の冗長構成を実現することができます(正確には、ミラーモード=マルチマスターというわけではないので、マルチマスターレプリケーションに似た機能です) 。
OpenLDAP-2.4系のレプリケーションドキュメント の中には、次のように興味深い記述があります。
18.2.2.1. マルチマスターレプリケーションに関する正しい議論
もしいずれかのプロバイダがダウンすると、他のプロバイダは更新要求を処理し続けることができる
Single Point Of Failureを回避することができる
プロバイダはディザスタリカバリの観点から物理的に離れた箇所に設置することができる
自動フェイルオーバー、冗長性に優れる
18.2.2.2. マルチマスターレプリケーションに関する誤った議論
ロードバランシングを行うわけではない
プロバイダは書き込みを他の全てのサーバに伝えなければならない、ネットワークトラフィックと書き込みはシングルマスタ時同様、ネットワーク上に広がっていく。
サーバのユーティライゼーションやパフォーマンスはシングルマスタ時とそれほど変わりない。最悪、インデックス処理、最適化を考えると、シングルマスタのほうが優れている場合もある。
さらに、ミラーモードに関しては、次のような記述があります。
18.2.3.1. ミラーモードのための議論
書き込みのためのHAソリューションを提供する
プロバイダが生きている限り、書き込み要求は安全に処理される
プロバイダノードは互いにレプリケーションを実行する。それらは常にアクティブであり他へテイクオーバーする準備ができている(ホットスタンバイ)
Syncreplは障害後の再同期をサポートする
18.2.3.2. ミラーモードに対する議論
ミラーモードはマルチマスターソリューションを意味するものではない。書き込み要求は1台のサーバのみに発生させなければならないためである。
ミラーモードはActive/Activeホットスタンバイとして定義できる。したがって、ロードバランサなどにより、どのプロバイダが現在アクティブなのか、選択させなければならない。
バックアップとは別物として考える
Delta-Syncreplは未サポート
つまり、ミラーモード構成の2台のLDAPサーバが存在していた場合、どちらか一方で更新処理を行うと、もう一方にもその更新が反映されます。しかし、複数のサーバで同時に更新処理が発生してしまうと、タイミングによっては全ノード上でのデータの一貫性が失われてしまう可能性があります。よって、ミラーモードは次のような形を想定して使用するのが一般的です[1] 。
図1 ミラーモードの使われ方(Active/Standby的な考え方。サーバ1に障害が発生しても、サーバ2が引き続き更新処理を受け付けることができる)
もしサーバ1に障害が発生した場合には、ハードやOSを正常に戻した後、正しいslapd.confを用意しプロセスを起動することで、サーバ2にある最新のデータを自動的に同期させることができます。
また、もし冗長化対策だけでなく、検索の負荷分散を行いたい場合は、ロードバランサ側で更新専用となる10389/tcpなどのポートを準備しておき、次のような形で検索と更新のトラフィックをコントロールすることもできるでしょう。
図2 更新時の冗長化、検索時の負荷分散を実現(検索トラフィックに関してはロードバランサでバランシングし、更新処理については常に一方のサーバのみを使用する)
ただし、検索アプリケーションでは389/tcpというポート番号を指定し、更新アプリケーションでは10389/tcpを指定しなければならないため、アプリケーションによってはこの方法が使用できない場合もあります[2] 。
ミラーモードのマルチマスター的使用方法
前述のように、OpenLDAPのミラーモードは一般的なマルチマスターを意味するわけではありません。
したがって、ロードバランサ配下のWebサーバを追加する時のように、同構成のLDAPサーバを手軽に増やすことはできません。なぜなら、ロードバランサによって更新用トラフィックが両ノードに振り分けられ、運悪くまったく同じタイミングで更新処理が発生した場合、全体の整合性がとれなくなってしまう可能性があるためです。
しかし、言い換えれば、同時更新が発生しないなど、更新処理の頻度が多くない条件下であれば、ミラーモードをあたかもマルチマスターのような形で使用することができます。
重要なのは、この同時更新のタイミングについてなのですが、実際に複数のサーバで同時更新を行い、どれくらいの更新頻度があれば不整合が発生するのか実験してみました.
まず、ミラーモード用の設定は次のようになります。前回のsyncrepl設定に似ていますが、CentOS-5.3に含まれるOpenLDAPは2.3系であるため、ミラーモードがサポートされていません。そこで今回はVMware上にFedora Core 11を準備して実験を行いました。ノード名はcl1、cl2としました。
リスト1 cl1というノードのslapd.conf
include /etc/openldap/schema/corba.schema
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/duaconf.schema
include /etc/openldap/schema/dyngroup.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/java.schema
include /etc/openldap/schema/misc.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/openldap.schema
include /etc/openldap/schema/ppolicy.schema
include /etc/openldap/schema/collective.schema
allow bind_v2
database monitor
database bdb
suffix "dc=example,dc=com"
rootdn "cn=Manager,dc=example,dc=com"
rootpw secret
directory /var/lib/ldap
loglevel 256
moduleload syncprov.la
# Indices to maintain for this database
index objectClass eq,pres
index ou,cn,mail,surname,givenname eq,pres,sub
index uidNumber,gidNumber,loginShell eq,pres
index uid,memberUid eq,pres,sub
index nisMapName,nisMapEntry eq,pres,sub
index entryUUID eq,pres
overlay syncprov
# それぞれのノードでユニークなサーバIDをつけること
serverID 1
# 1台目のサーバ情報
syncrepl rid=001
provider=ldap://10.0.0.62
bindmethod=simple
binddn="cn=Manager,dc=example,dc=com"
credentials=secret
searchbase="dc=example,dc=com"
schemachecking=on
type=refreshAndPersist
retry="10 +"
# 2台目のサーバ情報
syncrepl rid=002
provider=ldap://10.0.0.63
bindmethod=simple
binddn="cn=Manager,dc=example,dc=com"
credentials=secret
searchbase="dc=example,dc=com"
schemachecking=on
type=refreshAndPersist
retry="10 +"
# ミラーモードを有効化
mirrormode on
リスト2 cl2というノードのslapd.conf
include /etc/openldap/schema/corba.schema
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/duaconf.schema
include /etc/openldap/schema/dyngroup.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/java.schema
include /etc/openldap/schema/misc.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/openldap.schema
include /etc/openldap/schema/ppolicy.schema
include /etc/openldap/schema/collective.schema
allow bind_v2
database monitor
database bdb
suffix "dc=example,dc=com"
rootdn "cn=Manager,dc=example,dc=com"
rootpw secret
directory /var/lib/ldap
loglevel 256
moduleload syncprov.la
# Indices to maintain for this database
index objectClass eq,pres
index ou,cn,mail,surname,givenname eq,pres,sub
index uidNumber,gidNumber,loginShell eq,pres
index uid,memberUid eq,pres,sub
index nisMapName,nisMapEntry eq,pres,sub
index entryUUID eq,pres
overlay syncprov
# それぞれのノードでユニークなサーバIDをつけること
serverID 2
# 1台目のサーバ情報
syncrepl rid=001
provider=ldap://10.0.0.62
bindmethod=simple
binddn="cn=Manager,dc=example,dc=com"
credentials=secret
searchbase="dc=example,dc=com"
schemachecking=on
type=refreshAndPersist
retry="10 +"
# 2台目のサーバ情報
syncrepl rid=002
provider=ldap://10.0.0.63
bindmethod=simple
binddn="cn=Manager,dc=example,dc=com"
credentials=secret
searchbase="dc=example,dc=com"
schemachecking=on
type=refreshAndPersist
retry="10 +"
# ミラーモードを有効化
mirrormode on
ミラーモードを設定する上での主なポイントは次の通りです。
両ノードのsyncrepl設定を行う
mirrormode onを指定する
それぞれのノードでserverIDを指定する(これ以外の設定は同じ!)
次に、同時更新テストのため、リスト3のようなスクリプトを用いました。ごらんの通り、このスクリプトは1万件のエントリを表示しますので、パイプを使ってldapaddコマンドに渡します。
リスト3 add_many1.sh
#!/bin/sh
i=0
host=`hostname --short`
while [ $i -lt 10000 ]; do
echo "dn: cn=${host}_${i},ou=people,dc=example,dc=com"
echo "objectClass: person"
echo "cn: ${host}_${i}"
echo "sn: ${host}_${i}"
echo ""
i=`expr $i + 1`
done
では、このスクリプトを2ノード上で、次のようにほぼ同時に実行してみます。
cl1というノードで実行
./add_many1.sh | ldapadd -x -D "cn=Manager,dc=example,dc=com" -w secret
cl2というノードで実行
./add_many1.sh | ldapadd -x -D "cn=Manager,dc=example,dc=com" -w secret
筆者の環境では、処理が完了するのに、それぞれ約210秒ほどかかりましたので、追加処理のスループットとしては約47件/秒となりました。さらに、それぞれのノードでデータの件数を確認してみると、次のようになっていました。
cl1というノードで実行
# slapcat | grep '^dn: cn=c' | wc -l
19622
cl2というノードで実行
# slapcat | grep '^dn: cn=c' | wc -l
19954
このように、両サーバで合計2万件のデータを登録したはずが、何件かの同時書き込みにより100件以上が不整合となってしまっています.つまり、複数のクライアントからの更新処理が47件/秒の割合で発生するような環境では、同時書き込みによる不整合が発生してしまう可能性が考えられることになります。
続いて、別のスクリプト(リスト4)を使って同様の実験を行ってみました。こちらもそれぞれのノードで1万件のデータを登録するスクリプトですが、先ほどと異なり、データ1件1件に対してldapaddコマンドを使用しています。
最初の例では1回のTCPセッション(LDAPセッション)で一気に1万件のデータを追加していましたが、今回はldapaddコマンドを1万回実施してデータを登録しています(1万回のTCPセッション) 。当然ネットワークのオーバーヘッドを考えると、前者よりも当然追加スループットは低い(約33件/秒)ので、その分同時書き込みが発生する可能性も低くなるはずです。
リスト4 add_many2.sh
#!/bin/sh
i=0
host=`hostname --short`
while [ $i -lt 10000 ]; do
ldapadd -x -D "cn=Manager,dc=example,dc=com" -w secret <<EOF
dn: cn=${host}${i},ou=people,dc=example,dc=com
objectClass: person
cn: ${host}${i}
sn: ${host}${i}
EOF
i=`expr $i + 1`
done
cl1というノードで実行
./add_many2.sh
cl2というノードで実行
./add_many2.sh
ここで、先ほど同様整合性のチェックを行ってみると、次リストのように、33件/秒という同時更新処理を行ったにもかかわらず、何度実験を行っても不整合が発生することはありませんでした。
c1というノードで実行
# slapcat | grep '^dn: cn=c' | wc -l
20000
c2というノードで実行
# slapcat | grep '^dn: cn=c' | wc -l
20000
これはあくまでも実験の結果であるため、「 33件追加/秒という更新処理の環境下では同時更新でも不整合が絶対に発生しない」と断言できるものではありません。しかし、通常の運用であれば、複数のクライアントからこのような大量の更新処理が発生するのは希なはずです。従って、複数のクライアントから同時に大量のバッチ処理などを行ったりしない限り、このような条件下では検索、更新ともに単純にロードバランサで処理することができるのです[3] 。
これをふまえると、多くの環境では、次のようなシンプルな形で冗長化+検索時の負荷分散を実現させることができます。
図3 ミラーモードをマルチマスターとして使用する(更新頻度が極端に多くないことが条件)
また、もし予算の都合などでロードバランサが準備できないのであれば、次のようにクライアント側に2つの参照先を設定することで、同等の効果を期待することができます。
図4 クライアント側の設定により冗長性と負荷分散を実現
では、予算の都合でロードバランサを準備することができないし、アプリケーションの都合により、クライアント側では1つのアドレスしか指定できないような場合を考えてみましょう。この場合はクライアントが自発的にサーバを切り替えることができないため、必然的にActive/Standby方式のHAクラスタを選択することになります。
図5 HAクラスタによる冗長化
ロードバランサが不必要となる一方、iSCSIなどに対応した共有ディスクとheartbeatのようなクラスタソフトウェアが必要になります。共有ディスクをdrbdに置き換えることも可能ですが、drbdはネットワークRAIDであるため専用の共有ディスクと比べるとパフォーマンス的にはかなり不利です。
ここでは深くふれませんが、Linuxでheartbeatを使うためのドキュメントは数多く存在しますので、興味があれば探してみてください。
最後に
連載の冒頭から述べてきましたように、LDAPとは一言で言ってしまえば検索重視のデータベースであり、その役割は単純です。
しかし、OpenLDAPやRed Hat Directory Serverには本連載で紹介してきたもの以外にも様々な機能があります。おそらく全機能の紹介となると、筆者自身も完全に理解できてない部分もあるでしょうし、連載回数も軽く100回を超えてしまうと思います。しかし、LDAPにおける実用的な機能についてはこれまでの連載である程度網羅できたのではないかと思っています。
2年以上続いた本連載も今回でいったん終了となります。機会があれば、また復活するかもしれませんので、それまでにLDAPの良さを周りの友達などにぜひお伝えください。
最後の最後に宣伝ですが、このたび、日本LDAPユーザ会のメンバーで本を出しました。ぜひこちらも参考にしてみてください。