生涯未熟

生涯未熟

プログラミングをちょこちょこと。

Telegrafを用いたElastic Beanstalk環境のEC2インスタンスにあるDockerコンテナの監視

5ヶ月ぶりです。人生ですね。

Telegrafを使った監視をやってみたので、備忘録として残しておきます。

What is Telegraf

InfluxData社が作成したメトリクス集計、レポーティングツールになります。

github.com

構造として、

  1. インプット:メトリクスの集計先
  2. プロセッサー:集計したメトリクスの加工
  3. アグリゲーター: メトリクスから集計(最大、最小、平均 etc...)
  4. アウトプット:メトリクスの送信先

の4種類のプラグインで成り立っており、それぞれのプラグインの設定を記述して動かすことになります。
READMEに載っているプラグイン一覧を見ていただければ分かるのですが、凄い数のプラグインがあります。
無かったらGoで作ってPRを送りましょう。

概ね良いツールではあるのですが、如何せん設定周りの記述で分かりづらいところもあるので、以下のドキュメント集はさっくり見ておくことをオススメします。
(CONFIGRATION.mdだけでも読みましょう。筆者は読まなかったことでMetrics Filteringの存在に気付かず、痛く後悔をしました)

やりたい

こんな感じでメトリクスを集計・送信したいという願望図

f:id:syossan:20191219124249p:plain

やる

やっていきます。

まぁそんなにやることはなくて、手順としては

  • Telegrafのconfig fileを吟味する
  • .ebextensions でElastic Beanstalkに建てられるEC2インスタンス設定を弄る
    1. Telegrafをインストール
    2. Telegraf config fileを作成
    3. Telegrafを実行

てな流れになります。

Telegrafのconfig fileを吟味する

まずはTelegrafのデフォルトのconfig fileを見ていきましょう・・・ってのをやりたいのですが、6200行近くあるので必要なプラグインのみ記述されているconfig fileを出力しましょう。

$ telegraf --input-filter docker --output-filter cloudwatch config > ./telegraf.config

で、これでも600行近くあるので必要な部分をつまみ食いしたconfigを見てみましょう。

設定の意味合いとかを日本語で簡単に書きました。
特にこだわりなければデフォルトでもメトリクス吐いてくれますが、メトリクスを絞り込まなければかなりの量のカスタムメトリクスが生成されてしまうので注意しましょう。(カスタムメトリクスは即時削除できず、保有期間を過ぎるのを待たないといけない)
どのようなメトリクスが吐かれるのか知りたい場合は、 --test オプションを使って標準出力に吐いてみましょう。

また、各プラグインの詳細説明はREADMEにあるプラグイン一覧からリンクで飛べるため、使用する前に目を通しておくのをオススメします。
例えばDockerプラグインだと集計するメトリクスが書いてあったりするので、それを見ながらメトリクスフィルタリングすると良いでしょう。

メトリクスフィルタリング?

メトリクスを必要な分だけ絞り込む際に使うやつです。
基本的にどのプラグインでも使える設定値で、詳しくは以下参照。

https://github.com/influxdata/telegraf/blob/master/docs/CONFIGURATION.md#metric-filtering

例えば、Dockerからメモリ使用量・使用率しか集計したくない!って場合は、 [[inputs.docker]] に以下のような設定を記述することで実現できます。

[[inputs.docker]]
  namepass = ["docker_container_mem"]
  fieldpass = ["usage_percent", "usage"]

特にCloudWatchのようなクラウド環境に送る際に、不要なメトリクスを送ってしまうと無駄なコストが掛かってしまうのでメトリクスフィルタリングを是非活用したいですね。


また、今回のようにElastic Beanstalk環境でやる際には、 container_name_excludeecs-agent を追加しておくのが良いでしょう。特に見る必要のないコンテナなので・・・

というわけで、脳死でやりたいならデフォルトでも良いですが、変なことにならないよう諸々設定しておきましょうという話でした。

.ebextensions でElastic Beanstalkに建てられるEC2インスタンス設定を弄る

Elastic Beanstalkには .ebextensions という、EC2インスタンスが生成される際に走る設定があります。
今回はここにtelegrafのインストール・設定ファイルの生成・telegrafの実行、という3つの設定を書いていきましょう。

.ebextensionsの注意

地味に引っかかりそうなところなんですが、 .ebextensions はファイル名のアルファベット順に実行されますので注意してください。
例えば、

.ebextensions
  - install-telegraf.config
  - exec-telegraf.config

といったファイルがあった場合、予想に反して exec-telegraf.config から実行されてFailしてしまうので、 00.install-telegraf.config などファイル名の先頭に数字を付けておくと安全です。

telegrafのインストール

こちらが設定例になります。
yumのrepoを生成し、インストールしている簡単なものになります。

注意点として、インスタンスAmazon Linuxを使っている場合には baseurl を公式のインストールマニュアルにあるように https://repos.influxdata.com/rhel/$releasever/$basearch/stable とすると404エラーが発生するので、上記のようにしておきましょう。

Telegraf config fileを作成

config fileを /etc/telegraf/telegraf.conf に生成しています。

ここは content の部分が各々変わってくるところかなと。

Telegrafを実行

最後にTelegrafを実行しましょう。

これでデプロイ時に上手くtelegrafが動いて、CloudWatchにメトリクスが集計されるようになります。

おわり

という感じで簡単にTelegrafの使用例を書いてみました。
日本語でのTelegrafに関する記事が少なかったので、増やす意味で書きましたが参考にされる方が一人でもいれば幸いです。

このようにプラグインの設定をザッと書くだけで監視環境が整うのは、とても楽で良いですね。
他にもこんなツールは良いよ!というのがあれば是非教えて下さい🙇

