保守性と再利用性にすぐれたLogstashパイプラインの作り方
Logstashは、オープンソースのデータ処理パイプラインです。1つ以上のインプットからくるイベントを投入および変換し、各イベントを1つ以上のアウトプット先に送ることができます。ケースによっては、複数のインプットソースから来るイベントを処理するため、Logstashに多数のコード行を追加して実装していることもあります。この記事では、保守性にすぐれた実装にするため、モジュールコンポーネントでパイプラインを作成して、コードを再利用しやすくする方法をご紹介します。
背景
Logstashで、複数のインプットソースからくるイベントに共通のロジックサブセットを適用することが必要となるケースはよくあります。こうした適用は一般的に、次の2つの方法で行われています。
- 異なる複数のインプットソースからくるイベントを1つのパイプラインで処理し、すべてのソースのイベントに共通のロジックを簡単に適用できるようにします。このパターンの実装では通常、共通のロジックのほかに、かなり多くの条件付きロジックが用いられます。したがってこのアプローチによるLogstashの実装は、複雑でわかりにくいものになる可能性があります。
- イベントの処理を、インプットソースごとに個別のパイプラインで実行します。このアプローチでは、各パイプラインに共通の機能をコピーして複製を保つ必要があり、コードの共通部分を維持することが困難です。
今回ご紹介するのは、上記のアプローチの欠点を解決するテクニックです。モジュールコンポーネントを異なるファイルに格納し、それらのコンポーネントを組み合わせてパイプラインを構築するというやり方です。このテクニックを使うとパイプラインはさほど複雑にならず、コードの複製も回避することができます。
モジュール式のパイプライン構築
Logstash設定ファイルは、Logstashパイプラインが実行するインプット、フィルター、およびアウトプットで構成されています。 高度なセットアップでは、複数のパイプラインを実行するLogstashインスタンスを使用することが一般的です。引数を使わずデフォルトでLogstashを起動すると、Logstashはpipelines.ymlと呼ばれるファイルを読み込み、指定されたパイプラインをインスタンス化します。
Logstashのインプット、フィルター、およびアウトプットは複数のファイルに格納でき、glob表現を指定することで、パイプラインに含める選択ファイルとすることができます。glob表現に一致するファイルは、アルファベット順に組み合わされます。しかし、フィルターを実行する順番が重要な意味を持つことは少なくありません。そこで、ファイル名に数的な識別子を含めておき、ファイルの組み合わせが確実に望ましい順番で行われるようにすると便利です。
以下は、複数のLogstashモジュールコンポーネントを組み合わせて、2つの独立のパイプラインを定義する場合の例です。Logstashコンポーネントを、次のファイルに格納します。
- インプット宣言:
01_in.cfg,02_in.cfg - フィルター宣言:
01_filter.cfg,02_filter.cfg,03_filter.cfg - アウトプット宣言:
01_out.cfg
以下のようにglob表現を使用し、次にpipelines.ymlに定義するパイプラインが、望ましいコンポーネントで構成されるようにします。
- pipeline.id: my-pipeline_1
path.config: "<path>/{01_in,01_filter,02_filter,01_out}.cfg"
- pipeline.id: my-pipeline_2
path.config: "<path>/{02_in,02_filter,03_filter,01_out}.cfg"
上記のパイプライン設定で、02_filter.cfgというファイルが双方のパイプラインに存在しています。これが、2つのパイプラインに共通するコードを1つのファイルで定義および保守し、さらに複数のパイプラインで実行させる仕組みです。
パイプラインをテストする
このセクションでは、先ほどpipelines.ymlで定義した個別のパイプラインで、組み合わさるファイルの具体的な例を示します。次に、そのファイルを使ってLogstashを実行し、生成されたアウトプットを見てみます。
設定ファイル
インプットファイル:01_in.cfg
このファイルは、ジェネレーターによるインプットを定義します。ジェネレーターインプットはLogstashのテスト用に設計されています。今回のケースでは、単一のイベントを生成します。
input {
generator {
lines => ["Generated line"]
count => 1
}
}
インプットファイル:02_in.cfg
このファイルは、stdinをリッスンするLogstashインプットを定義します。
input {
stdin {}
}
フィルターファイル:01_filter.cfg
filter {
mutate {
add_field => { "filter_name" => "Filter 01" }
}
}
フィルターファイル:02_filter.cfg
filter {
mutate {
add_field => { "filter_name" => "Filter 02" }
}
}
フィルターファイル:03_filter.cfg
filter {
mutate {
add_field => { "filter_name" => "Filter 03" }
}
}
アウトプットファイル:01_out.cfg
output {
stdout { codec => "rubydebug" }
}
パイプラインを実行する
オプションを使用せずLogstashを起動すると、上で定義したpipelines.ymlファイルが実行されます。次のようにLogstashを実行します:
./bin/logstash
my-pipeline_1というパイプラインは、インプットイベントをシミュレーションするジェネレーターを実行しています。Logstashが初期化を完了するとすぐに、以下のようにアウトプットを確認できるはずです。この結果からは、01_filter.cfgおよび02_filter.cfgのコンテンツがこのパイプラインによって期待通りに実行されたことがわかります。
{
"sequence" => 0,
"host" => "alexandersmbp2.lan",
"message" => "Generated line",
"@timestamp" => 2020-02-05T22:10:09.495Z,
"@version" => "1",
"filter_name" => [
[0] "Filter 01",
[1] "Filter 02"
]
}
もう一方のパイプライン、my-pipeline_2はstdinのインプットを待機しており、このパイプラインで処理されたイベントはまだ確認できません。Logstashを実行している端末に何か入力し、[Return](または[Enter])キーを押して、このパイプライン用のイベントを作成してみましょう。この手順を完了すると、次のような結果になるはずです。
{
"filter_name" => [
[0] "Filter 02",
[1] "Filter 03"
],
"host" => "alexandersmbp2.lan",
"message" => "I’m testing my-pipeline_2",
"@timestamp" => 2020-02-05T22:20:43.250Z,
"@version" => "1"
}
上の結果から、02_filter.cfgおよび03_filter.cfgのロジックが期待通りに適用されていることがわかります。
実行の順番
Logstashはglob表現の中のファイルの順番を考慮しない点に注意してください。glob表現は、どのファイルを含めるかの判断に使用され、ファイルをアルファベット順に並べるだけです。たとえばmy-pipeline_2の定義を変えて、glob表現で03_filter.cfgが02_filter.cfgより先に現れるようにしても、各イベントは03_filter.cfgに定義されたフィルターより先に02_filter.cfgのフィルターを通過します。
まとめ
glob表現を使うと、個別のファイルとして格納できるモジュールコンポーネントでLogstashパイプラインを構成することができます。この方法で、コードの保守性や、再利用のしやすさ、可読性が向上します。
補足として、今回の記事で取り上げたテクニックのほかに、パイプラインツーパイプライン通信も検討すべきポイントです。パイプラインツーパイプライン通信は、Logstashの実装のモジュール性をさらに高める可能性を秘めています。