はじめに
AWSでメンテナンスが行われる場合、それぞれのアカウントに対してメールで通知が行われます。
対象のアカウントが多いと、メールも大量になり、重要なメンテナンス通知を漏らすことがあります。
(メールの文面が変わってしまったりすることもあり、完全にフィルタリングを行うことは難しいです。。。)
Health APIを用いると、APIで必要なメンテナンス情報を取得することができるので、対応漏れを防ぐことができます。
ドキュメントにあるように、Healthイベントのステータス変化をイベントとして、再起動や通知を行ったりもできますが、今回は対処が必要なリソースが、どのアカウントで何件あるのかをMackerelで確認できるようにしたいと思います。
構成
簡単ですが構成は以下の通りです。
日次でCloudWatch EventsからLambdaをスケジュール起動し、各アカウントのメンテナンス情報を収集します。
収集した結果をサービスメトリックとしてMackerlに投稿するようにしました。
Health APIへのアクセスにはhealth:Describe*
の許可が必要です。
各アカウントの環境にアクセスできるように、実行環境へのAssumeRoleを設定しています。
コード
MackerelのAPIKEYはひとまず環境変数に持たせています。
ターゲットアカウントのロールと、Mackerlへ投稿する際のサービス、メトリック名をパラメータとして渡すことにしています。
main.go
package main
import (
"log"
"os"
"time"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/health"
"github.com/aws/aws-sdk-go/service/sts"
mackerel "github.com/mackerelio/mackerel-client-go"
)
var (
client = mackerel.NewClient(os.Getenv("APIKEY"))
nowTime = time.Now()
)
// Request is argument specified at call
type Request struct {
TargetList []TargetAccount `json:"TargetList"`
}
// TargetAccount is target account
type TargetAccount struct {
Service string `json:"Service"`
Name string `json:"Name"`
Role string `json:"Role"`
}
func handler(req Request) (string, error) {
sess := session.Must(session.NewSession())
assumeRoler := sts.New(sess)
eventParam := &health.DescribeEventsInput{
Filter: &health.EventFilter{
EventStatusCodes: []*string{
aws.String(health.EventStatusCodeOpen),
aws.String(health.EventStatusCodeUpcoming),
},
EventTypeCategories: []*string{
aws.String(health.EventTypeCategoryScheduledChange),
},
},
}
for _, target := range req.TargetList {
creds := stscreds.NewCredentialsWithClient(assumeRoler, target.Role)
svc := health.New(sess, aws.NewConfig().WithRegion("us-east-1").WithCredentials(creds))
var arns []*string
err := svc.DescribeEventsPages(eventParam, func(resp *health.DescribeEventsOutput, lastPage bool) bool {
for _, event := range resp.Events {
arns = append(arns, event.Arn)
}
return true
})
if err != nil {
log.Println(err.Error())
continue
}
entityParam := &health.DescribeAffectedEntitiesInput{
Filter: &health.EntityFilter{
EventArns: arns,
},
}
var entities []*health.AffectedEntity
err = svc.DescribeAffectedEntitiesPages(entityParam, func(resp *health.DescribeAffectedEntitiesOutput, lastPage bool) bool {
entities = append(entities, resp.Entities...)
return true
})
if err != nil {
log.Println(err.Error())
continue
}
entities = removeUnknown(entities)
err = client.PostServiceMetricValues(target.Service, []*mackerel.MetricValue{
&mackerel.MetricValue{
Name: "monitor-maintenance." + target.Name,
Time: nowTime.Unix(),
Value: len(entities),
},
})
if err != nil {
log.Println(err.Error())
continue
}
}
return "ok", nil
}
func removeUnknown(e []*health.AffectedEntity) []*health.AffectedEntity {
result := []*health.AffectedEntity{}
for _, v := range e {
if *v.EntityValue == health.EntityStatusCodeUnknown {
continue
}
result = append(result, v)
}
return result
}
func main() {
lambda.Start(handler)
}
- Health APIの実行にはビジネス、またはエンタープライズサポートプランが必要です
- AWS Healthのエンドポイントは米国東部(バージニア北部)リージョンのみです
-
試してる間にメンテナンスが発生していなくて、ちゃんとテストできていません。。。
- 後日問題あれば修正します。スミマセン<( ̄∇ ̄)ゞ
実行結果(想定)
このように描かれるんじゃないかなーと思っています。。最後に
いよいよ週明け、12/23(月)は5周年イベントのMackerel Day#2ですね!
すごく楽しみです!
https://mackerelio.connpass.com/event/152583/