Amazon Connectを使って電話からEC2を再起動する

はじめに

Amazon Connectでは、事前に用意された処理を組み合わせることで、自由に電話応対フロー(Contact Flow)を作成することができます。
例えば、通話中にユーザがプッシュした番号によって案内の内容を変えたり、ユーザの電話番号によってどのオペレータにつながるか指定することができます。
事前に用意された処理はブロックと呼ばれていますが、その中にはAWS Lambdaを実行するブロックも含まれています。
なので、電話でAWSリソースの参照や操作ができます。
今回はAmazon ConnectをつかってEC2インスタンスを再起動する仕組みを作りました。

概要

電話をかけると次のようなやりとりが発生します。
(Amazon Connect)

You have 3 instances. Which one do you want to reboot?
i-xxxxxx is number 0.
i-yyyyyyy is number 1.
i-zzzzzzz is number 2.

(ユーザ)
"1#"と押す

(Amazon Connect)
指定されたi-yyyyyyyの再起動を開始した後

i-yyyyyyy is rebooting now.

電話切れる。

今回作ったContact Flowです。
amazon-connect-contact-flow
下記のような処理を行っています。
Contact Flow

処理の単位であるブロックの一覧はこちらをご覧ください。
現時点で30個近いブロックが用意されていますが、今回利用するのは次の5つです。

  • Check contact attributes: 電話番号等ユーザ情報を元に処理を分岐させる
  • Play prompt: ユーザにメッセージを返す
  • Store customer input: ユーザにメッセージを返した後、ユーザから入力(プッシュ番号)を受け取り、保存する
  • Invoke AWS Lambda function: Lambdaファンクションを実行する
  • Disconnect / hang up: 電話を切る

Contact Flowの作成

Contact Flowの作成はVirtual contact center instanceの管理ページから行います。
https://{エンドポイント}/connect/contact-flows
Virtual contact center instanceの作成方法はこちらのエントリーを参照ください。
AWSのコールセンターサービス Amazon Connectの試し方

画面右上にある「Create contact flow」から作成を開始します。
Screen Shot 2017-03-30 at 6.14.55 PM

発信元チェック

「Check contact attributes」ブロックで電話の発信元をチェックしています。
知らない人が僕のサーバを再起動するのは避けたいからです。
caller-check

僕の電話番号以外から電話がかけられた場合、「Play prompt」ブロックでメッセージを流して「Disconnect / hang up」ブロックで電話を切ります。
deny-call

EC2インスタンスリストの取得

発信元の電話番号に問題がない場合、「Invoke AWS Lambda function」ブロックでAWS Lambdaファンクションを呼び出し、インスタンスIDのリストを受け取ります。
Screen Shot 2017-03-30 at 6.28.59 PM
このブロックで指定するのはLambadaファンクションのARNとLambdaに渡すパラメータです。ここでは"Action: List"というパラメータを渡しています。
注意が必要なのはTimeoutの最大値が8秒と短い点です。なので、時間がかかる処理には向きません。
list-instances

AWS Lambdaファンクション側のコードです。

import boto3

def lambda_handler(event, context):
    ec2 = boto3.client("ec2")
    resultMap = {}
    action = event['Details']['Parameters']['Action']
    
    if action == 'List':
        instances = ec2.describe_instances()
        if len(instances['Reservations']) == 0:
            resultMap['NumInstances'] = '0'
        else:
            instance_ids = [i['InstanceId'] for r in instances['Reservations'] for i in r['Instances']]
            resultMap['NumInstances'] = str(len(instance_ids))
            resultMap['InstanceIds'] = ','.join(instance_ids)
            resultMap['InstanceIdList'] = '<speak>You have %s instances. Which one do you want to reboot?' % len(instance_ids)
            for i, instance_id in enumerate(instance_ids):
                resultMap['InstanceIdList'] += '<say-as interpret-as="characters">%s</say-as> is number %s.' % (instance_id, i)
            resultMap['InstanceIdList'] += '</speak>'
    elif action == 'Reboot':
        instance_ids = event['Details']['Parameters']['InstanceIds'].split(',')
        instance_id = instance_ids[int(event['Details']['Parameters']['InstanceIndex'])]
        ec2.reboot_instances(InstanceIds=[instance_id])
        resultMap['RebootInstance'] = '<speak><say-as interpret-as="characters">%s</say-as> is rebooting now.</speak>' % (instance_id)
        
    return resultMap

「Invoke AWS Lambda function」ブロックから渡されたパラメータは"event['Details']['Parameters']"で受け取ります。
また、Lambdaからの返り値はAWS Connectで利用できます。
Lambdaファンクションには必要な権限を持ったIAMロールを割り当ててください。

LambdaがAWS Connectから受け取る値や返り値の制限等、詳しい説明は下記ページを参照ください。
Granting Amazon Connect Access to AWS Lambda Functions
AWS ConnectからLambdaを呼び出すために、リソースポリシーの設定も必要です。その設定方法もこのページに記載されています。

ユーザによるインスタンスの選択

Lambdaから返されたEC2インスタンスのリストをユーザに提示し、リブートするインスタンスを選択させましょう。
「Store customer input」ブロックを使います。
select-instance
先ほどのLambdaファンクションのレスポンスはこんな感じなので、InstanceIdListの内容をユーザに伝えます。

{
  'InstanceIdList': '<speak>You have 3 instances.Which one do you want to reboot?<say-as interpret-as="characters">i-xxxx</say-as> is number 0.<say-as interpret-as="characters">i-yyyy</say-as> is number 1.<say-as interpret-as="characters">i-zzzz</say-as> is number 2.</speak>',
  'InstanceIds': 'i-xxxx,i-yyyy,i-zzzz'
 }

ここでユーザが入力した数字はシステムに保存されます。

インスタンス再起動

リブートするインスタンスが選択されたので、「Invoke AWS Lambda function」ブロックで指定のインスタンスを再起動します。
今回は3つのパラメータを送っています。

  • Action: Reboot
  • InstanceIds: (先ほどLambdaからかえされたInstanceId)
  • InstanceIndex: (ユーザの入力した番号)

Screen Shot 2017-03-30 at 6.54.27 PM
画像からは見切れていますが、ARNは先ほどのLambdaファンクションと同じにしています。
なので、先ほどのLambdaファンクションが上記パラメータを受け取り、指定されたインスタンスをリブートします。

終了

リブート開始後、LambdaからAmazon Connectに

{
  'RebootInstance': u'<speak><say-as interpret-as="characters">i-zzzz</say-as> is rebooting now.</speak>'
}

のような値が返されるので「Play prompt」ブロックでユーザに伝えます。
Screen Shot 2017-03-30 at 7.17.43 PM

電話を切るために「Disconnect / hang up」ブロックにつなげて終了です。
Screen Shot 2017-03-30 at 7.19.06 PM

最後に

Amazon Connectを使って電話からEC2を再起動する仕組みを作りました。
読み上げのリプレイやユーザ選択の確認、エラー処理等、実際はもっと作り込みが必要ですが基本的なフローであればあっという間に作ることができます。
Amazon Connectはコールセンター以外の用途としても便利なのでぜひ使って見てください。

参考URL