この記事は、Mackerel Advent Calendar 2017の18日目の記事です。
今日はMackerel移行時のお話をしようと思います。
11月にようやく旧環境からMackerel への移行が終わりました。
この移行時、MackerelとRedmineを連携してアラート管理する必要があったのでその方法をまとめます。
方法といっても、WebHookを利用するだけです。
ただLambdaと連携できればどんな用途にも応用がききます。
参考までに、アラート管理のツールとしてRedmineを選んだ理由もまとめます。
MackerelのアラートからRedmineまでの流れ
Mackerel => WebHook => CloudFront(WAF) => API GateWay => Lambda => Redmine
介在するサービスが多いですが、一つ一つの作業量はあまり多くありません。
要点は次の通りです。
- WebHookでイベントを発生させる
- CloudFrontにWAFをつけて、Mackerelからのアクセスに限定する
- LambdaでRedmineのAPIを叩く
1. Lambdaの設定
1.1 LambdaのIAMロール
VPC内のRedmineにアクセスする必要があるため、
Lambda関数のロールにネットワークインタフェースの作成削除の権限を与える必要があります。
管理ポリシーのAWSLambdaVPCAccessExecutionRole をLambdaのロールに与えます。
念のため何の権限を与えているのか確認しておくと、主にNetwork Interfaceの作成のためですね。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Resource": "*"
}
]
}
1.2 Lambda Function
Lambdaで受けて、RedmineのAPIを叩くだけの関数です。
いろいろとべた書きしています。
# coding: UTF-8
import requests
import json
import os
redmine_ip = os.environ["REDMINE_IP"]
redmine_host = os.environ["REDMINE_HOST"]
redmine_apikey = os.environ["REDMINE_APIKEY"]
redmine_project = os.environ["REDMINE_PROJECT"]
redmine_assigned = os.environ["REDMINE_ASSIGNED"]
redmine_tracker = os.environ["REDMINE_TRACKER"]
redmine_url = 'http://' + redmine_ip + '/issues.json?key=' + redmine_apikey
def lambda_handler(event, context):
parsed = json.dumps(event)
event_obj = ''
# 実験中,2通りのイベントデータが存在したため以下の処理を入れた
if json.loads(parsed).has_key('body'):
event_obj = json.loads(json.loads(parsed)['body'])
else:
event_obj = json.loads(parsed)
stats = event_obj['alert']['status']
opend = event_obj['alert']['isOpen']
if stats != "ok" and opend == "True":
alarm_host = get_alarm_host(event_obj)
monitor_name = event_obj['alert']['monitorName'].decode('utf-8')
subject = stats + u': "' + alarm_host + u' ' + monitor_name
custom_fields = [
{'id': 11, 'name': u'モニター名', 'value': monitor_name},
{'id': 22, 'name': u'アラートレベル', 'value': stats},
{'id': 33, 'name': u'アラート対象ホスト名', 'value': alarm_host}
]
create_ticket(subject, json.dumps(event_obj, indent=4, separators=(',', ': ')), custom_fields)
def get_alarm_host(event_obj):
alarm_host = ""
if event_obj.has_key('service'):
alarm_host = event_obj['service']['name']
if event_obj.has_key('host'):
alarm_host = event_obj['host']['name']
return alarm_host
def create_ticket(subject, description, custom_fields):
request_headers = {
'Host': redmine_host,
'Content-Type': 'application/json'
}
payload = {
'issue': {
'project_id': redmine_project,
'tracker_id': redmine_tracker,
'assigned_to_id': redmine_assigned,
'status_id': 1,
'priority_id': 4,
'subject': subject,
'description': description,
'custom_fields': custom_fields
}
}
requests.post(redmine_url, data=json.dumps(payload), headers = request_headers)
1.3 Lambdaに渡されるMackerelのイベントオブジェクト
こんな感じのオブジェクトが渡されてきます。
詳しくはドキュメントを読みましょう。
{
"orgName": "xxxxxxxxxx",
"host": {
"status": "working",
"isRetired": false,
"name": "web1",
"roles": [
{
"roleName": "web",
"fullname": "prod: web",
"serviceName": "prod",
"roleUrl": "[role url]",
"serviceUrl": "[service url]"
}
],
"url": "[host url]",
"memo": "",
"id": "aaaaaaaaaa"
},
"event": "alert",
"alert": {
"status": "critical",
"metricValue": 100,
"warningThreshold": 70,
"isOpen": true,
"url": "[alert url]",
"criticalThreshold": 90,
"metricLabel": "CPU %",
"trigger": "monitor",
"duration": 3,
"monitorName": "CPU %",
"monitorOperator": ">",
"createdAt": 1513319978252
}
}
1.4 Lambdaのその他の設定
- ネットワークインタフェースを作るために時間がかかるので、タイムアウト値は20秒程度に設定しました。
- 念のためサブネットはインターネットゲートウェイを持たないルートテーブルのサブネットを設定しました。
- requests は pip install requests -t . したものをパッケージに含めています。
- 環境変数は以下のように設定しています。
REDMINE_APIKEY xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
REDMINE_IP 10.0.0.4
REDMINE_ASSIGNED 999
REDMINE_HOST example.jp
REDMINE_TRACKER 111
REDMINE_PROJECT alert
2. API GateWayの設定
API GateWayは HTTPリクエストでLambdaを動かすため利用します。
設定は以下の通りです。
2.1 リソース/POSTメソッドの作成
APIを作成したら、リソースの作成で、URLのパスを定義します
パスが定義できたら、メソッドの作成ができるようになるのでPOSTメソッドを作成します。
MackerelのWebHookの仕様 でPOSTメソッドで受ければよいことがわかります。
メソッド作成する際に作成したLambda関数に紐づけます。
2.2 APIキーの作成
CloudFrontからのアクセスに限定させるためにAPIキーを作成します。
2.3 メソッドリクエストの設定
APIキーを持たないとリクエストが通らないようにします
2.4 APIのデプロイ
まだインターネットに公開されていない状態なので、公開する環境を用意します。
その環境にAPIをデプロイすることでインターネットに公開されます。
2.5 使用量プランを定義
APIキーを割り当てるために使用量プランを作成します。
スロットリングとクォータはお好みで設定します。
3. CloudFrontの設定
3.1 API GateWayのAPIキーをヘッダにつける
CloudFront経由したリクエストは必ずx-api-keyヘッダを付けるようにします。
API GateWayで作成したAPIキーを値としてOriginの設定を行います。
3.2 WAFの設定
許可すべきIPは公式の回答があります。
このページも監視対象にしておいたほうが良いかもしれません。
CloudFrontの設定に戻ってWAFを設定します。
3.3 CloudFrontのその他の設定
- OriginDomainNameにはAPI GateWayの呼び出しURLを設定します
- CloudFrontでキャッシュされないように設定しています
4. WebHookの設定
あとはURLにhttps://[cloudfrontのドメイン名]/[api-gatewayのパス]を入力すればアラートが発生すればチケットが作られるようになります。
チケットができた
ということで、アラートがなると、こんな感じでチケットが登録されるようになりました。
もろもろの連絡はSlackで行い、内容をこのチケットにまとめるというような利用イメージです。
【参考】インシデント管理でやりたかったこと
連携させるツールを選定する際の要件は主に以下の通りでした。
- 振り返りができること
- 誰が対応するかをはっきりさせること
- アラートの傾向と対策を行うための集計
- 対応漏れ防止
- 1つのインシデントにn個のアラートが発生したあと、このアラートはこのインシデント、と関連付ける仕組み欲しい
【参考】インシデント管理ツールの検討
やりたいことに対し、はじめはPagerDutyかReactioを利用しようとしていました。
Mackerelの通知チャネルの連携サービスに出てきていたので、簡単に利用できそうだったからです。
しかし、以下の理由で見送りました。
■ Reactio
調べた際、2016/07/04以降ブログの更新がなく、保守されているか不安を覚え利用をやめました。
※12/4にSSL証明書の更新があり、保守はされているようです。
■ PagerDuty
大人の事情で利用を断念しました。
残念ながら外部サービスに頼ることができなさそうであったため、
手軽に実装できそうな、Redmineを利用することにしました。
Redmineだけではエスカレーションの自動化が困難です。
通常ですと、通知のタイミングと手段と相手をあらかじめ決めたポリシーに沿って、自動的に通知する仕組みを入れたいところです。
良い代替案が見つからなかったため、いったんこの自動化は見送ることにしました。
まあ、Lambdaが利用できるので、エスカレーション機能を自作できないことはありませんね...
まとめ
AWSのサービスを駆使してMackerelのアラートをRedmineのチケットにすることができました。
まだ有用な集計ができるほど期間が経っていませんが、少なくとも振り返りの資料として役立っています。
今回はRedmineとの連携に限定しましたが、MackerelとLambdaで連携できるため、エスカレーションの自動化、アラート発生時のサーバ情報の収集、オートスケール、などにも転用できそうです。