LoginSignup
3
5

More than 3 years have passed since last update.

DatadogとAmazon Connectを用いた電話通知実施してみた【監視】

Last updated at Posted at 2020-04-07

TL; DR

監視において様々な通知方法が求められると思いますが、影響度の高いものに関しては電話連絡が必要だったりすると思います。
今回はその要件がありDatadogAmazon Connectを用いて実装しました。
思った以上に簡単に実装できたので備忘録も兼ねて書き起こしていきます。

簡単な概要図としては以下です。
dd_connect.png

では早速やっていきます。

Amazon Connect

Amazon Connectはコールセンターを容易に作成できるという触れ込みでGAされたと記憶してましたので、正直結構限定的なのかなーと思っていて今まで触ってきませんでした。
しかし今回のような単純に電話かけたいみたいな要件でも簡単に利用することができます。

インスタンス作成

まずはAmazon Connectインスタンスを作成します。
赤枠のインスタンスを追加するをクリックします。
sc.png
作成後アクセスできる設定用の画面があるのですが、おそらくそれとConnectの中身が動くためのターゲットグループ的なのを作るのでしょう。
Connect用コンソールのURLを決めます
sc 1.png
管理者を決めますが、ここで作らなくてもあとから作成できるのでスキップしても大丈夫です。
個人的にはここで作っといた方がいいと思います。
sc 2.png
ここはそのままで次へ
sc 3.png
ログなどはS3バケットに保存されるのですがデフォルトは新規で作成されますが、設定カスタマイズからバケットを指定することも可能です。
sc 4.png
設定を確認をして、インスタンスの作成を押下すると数分で作成されます。
sc 5.png
作成後はアクセスURLからアクセスが可能です。
sc 6.png

電話番号取得

上記のURLへアクセスするとダッシュボードが表示されます。
sc 7.png
左ペインから電話番号を選択します
sc 8.png
電話番号の取得を押下します。
sc 9.png
すると画面に遷移するのですが、日本を選択すると注意画面がでてきます。
最初反社チェック的なものかと思ったのですが、一旦クリックするとその後保存ボタンがアクティブになりました。
問い合わせたところバグらしくすみません…とおっしゃってたので仕様だと思ってぽちぽちしてくださいw
sc_10.png
無事に電話番号が取得できました。
sc 10.png

問い合わせフローの設定

次にこの番号がどのようなアクション(問い合わせフロー)をするのかをGUIで作成していきます。

左ペインから問い合わせフローを選択
sc 11.png
問い合わせフローの作成をポチします。
sc 12.png
こんな感じで作成してください。大事なのは赤枠です。
sc 13.png
プロンプトの再生をクリックすると右側ににょっと出てくるのでこんな感じで設定してください。赤枠は任意で大丈夫ですが、Lambdaで利用するものなのでお好みで設定してください。
sc 14.png
設定が終わったら保存と公開を押しましょう。後ほどLambdaで利用するのでURLを控えておきましょう。
sc 15.png

電話番号との紐付け

先ほど作成した電話番号の画面に行き、問い合わせフローを作成したものに変更します。
sc 16.png
変更後保存を押下します。
こちらも後ほどLambdaで利用するので電話番号を控えておきましょう。

これでAmazon Connect側の設定は完了です。

AWSリソース

必要なAWSリソースを作成していきます。

Lambda

実際にAmazon Connectを介して電話するLambda関数を作成していきます。
と言ってもConnect側は非常にシンプルです。

今回はPythonで実装したのでboto3のConnect Clientでstart_outbound_voice_contactを利用します。
コードはこんな感じです。

# coding=utf-8
import logging
import json
import os
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)
headquarters = os.environ['INCIDENT_MANAGEMENT_HEADQUARTERS']


def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    message = event['Records'][0]['Sns']

    connect = boto3.client('connect')
    ssm = boto3.client('ssm')

    alertMessage = message['Subject']
    message = alertMessage + 'です。確認してください。'

    logger.info("Message: " + str(message))

    for boss in headquarters.split(","):
        destination = ssm.get_parameter(
            Name=boss,
            WithDecryption=True
        )
        phoneNumber = destination['Parameter']['Value']
        connect.start_outbound_voice_contact(
            DestinationPhoneNumber=phoneNumber,
            ContactFlowId='1234567a-zyxw-9876-12ab-098765def432',
            InstanceId='987abc12-098v-gh56-78ij-4567klmn12op',
            SourcePhoneNumber='+811234567890',
            Attributes={
                'alarm': message + message
            }
        )

先ほど控えておいたURLと電話番号をここで使います。
InstanceId / ContactFlowIdは下記のようになっていますので、ここからとりましょう。
SourcePhoneNumberには取得した電話番号を入れます。

https://[アクセスURL].awsapps.com/connect/contact-flows/edit?id=arn:aws:connect:ap-northeast-1:12345678901:instance/[InstanceId]/contact-flow/[ContactFlowId]

これでOKです。

後ほど設定するDatadogから受け取るペイロードはこんな感じでした。
{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:test-connect:12345678-asnc-0987-zyxw",
      "Sns": {
        "Type": "Notification",
        "MessageId": "0987654-abcd-1234-zyxw-12345abcde",
        "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:test-connect",
        "Subject": "[Recovered] [Synthetics] アプリケーションサイト正常性確認",
        "Message": "@sns-test-connect\n\nMonitor Status: https://app.datadoghq.com/monitors#0987654?group=total · Edit Monitor: https://app.datadoghq.com/monitors#0987654/edit · Event URL: https://app.datadoghq.com/event/event?id=1234567890123456789",
        "Timestamp": "2020-04-07T10:28:12.148Z",
        "SignatureVersion": "1",
        "Signature": "hogehogefugafuga==",
        "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-abcdefghijklmnopqrstu.pem",
        "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:123456789012:test-connect:12345678-asnc-0987-zyxw",
        "MessageAttributes": {}
      }
    }
  ]
}

