はじめに
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 を作成します。
「Topic name」を任意のトピック名を設定し、「Create topic」をクリックします。
2. Email の Subscription を作成
Subscriptions から、「Create subscription」をクリック。
「Protocol」に「Email」を選択、「Endpoint」に通知先のメールを入力します。
Subscription ID が「PendingConfirmation」となっているのは、通知先のメールアドレスからの購読確認が完了していないためです。
3. 届いたメールのリンクから購読確認を行う
2. で Subscription を作成した際に、購読確認のためのメールが送信されます。
届いたメールのリンクをクリックし、購読確認を完了させます。
リンク先は、このような画面が表示されます。
Subscription ID が以下のように書き換われば、事前準備はOKです。
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' } } )
- http://boto3.readthedocs.org/en/latest/reference/services/sns.html#topic
- http://boto3.readthedocs.org/en/latest/reference/services/sns.html#SNS.Topic.publish
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」と表記しています。)
ちゃんとメールも届きました。
なお、上記を実行する前に、
<過去記事>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 が用意されています。
- Resource Waiters
- Low-level Client Waiters
ここでは、
<過去記事>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通知する方がよいでしょう。