#Mackerel +Blackbox exporter+Fluentdでプライベート外形監視

2022.06.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ども、ゲストのNTT東日本 大瀧です。

監視SaaSのMackerelには外形監視機能があり、インターネットから任意のURLにHTTPリクエストを送出、その応答性を監視できます。一方でインターネットからはアクセスできない、例えばオンプレミス環境に閉じるネットワークやAmazon VPCへDirect Connnect経由でアクセスするプライベートネットワークで外形監視を行う場合は、監視サーバーを自前で構築しMackerelに監視結果を送る形で対応できます。

本ブログでは、Prometheusの外形監視ソフトウェアであるBlackbox exporterとFluentdを組み合わせてMackerelのサービスメトリックAPIに送付する構成をご紹介します。

構成

監視サーバーの構成を以下に示します。

監視の仕組みとしては、以下の流れになります。

  1. FluentdからBlackbox exporterに監視実行のリクエストを送信
  2. Blackbox exporterが監視を実行、その結果をFluentdへのレスポンスで報告
  3. Fluentdは監視結果をMackerelに送信

Blackbox exporterは本来PrometheusのExporterとして、Prometheusサーバーからの監視ターゲットのURLや通信プロトコルなどを含めたHTTPリクエストに応じて監視を実行し、所要時間などを結果とまとめてHTTPレスポンスとして返します。HTTPの各メッセージは一般的なものなので、本構成ではPrometheusサーバーをFluentdのHTTP Pull Inputプラグインで代用しています。FluentdからMackerelへはMackerel Outputプラグインを利用しますが、Blackbox exporterのレスポンスをMackerelサービスメトリックAPIのデータ形式に成形するためにFluentdでフィルタ処理を嚙ませているのがミソです。Blackbox exporterとMackerelを連携する他の組み合わせ、例えばPrometheusでのMackerel連携やFluent-bitのPrometheus Scrapeなどいろいろ試してみたのですが、データ形式をそろえるためにFluentdで対応するのがベストでした。

また、Mackerelへのメトリック送信はサービスメトリックではなくホストのカスタムメトリックもありますが、今回はホストエージェントを監視サーバーで実行するメリットが無い事からサービスメトリックを選びました。

自前構築の運用コストを少しでも下げるために

監視サーバーはBlackbox exporterとFluentdが動けば要件としてはOKですが、構築と運用を楽にするためにいろいろ工夫できます。幸いにしてどちらも公式のDockerイメージが公開されているので、Docker環境で動かすのがおすすめです。

さらに、オンプレミスでDockerコンテナを管理するAmazon ECS AnywhereKubernetesを組み合わせることで運用が楽にできます。AWS環境のVPC内に監視ターゲットと監視サーバーを置く場合や監視ターゲットがオンプレミス環境にあってVPCからDirect Connect経由で監視する構成であれば、AWS Fargateも選択肢になりそうです。また、外形監視とは別にMackerelエージェントを監視サーバーで実行しMackerelで監視サーバー自身をモニタリングするのももちろん効果的です。

動作確認環境

今回はAmazon ECS Anywhere環境で構築しました。

  • Fluentd: バージョン1.14
  • Blackbox exporter: バージョンv0.20.0
  • Fluentd HTTP Pullプラグイン: バージョン0.8.3
  • Fluentd Mackerelプラグイン: バージョン1.1.0

手順1. Fluentdコンテナの設定

Blackbox exporterコンテナは公開されているDockerイメージをそのまま利用するので、Fluentdコンテナの構成を先に準備しておきます。まずはFluentdの設定ファイル fluent.conf をテキストエディタで作成します。

fluent.conf

<source>
  @type http_pull
  tag status.takipone-local
  url http://exporter:9115/probe?target=https://takipone.local&module=http_2xx
  interval 1m
  <parse>
    @type regexp
    expression "/^.*
probe_dns_lookup_time_seconds (?<dns_lookup_time_seconds>[0-9\.]+).*
probe_duration_seconds (?<duration_seconds>[0-9\.]+).*
probe_http_content_length (?<http_content_length>[0-9\\-]+).*
probe_http_redirects (?<http_redirects>[01]).*
probe_http_ssl (?<http_ssl>[01]).*
probe_http_status_code (?<http_status_code>[0-9]+).*
probe_http_version (?<http_version>[0-9.]+).*
probe_ssl_earliest_cert_expiry (?<ssl_earliest_cert_expiry>[0-9e\.\+]+).*
probe_success (?<success>[01]).*$/m"
  </parse>
