Help us understand the problem. What is going on with this article?

AWSのメンテナンス情報をSlackに通知する

2019/7/25 追記

AWS Chatbotというサービスで簡単にAWS Healthのイベントを
Slackに簡単に通知できるようになりました。

個別に通知内容をカスタマイズしたい場合は変わらずLambda関数を作る必要がありますので
そのような場合に本記事の内容が参考になれば幸いです。

はじめに

EC2の予定されたイベントなどはルートアカウントのメールアドレス宛に通知されますが、
これだけでは関係者で共有したい場合は不便な場面もあります。

CloudWatch Events では AWS Health イベント を イベントパターンに指定できるため、
アクションでLambda関数の実行を指定し、Slackへの通知を行ってみたいと思います。

Amazon CloudWatch Events での AWS Health イベントのモニタリング
https://docs.aws.amazon.com/ja_jp/health/latest/ug/cloudwatch-events-health.html

Incoming Webhookの設定(Slack)

まず Slack 側の設定を済ませておきます。
https://<your-team-domain>.slack.com/apps/manage/custom-integrations
から Incoming WebHooks の Configuration を追加します。

通知先のチャネル名を選択し、Add します。

image.png

追加後、発行される Webhook URL を控えておきます。

Lambda関数

ひよコードですが、ご容赦ください:hatched_chick:
環境変数で WEBHOOK_URL を設定します。
Description に関しては長すぎる場合があるので、以下では512文字までを表示するようにしています。

LambdaFunction.py
import json
import os
import logging

