Mackerel監視設定の管理をServerlessで自動化
目的
mackerelの監視設定を作成、削除、更新するときに mkrコマンドを利用して運用していました。 そこでの課題感が、
- 監視設定を調べてjsonを書く
- 監視項目を追加した際は監視idが自動で割り振られるため、jsonで定義してから
mkr push
して監視id
を反映するためにmkr pull
してその結果をgithubにpushする
多くの項目を一気に設定するにはとてもいいんですが、ちょっとした変更等であればWeb管理画面でポチッと変更したほうが楽だし効率的かもしれない🤔 また、誰がどの監視設定を変更したかの記録も残したいという気持ちがありました。そこで調べていたら、下記のはてなさんの公式ブログを見つけました。
本ブログでは、この記事を参考にやったことについて書いていきます。
アーキテクチャについて
■ 処理の流れ
- 管理者がwebコンソールで監視設定作業
- MackerelがwebhookでAPIGatewayにjsonペイロードをPost
- APIGatewayからLambdaをコール
- Lambdaでwebhookのjsonペイロードをパースして環境変数を付与してCodeBuildを実行
- CodeBuildでmkrで最新設定情報を取得して、GitHubにpush
実装について
Lambda ( Go )
コード以外の部分を先に作っておきます。
( Goのソースコードは後ほど記載します。)
一から作成
を選択- ランタイム:
Go 1.X
を選択 - ロールを選択 ( あらかじめ以下の権限で作成する )
- マネージドポリシー
AWSCodeBuildDeveloperAccess
- 以下のポリシーを付与
- マネージドポリシー
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*", "Effect": "Allow" } ] }
- ハンドラ:
main
これら以外はデフォルト値。
APIGateway
■ APIを作成する
リソースを作成する。
- 作成したリソースに対してメソッドをPostで作成する。
- 統合タイプ:
Lambda関数
- Lambda プロキシ統合の使用:
有効
- Lambdaリージョン:
ap-northeast-1
- Lambda関数を指定
- 統合タイプ:
3.1 統合リクエストの設定
- 統合タイプ: Lambda関数
■ リソースポリシーの設定する
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:<REGION>:<AWS_ACCOUNT-ID>:<APIGW-ID>/*", "Condition": { "IpAddress": { "aws:SourceIp": [ "52.193.111.118/32", "52.196.125.133/32", "13.113.213.40/32", "52.197.186.229/32", "52.198.79.40/32", "13.114.12.29/32", "13.113.240.89/32", "52.68.245.9/32", "13.112.142.176/32" ] } } } ] }
■ 許可されていないIPアドレスからAPIGatewayを実行してみたときのログはこんな感じに。
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:********:<api-id>/stg/POST/monitor"}
→ ちゃんと失敗してます。
注意) リソースポリシーの設定を反映させるためには、APIのデプロイ
を実施する必要があります。
Lambdaを指定すると以下のようにLambdaとAPIGatewayが連携されます。
Mackerel
通知チャンネルを追加する
最初にWebhookのURLにセットする値を確認する
Mackerelの管理画面から設定する。
Channels
--> 通知グループを追加
--> Webhook
メモリ監視の設定を変更したときのWebhookのjsonペイロード例。(lambdaでjsonを取得した結果)
{ "orgName": "ABC", "event": "monitorUpdate", "monitor": { "duration": 3, "maxCheckAttempts": 1, "isMute": "False", "metric": "memory%", "excludeScopes": [], "name": "Memory %", "warning": 80, "memo": "", "id": "ABCDEFGHIJK", "scopes": ["stg"], "type": "host", "operator": ">" }, "user": { "id": "ABCDEFGHIJK", "screenName": "example@com" } }
LambdaのGoコード
Goの処理としてやりたいことは、mackerelから受け取るwebhookのjsonをパースして
screenName
event
name(monitor)
をCodeBuildに環境変数としてセットし、実行させることです。
監視項目Memory %
の閾値を変更したときに取得した環境変数にセットする値です。
{“name”:“Memory %“,”event”:“monitorUpdate”,“screenName”:“example@com”}
Goのコード (main.go )
package main import ( "encoding/json" "fmt" "strings" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/codebuild" ) var ( projectName = "mackerel_monitor" config = aws.Config{Region: aws.String("ap-northeast-1")} svcCodeBuild = codebuild.New(session.New(&config)) ) // DataRequest set from json type DataRequest struct { Monitor Monitor `json:"monitor"` User User `json:"user"` Event string `json:"event"` } // User set from json type User struct { ScreenName string `json:"screenName"` } // Monitor set from json type Monitor struct { Name string `json:"name"` } // APIResponse return apigw type APIResponse struct { Name string `json:"name"` Event string `json:"event"` ScreenName string `json:"screenName"` } func main() { lambda.Start(Handler) } // Handler aws lambda hadoler func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { reqBody := request.Body jsonBytes := []byte(reqBody) mackerelReq := &DataRequest{} // JSON parse. json to struct if err := json.Unmarshal(jsonBytes, mackerelReq); err != nil { fmt.Println(err) } // Setup CodeBuild var ptrProjectName *string ptrProjectName = &projectName // ENV MONITOR_NAME trim '{' and '}' repMonitor := aws.String(strings.Replace(fmt.Sprint(mackerelReq.Monitor), "{", "", -1)) repMonitor = aws.String(strings.Replace(*repMonitor, "}", "", -1)) // ENV EMAIL trim '{' and '}' trimUserEmail := aws.String(strings.Replace(fmt.Sprint(mackerelReq.User), "{", "", -1)) trimUserEmail = aws.String(strings.Replace(*trimUserEmail, "}", "", -1)) // Set Env when codebuild start params := &codebuild.StartBuildInput{ ProjectName: ptrProjectName, EnvironmentVariablesOverride: []*codebuild.EnvironmentVariable{ { Name: aws.String("MONITOR_NAME"), Value: repMonitor, }, { Name: aws.String("EVENT"), Value: aws.String(fmt.Sprint(mackerelReq.Event)), }, { Name: aws.String("MKR_USER_EMAIL"), Value: trimUserEmail, }, }} // Start CodeBuild req, _ := svcCodeBuild.StartBuildRequest(params) err := req.Send() if err == nil { fmt.Println("no error codebuild request") } // Response APIGW event := aws.String(fmt.Sprint(mackerelReq.Event)) name := repMonitor screenName := trimUserEmail apiRes := APIResponse{ Name: *name, Event: *event, ScreenName: *screenName, } // Create json. struct to json resJSONBytes, _ := json.Marshal(apiRes) return events.APIGatewayProxyResponse{ Body: string(resJSONBytes), StatusCode: 200, }, nil }
CodeBuildに先程説明した3つを環境変数として渡し、gitのコミットメッセージで利用する。
環境変数 ( CodeBuild ) | webhook(json) |
---|---|
MKR_USER_EMAIL | screenName |
EVENT | event |
MONITOR_NAME | name(monitor) |
CodeBuild
■ CodeBuildを作成する
プロジェクト名は goのコードの中で定義しているので、今回は mackerel_monitor
。
Lambdaの環境変数で定義したほうが扱い易いので今後修正する予定。
■ 実行環境を設定する
コンテナイメージはDockerHubのものを指定。今回指定したのでは、はてなさんの公式ブログに記載のあった DockerHub imageのイメージを利用させていただきました。このイメージを利用したのは必要なパッケージが事前にインストールされているのでCodeBuildの実行時間が短くなるためです。
■ Buildspecを設定する
今回はCodePipelineを使わないのでCodeBuildの処理定義は ビルドコマンドの挿入
を選択し、エディタに切り替え
をクリックしてエディタを表示させて以下のようにbuildsepc.ymlのコードを定義します。
buildspec.yml
version: 0.2 env: variables: GITHUB_USER: "yhidetoshi" GIT_BRANCH: "mod_mackerel_monitor" parameter-store: GITHUB_TOKEN: "github_token" GITHUB_REPO: "mackerel_repo" MACKEREL_APIKEY: "mackerel_apikey" phases: pre_build: commands: - export MACKEREL_APIKEY=${MACKEREL_APIKEY} - mkr monitors pull - git clone -b ${GIT_BRANCH} --depth 1 https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_REPO}.git - cd ${GITHUB_REPO}/mackerel - git config --global user.name \"${MKR_USER_EMAIL}\" && git config --global user.email \"${MKR_USER_EMAIL}\" build: commands: - mv -f ../../monitors.json . - git add monitors.json && git diff --cached --exit-code --quiet || git commit -m \""${EVENT} ${MONITOR_NAME} by ${MKR_USER_EMAIL}"\" post_build: commands: - git push --force-with-lease origin HEAD
■ CodeBuildに付与するIAMロールにアタッチするポリシーは以下にしました。
AmazonSSMReadOnlyAccess
CloudWatchLogsFullAccess
- CodeBuildのログを保存するため
機密情報の扱い
今回、機密情報の取得はSSMパラメータストアから参照するようにしました。
- buildspec.yml (一部抜粋)
env: ・・・ parameter-store: GITHUB_TOKEN: "github_token" MACKEREL_APIKEY: "mackerel_apikey"
変数 ( SSMパラメータストア ) | webhook(json) |
---|---|
GITHUB_TOKEN | GitHubに接続するため |
MACKEREL_APIKEY | mkrでmackerelから監視設定を取得するため |
[参考]
AnsibleでSSMパラメータストアを管理する方法を以前のブログにまとめています。 yhidetoshi.hatenablog.com
Lambdaで作成した環境変数を利用
CodeBuildの処理の中でmackerelから受けるwebhookのjsonペイロードからLambda (Go) で以下の環境変数を作成して利用しています。
gitのコメントに付与して、GitHubのPullRequestを作成するときに どのユーザが何の監視項目をどうしたのか
を表示させるために利用しています。
${MONITOR_NAME}
${EVENT}
${MKR_USER_EMAIL}
monitorUpdate Memory % by example@com
デプロイについて
今回、LambdaとAPIGatewayのデプロイを Serverless Framework
を利用したデプロイについても記載しておきます。
CodeBuildのデプロイについては、上記で記載した方法で実施。
Makefile
GOCMD=go GOBUILD=$(GOCMD) build GOGET=$(GOCMD) get .PHONY: setup ## Install dependencies setup: $(GOGET) github.com/mitchellh/gox $(GOGET) -d -t ./... .PHONY: cross-build ## Cross build binaries cross-build: rm -rf ./main gox -os=linux -arch=amd64 -output=./main -ldflags "-s -w"
- serverspec.yml
service: mackerel frameworkVersion: ">=1.48.0" provider: name: aws stage: stg runtime: go1.x region: ap-northeast-1 endpointType: regional resourcePolicy: - Effect: Allow Principal: '*' Action: execute-api:Invoke Resource: - arn:aws:execute-api:ap-northeast-1:${opt:account}:*/* Condition: IpAddress: aws:SourceIp: - "52.193.111.118/32" - "52.196.125.133/32" - "13.113.213.40/32" - "52.197.186.229/32" - "52.198.79.40/32" - "13.114.12.29/32" - "13.113.240.89/32" - "52.68.245.9/32" - "13.112.142.176/32" functions: monitor: handler: main role: mackerelMonitorLambda timeout: 30 description: mackerel montiro memorySize: 256 events: - http: path: /monitor method: post integration: lambda-proxy resources: Resources: mackerelMonitorLambda: Type: AWS::IAM::Role Properties: RoleName: mackerelMonitorLambda AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSCodeBuildDeveloperAccess Policies: - PolicyName: mackerelMonitorLambda PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: "*"
■ デプロイコマンド
- Goコンパイル - $ make setup cross-build - ServerlessFrameworkでデプロイ - $ sls deploy --aws-profile <PROFILE> --account <AWS_ACCOUNT_ID>
まとめ
今回はMackerel監視設定の運用を効率的にする方法を調べて実際に実装してみました。 APIGatewayで簡単にAPIが作れて、バックエンドはLambda ( Go )で実装し、mkrやgitの処理をCodeBuildで行いました。 APIGatewayとLambdaはServerlessFrameworkでデプロイしました。