</source>

<filter status.**>
  @type record_transformer
  enable_ruby
  renew_record
  <record>
    success \${record["message"]["success"]}
    http_status_code \${record["message"]["http_status_code"]}
    duration_seconds \${record["message"]["duration_seconds"]}
    dns_lookup_time_seconds \${record["message"]["dns_lookup_time_seconds"]}
    http_content_length \${record["message"]["http_content_length"]}
  </record>
</filter>

<match status.**>
  @type mackerel
  api_key "#{ENV['MACKEREL_API_KEY']}"
  service "#{ENV['MACKEREL_SERVICE']}"
  metrics_name takipone-local.\${out_key}
  out_key_pattern .* 
</match>

いくつか解説します。

  • 4行目: Blackbox exporterへのHTTPリクエストなので、http://<Blackbox exporterのホスト名:ポート番号>/probe?target=<監視ターゲットのURL>&module=... という形式になります。今回は監視ターゲットのURLを https://takipone.local にしていますが、監視したいURLに置き換えてください。Blackbox exporterのホスト名とポート番号はDockerコンテナ構成に依存するので、後述します。
  • 8-17行目: Blackbox exporterはPrometheusのExporterのレスポンス形式に準ずる、複数行で メトリック名 値 の形式でレスポンスを返します。検証の限りでは以下の挙動が確認できたため、Rubyの正規表現の複数行オプションでレスポンスボディ丸ごとをパターンマッチングしています。
    • ASCIIコード順に毎回同じ順序
    • 監視結果がFailの場合でもメトリックをレスポンスに含める
  • 21-32行目: Makerel output PluginではネストJSONを扱えないため、ネスト構造を掘り出してフラットJSONに置き換えるフィルタ処理を行っています *1
  • 36,37行目: Mackerelについての設定値(APIキーとサービス名)はAWS Systems Managerパラメータストアの値を参照するよう、環境変数にしました。

続いてテキストエディタで同じディレクトリにDockerfileを作成します。

Dockerfile

FROM fluent/fluentd:v1.14

USER root

RUN apk add --no-cache --update --virtual .build-deps \
        sudo build-base ruby-dev \
 && sudo gem install fluent-plugin-http-pull fluent-plugin-mackerel \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem
COPY fluent.conf /fluentd/etc/fluent.conf

なお、7行目のプラグインのインストールではfluent-plugin-http-pullのインストールが依存するRubyの開発環境のために、前後のapk コマンドでインストール&クリーンアップしています(fluent/fluentd:v1.14イメージはAlpine Linuxベースです) *2

FluentdのDockerイメージのアーキテクチャはamd64なので、Rapsberry PiなどのArm環境ではベースイメージやパッケージコマンドの調整が必要です。ベースイメージは `fleunt/fluentd:v1.14-armhf-debian` などがあり、Debian GNU/Linuxベースなので `apt` で依存パッケージに対応します。Blackbox exporterのイメージはマルチアーキテクチャに対応しているのでArm向けイメージがあります。

これを利用して docker build コマンドでイメージをビルドし、 docker push コマンドでDocker Hubリポジトリにアップロードします *3

$ ls
Dockerfile
$ sudo docker build -t <YOUR_DOCKERHUB_USERNAME>/fluentd-with-plugins:v1.14 .
$ sudo docker push <YOUR_DOCKERHUB_USERNAME>/fluentd-with-plugins:v1.14

これで、Dockerイメージ <YOUR_DOCKERHUB_USERNAME>/fluentd-with-plugins:v1.14 としてECS AnywhereのコンテナインスタンスからPullできるようになりました。

手順2. Systems Managerパラメータストアの設定

