AWS DMSのエラーをSlackに通知する処理をCloud Formationで実装

概要

  • AWS DMS(Database Migration Service)のエラーをSlackへ通知するための実装法
  • AWS Chatbotを試したがUnsupportedEventsが発生していたため、Chatbotの代わりにLambdaで通知するようにした
  • Cloud Formation(yml)で実装

やったこと

先に実装内容を載せておきます

DMS, SNS, Lambda Permission, IAMの設定内容

AWSTemplateFormatVersion: 2010-09-09

Resources:
  DMSAlertInstanceEventSubscription:
    Type: "AWS::DMS::EventSubscription"
    Properties:
      Enabled: true
      EventCategories:
        - "configuration change"
        - failure
        - deletion
        - notification
      SnsTopicArn: !Ref DMSAlertSNSTopic
      SourceIds:
        - instance-test01
      SourceType: replication-instance

  DMSAlertTaskEventSubscription:
    Type: "AWS::DMS::EventSubscription"
    Properties:
      Enabled: true
      EventCategories:
        - "state change"
        - failure
        - deletion
      SnsTopicArn: !Ref DMSAlertSNSTopic
      SourceIds:
        - task-test01
      SourceType: replication-task

  SNSTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: DMSNotificationPublishPolicy
            Effect: Allow
            Principal:
              Service:
              - "dms.amazonaws.com"
            Action:
              - "SNS:Publish"
            Resource:
              - !Ref DMSAlertSNSTopic
      Topics:
        - !Ref DMSAlertSNSTopic
  DMSAlertSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: dms-alert
      Subscription:
        - Endpoint: !GetAtt DMSAlertLambdaLambdaFunction.Arn
          Protocol: lambda
        # - Endpoint: xxxxxx@gmail.com
        #   Protocol: email

  LambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !GetAtt DMSAlertLambdaLambdaFunction.Arn
      Principal: "sns.amazonaws.com"
      SourceArn: !Ref DMSAlertSNSTopic

  DMSAlertLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: dms-alert-lambda-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: dms-alert-lambda-policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:CreateLogGroup
                Resource:
                  - arn:aws:logs:ap-northeast-1:xxxxxx:log-group:/aws/lambda/dms-alert:*

おすすめ

AWS::DMS::EventSubscription の設定値SourceIdsEventCategories はnot requiredなので最初は設定せずに動作確認するのがおすすめです

replication-instanceの動作確認をする場合は、マネコンから設定値Allocated storage (GiB) あたりを適当に設定変更し即時反映 するとinstanceの終了/起動/設定変更の通知が飛ぶはずです

AWS::SNS::Topic の通知対象にLambdaだけでなく自身のEmailも設定しておくと、Slackに通知が来ない場合の原因特定の作業に役立ちます

Lambdaの設定内容

function:
  DMSAlertLambda:
    handler: app/dms_alert/handler.handler
    name: dms-alert
    memorySize: 256
    timeout: 900
    runtime: python3.8
    role: DMSAlertLambdaRole
    environment:
      WEBHOOK_URL: "https://hooks.slack.com/services/XXXX/YYYYYYYY/ZZZZZZZZZZZZZZZ"
      STAGE: ${self:provider.stage}
      CHANNEL: "#dms_alert_channel"
    package:
      individually: true
      include:
        - app/dms_alert/**
import json
import os
import requests
from logging import getLogger, INFO

logger = getLogger(__name__)
logger.setLevel(INFO)
url = os.environ['WEBHOOK_URL']
stage = os.environ['STAGE']
channel = os.environ['CHANNEL']

def handler(event, context):
    logger.info(json.dumps(event))

    text = 'DMS ALERT\n'
    try:
        json_dict = json.loads(event['Records'][0]['Sns']['Message'])
        for key, val in json_dict.items():
            text += f'{key}: {val}\n'

    except Exception as e:
        # 検知内容によってはjsonでないケースがあるが不要なメッセージであるため処理を止める
        # "This is a message to notify that DMS will attempt to send you event notifications.. "
        logger.info(e)
        return

    msg = {
        "channel": channel,
        "text": text
    }

    encoded_msg = json.dumps(msg).encode('utf-8')
    _ = requests.post(url, data=encoded_msg)
    logger.info("message: " + text)

event['Records'][0] の中身のどの値をSlackに通知するか迷いました

結局event['Records'][0]['Sns']['Message'] の中身を全部送信しています

SNS TopicからChatbotにメッセージが送信できなかった

当初Lambdaではなく、AWS Chatbotを経由してメッセージを送信しようと試しましたが、うまく通知されなかったのでここに内容を残しておきます

下記のSNSTopicの設定に加えて、マネージメントコンソールからChatbot Configured clientsの設定画面から上記で追加したTopic紐付けるよう設定(Chatbotの設定は既に登録されていたためCFnで作らなかった)

...  
DMSAlertSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: dms-alert
      Subscription:
        - Endpoint: https://global.sns-api.chatbot.amazonaws.com
          Protocol: HTTPS
        - Endpoint: xxxxxxx@gmail.com
          Protocol: email

しかし、DMSの設定を変更して通知を待てどEmailの方には届くけどSlackの方には届かない状況になりました

CloudWatchが出力しているChatbotのログを確認するとUnsupportedEvents と出ていました

DMSからのEventはSNS Topic経由でChatbotに流してくれないと判断し、渋々Lambdaで送信処理を実装することに決めました(参考サイト: Amazon SNS から AWS Chatbot を経由した通知がうまくいかない原因と対処方法)

まとめ

Chatbotにだって出どこによっては受け流したくないメッセージがある