はじめに
システム要求の度合により手厚さは変わりますが、どんなシステムも実運用をする上で、一定の可用性の確保は必要不可欠です。ここでは「3日目 midPointで構築するシステムの構成例」でご紹介した冗長構成を実践していきます。
また、今回はDockerを利用した環境構築を行っていきます。Docker実行環境があれば簡単に動かすことができますので、動くものベースに理解を深めていただければと思います。
GitHubで公開しています |
---|
本稿で紹介するDocker ビルドファイルは、以下のGitHubで公開しています。ぜひこちらも合わせてご参照ください。 ・https://github.com/naokiiiii/midpoint-dockerfiles |
冗長構成の概要
今回構築する冗長構成は、以下の図のようになります。
この構成により、midPointサーバーの処理が負荷分散され、また、片方のノードが停止した場合でも、残りのノードで処理が継続可能となります。なお、画面処理は「ロードバランサー」+「共有データベース」を活用して可用性が確保され、タスク処理はmidPointに内包するスケジューリングライブラリ「Quartz」+「共有データベース」+「ノード間通信(REST)機能(※)」を活用して可用性が確保されています。
※ リモートノードに対する死活監視、リモートノードへのスケジューリング登録および削除、リモートノードで実行中のタスクの強制終了を行います
次に、構成するDockerコンテナは、以下の表のようになります。
コンテナ名 | コンテナ名(物理名) | ポート (外部ポート:コンテナポート) |
役割 | 利用イメージ | 補足 |
---|---|---|---|---|---|
LBコンテナ | mp-ha-example-lb | 443:443 80:80 |
ロードバランサー(SSLアクセレータ) | fuww/alpine-nginx-sticky | SSLを復号し、構成されるIGAコンテナへロードバランシング(スティッキーセッション)するように構成 |
IGAコンテナ#1 | mp-ha-example-iga1 | 8080:8080 | midPoint本体(NodeA) | evolveum/midpoint:4.0.1 | 公式midPointのDockerイメージに、あらかじめ準備したmidPoint初期設定をインポートするように構成 |
IGAコンテナ#2 | mp-ha-example-iga2 | 8081:8080 | midPoint本体(NodeB) | 〃 | 〃 |
DBコンテナ | mp-ha-example-db4mp | 5432:5432 | midPointデータベース | postgres:10.6 | 公式PostgreSQLのDockerイメージに、midPointデータベース(DDL)を構築するように構成 |
そして、構成するコンテンツは、以下の表のようになります。
コンテンツ名 | URL | ID/PASSWORD |
---|---|---|
midPoint管理コンソール | https://iga.example.com/ | administrator/5ecr3t |
冗長構成の構築
前述を踏まえまして、実際に冗長構成を構築していきます。
前提条件
実行環境(OS)は問いませんが、「Docker CE」(※1)および「Docker Compose」(※2)がインストールされていることが、前提条件となります。
※1「Docker CE」は、コンテナ型の仮想化環境を提供するオープンソースソフトウェアのコミュニティー版である「Docker Community Edition」の略称です。 ※2「Docker Compose」は、複数のコンテナから成るサービスを構築、実行する手順を自動的にし、管理を容易にするツールです。 |
既に、Dockerの実行環境をお持ちの方は、この部分は読み飛ばして構いません。お持ちでない方は、以下のサイトを参考にインストールしてください。
Dockerは、さまざまなOSに対応しているので、任意の環境にインストールしてください。また、必要に応じて、DockerのHTTP/HTTPSプロキシの通し方について記載された「HTTP/HTTPS proxy」を参照し、設定を行ってください。
なお、筆者はCentOS 7で確認を行ったため、これ以降CentOS 7をベースにして記載しますが、読者の実行環境に読み替えて実施することも可能です。
事前準備
今回は、「Docker CE」および「Docker Compose」を使用して構築しますが、このDockerホストとなるCentOSへFQDNでアクセスできるように、クライアントマシンのhostsファイルに設定を追加します。
xxx.xxx.xxx.xxx iga.example.com
※「xxx.xxx.xxx.xxx」の部分はCentOSのIPアドレスです。
設定確認
構築手順に入る前に、マニュアルとDockerビルドファイルの設定箇所を確認してみましょう。
マニュアル
midPointのマニュアルに、セットアップ方法が解説されています。
[STEP]
1. 共有データベースの設定をする
2. クラスタリング設定をする(クラスタモードをオンにし、クラスタ設定項目を設定する)
POINT |
---|
共有データベースの例として記載がある`PostgreSQL`の他、`MySQL`、`Oracle`、`SQLServer`など、主要データベースに対応しています。但し、midPointの作り上、副問合せ処理が多用されているので、副問合せ処理の苦手な`MySQL`の選択は控えた方が良さそうです。 また、3.9以前のノード間通信は、`JMX`方式を採用していましたが、セキュリティの懸念により、4.0から`REST`方式が追加されました。4.0でも`JMX`方式の設定も可能ですが、非推奨となっていますので、`REST`方式を選択してください。 |
設定箇所
マニュアル通りに、<MP_HOME>/config.xml
に設定することも可能ですが、今回提供するDockerビルドファイルには、以下のように設定しています。
共有データベースの設定
- https://github.com/naokiiiii/midpoint-dockerfiles/blob/master/mp-ha-example/docker-compose.yml#L78-L88
- https://github.com/naokiiiii/midpoint-dockerfiles/blob/master/mp-ha-example/docker-compose.yml#L116-L126
POINT |
---|
オフィシャルDockerイメージは、環境変数`REPO_***`を指定することでデータベース設定が可能です。 |
クラスタリングの設定
- https://github.com/naokiiiii/midpoint-dockerfiles/blob/master/mp-ha-example/docker-compose.yml#L93
- https://github.com/naokiiiii/midpoint-dockerfiles/blob/master/mp-ha-example/docker-compose.yml#L131
POINT |
---|
オフィシャルDockerイメージは、環境変数`MP_JAVA_OPTS`の中に、クラスタ設定項目を指定することで、クラスタリングの設定が可能です。 |
構築手順
まずは、今回公開したDockerビルドファイルを任意の場所にダウンロードし、解凍します。
$ wget https://github.com/naokiiiii/midpoint-dockerfiles/archive/mp-ha-example4qiita.zip
$ unzip mp-ha-example4qiita.zip
次に、Dockerイメージのビルド、およびDockerコンテナの起動をします。
$ cd midpoint-dockerfiles/mp-ha-example/
$ docker-compose up -d --build
~ (省略) ~
Creating mp-ha-example-db4mp ... done
Creating mp-ha-example-lb ... done
Creating ha-example-iga2 ... done
Creating ha-example-iga1 ... done
最後に、以下の通り、コンテナが起動していることを確認します。
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------------------------
ha-example-iga1 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8080->8080/tcp
ha-example-iga2 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8081->8080/tcp
mp-ha-example-db4mp docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
mp-ha-example-lb nginx -g daemon off; Up 0.0.0.0:443->443/tcp,
0.0.0.0:80->80/tcp
これで、midPointの冗長構成の構築が完了です。
補足になりますが、Dockerコンテナを停止する場合には、以下のコマンドを実行します。
$ docker-compose down
Stopping mp-ha-example_iga2 ... done
Stopping mp-ha-example_iga1 ... done
Stopping mp-ha-example-lb ... done
Stopping mp-ha-example-db4mp ... done
Removing mp-ha-example_iga2 ... done
Removing mp-ha-example_iga1 ... done
Removing mp-ha-example-lb ... done
Removing mp-ha-example-db4mp ... done
Removing network mp-ha-example_default
冗長構成の動作確認
それでは、冗長構成の動作を確認していきましょう。今回は、以下の2つのシナリオで確認します。
-
[1] 画面処理の可用性
スティッキーセッションでロードバランシングされた側のノードがダウンした場合でも、残りのノードにロードバランシングされ、画面処理が継続されることを確認します。ただし、セッションは共有されていないので、再度ログインを求められます。 -
[2] タスク処理の可用性
タスクが実行中のノードがダウンした場合でも、残りのノードでタスクが自動リカバリーされ、タスク処理が継続されることを確認します。
[1] 画面処理の可用性
はじめに、画面処理の可用性の確認を行います。
まずは、midPoint管理コンソールにアクセスし、administrator
ユーザーでログインします。
ブラウザの開発者ツール等で、ロードバランサーのCookieの値(iga_lb_id
)を見ると、どちらのノードにロードバランシングされているか確認できます。ここでは、ノードA
にロードバランシングされています。
次に、ユーザーの登録を行います。
ユーザー一覧で現在のユーザー登録状態を確認し、「新規ユーザー登録画面」に遷移します。一旦、最低限の名前
のみ入力したものでユーザーを登録し、新しくユーザーが登録されたことを確認します。
このタイミングで、ノードA
で障害が発生したと仮定して、ノードA
のコンテナを強制終了します。
$ docker-compose kill iga1
Killing ha-example-iga1 ... done
$ docker-compose ps
Name Command State Ports
---------------------------------------------------------------------------------------------------------
ha-example-iga1 /usr/local/bin/startup.sh Exit 137
ha-example-iga2 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8081->8080/tcp
mp-ha-example-db4mp docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
mp-ha-example-lb nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
画面に戻り、ユーザー一覧の検索を行います。
エラー画面に遷移することなく、ノードB
にロードバランシングされますが、セッションは共有されてないので、再度、ログインを求められ、トップページに戻ってしまいます。しかし、先ほど登録したユーザーデータは共有されているので、画面処理が継続可能であることが確認できます。なお、もともとノードB
にロードバランシングされていたユーザーは、スティッキーセッションですので、ログインも求められることなく、画面処理が継続可能です。
そして、強制終了したノードA
が復旧したと仮定して、ノードA
のコンテナを起動します。
$ docker-compose start iga1
Starting ha-example-iga1 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------------------------
ha-example-iga1 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8080->8080/tcp
ha-example-iga2 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8081->8080/tcp
mp-ha-example-db4mp docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
mp-ha-example-lb nginx -g daemon off; Up 0.0.0.0:443->443/tcp,0.0.0.0:80->80/tcp
これで、両方のノードにロードバランシング(スティッキーセッション)が再開されます。
[2] タスク処理の可用性
続きまして、タスク処理の可用性を確認します。
midPoint管理コンソールにアクセスし、administrator
ユーザーでログインします。「サーバータスク画面」に遷移し、ノードの明細に、NodeA
とNodeB
のステータスが実行中
になっていれば、タスクの分散実行が可能な状態です。そして、テスト用タスクを用意してありますので、「タスク詳細」に遷移します。
このテスト用タスクは、ロギングしながら30秒間スリープするだけの処理が書かれているのですが、このタスクをスケジューリングします。ここでは確認がしやすいように、60秒間隔の繰り返しタスクで、何かしらの障害でタスクのスレッドが意図せず終了した場合にはリスタートするよう設定します。
また、タスクが実行された場合、「実行ステータス」に、実行ノードが表示されます。ここでは、ノードA
となっています。
このタスクが終了する前に、ノードA
で障害が発生したと仮定して、ノードA
を強制終了します。
$ docker-compose kill iga1
Killing ha-example-iga1 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------------------------
ha-example-iga1 /usr/local/bin/startup.sh Exit 137
ha-example-iga2 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8081->8080/tcp
mp-ha-example-db4mp docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
mp-ha-example-lb nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
サーバーログ確認すると、ノードA
で実行中のテスト用タスクが途中で終了しますが、ノードB
でタスクが自動リカバリーされていることが確認できます。
$ docker-compose logs -f
ha-example-iga1 | 2019-12-15 06:55:51,869 [MODEL] [midPointScheduler_Worker-7] INFO (com.evolveum.midpoint.expression): ==== doing test-task ==== Start ←'★ノードAでテスト用タスクが開始'
ha-example-iga1 | 2019-12-15 06:55:52,870 [MODEL] [midPointScheduler_Worker-7] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====.
ha-example-iga1 | 2019-12-15 06:55:53,871 [MODEL] [midPointScheduler_Worker-7] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====..
ha-example-iga1 | 2019-12-15 06:55:54,871 [MODEL] [midPointScheduler_Worker-7] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====...
~ (省略) ~
ha-example-iga1 | 2019-12-15 06:56:04,878 [MODEL] [midPointScheduler_Worker-7] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====.........+...
ha-example-iga1 exited with code 137 ←'★テスト用タスク処理の途中でノードAが強制終了された'
ha-example-iga2 | 2019-12-15 06:56:24,319 [] [QuartzScheduler_midPointScheduler-NodeB_ClusterManager] INFO (org.quartz.impl.jdbcjobstore.JobStoreTX): ClusterManager: detected 1 failed or restarted instances.
ha-example-iga2 | 2019-12-15 06:56:24,320 [] [QuartzScheduler_midPointScheduler-NodeB_ClusterManager] INFO (org.quartz.impl.jdbcjobstore.JobStoreTX): ClusterManager: Scanning for instance "NodeA" s failed in-progress jobs. ←'★ノードAでタスクが異常終了したことを検知'
ha-example-iga2 | 2019-12-15 06:56:24,344 [] [QuartzScheduler_midPointScheduler-NodeB_ClusterManager] INFO (org.quartz.impl.jdbcjobstore.JobStoreTX): ClusterManager: ......Deleted 1 complete triggers(s).
ha-example-iga2 | 2019-12-15 06:56:24,344 [] [QuartzScheduler_midPointScheduler-NodeB_ClusterManager] INFO (org.quartz.impl.jdbcjobstore.JobStoreTX): ClusterManager: ......Scheduled 1 recoverable job(s) for recovery.
ha-example-iga2 | 2019-12-15 06:56:24,415 [] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.task.quartzimpl.execution.JobExecutor): Recovering resilient task Task(id:1576390139067-0-1, name:test-task, oid:task0000-0000-0000-0000-00000test) ←'★ノードBでテスト用タスクを自動リカバリ'
ha-example-iga2 | 2019-12-15 06:56:25,217 [MODEL] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.expression): ==== doing test-task ==== Start ←'★ノードBでテスト用タスクが開始'
ha-example-iga2 | 2019-12-15 06:56:26,352 [MODEL] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====.
ha-example-iga2 | 2019-12-15 06:56:27,353 [MODEL] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====..
ha-example-iga2 | 2019-12-15 06:56:28,354 [MODEL] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====...
~ (省略) ~
ha-example-iga2 | 2019-12-15 06:56:55,387 [MODEL] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.expression): ==== doing test-task ====.........+.........+.........+
ha-example-iga2 | 2019-12-15 06:56:55,387 [MODEL] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.expression): ==== doing test-task ==== End ←'★ノードBでテスト用タスクが終了'
ha-example-iga2 | 2019-12-15 06:56:55,389 [] [midPointScheduler_Worker-5] INFO (com.evolveum.midpoint.model.impl.scripting.ExecutionContext): Script console message: Executed script on the pipeline
※ ポイントとなる行に、★印付きで解説を記載しています
このとき、「サーバータスク画面」よりノードの状態を確認すると、ノードA
はオフ
となっており、以降のタスクはノードB
で実行されます。
そして、強制終了したノードA
が復旧したと仮定して、ノードA
のコンテナを起動します。
$ docker-compose start iga1
Starting ha-example-iga1 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------------------------
ha-example-iga1 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8080->8080/tcp
ha-example-iga2 /usr/local/bin/startup.sh Up (healthy) 0.0.0.0:8081->8080/tcp
mp-ha-example-db4mp docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
mp-ha-example-lb nginx -g daemon off; Up 0.0.0.0:443->443/tcp,0.0.0.0:80->80/tcp
再度「サーバータスク画面」よりノードの状態を確認すると、ノードB
が実行中
となり、タスクの分散実行が再開されます。
なお、今回はタスクの異常終了時に自動リカバリーするシナリオで解説しましたが、実装するタスクによっては、エラー内容や影響範囲を把握せず闇雲に自動リカバリーしたくないケースもあるかと思います。スケジューリング設定の中で少し触れましたが、タスクが実行中に異常停止した場合の動作や、両ノードがダウンしていてスケジュール時刻に起動できなかった場合の動作は、運用要件に合わせて変更可能です。ここでは詳細な解説は割愛しますが、ぜひ、midPointのマニュアルを読みながら、このDocker環境で試して、理解を深めてみてください。
おわりに
以上で、2つのmidPointサーバーが冗長化されていることを確認できました。今回はmidPointのマニュアルをベースに構築を行ったのですが、アレンジでセッションの共有も行うことが可能です。midPointは「Spring Boot」をベースに構築されていますので、「Spring Session」を利用することで、MemcacheやRedisなどのインメモリデータストアにセッションが共有され、より理想的な冗長構成となります。ロードバランシングの切り替わりタイミングでログイン画面さえも表示させたくないといった高いシステム要求の場合には、障害ポイントが増えることにも注意しながら、採用を検討してみてください。
参考
Clustering / high availability setup
※ 公式のクラスタリング設定のマニュアルです
GitHub Evolveum/midpoint-docker - demo(clustering)
※ 公式のDockerのクラスタリングデモです(非推奨の「JMX」方式で構築されているので参考までに)
Task Manager
※タスクの動作について解説されています。