akiyoko blog

akiyoko の IT技術系ブログです

Python (Boto3) で Amazon SNS (Simple Notification Service) を操作して、メール通知をおこなう

はじめに

AWS で、ある決まったメールアドレスに何らかの通知をおこなう際には、Amazon SNS (Simple Notification Service) を使うのが簡単です。

何かを常にチェックして、ある条件が発生したらメールを送る、というのをやりたいというのはよくあること。しかし AWS の EC2 インスタンスからメールを直接送る方法はデフォルトで制限がかかっていて、解除に手続きごとが必要だったりする。


予め指定された通知先にメールを送るだけなら Amazon Simple Notification Service が圧倒的に簡単。


AWS - Python で Amazon Simple Notification Service を使う - Qiita」より


Amazon SNS は、Python (Boto3 や Boto) を使って操作することができるので、今回は、Boto3 から SNS を操作してメール通知をしてみることにします。



なお、SNS でメール通知を使うには、以下の事前準備が必要となります。


SNS を使う準備が整ったら、Boto3 で SNS を使ったメール通知をするコードを書いてテストしてみます。



 

1. Topic を作成

AWS Management Console 上の SNS Home から、Topic を作成します。
f:id:akiyoko:20151102023904p:plain

「Topic name」を任意のトピック名を設定し、「Create topic」をクリックします。
f:id:akiyoko:20151102023920p:plain


 

2. Email の Subscription を作成

Subscriptions から、「Create subscription」をクリック。
f:id:akiyoko:20151102023935p:plain

「Protocol」に「Email」を選択、「Endpoint」に通知先のメールを入力します。
f:id:akiyoko:20151102023950p:plain

Subscription ID が「PendingConfirmation」となっているのは、通知先のメールアドレスからの購読確認が完了していないためです。
f:id:akiyoko:20151102024010p:plain


 

3. 届いたメールのリンクから購読確認を行う

2. で Subscription を作成した際に、購読確認のためのメールが送信されます。

届いたメールのリンクをクリックし、購読確認を完了させます。

f:id:akiyoko:20151102090936p:plain


リンク先は、このような画面が表示されます。
f:id:akiyoko:20151102024130p:plain

Subscription ID が以下のように書き換われば、事前準備はOKです。
f:id:akiyoko:20151102024151p:plain



 

4. Boto3 で Topic を送信する

Boto3 を使って、Topic を送信するコードを書いてみます。

使用する Boto3 の API は以下となります。

import boto3

sns = boto3.resource('sns')
topic = sns.Topic('arn')

response = topic.publish(
    TargetArn='string',
    Message='string',
    Subject='string',
    MessageStructure='string',
    MessageAttributes={
        'string': {
            'DataType': 'string',
            'StringValue': 'string',
            'BinaryValue': b'bytes'
        }
    }
)



Topic を特定するには Topic の ARN が必要となるのですが、「arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:test-complete」などとベタで書くのではなく、sns.create_topic() を使ってトピック名から取得するようにしてみます。


sns_publish.py

#!/usr/bin/python

import boto3
import logging

TOPIC_NAME = 'test-complete'

logging.basicConfig()  # http://stackoverflow.com/questions/27411778/no-handlers-found-for-logger-main
logger = logging.getLogger(__name__)


def main():
    sns = boto3.resource('sns', 'ap-northeast-1')

    # Get a topic arn
    # This action is idempotent, so if the requester already owns a topic with the specified name,
    # that topic's ARN is returned without creating a new topic.
    # http://boto3.readthedocs.org/en/latest/reference/services/sns.html#SNS.ServiceResource.create_topic
    topic_arn = sns.create_topic(Name=TOPIC_NAME).arn
    print("topic_arn={}".format(topic_arn))

    # Publish a message
    response = sns.Topic(topic_arn).publish(
        Subject="test",
        Message="This is a message.",
    )
    print("response={}".format(response))


if __name__ == '__main__':
    main()


<実行結果>

