株式会社クリアコード > ククログ

ククログ


MacOSのインストーラーを作成するには

はじめに

クリアコードはFluentdの開発に参加しています。

Fluentdにはtd-agentというディストリビューションがあり、Treasure Dataというサービスにすぐに接続しに行けるプラグインをまとめてくれているパッケージやインストーラーが提供されています。
td-agentでは、td-agent 3からmacOS向けのインストーラーを試験的に提供しており、macOSへもインストールできます。
ただし、macOSでのtd-agentのインストーラーの提供はmacOSでのtd-agentのユーザーが少ないと言うこともあり、
提供が滞っていました。

今回は筆者畑ケがtd-agent4においてmacOS向けのインストーラーを作成した時の状況をもとに解説します。

macOSのインストーラー

macOSではインストーラーと呼ばれるものは、形式がいくつかあります。

  1. distribution形式の.pkg単体で配布する場合
  2. distribution形式の.pkgをさらにディスクイメージに同梱して配布する場合
  3. macOSのアプリケーション形式(.app)のアプリケーションを同県してディスクイメージで配布する場合

この3種類があります。また、macOSのインストーラーの.pkgはアンインストール機能はありません。代わりにインストールされたファイルはpkgutil --files <com.distributor.app.identifier>の形式でインストーラーが登録している識別子に紐づいているファイルを取得することができます。このコマンドを使用してmacOSのインストーラー形式である.pkgがインストールしたファイルをシェルスクリプトなどの手段で削除できます。

td-agent4のmacOS向けのインストーラーを作成する

td-agent4はchefが作成しているパッケージ作成システムのomnibusには依存しておらず、Apache Arrowのパッケージビルドシステムをもとに独自で開発したものになっています。
macOSは公式にはDockerコンテナ化されていないためmacOS向けのインストーラーはDockerコンテナでは行わず、macOSの環境が必要です。

macOSのインストーラーの原型を作成する。

macOS向けのtd-agent4のインストーラーを作成するにあたって、適切なインストーラーの形式を選択します。
まず、macOSのアプリケーションの形式(.app)としてtd-agentのアプリケーションを作成すると、td-agentをlaunchctl*1を使ってサービス化を行いたいと言う要求を満たすことはできません。launchctlでサービス定義*2を提供するのに、/Application配下の.app内のlaunchctl用のサービス定義は読み込んでくれないからです。

この要件から、3.のdmgに.app形式で作成したアプリケーションを同梱して配布すると言う形態は取ることができず、1.または2.の形態でインストーラーを配布することを選択することになります。

macOSの暗黙的な慣習ではdistribution形式のpkg単体で配布するよりも、distribution形式のpkgをディスクイメージにさらに同梱して配布することが好まれます。
これは、pkg単体では背景画像を設定したり、見栄えをよくしたり、最初に説明書を提示したりするカスタマイズ性が弱いことだろうと筆者は考えています。 *3

そこで、この記事では2.の「distribution形式の.pkgをさらにディスクイメージに同梱して配布する」と言う方式を選択することとします。これはomnibusのmacOS向けのパッケージングでも採用されている形式です。

基礎的なpkgを作成する

pkgutilでdistribution形式のpkgへ同梱する.pkgを作成しましょう。

pkgutilでインストーラーの機能のみを持つ定義ファイル(.plist)を作成します。定義を作成するには以下のコマンドを実行します。

$ pkgbuild --analyze --root /path/to/staging-path td-agent.plist

td-agent-builderの状況に当てはめるなら、

$ pkgbuild --analyze --root td-agent/staging td-agent.plist
pkgbuild: Inferring bundle components from contents of td-agent/staging
pkgbuild: Writing new component property list to td-agent.plist

となります。今回はtd-agent/stagingと言うディレクトリに必要なファイルが入っており、ディレクトリの階層構造をいじる必要がないためこの定義ファイルはそのまま使用します。

pkgbuildコマンドを使用して、td-agent4のインストールの機能のみを持つインストーラーを作成します。

$ pkgbuild --root /path/to/rootDir --component-plist /path/to/plist --scripts /path/to/scriptDir --identifier com.distributor.app.identifier --version version --install-location location PckageName.pkg

td-agent-builderの状況に当てはめるなら、

$ pkgbuild --root td-agent/staging --component-plist td-agent.plist --identifier com.treasuredata.tdagent --version 4.0.1 --install-location / td-agent.pkg [--scripts /path/to/installation-scripts-dir]
pkgbuild: Reading components from td-agent.plist
pkgbuild: Wrote package to td-agent.pkg

となります。

これで、td-agent4に最低限必要なファイル群をtd-agent.pkgに固めることができました。
実際にはtd-agent4のインストール時に作成することが必要なディレクトリがあるため、--scriptsにはpostinstallが入ったディレクトリを指定しています。
指定がない場合にはインストーラーを実行した際にインストールスクリプトの実行する候補がないため、インストールスクリプトは実行されません。

distribution形式のpkgを作成する

前段で、td-agent.pkgを作成しました。これは、背景画像や、welcomeテキストや、使用許諾表示を行うことができないインストーラーです。
distribution形式のpkgはこれらの機能を備えているインストーラー形式です。また、この形式のインストーラーは複数のインストーラー(.pkg)を内包できます。

前段のtd-agent.pkgを用いてdistribution形式のpkgの定義の雛形を作成します。

$ productbuild --synthesize --package td-agent.pkg Distribution.xml 
productbuild: Wrote synthesized distribution to Distribution.xml

これにてproductbuildコマンドで使用する定義ファイルのDistribution.xmlが作成できました。

<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
    <pkg-ref id="com.treasuredata.tdagent"/>
    <options customize="never" require-scripts="false"/>
    <choices-outline>
        <line choice="default">
            <line choice="com.treasuredata.tdagent"/>
        </line>
    </choices-outline>
    <choice id="default"/>
    <choice id="com.treasuredata.tdagent" visible="false">
        <pkg-ref id="com.treasuredata.tdagent"/>
    </choice>
    <pkg-ref id="com.treasuredata.tdagent" version="4.0.1" onConclusion="none">td-agent.pkg</pkg-ref>
</installer-gui-script>

インストーラーのタイトルを変更するには<title>要素、背景画像の変更には<background>要素、使用許諾表示には<license>要素、welcomeテキスト表示には<welcome>要素を指定します。

この記事ではこれらの要素は説明するには重要な箇所ではないため、省いてインストーラーを作成していきます。
productbuildを実行します。

$ productbuild  --distribution "Distribution.xml" --package-path /path/to/pkgDir --resources "/path/to/resourceDir" PackageName.pkg

td-agent-builderの状況に当てはめるなら、

$ productbuild --distribution Distribution.xml --package-path td-agent.pkg [--resources /path/to/resources such as welcome.html and license.html etc.] td-agent-4.0.1.pkg
productbuild: Wrote product to td-agent-4.0.1.pkg

これで、distribution形式となったインストーラー(.pkg)が作成できました。

ディスクイメージ(.dmg)を作成する

macOSでは、distribution形式のインストーラーを更にディスクイメージ(.dmg)に包んで配布することが多く見かけられます。

ディスクイメージを作成するには、同梱したいファイルを格納するディレクトリを作成し、同梱したいファイルを配置します。

$ mkdir dmg
$ cp td-agent-4.0.1.pkg dmg
$ hdiutil create -srcfolder dmg -fs HFS+ -format UDZO -volname Td-Agent td-agent-4.0.1.dmg
.......................................................................................................................................................
created: ~/GitHub/td-agent-builder/td-agent-4.0.1.dmg

となります。

これにてdistribution形式のインストーラー(.pkg)を同梱したディスクイメージ(.dmg)が作成できました。

余談

td-agent4のmacOSのインストーラーは単純にディスクイメージを作成しておしまい、ではなく、書き込みと読み込み可能な一時ディスクイメージを作成してディスクイメージの見た目と、Finderで開いた時のサイズ調整を行っています。
td-agent4におけるmacOS向けのインストーラーを作成する作業は fluent-plugins-nursery/td-agent-builder#192 にて実施しましたので、この記事では解説されなかった細かな箇所に関してはリンク先のpull requestをご覧ください。

まとめ

macOS向けのインストーラーの作成のやりかたを解説しました。筆者は別の案件でdistribution形式のmacOSのインストーラーの作成を実施したことがありました。
td-agent3ではインストーラーを更にディスクイメージに同梱しているやりかたでインストーラーを作成していたため、調査をしながらこの作業を実施しました。
ディスクイメージをhdiutil create -fs HFS+ -format UDZO ...で作成してしまうと読み込みのみ可能なディスクイメージとなってしまいます。読み込みと書き込みが可能なディスクイメージの作成が必要ということに中々気付けず、ディスクイメージのカスタマイズに難航しました。また、ディスクイメージのカスタマイズは基本的にGUI経由で行われる操作ですが、macOSはGUI Scripting環境(AppleScrtipt)も提供しており、CI環境でもディスクイメージのカスタマイズを実施することができました。

当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。

*1 macOSではlaunchctl/launchdを用いてサービスの起動・終了を行うことが推奨されます。

*2 launchctl向けのサービス定義は.plistと言う拡張子で、`/Library/LaunchDaemons`配下に置くことが推奨されます。