SNS

特段難しいことはないのでトピックを作成してサブスクリプションをLambdaで作成しましょう。

$ aws sns create-topic \
    --name test-connect
$ aws sns subscribe \
    --topic-arn arn:aws:sns:ap-northeast-1:123456789012:test-connect \
    --protocol lambda \
    --notification-endpoint arn:aws:lambda:ap-northeast-1:123456789012:function:test-connect

これでOKです。

Dadadog Synthetics

DatadogとAWSの連携はよしなにやってください。
Datadogでの外形監視にはSyntheticsというものが利用できます。
設定は非常に簡単でぽちぽちと入力するだけで利用が可能です。無料トライアルもありますし、10,000リクエスト/月まで$5で利用可能なので既に利用されている方はおすすめです。
APIのテストやブラウザテストなど幅広く実施できますが、今回は単純にサイトのダウンを検知したかったのでシンプルに設定していきます。
sc 17.png
こんな感じでURLを指定して閾値などを設定していきます。
sc_11.png

通知設定はこんな感じです。

{{#is_alert}} [サービスサイト異常検知](https://hogehoge.jp/) @sns-dd-2-connect  @slack-alert-test  {{/is_alert}}
{{#is_recovery}} [サービスサイト](https://hogehoge.jp/) 回復 @slack-alert-test {{/is_recovery}} 

あとはアラートを設定して適当にやるだけです。
Slack通知もちゃんときてます。
sc 18.png
電話もちゃんときてます。
Screenshot_20200408-020946.png

ばっちりですね。

所感

24/7で頑張って対応する!みたいなのが少しでも減らせたら嬉しいですね。
ただほんとにクリティカルなものってユーザーからの指摘で気付くみたいなこともあると思うので、システム側で全て拾いきるにはまだまだ難しいですね。

Appendix

コマンドでちまちま作るのめんどくさいのでTerraformでざばっとAWSリソースは作ったので一応

リソースざばっと作るコード
main.tf
provider "archive" {
  version = "~> 1.3"
}

provider "aws" {
  region  = "ap-northeast-1"
  version = "~> 2.50.0"
}

locals {
  name = test-connect
}

variable "params" {
  hoge = "+811234567890"
  fuga = "+810987654321"
}

resource "aws_ssm_parameter" "params" {
  for_each = var.params
  name     = each.key
  type     = "SecureString"
  key_id   = "alias/aws/ssm"
  value    = each.value
}

resource "aws_sns_topic" "topic" {
  name = local.name
}

resource "aws_sns_topic_subscription" "subscription" {
  topic_arn = aws_sns_topic.topic.arn
  protocol  = "lambda"
  endpoint  = aws_lambda_function.function.arn
}

resource "aws_cloudwatch_log_group" "log_group" {
  name              = "/aws/lambda/${local.name}"
  retention_in_days = 14
}

data "aws_iam_policy_document" "lambda_assume" {

  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

data "aws_iam_policy_document" "lambda_basic" {

  statement {
    sid     = "putLog"
    effect  = "Allow"
    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]
    resources = "${aws_cloudwatch_log_group.log_group.arn}:*"
  }
}

data "aws_iam_policy_document" "lambda_get_params" {

  statement {
    sid     = "getParams"
    effect  = "Allow"
    actions = [
      "ssm:GetParameters",
      "ssm:GetParameter"
    ]
    resources = [for params in aws_ssm_parameter.params : params.arn]
  }
}

resource "aws_iam_role" "lambda_basic" {
  name = "lambda_basic_${local.name}"
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}

resource "aws_iam_policy" "lambda_basic" {
  name   = "lambda_basic_${local.name}"
  policy = data.aws_iam_policy_document.lambda_basic.json
}

resource "aws_iam_policy" "lambda_get_params" {
  name   = "lambda_${local.name}_get_params"
  policy = data.aws_iam_policy_document.lambda_get_params.json
}

resource "aws_iam_role_policy_attachment" "lambda_basic" {
  role       = aws_iam_role.lambda_basic.name
  policy_arn = aws_iam_policy.lambda_basic.arn
}

resource "aws_iam_role_policy_attachment" "lambda_get_params" {
  role       = aws_iam_role.lambda_basic.name
  policy_arn = aws_iam_policy.lambda_get_params.arn
}

resource "aws_iam_role_policy_attachment" "lambda_connect_fullaccess" {
  role       = aws_iam_role.lambda_basic.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonConnectFullAccess"
}

data "archive_file" "function" {
  type        = "zip"
  source_file = "${local.name}.py"
  output_path = "${local.name}.lambda.zip"
}

resource "aws_lambda_function" "function" {
  function_name    = local.name
  filename         = data.archive_file.function.output_path
  runtime          = "python3.7"
  handler          = "${local.name}.lambda_handler"
  source_code_hash = data.archive_file.function.output_base64sha256
  role             = aws_iam_role.lambda_basic.arn

  environment {
    INCIDENT_MANAGEMENT_HEADQUARTERS = "hoge,fuga"
  }

  lifecycle {
    ignore_changes = [
      last_modified,
      source_code_hash,
    ]
  }
}

resource "aws_lambda_permission" "sns" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.function.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = aws_sns_topic.topic.arn
}

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5