Liferay DXP 7.0は、検索関連の機能でElasticsearchと密接な関係があります。 たまたまですが、ElasticsearchはELKスタックの1部です。 ですから、"ELKスタックを全部使ったら何ができるのか?"と考えるのは自然なことです。
答えは、「チャンスが多い」です。 ここで紹介するのは、DXP 7.0の「ダッシュボード」の構築で、複雑な情報をグラフィカルに表現するための様々なチャートやグラフを作成するオプションです。 情報を素早く消費できるようにまとめることができるため、人気があります。
この記事では、ELKスタックを使ってダッシュボードのグラフを作成します(ELKスタックがインストールされていることが前提です)。
ここで紹介する内容は、開発したものを使ってテストしたものです:
- Liferay DXP 7.0 FP22+の場合
- Elasticsearch 2.3.3
- Kibana 4.5.1
- Logstash 2.3.2
- Apache HTTPd 2.4
Liferay DXP
構築されるダッシュボードは、Liferay DXPに組み込まれたLiferay Auditの機能を活用する予定です。 そのため、これらはLiferay Portal 7.0 CEでは機能が不足しているため、動作しません。 Liferay Portal 7.0 CE用の監査レイヤーを構築することは可能ですが、このドキュメントの範囲外です。
Elasticsearch
前述したように、DXP 7.0では検索関連の機能にElasticsearchを採用しています。 本番環境では、ESはアプリケーションコンテナの外に別途インストールされ、一般に公開されないバックエンドシステムにインストールされる可能性があります。
Kibana
Kibanaは、Elasticsearchをベースにした可視化ツールです。 Kibanaでは、ESに対する検索クエリを使用し、その結果に基づいてチャートやグラフなどのビジュアライゼーションを構築することができます。 本資料ではいくつかの簡単なビジュアライゼーションを紹介しますが、Kibanaは複雑なクエリを使用して魅力的なビジュアライゼーションを構築することをサポートしています。 ただし、複雑なクエリやビジュアライゼーションは、このドキュメントの範囲外です。
Kibanaはバックエンドにもインストールする必要があり、ESサーバーか専用サーバーにインストールします。 クライアントのブラウザはリソースのためにKibanaサーバを叩く必要がありますが、これはプロキシを経由して行うべきです。 本資料では、KibanaサーバーへのリクエストのプロキシにApache httpdを使用します。
Logstash
logstashという名前は、ログファイルのためのツールという印象を与え、誤解を招きやすい。 実はlogstashは、非構造化データソースからデータを抽出して別の場所に送るのに適したETL(Extract, Transform and Load)ツールなんです。 ログファイルから情報を抽出してドキュメントとしてElasticsearchに読み込むためのツールとしてスタートしましたが、logstashがデータベースやファイル、twitterフィードなどから作業できるように、さまざまなプラグインが開発されています。 フィルタと変換機構は、抽出されたデータをElasticSearchのドキュメントとして読み込むのに適したドキュメントにするのを容易にします。
Beats
logstashの関連ツールにbeatsフレームワークがあります。 Logstashはリソース集約型のツールであり、すべてのプロダクションシステムにインストールするには重すぎる可能性があります。 filebeatsツールを含むbeatsフレームワークがその解決策です。 beatsフレームワークは、データをネットワーク経由で中央のlogstashインスタンスに送信する前に、ローカルで読み取りとフィルタリングを行う軽量エージェントを提供します。これにより、beatsエージェントはデータが存在するシステムにインストールでき、logstash処理の大部分を別の(専用の)サーバーにオフロードすることができます。
本書ではbeatsツールを使用していませんが、本番環境では、バックエンドにlogstashをインストールし(ESサーバーまたはおそらく専用サーバー)、他のシステムでfilebeatsエージェントを使用してlogstashへのデータ送信を処理することが推奨されます。
Apache HTTPd
このドキュメントでは、Liferay TomcatインスタンスとバックエンドのKibanaサーバーへのリクエストをプロキシするために、Apache HTTPdを使用しています。
解決
ここで紹介する実装は、以下の要件を満たすように設計されています:
- ELKスタックを使用して、ログインアクティビティを表示するダッシュボードを作成します:
- 現在の1週間の1日あたりのログイン数。
- 現在の日の1時間あたりのログイン数。
- ELKスタックを使用して、1日あたりのフォーム送信を表示するダッシュボードを作成します。
- ELKスタックを使用して、ページの人気を表示するダッシュボードを作成します。
これらのダッシュボードを構築するために必要なデータはアクティビティベースであるため、データ収集に対応するためにAuditフレームワークを活用する予定です。 アーキテクチャ図は次のような感じです:
Audit Frameworkを使用すると、イベントを生成するために使用されるロジックは、監査ファイルを維持するコードから分離されます。 また、監査フレームワークを活用することで、DXPのソース上で監査メッセージの生成例をすぐに利用することができます。
実装内容
要件を満たすダッシュボードを構築するためには、以下のものを生成する必要があります:
- ダッシュボードに必要なデータを提供するために、適切な監査メッセージを生成するカスタム監査コード。 ログインダッシュボードは、OOTBユーザーモデルリスナー監査イベントジェネレータを使用して満足させることができます。
- 監査メッセージを受信し、監査ファイルに書き込むためのAuditMessageProcessorです。
- 監査ファイルを処理し、ESにドキュメントをロードするためのlogstashの設定ファイルです。
- 各ダッシュボードのKibanaビジュアライゼーション。
- Liferay/TomcatとKibanaサーバーにメッセージをプロキシするためのApacheのセットアップ。
監査規程
ダッシュボードの場合、以下のアクションに対して監査情報をトリガーする必要があります:
- ユーザーログイン - 幸いにも、これは com.liferay.portal.security.audit.event.generators.events.LoginPostAction の OOTB 監査イベント生成コードですでに処理されています。 ログインが発生すると、このログイン後のハンドラーは監査メッセージを生成し、この監査メッセージはダッシュボードのグラフに利用することができます。
- フォームの送信-LR7でフォームが送信されると、フォームフィールドはシリアライズされ、DDMContentレコードとして保存されます。 そこで、フォームが保存、更新、削除されるタイミングを特定するために、DDMContent ModelListenerが必要になる。 テスト中に、フォームデータのデシリアライズに必要な対応するDDMStorageLink要素の前にDDMContentsが保存されることが判明したので、最初のフォーム送信を監査するためにDDMStorageLinksのモデルリスナーが追加されました。
- ページビューはさまざまな方法で監査できますが、この実装では、レンダリングされようとしているページを特定するためにServicePreActionが使用されています。 LayoutActionクラスから転用したコードで、レンダリングするページを特定し、監査します。
添付のプロジェクトでは、該当するクラスは以下の通りです:
- com.liferay.portal.security.audit.event.generators.DDMContentModelListener
- com.liferay.portal.security.audit.event.generators.DDMStorageLinkModelListener
- com.liferay.portal.security.audit.event.generators.AuditServicePreAction
また、AuditMessage のインスタンスを作成するために使用される com.liferay.portal.security.audit.generators.util
パッケージには、いくつかのユーティリティクラスが存在します。
AuditMessageProcessor
DXPの監査処理機構では、AuditMessageインスタンスをLiferayMessageBusに載せています。 デフォルトのメッセージバスリスナーである com.liferay.portal.security.audit.router.internal.DefaultAuditRouter
は、各監査メッセージを受信して、登録された各 AuditMessageProcessor インスタンスに転送します。
ELKスタックのlogstashコンポーネントを活用するため、ここで紹介する実装では、監査メッセージをJSONファイルに書き出すことにしています。 各監査メッセージは、JSONにシリアライズされ、回転ファイルに書き込まれます。
この実装クラスは com.liferay.portal.security.audit.json.log.JsonLoggingAuditMessageProcessor
で、対応するクラスはcom.liferay.portal.security.audit.json.log.JsonLoggingAuditMessageProcessorConfiguration
です。
イベントの展開と生成
ビジュアライゼーションを構築するには、ESインデックスで利用可能ないくつかのドキュメントが必要です。 バンドルをビルドしてDXP環境にデプロイし、イベントの生成を開始します。 何度かログインとログアウトを繰り返し、ポータルサイト内を移動し、フォームを定義して値の送信を開始します。
次のステップに進むとき、環境に戻って新しいイベントを発生させてください。
Logstashの設定ファイル
logstashの設定ファイルは、特定のプロセスの入力、フィルター、出力を定義します。
入力構成
今回の実装では、入力は監査用jsonファイルです:
input { file { # For the demo just using a fixed path to file. path => "/Users/dnebinger/liferay/clients/liferay/elk/bundles/logs/json/audit.json" # Since the file may roll over, we should start at the beginning start_position => beginning # Don't ignore older records ignore_older => 0 # our file is a json file so use it for the codec. codec => "json" } }
フィルター構成
フィルタは、読み込んだレコードに対していくつかの変換を行うことになります:
filter { # Apply some changes to the incoming records mutate { add_field => { # Copy the eventType field to the action field. "action" => "%{eventType}" } # Strip out the values that we don't care to include in the index. remove_field => [ "sessionID","path","host" ] } # If path is provided, clone to the not analyzed and not indexed fields. if "" in [additionalInfo][path] { mutate { add_field => { "[additionalInfo][pathUrl]" => "%{[additionalInfo][path]}" } add_field => { "[additionalInfo][pathString]" => "%{[additionalInfo][path]}" } } } # The audit timestamp is mashed together, we need to extract it out. # Date follows the following format: 20160608135725305 date { match => [ "timestamp", "yyyyMMddHHmmssSSS" ] } }
出力構成
出力については、レコードをElasticsearchにアップロードすることになります:
output { # Target elasticsearch elasticsearch { # Point at the backend server. If a cluster we'd use multiple hosts. hosts => ["192.168.1.2"] # Specify the index where our records should go index => "audit-%{+YYYY.MM.dd}" } # For debugging purposes, also write the records to the console. stdout { codec => rubydebug } }
Logstashの実行
インデックスを作成する
インデックスを読み込む前に、一部のフィールドを手動で定義し、解析とインデックス作成を無効化します。 このような変更は、インデックスが初めてロードされる前に行わなければならず、すべてのデータを再インデックス化する必要があります。
コマンドラインで次のコマンドを発行して、フィールドをあらかじめ定義しておきます:
curl -XPUT 192.168.1.2:9200/audit/logs/_mapping -d ' { "logs": { "properties": { "additionalInfo": { "properties": { "pathUrl": { "type":"string", "index":"not_analyzed" }, "pathString": { "type":"string", "index":"no" } } } } } } '
監査ログがlogstashで処理されるとき、残りのカラムはデフォルトの設定で追加されます。
Logstashの実行
設定ファイルの準備ができたら、logstashを起動することができます。 logstashディレクトリから、以下のコマンドを実行します。
bin/logstash agent -f audit.conf
すでにいくつかの監査イベントが作成されているので、logstashが起動するとファイルの処理が開始され、コンソールに以下のようなメッセージが表示されるはずです:
{ "companyId" => "20116", "classPK" => "20164", "clientHost" => "::1", "clientIP" => "::1", "serverName" => "localhost", "className" => "com.liferay.portal.kernel.model.User", "eventType" => "LOGIN", "serverPort" => 80, "userName" => "Test Test", "userId" => "20164", "timestamp" => "20160610231917907", "@version" => "1", "@timestamp" => "2016-06-11T03:19:17.907Z", "action" => "LOGIN" }
これらのメッセージが流れてくると、レコードはファイルから消費され、変換され、Elasticsearchにドキュメントとしてロードされます。
Kibanaビジュアライゼーション
Kibanaのビジュアライゼーションは、実はKibanaのUI内で作成されます。 まず最初に、インデックスパターンを定義します。 インデックスパターンは、ビジュアライゼーションを構築するための基礎となるものです。
インデックスパターンを作成する
下図にしたがってインデックスパターンを作成する:
検索クエリを定義する
次のステップは、検索クエリの構築です。 最初のものは、現在の日の時間ごとのログイン数です。 Discover のタブをクリックして開始します。
右上のタイムフレームを変更することから始めます。 リンクをクリックし、時間枠を「今日」に変更します。これにより、表示するレコードを制限する時間枠が定義されます。
次に、Kibanaバナーの下のドロップダウンにあるインデックスを、新しく定義した監査インデックスパターンに変更します。 というようなページになります:
Available Fields セクションから、action、userName、userId フィールドを追加します。 これにより、レコードのタイムスタンプ、アクション(NAVIGATEやLOGINなど)、イベントのユーザー詳細が表示されます。 今回は、1時間あたりのログイン数のクエリを作成するので、検索バーを次のように変更します。
action:LOGIN
と入力してエンターキーを押す。
保存 ボタンをクリックして、クエリを ログインとして保存します。 この検索は、後ほどご紹介する「時間別ログイン数」と「日別ログイン数」のビジュアライゼーションにも使用されます。
次にページヒット検索です。 New Search リンクをクリックし、新しい検索を開始します。 クエリーを設定する:
action:NAVIGATION
選択したフィールドにアクション、userName、additionalInfo.pageUrlを追加します。 この検索結果を保存する ページヒット数.
フォーム送信検索の場合、 New Search リンクをクリックし、新しい検索を開始します。 クエリーを設定する:
action:ADD AND className:com.liferay.dynamic.data.mapping.model.DDMContent
アクションとclassNameを選択フィールドに追加し、この検索を Forms Submittedとして保存します。
ビジュアライゼーションの作成
Visualize リンクをクリックし、ビューを切り替えます。
Vertical Bar Chart をクリックし、Logins by Hour の可視化を作成します。 保存した検索から オプションを選択し、 Logins 検索を選択します。
右上の「 Today 」が選択されていることを確認します。 Y-Axis セクションで、カスタムラベルを Loginsに設定します。
バケット の項目で、 X-Axis のオプションを選択します。 集計に 日付ヒストグラム を選択し、間隔を 時間ごとに設定し、カスタムラベルを 時間ごとに設定します。
緑色の > ボタンをクリックすると、ビジュアライゼーションが更新されます。 うまくいけば、こんな風に表示されるかもしれません:
この可視化を Logins By Hourとして保存します。
次の可視化では、 新しい可視化 ボタン、 保存された検索から と ログイン 検索をクリックします。 X軸 のラベルには、 日を使用します。 右上の「 This Week 」オプションに変更します。 この可視化を Logins By Dayとして保存します。
次の可視化では、上記の手順を繰り返しますが、 Forms Submitted の検索を使用します。 Y 軸には、 Forms を使用し、 X 軸 ラベルには、 Dayを使用します。 この可視化を Forms Submitted By Dayとして保存します。
最後の可視化では、 新規可視化 ボタンをクリックしますが、 円グラフを使用します。 検索には Page Hits をご利用ください。 バケットタイプで、 Split Slices を選択し、 Terms を集計に使用します。 フィールドには additionalInfo.pathUrl を、サイズには 10 を、カスタムラベルには Page を使用してください。
円グラフは次のようになります:
選択 - 視覚化かダッシュボードか
Liferay内で個別に使用できる4種類のビジュアライゼーションが登場しました。 もう一つの選択肢は、Kibana Dashboardを作成することです。
Kibana DashboardsはKibana側で定義し、フリーフォームのLiferayページとして機能します。 ダッシュボード上では、ビジュアライゼーションの追加、サイズ変更、移動が可能です。
どちらもLiferayのページに埋め込むことができますが、ダッシュボードオプションはページの大きな面積を占めたいものです。 そのため、個々のビジュアライゼーションがより効果的な場合もあります。
Liferayにビジュアライゼーションを配置する
Kibanaのサーバーはバックエンドサーバーにあるため、通常はブラウザでアドレスを取得することはできませんが、Kibanaは可視化を動作させるために、いくつかのリソース(jsやcss)をブラウザに公開する必要があります。
IFrameポートレットはLiferayでビジュアライゼーションをページに配置するために使用されますが、Liferayはリソースをプロキシングしません。
Kibanaサーバーをブラウザに公開するために、フロンティングWebサーバーを使用して、ほとんどのリクエストをLiferayに、一部のリクエストをKibanaサーバーにルーティングします。
今回使用した設定では、Apache HTTPdを使用して、AJP経由でLiferay/Tomcatにリクエストを送信し、/kibana/と/bundles/のリクエストをKibanaサーバーにルーティングしました。 Kibanaはserver.basePathの設定とhttpdのプロキシステートメントを定義する必要がありました。 これらのファイルは、src/main/resources/kibana/kibana.ymlとsrc/main/resources/apache/mod_jk.confとしてプロジェクト内で利用できます。
ビジュアライゼーションのURLの取得
IFrameポートレットを設定するためには、URLが必要です。 これらのURLは、Kibanaから(server.basePathが設定され、Kibanaが再起動された後に)送られてきたものです。
ビジュアライゼーションでもダッシュボードでも、そのプロセスは同じです。 Liferayに配置するビジュアライゼーションやダッシュボードを開き、 Share ... ボタンをクリックします。 2行用意されており、一番上がEmbed(IFrame)リンク、二番目がShared(共有)リンクです。 どちらのリンクも同じですが、Shareのリンクには周囲のIFrameタグがありません。
それぞれのリンクの右側には、2つのボタンがあります。 1つ目は、完全な視覚化のためのコード文字列を返す Generate Short URL ボタンで、2つ目は Copy to Clipboard リンクです。 短いURLを使う方が管理上もすっきりしますが、長い形にも価値はあるのです。 使いたいフォームを選び、値をコピーします。 Liferay側では、ページにIFrameポートレットを配置し、そのURLをソースURLとして使用するよう設定します。 すべてが正しく動作している場合、ビジュアライゼーションはページ上に表示されるはずです:
だから、URLのロングフォームにも価値がある。 URLには、実際にビジュアライゼーションやダッシュボードをレンダリングするために使用されるすべての詳細が記載されています。 ある程度の時間と理解があれば、Kibanaに作業を依頼することなく、Kibanaのビジュアライゼーションを構築したり修正することが可能です。 確かにKibanaのUIを活用してビジュアライゼーションを定義する方が簡単ですが、必須ではありません。
追加情報
ダッシュボードの作成は、ELKスタックを使い、Liferayの機能を活用することで、とても簡単にできるようになります。 スタックにあるすべてのツールの柔軟性は、以前はあまり利用できなかったあらゆる可能性の扉を開くものです。
Google で Kibana Dashboard Examples と検索してみると、実にクールなものがいくつも出てきます。 そして今回、Liferayのページ内に埋め込むことができ、Liferayのデータも入力することができるようになりました。
このドキュメント、これらの例、そしてこのサンプルコードは、可能性の表面をかすめるに過ぎませんが、これらは実世界の例です。
現在進行中のあるプロジェクトでは、ある期間中に提出されたさまざまな種類のフォームの数をダッシュボードで表示することが求められています。 この例のコードを使うと、すべてのフォーム送信は監査レコードになるので、すべてのフォームデータは消費可能なフォーマットでElasticsearchにあります。 フォームと異なるデータタイプに基づくビジュアライゼーションにより、要件が要求する個々のダッシュボードチャートが完成します。 クライアントには、レスポンシブなチャートやグラフが用意されていますが、実装チームは、監査イベントジェネレータの追加や、Elasticsearchの検索に基づく可視化の定義などを行うだけで、ほとんど何もする必要がありません。