*3 筆者は実際のところどうなんだろう?と長年疑問に感じています。

タグ: Fluentd
2020-09-08

Fluent-plugin-mongoでTTLをサポートした話

はじめに

クリアコードはFluentdの開発に参加しています。

Fluentdにはプラグインというしくみがあり、たくさんのプラグインが開発されています。
今回は筆者畑ケがfluent-plugin-mongoというMongoDBにFluentdからログを流すプラグインでTTL(Time To Live)の機能をサポートした話を書きます。

MongoDBでのTTL

TTL(Time To Live)とは、あるデータが破棄されるまでの有効期限のことです。

MongoDBでは、このTTLはコレクションではなく、コレクションに対して貼るインデックスで設定します。

MongoDBのRuby Driverのマニュアルページを見ると、
MongoDBのコレクションに対して貼るインデックスのオプションにexpire_afterパラメーターがいる事がわかります。

インデックス名をきちんと設定しないとコレクションに対するインデックスが貼れないのは少し面倒なので、
インデックスを作成する時のパラメーターの要件を調べます。

MongoDBのAPIドキュメントを参照します。
インデックスのオプションのnameの項を見ていきます。

Parameter Type Description
name string Optional. The name of the index. If unspecified, MongoDB generates an index name by concatenating the names of the indexed fields and the sort order.

とあるため、MongoDBのコレクションに対しては、以下のようにレコードのキーの指定とexpire_afterオプションの指定でTTLの指定ができる事がわかります。
また、nameオプションは省略可能(Optional)です。

client[:test_name].indexes.create_one(
  {"time": 1}, expire_after: 120
)
実際に組み込んでみる

fluent-plugin-mongoでは、時刻に関するキーは@inject_config.time_keyにて設定されます。
また、プラグインに組み込む時に設定ファイルでTTLの長さを設定できるとより便利です。

これらの要素を入れたパッチが以下のようになります。

diff --git a/lib/fluent/plugin/out_mongo.rb b/lib/fluent/plugin/out_mongo.rb
index fe9109f..4727762 100644
--- a/lib/fluent/plugin/out_mongo.rb
+++ b/lib/fluent/plugin/out_mongo.rb
@@ -49,6 +49,9 @@ module Fluent::Plugin
     desc "Remove tag prefix"
     config_param :remove_tag_prefix, :string, default: nil,
                  deprecated: "use @label instead for event routing."
+    # expire indexes
+    desc "Specify expire after seconds"
+    config_param :expire_after, :time, default: 0
 
     # SSL connection
     config_param :ssl, :bool, default: false
@@ -270,6 +273,13 @@ module Fluent::Plugin
       unless collection_exists?(name)
         log.trace "Create collection #{name} with options #{options}"
         @client[name, options].create
+        if @expire_after > 0 && @inject_config
+          log.trace "Create expiring index with key: \"#{@inject_config.time_key}\" and seconds: \"#{@expire_after}\""
+          @client[name].indexes.create_one(
+            {"#{@inject_config.time_key}": 1},
+            expire_after: @expire_after
+          )
+        end
       end
       @collections[name] = true
       @client[name]

このパッチを組み込み、設定ファイルを作成します。

<match **>
  @type mongo
  connection_string mongodb://localhost:27017/testDb
  collection test1
  expire_after 120
  # ...
</match>

とすると、testDbデータベースへtest1コレクションを作成し、このコレクションに入るレコードのTTLは120秒に設定されます。

まとめ

fluent-plugin-mongoを題材にして普段どのようにFluentdプラグインのメンテナンスをしているかを解説しました。

当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。

タグ: Fluentd
2020-09-04

Windowsの新しいパッケージ管理システムのリポジトリにパッケージを登録するには

はじめに

クリアコードはFluentdの開発に参加しています。

Fluentdにはtd-agentというディストリビューションがあり、Treasure Dataというサービスにすぐに接続しに行けるプラグインをまとめてくれているパッケージやインストーラーが提供されています。
td-agentでは、td-agent 3からWindows向けのインストーラーを提供しており、Windowsへもインストールできます。

wingetとは

wingetとはMicrosoftが新しく開発したWindows向けのパッケージ管理システムです。

このwingetはフリーソフトウェアとして公開されており、また、パッケージを登録するリポジトリやプロセスも公開されています。
winget本体はmicrosoft/winget-cliリポジトリとして管理され、wingetのパッケージはmicrosoft/winget-pkgsとして公開されています。

wingetに登録するパッケージマニフェストを作成する

winget-pkgsのリポジトリをcloneしてきます。

PS> git clone git@github.com:microsoft/winget-pkgs.git

cloneしたwinget-pkgsにはTools以下のフォルダにYamlCreate.ps1という対話的にYamlのマニフェストを作成するPowerShellスクリプトが用意されています。
また、winget-pkgsをforkしてoriginとしてリモートリポジトリを登録しておきます。

実際にマニフェストを作成してみる

実際にtd-agent 4.0.1のマニフェストを作成したログは以下の通りです。

PS> .\Tools\YamlCreate.ps1
Enter the URL to the installer: http://packages.treasuredata.com/4/windows/td-agent-4.0.1-x64.msi


Downloading URL.  This will take awhile...
Url: http://packages.treasuredata.com/4/windows/td-agent-4.0.1-x64.msi
Sha256: E9BEBBDB2FF488583DD779505A714C44560CA16499EFDCC571E1A14E18BD383C


File downloaded. Please Fill out required fields.
Enter the package Id, in the following format <Publisher.Appname>
For example: Microsoft.Excel: TreasureData.TDAgent
Enter the publisher: Treasure Data Inc.
Enter the application name: Treasure Agent
Enter the version. For example: 1.0, 1.0.0.0: 4.0.1
Enter the License, For example: MIT, or Copyright (c) Microsoft Corporation: Apache Lincense 2.0
Enter the InstallerType. For example: exe, msi, msix, inno, nullsoft: msi
Enter the architecture (x86, x64, arm, arm64, Neutral): x64
[OPTIONAL] Enter the license URL: https://www.apache.org/licenses/LICENSE-2.0
[OPTIONAL] Enter the AppMoniker (friendly name). For example: vscode: td-agent
[OPTIONAL] Enter any tags that would be useful to discover this tool. For example: zip, c++: fluentd, logging
[OPTIONAL] Enter the Url to the homepage of the application: https://www.fluentd.org
[OPTIONAL] Enter a description of the application: A data collector for Treasure Data.


Id: TreasureData.TDAgent
Version: 4.0.1
Name: Treasure Agent
Publisher: Treasure Data Inc.
License: Apache Lincense 2.0
LicenseUrl: https://www.apache.org/licenses/LICENSE-2.0
AppMoniker: td-agent
Tags: fluentd, logging
Description: A data collector for Treasure Data.
Homepage: https://www.fluentd.org
Arch: x64
Url: http://packages.treasuredata.com/4/windows/td-agent-4.0.1-x64.msi
Sha256: E9BEBBDB2FF488583DD779505A714C44560CA16499EFDCC571E1A14E18BD383C
InstallerType: msi
Yaml file created:  C:\Users\cosmo\Documents\GitHub\winget-pkgs\manifests\TreasureData\TDAgent\4.0.1.yaml
Now place this file in the following location: \manifests\TreasureData\TDAgent

この一連の対話的な操作により、td-agent 4.0.1のマニフェストが~\GitHub\winget-pkgs\manifests\TreasureData\TDAgent\4.0.1.yamlに作成されました。

動作確認をしてみます。

PS> winget validate .\manifests\TreasureData\TDAgent\4.0.1.yaml
マニフェストの検証は成功しました。
PS> winget install -m .\manifests\TreasureData\TDAgent\4.0.1.yaml
Found Treasure Agent [TreasureData.TDAgent]
このアプリケーションは所有者からライセンス供与されます。
Microsoft はサードパーティのパッケージに対して責任を負わず、ライセンスも付与しません。
Downloading http://packages.treasuredata.com/4/windows/td-agent-4.0.1-x64.msi
  ██████████████████████████████  24.0 MB / 24.0 MB
インストーラーハッシュが正常に検証されました
パッケージのインストールを開始しています...

winget validate /path/to/manifest.ymlwinget install -m /path/to/manifest.ymlも検証できたようです。

変更をコミットします。

PS> git checkout -b td-agent-4.0.1
PS> git add manifests/TreasureData/TDAgent/4.0.1.yaml
PS> git commit -s
[td-agent-4.0.1 545f751] Add Treasure Agent 4.0.1
 1 file changed, 18 insertions(+)
 create mode 100644 manifests/TreasureData/TDAgent/4.0.1.yaml

forkしたリモートリポジトリにpushします。

PS> git push origin td-agent-4.0.1
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 837 bytes | 418.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
#...

winget-pkgsのリポジトリにマニフェストを登録する

forkしたリモートリポジトリに作成したマニフェストをpushしたらfork元のmicrosoft/winget-pkgsに対してPull Requestを作成します。

今回作成したマニフェストは microsoft/winget-pkgs#3153 として登録しました。

td-agentは現在署名をしていないパッケージなので、Microsoft Defender SmartScreenの警告に引っかかってしまうため少しマージまでに時間がかかりましたが、無事に取り込まれました。