from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    webhook_url = os.environ['WEBHOOK_URL']
    message = 'AWS Healthイベントを検知しました。対象のリソースを確認してください。' 

    slack_message = {
        'username': 'AWS Health Event Notification',
        'icon_emoji': ':warning:',
        'text': message,
        'attachments': [
            {
                'fallback': 'AWS Health Event Description.',
                'color': 'warning',
                'title': event['detail']['eventTypeCode'],
                'title_link': 'https://phd.aws.amazon.com/phd/home',
                'fields': [
                    {
                        'title': 'Account ID',
                        'value': event['account'],
                        'short': True
                    },
                    {
                        'title': 'Region',
                        'value': event['region'],
                        'short': True
                    },
                    {
                        'title': 'Service',
                        'value': event['detail']['service'],
                        'short': True
                    },
                    {
                        'title': 'Start Time',
                        'value': event['detail']['startTime'],
                        'short': True
                    },
                    {
                        'title': 'Description',
                        'value': event['detail']['eventDescription'][0]['latestDescription'][0:511],
                        'short': False
                    }
                ]
            }
        ]
    }

    req = Request(webhook_url, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted.")
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

CloudWatch Events の設定

イベントソースの設定で Health を設定します。
イベントタイプを 特定のヘルスイベント に設定すると任意のサービスやイベントに絞って通知を行えますが、
ここでは すべてのイベント を設定します。

ターゲットには上記で作成した Lambda 関数を指定します。
入力の設定は、発生したイベント全体を関数に渡したいため、デフォルトの 一致したイベント で OK です。

image.png

ルール名を入力し、ルールの作成を完了します。

image.png

対象の Lambda 関数が自動的に更新され、トリガーに CloudWatch Events が設定されます。

image.png

結果サンプル

image.png

注意点など

CloudWatch Events で検知できるのは EBS ボリュームの消失イベント、
すべての予定された変更イベントなど自アカウントに関連するもののみです。
サービスの障害情報など AWS アカウントに固有でないものは対象外です。

2018/7/23 追記

面倒ですが、通知が必要なリージョン毎に CloudWatch Events と Lambda を設定する必要があります。
通常、イベントパターンに設定するサービスがグローバルサービスである場合は、エンドポイントが
us-east-1 米国東部(バージニア北部)にのみ存在するため、CloudWatch Events も us-east-1 に設定します。
AWS Health もグローバルサービスなのですが、各リージョン毎にイベントが発生するため
今回の設定もリージョン毎に行う必要があります。(サポート確認済)

毎回手作業で設定するのは厳しいので、CloudFormation StackSets で一括設定できるよう
テンプレートを作成できればと考えています。

8/9 追記 テンプレート書いた

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS Health Events to Slack

Resources:
# Create IAM Role
  LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: LambdaBasicExecution
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*

# Create Lambda Function
  LambdaFunction: 
    Type: "AWS::Lambda::Function"
    Properties: 
      Handler: "index.lambda_handler"
      Role: !GetAtt LambdaExecutionRole.Arn
      Code: 
        ZipFile: !Sub |
          import json
          import os
          import logging
          from urllib.request import Request, urlopen
          from urllib.error import URLError, HTTPError

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
            webhook_url = os.environ['WEBHOOK_URL']
            message = 'AWS Healthイベントを検知しました。対象のリソースを確認してください。' 
            slack_message = {
              'username': 'AWS Health Event Notification',
              'icon_emoji': ':rain_cloud:',
              'text': message,
              'attachments': [
                {
                  'fallback': 'AWS Health Event Description.',
                  'color': 'warning',
                  'title': event['detail']['eventTypeCode'],
                  'title_link': 'https://phd.aws.amazon.com/phd/home',
                  'fields': [
                    {
                      'title': 'Account ID',
                      'value': event['account'],
                      'short': True
                    },
                    {
                      'title': 'Region',
                      'value': event['region'],
                      'short': True
                    },
                    {
                      'title': 'Service',
                      'value': event['detail']['service'],
                      'short': True
                    },
                    {
                      'title': 'Start Time',
                      'value': event['detail']['startTime'],
                      'short': True
                    },
                    {
                      'title': 'Description',
                      'value': event['detail']['eventDescription'][0]['latestDescription'][0:511],
                      'short': False
                    }
                  ]
                }
              ]
            }

            req = Request(webhook_url, json.dumps(slack_message).encode('utf-8'))
            try:
              response = urlopen(req)
              response.read()
              logger.info("Message posted.")
            except HTTPError as e:
              logger.error("Request failed: %d %s", e.code, e.reason)
            except URLError as e:
              logger.error("Server connection failed: %s", e.reason)
      Runtime: "python3.6"
      MemorySize: "128"
      Timeout: "5"
      Environment:
        Variables:
          WEBHOOK_URL: "https://hooks.slack.com~"   

# Create CloudWatch Events Rule
  EventRule:
    Type: "AWS::Events::Rule"
    Properties: 
      Description: "メンテナンス情報をSlackに通知する"
      EventPattern:
        source: 
          - aws.health
      Name: AWSHealth-to-Slack
      State: "ENABLED"
      Targets:
        - 
          Arn: !GetAtt LambdaFunction.Arn
          Id: "TargetFunctionV1"

# Create Invoke Lambda Permission
  PermissionForEventsToInvokeLambda: 
    Type: "AWS::Lambda::Permission"
    Properties: 
      FunctionName: !Ref LambdaFunction
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt EventRule.Arn

WEBHOOK_URL は書き換えてください。
テンプレートの見通しが悪くなってしまいますが、
以下の理由により、直接Lambdaのコードを埋め込んでいます。

直接コードを埋め込む場合の制限として、サイズが4096バイトまで、
アップロードされたコードのファイル名は index.py になります。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html

スタックセット の事前準備

スタックセットを作成するアカウントに AWSCloudFormationStackSetAdministrationRole を
実際のスタックを作成するターゲットに AWSCloudFormationStackSetExecutionRole を
作成する必要があります。以下のドキュメントにロール作成用のテンプレートが用意されています。

image.png

前提条件: スタックセットオペレーションのアクセス権限の付与
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html

今回は、今回は1アカウント内で設定しますので、作業アカウントで両方のロールを作成しておきます。

スタックセットの作成

作成したテンプレートを直接アップロードするか、予め格納したS3URLを指定します。

image.png

詳細の指定ではスタックセット名を指定します。

image.png

デプロイオプションのアカウントの指定で、スタックをアカウントにデプロイを選択し
対象のアカウントIDを入力します。

image.png

リージョンの指定で、デプロイ対象のリージョンを指定します。
デプロイオプションは必要に応じて設定を変更します。

image.png

オプションで任意のタグを設定し、アクセス権限で事前準備で作成したIAMロールを指定します。

image.png

確認画面で設定項目を確認します。
CloudFormationによってIAMリソースが作成されるため、CAPABILITYにチェックを入れて
スタックセットを作成します。

image.png

スタックセットの作成が成功し、指定したリージョンのでスタックが作成されると、
ステータスが OUTDATED から CURRENT に変化します。

image.png

スタックセットの更新、削除

スタックセット作成後に対象のリージョンの追加や削除等が可能です。
スタックセット自体を削除するには、スタックセットで管理されているスタックをすべて削除する必要があります。

image.png

管理しているスタックを削除するには、スタックセットの管理からスタックの削除へ進みます。

image.png

デプロイ時と同様に、スタックを削除するアカウントと削除するリージョンの指定を行います。

image.png

DELETEオペレーションが成功し、管理対象のスタックインスタンスが無くなれば
スタックセットを削除できます。

以上です。
参考になれば幸いです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away