MD650LにKarabinerでアンダースコアを割り当てる

地味に困ったのでメモ代わりに書いておく。

MD650Lを日本語配列で使っているとアンダースコアが打てなくて困ることがありました。
Karabinerで上手いことキー割り当てたらええかーと思ったのですが、どのキーを割り当てたらいいのか分からず・・・

f:id:syossan:20190728000017p:plain
どれやねん

で、アレコレ試行錯誤した結果この「international1」ってのがアンダースコアに該当するキーのようで、右のAltに割り当てることで事なきを得ました。
いやー、これは分からん😇

f:id:syossan:20190728000241p:plain
わからん

Protocol Buffersにclang-formatをかけるとインデント崩れを起こすパターンがある

Protocol Buffersをシコシコ使ってるんですが、clang-formatをかけると具合の悪くなるパターンを発見したのでメモ。

ちなみに一応書いときますがclang-formatはProtocol Buffersをサポートしてます。

github.com

どういうパターン?

例えばこういうパターンです。

これに対してclang-formatをかけると・・・

🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔

なんでこうなるの?

調べたけどソースコード読むのダルくてわからん。
ただLLVMのbug reportはされているみたいなので、バグという認識をしている人はちゃんといるっぽい。

bugs.llvm.org

応急措置

このままじゃ困るので一旦の応急措置。

はい。微妙な違いなのですがoption内でセミコロンを使わないってことをしています。
こうするとclang-formatをかけても・・・

schema のところがちょっと変わっちゃいましたが、まぁ概ね問題無しですね。

まとめ

というわけで、clang-formatでインデント崩れ起こしちゃうパターンがあるよという話でした。
まぁバグっぽい挙動なので直るのを粛々と待つしかないですね・・・

gRPC GatewayとServerで異なるレスポンスで返す方法

前置き

gRPCにはgrpc-gatewayという、gRPC ServerのレスポンスをRestful APIの形にして返すプラグインがあります。
Protocol Buffersに少し記述を足すだけで、簡単にRestful APIの対応が出来るのでとても重宝しているのですが、一つ困ったことが起きました。それが掲題の件です。

ページネーションに対応したレスポンスを返すエンドポイントを作成する際に、以下のような形で返したかったというのが理由になります。

Serverレスポンス

Gatewayレスポンス

Server側ではページ移動する際に必要なリクエストの内容を返却し、Gateway側では直接URLを返却するという感じになります。
で、ここで問題になるのがServerのレスポンス形式とGatewayのレスポンス形式は同じにしか出来ないというところです。

例えば、このようなprotocを記述すると思うのですが、

こうした場合、Server・Gatewayのどちらもレスポンスが ExampleResponse の形式に依存することになります。 google.api.httpGatewayのレスポンスを設定が出来たらいいんですが、そういったものも見つかりませんでした。

対処法

対処の流れ説明すると

  • ResponseにAny型を使用する
  • Serverから返す場合にはServer用のレスポンス形式で返すようにする
  • Gatewayから返す場合にはGateway用のレスポンス形式で返すようにする

ってな感じです。(言語はGoを想定しているのであしからず)

ResponseにAny型を使用する

protocol buffersは google/protobuf/any.proto をインポートすることでAny型というものが使えます。Anyの名前が指し示す通りどんなメッセージ型でも受け入れることが出来ます。まずは、これをResponseに入れ込みましょう。

これで Responsepagination を通して、Server側のレスポンスとしては ServerPaginationMessage を、Gateway側のレスポンスとしては GatewayPaginationMessage を返す土壌は出来ました。

Serverから返す場合にはServer用のレスポンス形式で返すようにする

Any型のpagination fieldにServer側レスポンスを当て込むように実装していきます。
コードとしては以下のようなイメージです。

これでAny型にServer側レスポンスを上手く流せ込めました。

Gatewayから返す場合にはGateway用のレスポンス形式で返すようにする

さて、ここからが本番なのですがGateway側の場合、少し複雑なプロセスが必要になります。
https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/cmd/example-gateway-server/main.go#L21-L37 を参考に説明していきます。

grpc-gatewayにはruntimeパッケージが用意されており、まずはその中の runtime.WithForwardResponseOption を使って「レスポンスを返す前の処理」を実装していきます。

実に面倒なのが分かりますね。こんな風に Server側レスポンスを受け取る→Gateway側レスポンスを構築する→Any型に埋め込んで返す といった一連の流れになります。この辺のAny型の扱い、なかなか記事としても無いので難しいですね・・・

まとめ

という感じでこの問題は解決できましたが、Google API Design Guideを読むとどうやらGoogle様的には「ページネーションはTokenでやり取りせえよ!」というスタンスみたいなので、(Common Design Patterns  |  Cloud APIs  |  Google Cloud)もしかしたらこのやり方は邪道なのかもしれません。
しかし、昔ながらのページネーションを踏襲しつつやるにはこういった方法も必要になる場面が多いんじゃないかな?と一応書き残しておきます。

Protocol Buffersが出力するSwaggerのParameter descriptionを記述する方法

Protocol BuffersでSwaggerを吐くことが出来ますが、その際にParametersの個々のfieldにdescriptionをどうやったら付けれるのかよく分からなかったのでメモ。

どういうこと?

f:id:syossan:20190311192111p:plain

これを追加したかった。

やり方

至極簡単で、OpenAPIのfieldを使ってゴニョゴニョすればいい。

例えば以下のような感じで、protocファイルに記述する。

message HogeRequest {
  uint64 id = 1 [(grpc.gateway.protoc_gen_swagger.options.openapiv2_field) = {description: "Test hoge"}];
}

簡単なことだけど、これに気付くまでにめちゃくちゃかかった・・・