microsoft/winget-pkgsにマニフェストが取り込まれてしばらくすると、マニフェスト情報が更新され、td-agent 4.0.1がwinget searchで引っかかるようになりました。

PS> winget search td-agent
名前             ID                   バージョン 一致
-----------------------------------------------------------
Treasure Agent TreasureData.TDAgent 4.0.1 Moniker: td-agent

インストールすることもできます。

PS> winget install td-agent
Found Treasure Agent [TreasureData.TDAgent]
このアプリケーションは所有者からライセンス供与されます。
Microsoft はサードパーティのパッケージに対して責任を負わず、ライセンスも付与しません。
Downloading http://packages.treasuredata.com/4/windows/td-agent-4.0.1-x64.msi
  ██████████████████████████████  24.0 MB / 24.0 MB
インストーラーハッシュが正常に検証されました
パッケージのインストールを開始しています...
インストールが完了しました 

まとめ

FluentdのWindows周りの作業はクリアコードが得意としている領域です。td-agentのインストールがより手軽にできるようになれば、結果的にWindowsでのFluentdやtd-agentのユーザーを増やす事ができます。
この取り組みはWindowsにおいてtd-agentをより簡易に導入できるようにする施策の一環として実施されました。

当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。

タグ: Fluentd
2020-08-25

Fluentd-kubernetes-daemonsetのElasticsearchイメージでILMを使う

はじめに

クリアコードはFluentdの開発に参加しています。

Fluentdにはプラグインというしくみがあり、たくさんのプラグインが開発されています。
Fluentdはログ収集ソフトウェアということからkubernetes(以下k8sと略記)にも載せることができます。
Fluentdの開発元が公式に出しているk8sでのログ収集の仕組みの一つとしてFluentdのDaemonSetを提供しています。

筆者畑ケはElasticsearchのILM対応を最近fluent-plugin-elasticsearchに入れました。*1
筆者が対応したILMをFluentdのDaemonSetでも有効化して動かすことができたので、報告します。

ILMを有効化していると、古くなったインデックスを定期的に消すというオペレーションをElasticsearch自体に任せることができ、Elasticsearchのクラスターの管理の手間を減らせます。

k8sのkind

まず、Fluentdのログ収集を解説する前に、k8sに少し触れておきます。
この記事ではminikube v1.11.0、Kubernetes v1.18.3を想定しています。
k8sではいくつかのリソースの管理方法があります。リソースはオブジェクト毎に名前が付けられており、yamlでオブジェクトの振る舞いを決定します。
ここで、単純なコンテナをk8sにデプロイするには、例えば以下のようにnginxのDeploymentを作成します。

apiVersion: apps/v1
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

これを

$ kubectl apply -f ngix-deployment.yaml
deployment.apps/nginx-deployment created

とする事で、nginxのコンテナがk8s上で動作し始めます。

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-6b474476c4-vhtmn   1/1     Running   0          34s
nginx-deployment-6b474476c4-xqr4f   1/1     Running   0          34s

動作確認が終わったらDeploymentを片付けておきましょう。

$ kubectl delete deployment nginx-deployment
deployment.apps "nginx-deployment" deleted

k8sのDaemonSetとは

k8sにはDaemonSetというkindがあり、これはクラスターを構成するNode上にDaemonSetが構成するPodを自動的に配置するために使用されるkindです。

DaemonSetのこの性質を用いる事で、各Node上のログを集めるPodを自動的に配置するFluentdのDaemonSetが作成できます。

FluentdのDaemonSet

FluentdのDaemonsetは公式では https://github.com/fluent/fluentd-kubernetes-daemonset にて開発がされています。
執筆時点では以下のイメージがDaemonSet用に提供されています。

  • debian-elasticsearch7
  • debian-elasticsearch6
  • debian-loggly
  • debian-logentries
  • debian-cloudwatch
  • debian-stackdriver
  • debian-s3
  • debian-syslog
  • debian-forward
  • debian-gcs
  • debian-graylog
  • debian-papertrail
  • debian-logzio
  • debian-kafka
  • debian-kinesis

この記事では、debian-elasticsearch7のdocker imageを参照しているfluentd-kubernetes-daemonsetの設定を元にして、ILMを有効化したロギング環境を構築します。

FluentdのElasticsearch7 Daemonset

Fluentdでログ収集をした後に、Elasticsearchを用いてログをストアするDaemonSetは例えば、 fluentd-daemonset-elasticsearch.yamlです。この設定ではRBACを使っていませんが、簡単のためこのDaemonSetをもとにして構成します。
また、この記事で使用するdebian-elasticsearchのimageのtagはfluent/fluentd-kubernetes-daemonset:v1.11.1-debian-elasticsearch7-1.3 または fluent/fluentd-kubernetes-daemonset:v1-debian-elasticsearchを用いています。
記事が公開されるタイミングではどちらのタグを使用しても大丈夫です。

元のDaemonSetの構成ではElasticsearchのテンプレート設定が入っていないため、テンプレートの設定をConfigMapで表現することにします。

apiVersion: v1
data:
  index_template.json: |-
    {
        "index_patterns": [
            "logstash-default*"
        ],
        "settings": {
            "index": {
                "number_of_replicas": "3"
            }
        }
    }
kind: ConfigMap
metadata:
  name: es-template
  namespace: kube-system
---

ConfigMapをk8sではpodからストレージボリュームとして参照することが出来ます。

      volumes:
      - name: es-template
        configMap:
          name: es-template

k8sのvolumeオブジェクトはConfigMapの名前を指定してボリュームとしてPodから認識させます。
このオブジェクトをマウントします。

        volumeMounts:
        - name: es-template
          mountPath: /host
          readOnly: true

fluent-plugin-elasticsearchのILMの設定を入れていきます。

          # ILM parameters
          # ==============
          - name: FLUENT_ELASTICSEARCH_ENABLE_ILM
            value: "true"
          - name: FLUENT_ELASTICSEARCH_ILM_POLICY
            value: '{ "policy": { "phases": { "hot": { "min_age": "0ms", "actions": { "rollover": { "max_age": "1d", "max_size": "5g
b" } } }, "delete": { "min_age": "2d", "actions": { "delete": {}}}}}}'
          - name: FLUENT_ELASTICSEARCH_TEMPLATE_FILE
            value: /host/index_template.json
          - name: FLUENT_ELASTICSEARCH_TEMPLATE_NAME
            value: "logstash-default"

上記の設定を入れればElasticsearchのILMの機能を有効化する為に必要な設定が入れられました。
変更の全体はこのコミットで見ることができます。

実際に適用してみる

適用する前に、以下の設定を実際のElasticsearchが動いているサーバーの値に書き換えておいてください。

          - name:  FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch-master.default.svc"
          - name:  FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
この記事で実際に使用しているElasticsearchとそのデプロイ方法の概要

この記事で使用しているElasticsearchはElastic社がメンテナンスしているhelm chartsの7.8.0タグを用いてminikubeで作成したk8sにデプロイしました。

k8sの外からElasticsearchのAPIを叩けるように9200ポートのポートフォワードを設定しておきます。

$ kubectl port-forward svc/elasticsearch-master 9200 
Forwarding from 127.0.0.1:9200 -> 9200
Forwarding from [::1]:9200 -> 9200
...

別のターミナルからElasticsearchの応答を確認します。

