GCP Stackdriver 公式パートナーBlueMedora社のMonitoring Agentの紹介と導入方法

[@koshi_life](https://twitter.com/koshi_life) です。

最近、GCE上でRails、MongoDBの環境でサービス提供しているプロダクトに対し、[Stackdriver Monitoring](https://cloud.google.com/monitoring/?hl=ja) を用いて、インフラリソースの見える化、異常検知を行えるよう監視基盤を整えるようなことをしました。

見える化のため各種メトリクスを送信するAgentを導入していくわけなのですが、
MongoDBを含め Tomcat, PostgreSQL, Elasticsearchなどのサードパーティ製アプリケーション向けにGCPから提供されていたAgentは[2019年2月末でサポート終了](https://cloud.google.com/stackdriver/docs/deprecations/third-party-apps)[^1]となっており、[BlueMedora](https://bluemedora.com/)社のサービス(BindPlane)に移行してね、という公式案内の元、MongoDB版のAgentの設定を行いました。

BindPlane情報が世にあまり出ておらずハマったのでこちらに備忘します。
この記事が誰かの救いになったら幸いです。

[^1]: (感想)2019年5月現在サードパーティ製のアプリケーションの中でGCPが継続サポートしているのは Apache,スタンドアロン JVM, Memcached, MySQL, Nginx, Redis, StatsDです。切り離した技術、残した技術からGCP側のビジネス戦略に思いを馳せました。

# 準備1. GCP側の設定

BindPlane側のエージェントがGCPプロジェクトのStackdriverの情報を把握し、メトリクスを書き込み可能な「サービスアカウント」と「必要なAPI」を有効化します。 [(参考 公式ガイド)](https://docs.bindplane.bluemedora.com/docs/google-stackdriver)

- サービスアカウントの作成 [(参考)](https://cloud.google.com/iam/docs/creating-managing-service-accounts?hl=ja)
  - ロールは「モニタリング管理者 (Monitoring Admin)」[^2]
  - JSONキーを作成
- [CloudResourceManager](https://cloud.google.com/resource-manager/?hl=ja) のAPIを有効化

[^2]: 強すぎるロール`Monitoring Admin`は与えたいので`Monitoring Editor`のロールを設定した。今の所問題は起きていない。ちなみに「`Monitoring Metric Writer`」はテスト接続で弾かれた。

# 準備2. BindPlaneにサインアップ

[BindPlane](https://bindplane.bluemedora.com)のアカウントを作成します。

<img width="400" alt="Screen Shot 2019-05-22 at 12.57.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/6068216f-73a6-0dab-2a58-dacd0be656b0.png">

チュートリアル的な案内に沿って進めます。
３ステップでした。

- STEP 1. 送信先 Destination の設定
- STEP 2. データ収集サーバ Collector の設定
- STEP 3. 取得元 Source の設定

<img width="500" alt="Screen Shot 2019-05-24 at 16.05.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/96260c41-5951-f63a-f95c-a21ea5e76ea5.png">


# STEP 1. 送信先 Destination の設定

この設定は、準備１で作成したサービスアカウントのJSONキーのJSONを貼り付けて完了です。

<img width="400" alt="Screen Shot 2019-05-24 at 16.08.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/d50454d3-05ba-dd4e-dcf1-162f873140da.png">

# STEP 2. データ収集サーバ Collector の設定

取得元へアクセス可能なデータ収集サーバを設定します。

<img width="350" alt="Screen Shot 2019-05-24 at 16.18.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/c40322f1-4101-884d-f70e-6845b15c0651.png">

私の環境では同一GCPプロジェクト内にいたGCE(Ubuntu)を Collector に設定。
Linuxを選択しました。[^3]

[^3]: ちなみにLinuxボタンではなく、別ボタンのGCPボタンを押した時に生成されるコマンドはインスタンス生成をCUI対話形式でやってくれるコマンドが生成されました。

表示されたコマンドをコピーします。

<img width="800" alt="collector1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/81c9864b-7bff-8d85-4058-575c544d9875.png">

該当のインスタンスにログインし、コピーしたコマンドを実行しデータ収集エージェントインストールします。

<img width="400" alt="collector2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/3a5dc5dc-7f22-7c6e-9638-bd7caa67dff9.png">


インストール完了後、ブラウザのBindPlane画面に戻ると、リロードすることなく進捗状況が以下のように反映され、右下のチャットエリアにいる Kyleさん が「設定は最後の一個だぜ！」って応援してくれます。[^4]

[^4]: 随所に離脱しないようUXの作りがキメ細かく工夫されており、離脱させないサービス作りの観点でも勉強になりました。

<img width="700" alt="Screen Shot 2019-05-24 at 21.33.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/c6474d52-7895-d31b-2ae9-459f2ec3daea.png">


# STEP 3. 取得元 Source の設定

最後に取得元情報を設定していきます。
登録されているインテーグレーションから選択します。

ちなみに2019年5月現在168のインテグレーションに対応しているようです。
[インテーグレーション一覧](https://bluemedora.com/solutions/by-integration/)

<img width="600" alt="Screen Shot 2019-05-24 at 16.21.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/e615d2e1-5200-e6ed-5a7f-add842e96f42.png">

ここではMongoDBを選択します。
設定方法は各インテグレーションごとにドキュメントがあるので参考に。

- [参考 BindPlane Guide MongoDB](https://docs.bindplane.bluemedora.com/docs/mongo-mongodb)
- [参考 BindPlane Guide Sources](https://docs.bindplane.bluemedora.com/docs/alibabacloud-ecs)


以下のような入力項目に Source 情報を登録し、テスト接続を経て登録します。

<img width="400" alt="Screen Shot 2019-05-24 at 22.56.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/96a2f4d6-ea95-2316-2098-5e5d583c1311.png">

BindPlaneでの全ての設定が完了したので、Kyle が褒めてくれます。

<img width="500" alt="Screen Shot 2019-05-24 at 23.41.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/4dcbce39-6598-4922-1187-caba6b2770c0.png">



# 設定反映確認. Stackdriverでメトリクスを確認して完了

設定の反映確認をします。

## Stackdriver上で確認

<img width="600" alt="Screen Shot 2019-05-24 at 23.37.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/c7049602-abbb-4ca7-8c76-eeba114cd68f.png">

MongoDBへのリクエスト数が確認できました。

## BindPlane上でも確認可能

<img width="600" alt="Screen Shot 2019-05-24 at 23.34.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/2f0dd943-f8e1-7c7a-98a2-0150f89a187d.png">

BindPlane上でも概要レベルでは確認ができるようです。

# おわりに

BindPlane側のUXがとてもキメ細やかに設計されているおかげで、Mongoのユーザ認証ロール設定でハマった以外は比較的スムーズに導入することができました。

今後、取得元リソースが増えたしても簡単に設定を追加できそうです。

以下にハマったポイントを参考トラブルシューティングとしてまとめておきます。

# 参考:トラブルシューティング

## Step1. Destination 設定時 遭遇したエラー

最初がCloudResourceManagerAPIの有効化が必要なエラー。
次のエラー画像がRoleの権限 `monitoring.metricDescriptors.delete` が足りないエラー。

<img width="362" alt="Screen Shot 2019-05-24 at 23.01.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51734/868b375e-df18-d32b-f999-5be492f96197.png">

## Step3. Source 設定時 遭遇したエラー

Mongodの情報を登録する際に以下エラーでハマった。

>Unable to run `dbStats` command on databases with server address list [mongodb-cluster-0:27017]. (Check if a primary mongod is down.)

Mongodが死んでいる?とエラー表示からIPの登録が間違っているとミスリードしハマった、
実際には指定したユーザの権限が足りなかっただけであった。mongod側のログを見るとよい。

Mongoユーザの権限を一時的に root を付与したらテスト接続を通過でき設定ができた。

```.js
mongo> db.grantRolesToUser("stackdriver", [ { role: "root", db: "admin" } ])
```

その後、root権限はセキュリティ的に渡したくなかったので、mongodのログを見ながらBindPlaneの画面からテスト接続を繰り返し、
できるだけ最小限になるよう読込権限のみのロールを導いた。（今の所、メトリクス取得に問題は出ていない。)[^5]

[^5]: [Guide](https://docs.bindplane.bluemedora.com/docs/mongo-mongodb) では `the admin database with read-only credentials`って記載があり、adminに対するreadロールだけで最初試したがテスト接続が通らないので注意。

参考：Sourceに設定可能なmongoユーザの読み込み権限のみのロール

```.js
mongo> db.createRole({
	"role": "getStackdriverInfo",
	"privileges": [{
			"resource": {
				"db": "admin",
				"collection": ""
			},
			"actions": [
				"viewUser"
			]
		},
		{
			"resource": {
				"db": "admin",
				"collection": "system.roles"
			},
			"actions": [
				"collStats"
			]
		},
		{
			"resource": {
				"db": "admin",
				"collection": "system.users"
			},
			"actions": [
				"collStats"
			]
		},
		{
			"resource": {
				"db": "admin",
				"collection": "system.version"
			},
			"actions": [
				"collStats"
			]
		},
		{
			"resource": {
				"db": "admin",
				"collection": "system.keys"
			},
			"actions": [
				"collStats"
			]
		},
		{
			"resource": {
				"db": "config",
				"collection": "system.sessions"
			},
			"actions": [
				"collStats"
			]
		},
		{
			"resource": {
				"db": "local",
				"collection": "system.rollback.id"
			},
			"actions": [
				"collStats"
			]
		},
		{
			"resource": {
				"db": "local",
				"collection": "system.replset"
			},
			"actions": [
				"collStats"
			]
		}
	],
	"roles": [
		"clusterMonitor",
		"readAnyDatabase"
	]
})
mongo> db.createUser({user: "stackdriver", pwd: "password", roles: ["getStackdriverInfo"] })
```