前の手順でFluentdの設定のうちMackerelの設定値は環境変数を参照する形にしました。Amazon ECSではDockerコンテナの環境変数にSystems Managerパラメータストアの値をセットする機能があるので、これに値を入れておきます。Systems Manager管理画面のメニュー - パラメータストアを選択し、[パラメータの作成]ボタンをクリックし、以下の名前とデータ型、値をそれぞれ入力、2つのパラメータを作成します。

名前 データ型
mackerel-api-key 安全な文字列 Mackerelのオーガニゼーション管理画面 - APIキー タブで確認できるAPIキー
mackerel-service 文字列 Mackerelのサービス名(新規の場合は作成しておく必要があります)

手順3. ECS Anywhereの設定

オンプレミスの監視サーバーでDockerコンテナを実行するためにECS Anywhereをセットアップします。ECS Anywhereの動作要件を満たしていれば、仮想マシンでも物理マシンでもOKです。ECSの管理画面は新しい世代への代替わりの最中ですが、新しい画面では今回行う操作のうちタスク定義のEXTERNAL指定と環境変数のパラメータストア参照に対応していないため、古い画面で操作を進めます。新旧の判別は画面左のメニュー上部にあるスイッチで確認できるので、スイッチをオフにし旧画面に切り替えておきましょう。

旧画面のクラスター管理画面から、[クラスターの作成]ボタンをクリックし作成ウィザードを進めます。

  • [クラスターテンプレートの選択]画面は「ネットワーキングのみ」を選択し、[次のステップ]をクリックします
  • [クラスターの設定]画面は任意のクラスター名を入力、[作成]をクリックします
  • クラスター作成完了画面が出たら[クラスターを表示]ボタンをクリックしクラスターの画面を表示します

続いてECSインスタンス(ECS Anywhereの場合はExternalインスタンス)の登録を進めます。[ECSインスタンス]タブをクリックし、[Externalインスタンスの登録]ボタンをクリックします。

Externalインスタンスの登録ウィザードでは、ステップ1はそのまま[次のステップ]をクリックし、ステップ2で表示されるコマンドラインのうち[Linuxコマンド]の内容をコピーします。

監視サーバー上でrootユーザーの権限でコマンドラインを実行します。

$ sudo -i
# curl --proto "https" -o "/tmp/ecs-anywhere-install.sh" "https://amazon-ecs-agent.s3.amazonaws.com/ecs-anywhere-install-latest.sh" && bash /tmp/ecs-anywhere-install.sh --region "ap-northeast-1" --cluster "devio-demo" --activation-id "XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX" --activation-code "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  :(略 Docker CEやSystems Managerエージェント、ECSエージェントをインストールしたのちECSへの登録が行われます)
# ok
##########################


##########################
This script installed three open source packages that all use Apache License 2.0.
You can view their license information here:
  - ECS Agent https://github.com/aws/amazon-ecs-agent/blob/master/LICENSE
  - SSM Agent https://github.com/aws/amazon-ssm-agent/blob/master/LICENSE
  - Docker engine https://github.com/moby/moby/blob/master/LICENSE
##########################
#

コマンドが正常に完了すると、ECSインスタンスの一覧に監視ホストの項目が追加されます。

これでOKです。

次にECSのタスク定義にDockerコンテナを登録します。タスク定義の作成画面では、[起動タイプの互換性の選択]で「EXTERNAL」を選択し、[次のステップ]をクリックします。

[タスクとコンテナの定義の設定]画面では以下とコンテナの定義を設定し、他の項目は初期値のままでOKです。

  • ネットワークモード: FluentdコンテナからBlackbox exporterコンテナへのアクセスにDockerのLink機能を利用します。そのため<default>を設定します
  • タスク実行ロール: 自動で作成されるロールに、パラメータストアの読み取り権限ssm:GetParametersを追加します

コンテナの定義は以下の通りです。

項目名
コンテナ名 blackbox-exporter
イメージ prom/blackbox-exporter:v0.20.0
ポートマッピング - ホストポート <空欄のまま>
ポートマッピング - コンテナポート 9115
ポートマッピング - プロトコル tcp
ログ設定 Auto-configure CloudWatch Logsをオン