$ curl localhost:9200
{
  "name" : "elasticsearch-master-2",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "Vskz9aTjTZSCg8klQMz5mg",
  "version" : {
    "number" : "7.8.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "757314695644ea9a1dc2fecd26d1a43856725e65",
    "build_date" : "2020-06-14T19:35:50.234439Z",
    "build_snapshot" : false,
    "lucene_version" : "8.5.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Elasticsearch v7.8.0が動作していることが確認できました。

kubectlを用いて実際に適用

では、kubectlで実際に適用してみます。

$ kubectl apply -f fluentd-daemonset-elasticsearch.yaml
configmap/es-template created
daemonset.apps/fluentd created

Podが動作しているかを確認します。

$ kubectl get pods -n=kube-system
NAME                               READY   STATUS    RESTARTS   AGE
coredns-66bff467f8-pswng           1/1     Running   1          13d
etcd-minikube                      1/1     Running   0          6d1h
fluentd-hqh9n                      1/1     Running   0          7s
kube-apiserver-minikube            1/1     Running   0          6d1h
kube-controller-manager-minikube   1/1     Running   1          13d
kube-proxy-kqllr                   1/1     Running   1          13d
kube-scheduler-minikube            1/1     Running   1          13d
storage-provisioner                1/1     Running   3          13d

FluentdのDaemonSetが動作しているのを確認できました。
Fluentdが正常に動いているかをPodのログを見て確認してみます。

$ % kubectl logs fluentd-hqh9n -n=kube-system
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-dedot_filter' version '1.0.0'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-detect-exceptions' version '0.0.13'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-elasticsearch' version '4.1.1'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-grok-parser' version '2.6.1'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-json-in-json-2' version '1.0.2'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-kubernetes_metadata_filter' version '2.3.0'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-multi-format-parser' version '1.0.0'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-prometheus' version '1.6.1'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-record-modifier' version '2.0.1'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-rewrite-tag-filter' version '2.2.0'
2020-07-22 08:06:15 +0000 [info]: gem 'fluent-plugin-systemd' version '1.0.2'
2020-07-22 08:06:15 +0000 [info]: gem 'fluentd' version '1.11.1'
2020-07-22 08:06:16 +0000 [info]: using configuration file: <ROOT>
...
<snip>
...
2020-07-22 08:06:16 +0000 [info]: starting fluentd-1.11.1 pid=6 ruby="2.6.6"
2020-07-22 08:06:16 +0000 [info]: spawn command to main:  cmdline=["/usr/local/bin/ruby", "-Eascii-8bit:ascii-8bit", "/fluentd/vendor/bundle/ruby/2.6.0/bin/fluentd", "-c", "/fluentd/etc/fluent.conf", "-p", "/fluentd/plugins", "--gemfile", "/fluentd/Gemfile", "-r", "/fluentd/vendor/bundle/ruby/2.6.0/gems/fluent-plugin-elasticsearch-4.1.1/lib/fluent/plugin/elasticsearch_simple_sniffer.rb", "--under-supervisor"]
2020-07-22 08:06:16 +0000 [info]: adding match in @FLUENT_LOG pattern="fluent.**" type="null"
2020-07-22 08:06:16 +0000 [info]: adding filter pattern="kubernetes.**" type="kubernetes_metadata"
2020-07-22 08:06:16 +0000 [info]: adding match pattern="**" type="elasticsearch"
2020-07-22 08:06:17 +0000 [info]: adding source type="systemd"
2020-07-22 08:06:17 +0000 [info]: adding source type="systemd"
2020-07-22 08:06:17 +0000 [info]: adding source type="systemd"
2020-07-22 08:06:17 +0000 [info]: adding source type="prometheus"
2020-07-22 08:06:17 +0000 [info]: adding source type="prometheus_output_monitor"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: adding source type="tail"
2020-07-22 08:06:17 +0000 [info]: #0 starting fluentd worker pid=18 ppid=6 worker=0
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-scheduler-minikube_kube-system_kube-scheduler-986d31752d921b9cee830917de6372781bd418c4674e7c890ef2ccb082292f50.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/elasticsearch-master-1_default_configure-sysctl-f8b971b868b6536e084453d8890a7677640446a20b8dc6397ed7715ada823be5.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/fluentd-hqh9n_kube-system_fluentd-65a884bccf20d3134d96f836a3a1a9e1116bee9fd01a8298206b54baf9340f84.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/elasticsearch-master-1_default_elasticsearch-6393ceacc5dc158689f5f4c20a3b072c91badab6974469e2652882eadc8b0964.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/elasticsearch-master-0_default_elasticsearch-7a47537855db7869b905cff73434348b7244a3f2a0a2f31b7703fdff864d3838.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/storage-provisioner_kube-system_storage-provisioner-1a2e641e4dfa4470903315c22c369cd02f43b819a44911130cb775b771bf2f42.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-proxy-kqllr_kube-system_kube-proxy-c756169c6e4a9c348719703e49630442f48327552cbaf33a7a61bf6d60ffa3f8.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/elasticsearch-master-0_default_configure-sysctl-5fee5b507865277bf15f8f5412d72f977038b21e657fa39d51f73705edfe8b6b.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-scheduler-minikube_kube-system_kube-scheduler-fdec557596b5c9c38040b9a04753b3407fa51f3e07bd1fea441b8c72bcc33f6a.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-proxy-kqllr_kube-system_kube-proxy-68c7040be16381fa6e8179312482f36c521bb0588053c879138199e2c89deca5.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/etcd-minikube_kube-system_etcd-8ece6d2d408533810f2bc33e9aeeb534ea2781259c8046331b297c446ec24fe9.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-apiserver-minikube_kube-system_kube-apiserver-ffa76a89be8bf37a82e68a36ea165d4db957b2156c277131f592d7b1b9497279.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/coredns-66bff467f8-pswng_kube-system_coredns-3f5612e80916072da46574a47f2b782dce33a536c1fa62d95450d1673ff63105.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-controller-manager-minikube_kube-system_kube-controller-manager-015b2b7520797e279dd4783eeb68fa8b8a26db6ecc1d684f67bfdc13411791e3.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/kube-controller-manager-minikube_kube-system_kube-controller-manager-9a1b2e08d497b4ca2d144e42972b23fad15bcc1beb996a2b8e6fab0aee3999c0.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/elasticsearch-master-2_default_elasticsearch-c179b54e4fdddbb20068269b75cd88325658c98847d6d906d3a4a954860885af.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/coredns-66bff467f8-pswng_kube-system_coredns-3a7f59d8a9cec5ed207b061d89853cd19c3f2388a09a628c590d079c76af0323.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/storage-provisioner_kube-system_storage-provisioner-73c205f99d913ad3915df4004aeab5a03a549c7d00dbc47288bfec9cdedfdcf8.log
2020-07-22 08:06:17 +0000 [info]: #0 [in_tail_container_logs] following tail of /var/log/containers/elasticsearch-master-2_default_configure-sysctl-a4ffd4fa272b2eaaa4bb6daf8e2618ee109fdb9aba51ce9130226823f1be08a0.log
2020-07-22 08:06:17 +0000 [info]: #0 fluentd worker is now running worker=0

#0 fluentd worker is now running worker=0 となるので正常に動作しています。

動作させていくらか経ったElasticsearchのインデックスの状態を確認します。

$ curl localhost:9200/_cat/indices
green open logstash-default-2020.07.22-000004 1BNxJGy2R_u2ETFog8hI5g 1 3     0 0    624b   208b
green open logstash-default-2020.07.25-000002 yesiqdW4Qdi_PD5JEGwctw 1 3     0 0    624b   208b
green open logstash-default-2020.07.27-000002 y0-NGO_3SmmcRRnLNNy9cw 1 3     0 0    624b   208b
green open logstash-default-2020.07.22-000003 B8Z41XVvTpCIjrLewEutMA 1 3    61 0 153.5kb 51.1kb
green open logstash-default-2020.07.21-000001 5JzBvzkgSve-pJBOl-_AOA 1 3  3662 0   4.5mb  1.5mb
green open logstash-default-2020.07.22-000002 hsojlNScTOCaMDKCLlTEjQ 1 3     0 0    624b   208b
green open logstash-default-2020.07.25-000001 L9mPpFjrSYmex27-oUlqsQ 1 3 22798 0    17mb  5.6mb
green open logstash-default-2020.07.28-000001 kYvZ7R_fSVaXcIMBdP7OgA 1 3   132 0 518.8kb  185kb
green open logstash-default-2020.07.27-000001 CJTNpX4lQf6frlc5v1aHIg 1 3 62061 0  45.2mb 15.1mb
green open logstash-default-2020.07.25-000003 g_oSzQTRS42Pn5DZm8NSMA 1 3     0 0    624b   208b

logstash-default-2020.07.22-000001のインデックスは作成されてから2日以上経っているので消されていることがわかります。
インデックスがILMで管理されているかどうかを確認します。

$ curl localhost:9200/logstash-2020.07.28/_ilm/explain | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   550  100   550    0     0  36666      0 --:--:-- --:--:-- --:--:-- 36666
{
  "indices": {
    "logstash-default-2020.07.28-000001": {
      "index": "logstash-default-2020.07.28-000001",
      "managed": true,
      "policy": "logstash-policy",
      "lifecycle_date_millis": 1595898121210,
      "age": "4.37h",
      "phase": "hot",
      "phase_time_millis": 1595898121544,
      "action": "rollover",
      "action_time_millis": 1595898216494,
      "step": "check-rollover-ready",
      "step_time_millis": 1595898216494,
      "phase_execution": {
        "policy": "logstash-policy",
        "phase_definition": {
          "min_age": "0ms",
          "actions": {
            "rollover": {
              "max_size": "5gb",
              "max_age": "1d"
            }
          }
        },
        "version": 7,
        "modified_date_in_millis": 1595398857585
      }
    }
  }
}

"managed": true,とあるため、このインデックスはILMにより管理されています。
試しにElasticsearchに検索リクエストを飛ばしてみましょう。

$ curl -XGET -H "Content-Type: application/json" localhost:9200/logstash-2020.07.28/_search -d '{"size": 2, "sort": [{"@timestamp": "desc"}]}' | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2502  100  2457  100    45   5906    108 --:--:-- --:--:-- --:--:--  6014
{
  "took": 391,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 242,
      "relation": "eq"
    },
    "max_score": null,
    "hits": [
      {
        "_index": "logstash-default-2020.07.28-000001",
        "_type": "_doc",
        "_id": "OXzek3MBhNn_Y01M22q7",
        "_score": null,
        "_source": {
          "log": "2020-07-28 05:21:57 +0000 [info]: #0 [filter_kube_metadata] stats - namespace_cache_size: 2, pod_cache_size: 2, namespace_cache_api_updates: 4, pod_cache_api_updates: 4, id_cache_miss: 4\n",
          "stream": "stdout",
          "docker": {
            "container_id": "65a884bccf20d3134d96f836a3a1a9e1116bee9fd01a8298206b54baf9340f84"
          },
          "kubernetes": {
            "container_name": "fluentd",
            "namespace_name": "kube-system",
            "pod_name": "fluentd-hqh9n",
            "container_image": "fluent/fluentd-kubernetes-daemonset:v1.11.1-debian-elasticsearch7-1.3",
            "container_image_id": "docker-pullable://fluent/fluentd-kubernetes-daemonset@sha256:af33317d3b8723f71843b16d1721a3764751b1f57a0fe4242a99d1730de980b0",
            "pod_id": "fdfd5807-8164-4907-a1a9-9b782c3eb97e",
            "host": "minikube",
            "labels": {
              "controller-revision-hash": "57997fd64d",
              "k8s-app": "fluentd-logging",
              "pod-template-generation": "1",
              "version": "v1"
            },
            "master_url": "https://10.96.0.1:443/api",
            "namespace_id": "5c1df0cc-d72b-4c24-a4af-a8d595d62713"
          },
          "@timestamp": "2020-07-28T05:21:57.002062084+00:00",
          "tag": "kubernetes.var.log.containers.fluentd-hqh9n_kube-system_fluentd-65a884bccf20d3134d96f836a3a1a9e1116bee9fd01a8298206b54baf9340f84.log"
        },
        "sort": [
          1595913717002
        ]
      },
      {
        "_index": "logstash-default-2020.07.28-000001",
        "_type": "_doc",
        "_id": "gWLek3MByRVHYKQQ2yC5",
        "_score": null,
        "_source": {
          "log": "2020-07-28 05:21:56.801939 I | mvcc: finished scheduled compaction at 59275 (took 3.825907ms)\n",
          "stream": "stderr",
          "docker": {
            "container_id": "8ece6d2d408533810f2bc33e9aeeb534ea2781259c8046331b297c446ec24fe9"
          },
          "kubernetes": {
            "container_name": "etcd",
            "namespace_name": "kube-system",
            "pod_name": "etcd-minikube",
            "container_image": "k8s.gcr.io/etcd:3.4.3-0",
            "container_image_id": "docker-pullable://k8s.gcr.io/etcd@sha256:4afb99b4690b418ffc2ceb67e1a17376457e441c1f09ab55447f0aaf992fa646",
            "pod_id": "27093604-c8b7-4b22-a0df-c7eebe63afb3",
            "host": "minikube",
            "labels": {
              "component": "etcd",
              "tier": "control-plane"
            },
            "master_url": "https://10.96.0.1:443/api",
            "namespace_id": "5c1df0cc-d72b-4c24-a4af-a8d595d62713"
          },
          "@timestamp": "2020-07-28T05:21:56.802153342+00:00",
          "tag": "kubernetes.var.log.containers.etcd-minikube_kube-system_etcd-8ece6d2d408533810f2bc33e9aeeb534ea2781259c8046331b297c446ec24fe9.log"
        },
        "sort": [
          1595913716802
        ]
      }
    ]
  }
}

