Health APIでメンテナンス必要な件数をMackerelで可視化

## はじめに

AWSでメンテナンスが行われる場合、それぞれのアカウントに対してメールで通知が行われます。
対象のアカウントが多いと、メールも大量になり、重要なメンテナンス通知を漏らすことがあります。
（メールの文面が変わってしまったりすることもあり、完全にフィルタリングを行うことは難しいです。。。）

Health APIを用いると、APIで必要なメンテナンス情報を取得することができるので、対応漏れを防ぐことができます。

[ドキュメント](https://docs.aws.amazon.com/ja_jp/health/latest/ug/cloudwatch-events-health.html)にあるように、Healthイベントのステータス変化をイベントとして、再起動や通知を行ったりもできますが、今回は対処が必要なリソースが、どのアカウントで何件あるのかをMackerelで確認できるようにしたいと思います。

## 構成

簡単ですが構成は以下の通りです。
日次でCloudWatch EventsからLambdaをスケジュール起動し、各アカウントのメンテナンス情報を収集します。
収集した結果をサービスメトリックとしてMackerlに投稿するようにしました。

Health APIへのアクセスには`health:Describe*`の許可が必要です。
各アカウントの環境にアクセスできるように、実行環境へのAssumeRoleを設定しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320367/68fc1414-4b7e-be73-5126-fc3a3ef25a37.png" width=75%>


## コード

MackerelのAPIKEYはひとまず環境変数に持たせています。
ターゲットアカウントのロールと、Mackerlへ投稿する際のサービス、メトリック名をパラメータとして渡すことにしています。

<img width="75%" alt="testparam.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320367/89ffa3b9-3736-0b05-1c40-f06154bb5438.png">

<details><summary>main.go</summary><div>

```go: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)
}
```
</div></detail>


* Health APIの実行にはビジネス、またはエンタープライズサポートプランが必要です
* AWS Healthのエンドポイントは米国東部(バージニア北部)リージョンのみです
* **試してる間にメンテナンスが発生していなくて、ちゃんとテストできていません。。。**
  * 後日問題あれば修正します。スミマセン<(￣∇￣)ゞ

## 実行結果(想定)
<img width="70%" alt="mackerl_sample-service.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320367/0c08c14f-d318-6919-e886-effd8c684f63.png">
このように描かれるんじゃないかなーと思っています。。


## 最後に
いよいよ週明け、12/23(月)は5周年イベントのMackerel Day#2ですね！
すごく楽しみです！
https://mackerelio.connpass.com/event/152583/