項目名
コンテナ名 fluentd
イメージ <YOUR_DOCKERHUB_USERNAME>/fluentd-with-plugins:v1.14
環境変数 - MACKEREL_API_KEY ValueFrom: mackerel-api-key
環境変数 - MACKEREL_SERVICE ValueFrom: mackerel-service
ネットワーク設定 - リンク blackbox-exporter:exporter
ログ設定 Auto-configure CloudWatch Logsをオン

コンテナでは、先に設定したパラメータストアを環境変数にセットする設定と、blackbox-exporterコンテナにアクセスするためのリンク設定(ホスト名exporterにblackbox-exporterコンテナのIPアドレスが紐づく)がポイントです。

最後にECSサービスでタスク定義を指定すれば、コンテナが実行されます。クラスター管理画面の[サービス]タブを表示、[作成]ボタンをクリックします。

サービスの設定では、起動タイプをEXTERNAL、先ほど定義したタスク定義/リビジョンを選択し、任意のサービス名を設定します。サービスタイプはREPLICAとDAEMONのどちらでも動作しますが、今後のタスク更新時のダウンタイムを最小化するのであればREPLICAがおすすめです。

サービスを作成ししばらく待つと実行中のタスクが増加(=コンテナを実行)し、監視がスタートします。

動作確認

Mackerelのサービスメトリックの画面を見てみると。。。

Fluentdから送信された監視結果が表示されました!MonitorやAlertを他のメトリックと同様に設定して監視に利用できます。Mackerelの外形監視にはHTTPレスポンスコードの判別やアラート設定が最初から組み込まれている一方で、今回の構成ではメトリックからstatus codeを拾ったりいき値を設定したりと手間がかかる点は注意が必要です。メトリックの一覧を以下に示します。

メトリック名 説明
dns_lookup_time_seconds DNS名前解決にかかった秒数
duration_seconds 監視にかかった全体の秒数
http_content_length HTTPレスポンスのContent Length(レスポンスヘッダが返らなかった場合は-1)
http_status_code HTTPレスポンスのステータスコード
success 監視の結果。成功は1.0、失敗は0.0(HTTPレスポンスが2xx以外のとき、レスポンスに10秒以上かかったとき、通信に失敗したときのいずれかが失敗になります)

Blackbox exporterには、HTTP以外の通信プロトコルのサポートや今回拾っていないメトリックもあります。興味のある方はBlackbox exporterのGitHubを参照してみてください。

Makerelサービスメトリック監視の補足

今回の構成ではFluentdの正規表現によるパース部分が非常に貧弱 *4で、Blackbox exporterのレスポンス仕様がバージョンアップなどで変わるとパースに失敗してメトリックが取れなくなる恐れがあります。そのため、Mackerelのサービスメトリック監視の設定にある途切れ監視で、パース失敗を検出できるようにしておくことをお奨めします *5

また、Fluentdはデータをバッファする仕組みがあり、Mackerelへのメトリック送信がまとめて送られることがあります。サービスメトリック監視では毎分メトリックをチェックするようなアラートは避け、平均値監視を設定するのがおすすめです。

まとめ

Mackerelでプライベート外形監視を行う、FluentdとBlackbox exporterを組み合わせる構成をご紹介しました。プライベートネットワークで外形監視を諦めていたそこのあなた、ぜひ試してみてください!

おまけ: 設定を外出しする

本ブログではFluentdの設定をDockerイメージに含めましたが、後からの構成変更のために設定を外部に置いて起動時に読み込む形がおすすめです。パラメータストアにFluentdの設定を含める以下の記事を参考にするのも良いでしょう。

脚注

  1. ネスト構造が扱えないことは、Fluentdのドキュメントでの明示があまり見られない暗黙知という印象があります。レコード本文(パース結果)がrecord.messageに入るという記述もドキュメントでは見つけられなかったため、Fluentdのstdout outputでダンプして探し出しました。
  2. 古臭い書き方なので、Multi Stage Buildにすれば良かったです。。。
  3. Amazon ECRを利用する場合は、アップロード先を調整してください。
  4. Fluentdが弱いのではなく筆者の正規表現力の弱さからなので、Fluentdをdisる意図はありません。
  5. もちろん監視サーバー自体が停止してもメトリックが途切れるので、監視サーバー自体の監視を組み合わせてサーバー障害起因はソフトウェア障害起因かを切り分けできるのがベターです。