確かにElasticsearchにk8s内部で発生したログがストアされていることが確認出来ました。

まとめ

FluentdのDaemonSetにより、k8s内部のログをILMを有効化してElasticsearchにストアするやり方を解説しました。
この記事の方法でElasticsearchのILMを有効化する場合、helmを使っているのであればElasticvsearchをデプロイする際に注意点があります。helmの公式リポジトリでデプロイできるElasticsearchは古く、
Elasticsearchの開発元のものを使ってhelmでElasticsearchのクラスターをデプロイしてください。

また、記事中で使用しているILMのポリシーは2日経ったらインデックスを消去するという単純なものですが、実際のプロダクション環境ではhotのあとにwarmやcold状態を挟んでdeleteに移行するポリシーを作成するよう検討してください。

当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。

*1 fluent-plugin-elasticsearch v4.1.1でILM関連のバグは直しました。

タグ: Fluentd
2020-07-29

Fluentdのベンチマークツールの開発

はじめに

クリアコードはFluentdの開発に参加しています。

Fluentdにはプラグインというしくみがあり、たくさんのプラグインが開発されています。
Fluentdのプラグインでは各種APIを使用しており、プラグインによって消費するリソースの傾向が異なるということがあります。
今回そのリソースの傾向はどの程度なのかを知るために筆者畑ケがベンチマークツールを開発し、傾向を測定しました。

Windows EventLogを扱うプラグイン

Windows EventLogを引っ張ってくるプラグインは https://github.com/fluent/fluent-plugin-windows-eventlog にて開発されています。Fluentdの開発チームはwin32-eventlog gemでは対処しきれないEventLogの形式があることから、winevt_c gemを開発しています。
このgemは基本的にCで書かれているため、大きなボトルネックになることはありませんが、リソースの消費傾向を把握するのは重要と考えています。

Fluentdのベンチマークの考え方

FluentdのInputプラグインやOutputプラグインは基本的にある間隔で動作します。また、Outputプラグインはすぐに送ることはせずにbufferingをします。このことから、Fluentdが消費しているリソースを時間ごとにモニタリングした生データを単にプロットするだけではどの程度のリソース消費をするかが判りにくくなります。
リソースの消費傾向は中央値(メジアン)・25パーセンタイル〜75パーセンタイルの範囲、そして99パーセンタイル程度の測定値がどの程度の範囲内にあるかを図示した方がリソースの消費傾向が分かりやすくなります。

箱ヒゲ図(Box Plot)とは

箱ヒゲ図とは、データのばらつきを分かりやすく表現するためのグラフの一種です。 例として以下の図にあるような箱ヒゲ図を見てみます。