$ python sns_publish.py
topic_arn=arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:test-complete
response={'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '5852f1b1-c70b-5d25-970b-bf0187e62a75'}, u'MessageId': '8eddf5cf-c066-5bf3-bd4d-bb7f808b1ca3'}

(ただし、AWS Account ID は、「xxxxxxxxxxxx」と表記しています。)



ちゃんとメールも届きました。

f:id:akiyoko:20151102024510p:plain



なお、上記を実行する前に、


<過去記事>akiyoko.hatenablog.jp

で書いたように、AWS ユーザのアクセスキーを設定しておく必要があります。

$ aws configure
AWS Access Key ID [None]: AKIXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: json


 

5. Boto3 の Waiter と組み合わせて Job の完了通知をする

Boto3 には Waiter と呼ばれる機能が追加され、AWS側で進行中の非同期処理のステータスを定期的にポーリングすることができるようになりました。

Waiter

Boto3 には、AWS リソースにおける事前定義ステータスの変化を自動的にポーリングする "waiter" が付属しています。例えば、Amazon EC2 インスタンスを開始し、waiter を使用してそのインスタンスが "running" 状態になるまで待機する、または新しい Amazon DynamoDB テーブルを作成し、それが使用可能になるまで待機するといったことが可能です。Boto3 では、クライアント API とリソース API の両方に waiter が用意されています。


AWS SDK for Python | アマゾン ウェブ サービス(AWS 日本語)」より



ここでは、

<過去記事>akiyoko.hatenablog.jp
の Python コードを少し書き換えて、トランスコードの完了時に SNS 経由でメールで通知するようにしてみます。


elastic_transcoder_with_sns.py(変更分を抜粋)

    # Wait the job completed
    waiter = transcoder.get_waiter('job_complete')
    waiter.wait(Id=job_id)
    end_time = datetime.now().strftime("%H:%M:%S.%f")[:-3]
    print("end time={}".format(end_time))

    # Publish a message
    response = sns.Topic(topic_arn).publish(
        Subject="Elastic Transcoder job has completed",
        Message="Elastic Transcoder job({}) has completed. end_time={}".format(job_id, end_time),
    )

boto3-sample/elastic_transcoder_with_sns.py at master · akiyoko/boto3-sample · GitHub


取得した Waiter は Elastic Transcoder のジョブの完了を同期的に待ち受けることができるので、ジョブが完了すると、SNSへの通知が実行されることになります(完了しないまま 60分経過するとエラーが発生します)。ただし、Waiter は 30秒毎にポーリングしているので、ジョブの完了直後に即座に次の処理(ここでは SNS の通知)が実行されないのは、少し注意が必要です。




<実行結果>

 python elastic_transcoder_with_sns.py
response={u'Pipeline': {u'Status': u'Active', u'ContentConfig': {u'Bucket': u'boto3-transcoder-out', u'Permissions': []}, u'Name': u'HLS Transcoder', u'ThumbnailConfig': {u'Bucket': u'boto3-transcoder-out', u'Permissions': []}, u'Notifications': {u'Completed': u'', u'Warning': u'', u'Progressing': u'', u'Error': u''}, u'Role': u'arn:aws:iam::xxxxxxxxxxxx:role/Elastic_Transcoder_Default_Role', u'InputBucket': u'boto3-transcoder-in', u'OutputBucket': u'boto3-transcoder-out', u'Id': u'1446426875852-txifda', u'Arn': u'arn:aws:elastictranscoder:ap-northeast-1:xxxxxxxxxxxx:pipeline/1446426875852-txifda'}, 'ResponseMetadata': {'HTTPStatusCode': 201, 'RequestId': '14c3cc72-80ff-11e5-a890-418cda4d8c85'}}
start time=10:14:36.256
job={u'Job': {u'Status': u'Submitted', u'Playlists': [], u'Outputs': [{u'Status': u'Submitted', u'PresetId': u'1351620000001-200030', u'Watermarks': [], u'SegmentDuration': u'10.0', u'Key': u'HLS/1M/D0002022073_00000/sample', u'Id': u'1'}], u'PipelineId': u'1446426875852-txifda', u'Output': {u'Status': u'Submitted', u'PresetId': u'1351620000001-200030', u'Watermarks': [], u'SegmentDuration': u'10.0', u'Key': u'HLS/1M/D0002022073_00000/sample', u'Id': u'1'}, u'Timing': {u'SubmitTimeMillis': 1446426876240}, u'Input': {u'Container': u'auto', u'FrameRate': u'auto', u'Key': u'D0002022073_00000/sample.mp4', u'AspectRatio': u'auto', u'Resolution': u'auto', u'Interlaced': u'auto'}, u'Id': u'1446426876199-g5laan', u'Arn': u'arn:aws:elastictranscoder:ap-northeast-1:xxxxxxxxxxxx:job/1446426876199-g5laan'}, 'ResponseMetadata': {'HTTPStatusCode': 201, 'RequestId': '14fd2bc1-80ff-11e5-ac43-6395652a5e7d'}}
end time=10:15:06.438


<メール件名>

Elastic Transcoder job has completed

<メール本文>

Elastic Transcoder job(1446426876199-g5laan) has completed. end_time=10:15:06.438

 

しかしながら・・

Elastic Transcoder の完了通知として使うのであれば、create_pipeline() 時に Notificationsキーワードを引数に追加することで、非同期で SNS の Topic を送信することができるので、そちらの方が何かと都合のよいことがありそうです。 *1


elastic_transcoder_with_sns2.py

# Create a pipeline
response = transcoder.create_pipeline(
    Name=PIPELINE_NAME,
    InputBucket=IN_BUCKET_NAME,
    OutputBucket=OUT_BUCKET_NAME,
    Role=role.arn,
    Notifications={
        'Progressing': '',
        'Completed': topic_arn,
        'Warning': '',
        'Error': ''
    },
)

boto3-sample/elastic_transcoder_with_sns2.py at master · akiyoko/boto3-sample · GitHub


<メール件名>

Amazon Elastic Transcoder has finished transcoding job 1446431199244-xkwsd6.

<メール本文>

{
  "state" : "COMPLETED",
  "version" : "2012-09-25",
  "jobId" : "1446431199244-xkwsd6",
  "pipelineId" : "1446431198858-ib1znz",
  "input" : {
    "key" : "D0002022073_00000/sample.mp4",
    "frameRate" : "auto",
    "resolution" : "auto",
    "aspectRatio" : "auto",
    "interlaced" : "auto",
    "container" : "auto"
  },
  "outputs" : [ {
    "id" : "1",
    "presetId" : "1351620000001-200030",
    "key" : "HLS/1M/D0002022073_00000/sample",
    "segmentDuration" : 10.0,
    "status" : "Complete",
    "statusDetail" : "Some individual segment files for this output have a higher bit rate than the average bit rate of the transcoded media. Playlists including this output will record a higher bit rate than the rate specified by the preset.",
    "duration" : 40,
    "width" : 640,
    "height" : 360
  } ]
}


 

まとめ

Amazon SNS でメール通知ができるようになると、AWS の非同期タスクが完了したタイミングやエラーが発生した場合に通知を出すことができ、例えば、CloudWatch ログだと少しデバッグがしにくいというケースで、容易にデバッグができるようになったりします。

便利ですね。

*1: Lambda を使う場合にも、タイムアウト時間を校了する必要がないので、非同期で SNS通知する方がよいでしょう。