(https://towardsdatascience.com/understanding-boxplots-5e2df7bcbd51 より引用)

この箱ヒゲ図では、真ん中の箱の範囲に25パーセンタイルから75パーセンタイルの中位50パーセンタイルの数値が入ります。
また、下ヒゲから箱の下までが下位24.65パーセンタイル、上ヒゲから箱の上までが上位24.65パーセンタイルの数値が入ります。下ヒゲから上ヒゲまでが99.3パーセンタイルの数値が入ります。時折生じるリソース消費のスパイク現象を除いたリソースの消費量を見るには、下ヒゲから上ヒゲの99.3パーセンタイルの範囲の数値をみると良いことになります。

この箱ヒゲ図を正規分布に対応させると以下のようになります。

(https://towardsdatascience.com/understanding-boxplots-5e2df7bcbd51 より引用)

ただし、この箱ヒゲ図には重要な仮定があります。値の分布が正規分布*1 に従っている*2という条件があります。

ベンチマーク環境の作成

こちらはTerraformを用いてAzureにベンチマーク環境を整えることで実施しました。
また、ベンチマーク後のグラフの描画にはmatplotlibを基にしたseabornを用いています。

Windows EventLogのベンチマークを実施する
ベンチマークの準備

まず、Python3の環境をセットアップします。ベンチマーク環境を整備するにはpython3のインストールが必要です。
ここでは、ホスト環境がUbuntuであると仮定します。

$ sudo apt install python3 python3-venv build-essentials

ベンチマーク環境をセットアップするスクリプトをgit cloneします。

$ git clone https://github.com/fluent-plugins-nursery/fluentd-benchmark-azure-environment.git
$ cd fluentd-benchmark-azure-environment

venvを使ってシステムのPython3環境と分離します。

$ python3 -m venv management
$ source management/bin/activate

requirements.txtを使って必要なPython3のライブラリをインストールします。

$ pip3 install -r requrirements.txt

Ubuntuで実行可能なTerraformを https://www.terraform.io/downloads.html からダウンロードして来てインストールします。
この記事ではWindows EventLogのベンチマークの実行を例にするため、winevtlog_benchディレクトリにcdします。

$ cd winevtlog_bench

Terraformを初期化して、必要なProviderをダウンロードします。

$ terraform init

terraform.tfvars.sampleをコピーします。

$ cp terraform.tfvars.sample terraform.tfvars

以下の変数の値を実際に使用するものに書き換えます。

linux-username       = "admin"
linux-password       = "changeme!"
region               = "Japan East"
windows-username     = "admin"
windows-password     = "changeme"
ssh-private-key-path = "/path/to/private_key"
resource-group       = "ExampleGroup"

ssh-keygenを用いてid_rsa_azureというRSA 2048ビットの秘密鍵と公開鍵を生成します。id_rsa_azure.pubを、azure_keyというディレクトリに格納します。

Azureの認証情報は Terraformの導入 - 検証環境をコマンドで立ち上げられるようにする その1 を参考に取得します。
env.shをコピーし、

$ cp env.sh.sample env.sh
#!/bin/sh

echo "Setting environment variables for Terraform"
export ARM_SUBSCRIPTION_ID=<SUBSCRIPTION_ID>
export ARM_CLIENT_ID=<APP_ID>
export ARM_CLIENT_SECRET=<APP_PASSWORD>
export ARM_TENANT_ID=<TENANT_ID>

スクリプト中の角かっこの値を埋めて、env.shを読み込みます。

$ source env.sh

また、AzureのCLIでのログインは事前に行っておいてください。

ここまででTerraformを使ったベンチマーク用のAzureインスタンスを建てる準備が整いました。

ベンチマークの実施

ベンチマークの実施方法はMakefileとAnsible Playbookに集約されているので順番に実行していけば良いです。

$ make apply

により、Azure上にベンチマーク用のインスタンスが建ちます。

$ make provision

により、ベンチマークに必要なライブラリやツールが建てたAzureのインスタンスにダウンロードされ、インストールされます。

Windows EventLogの単純なベンチマークは、

$ make windows-bench

により実施されます。このコマンドを実行すると、裏ではAnsible Playbook化されたタスクが走ります。
このタスクはWindows上で採取されたデータを収集する部分まで含まれます。

ベンチマーク結果の可視化には次のコマンドも実行します。

$ make visualize

このコマンドにより、ベンチマーク結果を箱ヒゲ図で可視化できます。
ベンチマークが終わった後は、ベンチマークに使ったAzureインスタンスを破棄してしまいましょう。

$ make clean

ベンチマーク結果の一例

CPUの消費傾向を箱ヒゲ図で見てみます。
また、記事中では解説していませんが、外れ値があるかどうかもチェックしたいため、strip plotで実際の値も箱ヒゲ図に重ねてプロットしています。小数点以下第3位で四捨五入したメジアンの値ラベルについても、箱ヒゲ図に重ねてプロットしています。
およそ12分で120000イベントのWindows EventLogをin_windows_eventlog2で受け取った場合のFluentdのワーカーのCPU使用率です。

おおよそ1分間に159イベント程度を決められたチャンネルに書き込む流量があります。
このベンチマークでは、イベントの流量とサイズが大きくないため、ワーカープロセスのCPU使用率には差が出ていません。

ワーカープロセスのメモリ使用量はどうでしょうか。

こちらは、受け取ったメッセージサイズに多少影響を受けるところが見て取れます。

まとめ

Fluentdのプラグインのベンチマークの方法を解説してみました。
Windows向けに開発したプラグインでは、Linux向けとは違うリソースを消費する傾向になってしまう事があります。
Windows EventLogを扱う際にはWindowsが提供するAPI経由となるため、Cで書いている箇所に関しては大幅なボトルネックとなってしまう箇所が少ない事が確認できました。
また、ある程度の流量にも耐えられうる状態で提供できていることも確認できました。

*1 https://www.mathsisfun.com/data/standard-normal-distribution.html

*2 実際には正規分布というよりもリソースは有限なので下位側に潰れた分布になりますが、議論を簡単にするためこの仮定を置いて問題はないでしょう。

タグ: Fluentd
2020-07-07

FluentdのInputプラグインでProtocol Buffersを扱う

はじめに

Fluentdの周辺のIssueチケットを見ていたらProtocol BuffersをFluentdのInputプラグインで扱えると面白そう*1ということで、対応を考えてみた畑ケです。

クリアコードはFluentdの開発に参加しています。

Fluentdにはプラグインというしくみがあり、たくさんのプラグインが開発されています。
Fluentdのプラグインには、Parserプラグインという種類のプラグインがあります。
この種類のプラグインはInputプラグインが受け取ったテキストやバイナリーデータをFluentdで扱いやすくするために使用されます。

Protocol Buffersとは

Protocol Buffersとは、プログラミング言語間の差異を吸収してデータのやり取りを行うデータ形式です。
データ構造を定義して、それをいくつかの言語のProtocol Buffersを扱える表現にコンパイルすることでProtocol Buffersの形式にシリアライズしたり、デシリアライズできます。

例えば、Goで書いたプログラムでProtocol Buffers形式でシリアライズされたデータをC++で書いたプログラムからProtocol Buffersのデータ定義を用いて元のデータを復元できます。

FluentdにProtocol Buffersを組み込む

Fluentdにはin_tcpin_httpのような<parse>ディレクティブを処理できるInputプラグインがあります。InputプラグインはParserプラグインとしてテキストやバイナリーのパースの処理をプラグインとして持ってこれるものがあります。この任意のパース処理を差し替えることができるようにする仕組みがParserプラグインです。

まとめると、FluentdでProtocol Buffersを処理するのに最適な場所はParserです。

そこで、InputプラグインでProtocol Buffersでシリアライズされたバイナリデータを処理できるようにProtocol Buffersを処理するParserプラグインを作成しました。

例えば、このParserプラグインを使ったin_httpプラグインを使ったHTTPエンドポイントを立てると、
FluentdがProtocol BuffersのAPIのHTTPエンドポイントとして振る舞うことができ、色んなシステムと直接つなげることができます。

fluent-plugin-parser-protobufの使い方

fluent-plugin-parser-protobufはProtocol Buffersのコンパイラーが必要です。この記事ではProtocol Buffers v3の場合について解説します。

Protocol Buffers v3のコンパイラーや各言語ごとのライブラリーは https://github.com/protocolbuffers/protobuf/releases からダウンロードできます。
Protocol Buffers v3をFluentdで使う手順では、Protocol Buffersのコンパイラーであるprotocがインストール済みであると仮定して解説をします。

Protocol BuffersのIDLの文法はProtocol Buffersの公式ドキュメントの概要を参照してください。

例えば、下記のようなprotobufのIDLを作成します。

simple.proto
syntax = "proto3";
import "google/protobuf/timestamp.proto";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
  google.protobuf.Timestamp timestamp = 5;
}

これを、protocでコンパイルします。

$ protoc --proto_path=/path/to/idl --ruby_out=/path/to/output simple.proto

すると、下記のRubyのクラスが生成されます。

simple_pb.rb
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: simple.proto

require 'google/protobuf'

require 'google/protobuf/timestamp_pb'
Google::Protobuf::DescriptorPool.generated_pool.build do
  add_file("simple.proto", :syntax => :proto3) do
    add_message "SearchRequest" do
      optional :query, :string, 1
      optional :page_number, :int32, 2
      optional :result_per_page, :int32, 3
      optional :corpus, :enum, 4, "SearchRequest.Corpus"
      optional :timestamp, :message, 5, "google.protobuf.Timestamp"
    end
    add_enum "SearchRequest.Corpus" do
      value :UNIVERSAL, 0
      value :WEB, 1
      value :IMAGES, 2
      value :LOCAL, 3
      value :NEWS, 4
      value :PRODUCTS, 5
      value :VIDEO, 6
    end
  end
end

SearchRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("SearchRequest").msgclass
SearchRequest::Corpus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("SearchRequest.Corpus").enummodule

このprotobufクラスがあれば、fluent-plugin-parser-protobufにProtocol Buffersの定義を読み込ませることができます。
<parse>ディレクティブの中にprotobufパーサーに関連する設定を書きます。

fluent.conf
<source>
  @type http
  port 8080
  <parse>
    @type protobuf
    class_name SearchRequest
    class_file "#{File.expand_path(File.join('path', 'to', 'simple_pb.rb'))}"
    protobuf_version protobuf3
  </parse>
</source>
<match protobuf>
  @type stdout
</match>

疎通確認テストとしてHTTPでProtocol Buffersでシリアライズしたリクエストを送るようにします。
そのため、以下のRubyスクリプトを用意します。

test_http.rb
require 'google/protobuf'
require "net/http"

require_relative "path/to/simple_pb"

def encoded_simple_binary
  request = SearchRequest.new(query: "q=Fluentd",
                              page_number: 404,
                              result_per_page: 10,
                              corpus: :WEB,
                              timestamp: Time.now)
  SearchRequest.encode(request)
end

uri = URI.parse("http://localhost:8080/protobuf")
params = encoded_simple_binary
req = Net::HTTP.new(uri.host, uri.port)
req.post(uri.path, params.to_s)
実行結果

Fluentdとテストスクリプトはそれぞれ、別の端末で実行します。

$ bundle exec ruby test_http.rb
$ bundle exec fluentd -c fluent.conf -p lib/fluent/plugin
<snip>
2020-06-01 16:45:43 +0900 [info]: #0 fluentd worker is now running worker=0
2020-06-01 16:45:46.377515000 +0900 protobuf: {"query":"q=Fluentd","page_number":404,"result_per_page":10,"corpus":"WEB","timestamp":{"seconds":1590997546,"nanos":371940000}}

最後の2020-06-01 16:45:46.377515000 +0900 protobuf: {"query":"q=Fluentd","page_number":404,"result_per_page":10,"corpus":"WEB","timestamp":{"seconds":1590997546,"nanos":371940000}} により、Protocol BuffersでシリアライズされていたHTTPリクエストbodyがfluent-plugin-parser-protobufによりパースされ、Hashオブジェクトに分解されているのが確認できます。

まとめ

FluentdでProtocol Buffersを扱うにはどのようにしたら良いのかの方針を立て、実際にProtocol Buffersをパースするための手順を解説しました。
記事で解説した方法でFluentdがProtocol BuffersのAPIのHTTPエンドポイントとして振る舞うことができればより色んなシステムと直接つなげることができます。
今回のParserプラグインでは取り込むだけですが、FluentdのProtocol BuffersのFormatterプラグインを作成するとProtocol Buffersを用いてデータのやり取りを行うシステムに直接データを送ることができるようになるでしょう。

当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。

*1 https://github.com/fluent/fluentd/issues/3000

タグ: Fluentd
2020-06-04

FluentdとそのプラグインのWindowsでの開発環境

はじめに

クリアコードはFluentdの開発に参加しています。

また、Fluentdにはプラグインというしくみがあり、たくさんのプラグインが開発されています。
FluentdはWindowsでも動くようになっています。FluentdやそのプラグインはmacOSやLinux上で開発するのが一般的であり、Windowsでプラグインを開発するときの開発環境についての記事を見かけないため、筆者畑ケの環境をまとめてみます。

Fluentdやそのプラグインの開発環境

筆者はFluentdやそのプラグインの開発をするときにWindows環境を使う時があります。
Windows 10 Pro 1909環境で、少しRubyのバージョンが古めですが、2.5.7を使用しています。
このRubyはRubyInstaller2のプロジェクトが公開しているインストーラーです。

シェル環境は非Windows環境と統一していません
ターミナルエミュレータにはWindows Terminal (Preview)を使用しています。
Windows Terminal (Preview)はv0.4.2382.0辺りのころから使用しています。
そこで動かすシェルはPowerShellを使用しています。執筆時点ではPowerShell 6.2.4を動かしています。
PowerShellを使っている理由は筆者畑ケが.NET環境の言語やライブラリに馴染みがあるからです。
RubyInstaller2からはMSYS2というWindows環境でgccを用いた開発環境を作成できるツールチェインがあるため、これをRubyの拡張ライブラリのビルドに使用しています。

GitはWindows用のインストーラーをGit for Windowsが用意しているのでこれを使用します。

PowerShellにはposh-gitを入れてgitのbranchの様子などを表示させています。

また、Docker Desktop for Windowsを使用し、Dockerコンテナを動かす必要があるときはこのパッケージ由来のdockerコマンドを使っています。

Docker Desktop for WindowsのdockerコマンドはWindows環境ではコマンドプロンプトやPowerShellでの動作が想定されており、それ以外のシェルでの動作が想定されていないようです。

エディタはGNU EmacsまたはVisual Studio Codeを使用しています。
エディタに関しては普段使っている環境との乖離を少なくするため、非Windows環境と共通のGNU Emacsを使っていることが多いです。

まとめ

FluentdやそのプラグインのWindows開発環境を解説してみました。
WindowsではPowerShell環境が.NETと親和性が高く、スクリプトとしても痒いところに手が届くようになっています。
そのため、筆者はMSYS2付属のbashやコマンドプロンプトを使わずにPowerShellをWindowsでは普段使いのシェル環境としています。

タグ: Fluentd
2020-02-14

Prometheus Meetup Tokyo #3でLTをしました

はじめに

Prometheus Meetup Tokyo #3でLTをしてきた畑ケです。
Prometheusは近年注目されるメトリクスモニタリングツールです。特徴としては非常にスケーラビリティが高く、Pull型でメトリクスを収集するため、Prometheusのインスタンス1つで1万インスタンスのサーバー群を監視できます。このPrometheus Meetup Tokyo #3ではPrometheus本体というよりもそれを取り巻くエコシステムを対象としたミートアップでした。

筆者は最近Prometheusのエコシステムの一つでGrafana Labが出しているLokiというログ基盤を触ったことがあり、触った時の成果の一つがLTのネタになるのではないかとのことでLTに応募してみたところ、LTに採択されました。

イベントの内容

まず他の方のされた発表をご紹介します。

Remote Write API と Thanos を活用したメトリクス永続化
  • Moto Ishizawa 氏(@summerwind), Z Lab Corporation

コンテナを基盤とする環境でPrometheusを運用していると、例えば、k8sのクラスターを作り直したときにPrometheusで収集したメトリクスが消失してしまう問題があるそうです。
このメトリクスがk8sクラスターに紐づいてしまっている問題を解消するのに永続化ストレージを採用し、永続化を試みていました。ThanosというPrometheusを高可用性にし、長期間のログ保存を可能にするソフトウェアを用いて方法をPrometheusで収集したメトリクスを永続化する方法を丹念に調査していた発表でした。

Victoria Metricsで作りあげる大規模・超負荷システムモニタリング基盤
  • 入江 順也 (GitHub: inletorder)氏, 株式会社コロプラ

Victoria MetricsというこちらもPrometheusで収集したメトリクスを長期間永続化するソフトウェアを使って、Prometheusを高負荷環境にも耐えられえるようにした試行錯誤を発表されていました。このソフトウェアにたどり着くまでにいくつかのPrometheus関連のメトリクス永続化ストレージを試されたそうです。Victoria Metricsはいくつかのコンポーネント(VMStorage, VMSelect, VMInsert)に分かれており、そのうちVMStorageは持つデータによって状態を持つのでk8sではStatefulSetとしてデプロイする必要があるそうです。k8sのマルチテナント構成では合計1万Pod以上の監視を安定的に行えるようになったそうです。

次世代のログ基盤 Grafana Lokiを始めよう!
  • 仲亀 拓馬氏(@kameneko1004, さくらインターネット 株式会社), 上村 真也氏(@uesyn, Z Lab Corporation)

本ククログで何回か筆者が開発者視点で取り上げているGrafana Lokiについての発表です。Lokiについてのデモを通じてどのようなソフトウェアなのかを解説するのが前半の発表でした。後半はpromtail特集でした。promtailはpromtail.yamlにてPrometheusの設定と同様の設定を流し込むことで設定できるそうです。Prometheusと同様にGrafana Lokiも時系列データを保持するのにTSDBを使用しており、これに入れたデータをクエリするのにラベルが必要になるのですが、このラベル設計がうまくないと後に目的の時系列データをクエリするのに苦労するようです。

LTの内容

以下は筆者が行ったLTの内容です。

Grafana Lokiの開発元にFluent BitのGo製のLokiプラグインをフィードバックしてみた話をしました。
このFluent BitのGo製LokiプラグインはFluent BitからGrafana Lokiに転送する方法をまとめたときに紹介しました。
Grafana Lokiは開発が活発なソフトウェアということもあり、ドキュメントがあまり見当たりませんでした。
そのため、promtailがやっていることをソースコードを見つつFluent BitのGo製Lokiプラグインとして仕上げました。

LTではフリーソフトウェアにフィードバックする作法を軽く触れました。
Grafana Lokiもフリーソフトウェアです。フリーソフトウェアの開発は通常開発者同士が同時に同じ場所に集まっているのではなく、住んでいる国や文化、ひいては暮らしているタイムゾーンも異なる場合があります。
フィードバックするには言葉で説明しなければいけません。

まずは、動機や今困っていることを説明します。

  • なぜこの機能が必要か?なぜこの問題を報告したのか?
  • このIssueチケットやプルリクエストでは何を問題にするのか?

プルリクエストは小粒なtypo修正ではない限り、Issueチケットに関連付けられるものとして出す方が良いと筆者は考えています。しかし、この方針はプロジェクトによって異なります。プルリクエストを出す時はプロジェクトの方針を確認してみてください。

  • プルリクエストでは方針を議論するよりも提出したパッチが前もって議論した方針に合っているか、このプロジェクトに受け入れられる品質となっているか?を議論する場だからです。

プルリクエストやIssueチケットにチェックリストが付いているのであれば一通り確認すべきです。

  • 問題が発生している報告者の環境を開発者が再現するには十分な情報が書き込まれている必要があります。
  • よいIssueチケットは開発者が見た時にどのようにすればこの問題が再現できるか?がチケットを見ただけで理解できるチケットです。
  • レビュアーが見るべき箇所が発散しておらず、実現したい機能が実現できているか、パッチの変更は妥当かのレビューに集中できるものがよいプルリクエストです。

Grafana LokiにFluent BitのGo製プラグインをフィードバックしてみたところ、ユーザーもそこそこ出てきたようです。

Grafana Loki自体は一個のバイナリですし、promtailもバイナリ一個で済みます。そのため、Dockerコンテナに載せやすいです。
これらの特徴に加え、Grafana LokiはPrometheusファミリーということもありk8sと非常に親和性がよいです。

k8sに載せるにはまずDockerコンテナ化しないといけないということで、Dockerコンテナ化の要望が新たにIssueチケットとして切られました。
このFluent BitのGo製のLokiプラグインDockerイメージもGrafana Lokiの公式イメージとして提供されることになりました。

k8sでは複数のサービスを連携して動作させる必要がありますが、手動で連携させるには面倒な場合があります。
この煩雑さを解決するソフトウェアはいくつか出ています。Grafana Lokiの開発元からはこの煩雑さを解決するソフトウェアのhelmを用いたFluent BitのGo製Lokiプラグインのレシピが提供されることになりました。

筆者はこのhelmのレシピがきっかけでk8sをより深く理解することになりました。自身の成果をフリーソフトウェアにフィードバックするだけで終わりではなく、フィードバックすることにより学びのきっかけを頂けました。

まとめ

日本ではあまり事例の少ないFluent BitとGrafana Lokiを題材にしてフリーソフトウェアの開発元にフィードバックする作法をLTしました。
フリーソフトウェアの問題を手元で回避するのではなく、開発元にフィードバックするのはクリアコードが普段実践している開発スタイルです。

フリーソフトウェアを普段使っている方でもフリーソフトウェアの開発元にフィードバックする方法が分からず、手元で問題を回避していたり、手元でパッチを持ったままになっている方もいると思います。
その時には本記事のフリーソフトウェアであるGrafana Lokiの開発元へフィードバックした事例をヒントにしてフィードバックに挑戦してみてはいかがでしょうか?

また、フリーソフトウェアの開発にまだ参加したことがない人を対象にしてOSS Gateワークショップを開催しています。こちらも併せて検討してみてください。

タグ: Fluentd
2020-01-17

「Fluentd」のサポートサービスを開始しました。

クリアコードでは2015年からFluentdの開発コミュニティに参加し、Fluentd本体とプラグインの開発、サポート、各種ドキュメントの整備を行っています。
2016年以降、既存の取引先様に対してFluentdのサポートを提供していましが、この度満を持してサポートサービスの開始を宣言しました。
サービスの概要はこちらです。
先日プレスリリースも出しましたので、併せてご参照ください

Fluentdに関するお問い合わせはこちらまで。

タグ: Fluentd
2019-11-20

Fluent BitからGrafana Lokiに転送するには

はじめに

Fluent BitはFluentdファミリーを構成するソフトウェアの一つです。
Fluent BitはGo Pluginプロキシが提供されており、Golangにて共有ライブラリを作成することにより、プラグインとして振る舞わせることのできるインターフェースが提供されています。
この機能については、fluent-bit-go-s3とfluent-bitのGo Pluginプロキシの話でも解説しました。
Fluent BitのGolang製のプラグインのDockerfileを作った話にて突然fluent-bit-go-lokiプラグインが登場してしまっていたので、そのプラグインについての解説を書きます。

Grafana Lokiとは

Lokiとは、新しく開発されたGrafanaのデータソースです。
Lokiにはログを入力するためのAPIが整備されています。

Lokiにレコードを送信するには

ログをPushするのであればPOST /api/prom/pushがAPIのエンドポイントになります。

このAPIのエンドポイントにはJSONまたはProtocol BufferでログをPushできます。
JSON形式でログをLokiに送るにはlabelsを用意するのが少々面倒だったため、fluent-bit-go-lokiではProtocol Bufferでやり取りを行うLokiのクライアントライブラリを使用することにしました。

これをGolangのコードで表現すると次のようになります。

package main

import "github.com/grafana/loki/pkg/promtail/client"
import "github.com/sirupsen/logrus"
import kit "github.com/go-kit/kit/log/logrus"
import "github.com/cortexproject/cortex/pkg/util/flagext"
import "github.com/prometheus/common/model"

import "fmt"
import "time"

func main() {
	cfg := client.Config{}
	// Init everything with default values.
	flagext.RegisterFlags(&cfg)
	var clientURL flagext.URLValue

	url := "http://localhost:3100/api/prom/push"
	// Override some of those defaults
	err := clientURL.Set(url)
	if err != nil {
		fmt.Println("Failed to parse client URL")
		return
	}
	cfg.URL = clientURL
	cfg.BatchWait = 1
	cfg.BatchSize = 10 * 1024

	log := logrus.New()

	loki, err := client.New(cfg, kit.NewLogrusLogger(log))

	line := `{"message": "Sent from Golang!"}`

	labelValue := "from-golang"
	labelSet := model.LabelSet{"lang": model.LabelValue(labelValue)}
	err = loki.Handle(labelSet, time.Now(), line)
	if err != nil {
		fmt.Println("Failed to send Loki")
	} else {
		fmt.Println("Success")
	}
	// Ensure to send record into Loki.
	time.Sleep(3 * time.Second)
}

このLoki向けのクライアントライブラリはバッチ単位で送るため、Handleを呼び出してもすぐにはLokiのAPIエンドポイントには送られないことに注意してください。

Fluent BitのGolang製のプラグインでLokiへイベントを送る

前節でLokiへアクセスするためのGolangのクライアントライブラリの使い方が分かったので、実際にfluent-bit-go-lokiへ組み込んでみます。
FLBPluginInitでLokiにアクセスするための設定を組み立て、FLBPluginFlushでLokiに一行づつイベントを送信するためのバッファに溜めています。
また、Fluent Bitのレコードの情報を余さずLokiに送信するためにJSONへエンコードし直しています。

package main

import "github.com/fluent/fluent-bit-go/output"
import "github.com/grafana/loki/pkg/promtail/client"
import "github.com/sirupsen/logrus"
import kit "github.com/go-kit/kit/log/logrus"
import "github.com/prometheus/common/model"
import "github.com/cortexproject/cortex/pkg/util/flagext"
import "github.com/json-iterator/go"

import (
	"C"
	"fmt"
	"log"
	"time"
	"unsafe"
)

var loki *client.Client
var ls model.LabelSet

//export FLBPluginRegister
func FLBPluginRegister(ctx unsafe.Pointer) int {
	return output.FLBPluginRegister(ctx, "loki", "Loki GO!")
}

//export FLBPluginInit
// (fluentbit will call this)
// ctx (context) pointer to fluentbit context (state/ c code)
func FLBPluginInit(ctx unsafe.Pointer) int {
	// Example to retrieve an optional configuration parameter
	url := output.FLBPluginConfigKey(ctx, "url")
	var clientURL flagext.URLValue
	err := clientURL.Set(url)
	if err != nil {
		log.Fatalf("Failed to parse client URL")
	}
	fmt.Printf("[flb-go] plugin URL parameter = '%s'\n", url)

	cfg := client.Config{}
	// Init everything with default values.
	flagext.RegisterFlags(&cfg)

	// Override some of those defaults
	cfg.URL = clientURL
	cfg.BatchWait = 10 * time.Millisecond
	cfg.BatchSize = 10 * 1024

	log := logrus.New()

	loki, err = client.New(cfg, kit.NewLogrusLogger(log))
	if err != nil {
		log.Fatalf("client.New: %s\n", err)
	}
	ls = model.LabelSet{"job": "fluent-bit"}

	return output.FLB_OK
}

//export FLBPluginFlush
func FLBPluginFlush(data unsafe.Pointer, length C.int, tag *C.char) int {
	var ret int
	var ts interface{}
	var record map[interface{}]interface{}

	dec := output.NewDecoder(data, int(length))

	for {
		ret, ts, record = output.GetRecord(dec)
		if ret != 0 {
			break
		}

		// Get timestamp
		timestamp := ts.(output.FLBTime).Time

		js, err := createJSON(timestamp, record)
		if err != nil {
			fmt.Errorf("error creating message for Grafana Loki: %v", err)
			continue
		}

		err = loki.Handle(ls, timestamp, string(js))
		if err != nil {
			fmt.Errorf("error sending message for Grafana Loki: %v", err)
			return output.FLB_RETRY
		}
	}

	// Return options:
	//
	// output.FLB_OK    = data have been processed.
	// output.FLB_ERROR = unrecoverable error, do not try this again.
	// output.FLB_RETRY = retry to flush later.
	return output.FLB_OK
}

func createJSON(timestamp time.Time, record map[interface{}]interface{}) (string, error) {
	m := make(map[string]interface{})

	for k, v := range record {
		switch t := v.(type) {
		case []byte:
			// prevent encoding to base64
			m[k.(string)] = string(t)
		default:
			m[k.(string)] = v
		}
	}

	js, err := jsoniter.Marshal(m)
	if err != nil {
		return "{}", err
	}

	return string(js), nil
}

//export FLBPluginExit
func FLBPluginExit() int {
	loki.Stop()
	return output.FLB_OK
}

func main() {
}

このファイルをout_loki.goとして保存します。
依存関係のパッケージを準備した後*1、以下のコマンドを実行するとFluent Bit用のLokiプラグインの振る舞いをする共有オブジェクトが作成できます。

$ go build -buildmode=c-shared -o out_loki.so .
Golang製のプラグインの動かし方

Fluent BitのGolang製の共有オブジェクトのプラグインを動かすには例えば、以下のような設定ファイルとコマンドが必要です。

[INPUT]
    Name cpu
    Tag  cpu.local
    # Interval Sec
    # ====
    # Read interval (sec) Default: 1
    Interval_Sec 1

[OUTPUT]
    Name  loki
    Match *
    Url http://localhost:3100/api/prom/push
$ fluent-bit -c /path/to/fluent-bit.conf -e /path/to/out_loki.so

Fluent Bitが以下のようなログを吐き出していれば読み込みに成功して動作しています。


Fluent Bit v1.2.2
Copyright (C) Treasure Data

[2019/07/31 12:15:20] [ info] [storage] initializing...
[2019/07/31 12:15:20] [ info] [storage] in-memory
[2019/07/31 12:15:20] [ info] [storage] normal synchronization mode, checksum disabled, max_chunks_up=128
[2019/07/31 12:15:20] [ info] [engine] started (pid=13346)
[flb-go] plugin URL parameter = 'http://localhost:3100/api/prom/push'
[2019/07/31 12:15:20] [ info] [sp] stream processor started
まとめ

Fluent BitのGo製の共有オブジェクトでのプラグインについてまとまった解説を書きました。
実際のfluent-bit-go-lokiはlabelSetsが複数指定できるようになっていたり、テストが書きやすいようにFluent Bitが関わる部分をinterfaceに分離しています。*2
GolangでもFluent Bitのプラグインを書くことが出来ますからぜひ試してみてください。

*1 筆者は執筆時点ではGolangの依存関係を管理するパッケージマネージャーはdepを使用しています。depでの依存パッケージの管理の開始方法はdepのドキュメントを参照してください。

*2 実際のコードはGitHubリポジトリを参照してください。

タグ: Fluentd
2019-07-31

タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|