EC2スケジュールイベントをLambdaを使って通知してみた

2016.08.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

西澤です。EC2のメンテナンス通知は、AWSからの英語メールが届くものの見落としがちなので、Lambdaから通知できるようなスクリプトを書いてみました。python初心者ですが、動作上は問題が無いことが確認できたので、作成時に苦労した点等について備忘録として残しておこうと思います。もし、おかしな点等あればご指摘いただけるとありがたいです。

EC2スケジュールイベント

EC2インスタンスでは、ハードウェアのメンテナンス等の理由により、再起動、停止/開始、またはリタイア等のイベントが発生することがあります。通常、この通知はAWSアカウントに紐づくEメールアドレス宛に通知されます。詳細については、下記公式ドキュメントをご欄ください。

このメールなのですが、全部英語で届く為に埋もれやすく、注意しないと見落としてしまうこともあります。この情報については、AWS Management Consoleからも確認できますし、下記のようにAPI経由で情報を取得することも可能であることがわかりました。

EC2スケジュールイベントをLambdaから通知する

ということで、これをLambdaから通知する仕組みを作ってみました。

IAMロールの準備

今回のスクリプト実行に必要となるIAM権限は下記の通りです。

  • CloudWatchLogsへのログ出力
  • EC2インスタンス情報の参照
  • SNSへの通知

具体的には下記のようなIAMロールを作成してLambda実行用としました。

  • Permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:Describe*",
"sns:Publish"
],
"Resource": "*"
}
]
}
  • Trust Relationships
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

SNSトピックの準備

今回は、メール通知を想定したスクリプトとなっています。SNSトピックを作成し、通知したいメールアドレスをサブスクライブしておきましょう。具体的な手順はここでは割愛します。

Lambdaスクリプト

詳しい方に教えてもらいつつ、python(boto3)で書いてみました。今回苦労した点は後述することにして、処理の概要とスクリプトを載せておきます。

  1. DescribeInstanceStatus結果を取得
  2. 1の結果から、Events情報が存在しなければ処理なしで終了、Events情報があればインスタンスIDを取得して次へ
  3. 対象インスタンスのNameタグを取得
  4. 収集した情報を整形して、SNS publish
# -*- encoding: utf-8 -*-
import boto3

# 通知先のTOPIC名を指定
topic = 'arn:aws:sns:us-west-2:123456789012:cm-nishizawa-test-topic'

def lambda_handler(event, context):
ec2 = boto3.client('ec2')

# describe_instance_statusから情報取得
instance_statuses = ec2.describe_instance_status()['InstanceStatuses']
### Test Eventを利用してテストを行う場合に利用
### instance_statuses = event
print instance_statuses

# Events情報を含むインスタンスのみ処理する
for i in instance_statuses:
instance_events = i.get('Events')

if instance_events is None:
continue
else :
print instance_events

instance_id = i['InstanceId']
print instance_id

# Nameタグを取得
tags = ec2.describe_instances(
InstanceIds=[
instance_id
]
)['Reservations'][0]['Instances'][0].get('Tags')

if tags is None:
instance_name_tag = 'NoNameTag'
else:
for j in tags:
if j['Key'] == 'Name':
instance_name_tag = j['Value']
print instance_name_tag

# 通知用メッセージ作成
message = u'予定されたEC2イベントが見つかりました。以下をご確認ください。\n\n'
for k in i['Events']:
message = message \
+ 'Instance: ' + instance_name_tag + '(' + instance_id + ')\n' \
+ 'Code: ' + k.get('Code') + '\n' \
+ 'Description: ' + k.get('Description') + '\n' \
+ 'NotAfter: ' + format(str(k.get('NotAfter'))) + '\n' \
+ 'NotBefore: ' + format(str(k.get('NotBefore'))) + '\n' \
+ '\n'
print message

# SNS通知
sns = boto3.client('sns')
subject = '[WARN] EC2スケジュールイベント通知: ' + instance_name_tag + '(' + instance_id + ')'
response = sns.publish(
TopicArn=topic,
Message=message,
Subject=subject
)
print response

Lambda設定については、あまり特別なことはしていません。念の為、Timeout=30sに変更しておきました。

課題

今回のスクリプトの課題は下記の通りです。今回はCloudWatch Eventsのスケジュールから、1日1回実行するような想定で作成しました。

  • AWSからの通知の補助的な位置付けなので、エラー処理は一切含めていない
  • チェックしたタイミングでスケジュールされているイベントは、繰り返し通知される
  • 本当にスケジュールイベントが発生するまで、完全なテストを行うことができない

苦労したところ

今回の処理で工夫したところ、詰まったところを簡単にメモしておきます。

  • 日本語コメント、日本語の件名、本文のメール送信を行いたかったので、# -*- encoding: utf-8 -*-を先頭に記載した
  • datetime型を文字列に変換する為、strを使った
  • Noneが返される可能性があるKeyの指定では、getを使った
  • そもそもスケジュールイベントがある状態を作り出すことができない為、想定でコマンド出力結果を用意する必要があった

ちなみにLambdaでは、任意のTest Eventを設定することが可能なので、今回は下記のような方法で動作確認を行いました。

  1. aws ec2 describe-instance-status --query "InstanceStatuses[]"でまずは雛形のjsonを用意する
  2. インスタンスの予定されたイベント - Amazon Elastic Compute Cloudの出力サンプルを参考にして、1の結果からテストイベント用jsonを作って登録
  3. Lambdaスクリプト内の13行目をコメントインして動作確認

下記はテストイベントとして設定したサンプルです。

[
  {
    "InstanceId": "i-xxxyyyzz",
    "InstanceState": {
      "Code": 16,
      "Name": "running"
    },
    "AvailabilityZone": "us-west-2b",
    "SystemStatus": {
      "Status": "ok",
      "Details": [
        {
          "Status": "passed",
          "Name": "reachability"
        }
      ]
    },
    "InstanceStatus": {
      "Status": "ok",
      "Details": [
        {
          "Status": "passed",
          "Name": "reachability"
        }
      ]
    },
    "Events": [
      {
        "Code": "instance-stop",
        "Description": "The instance is running on degraded hardware",
        "NotAfter": "2016-08-10T18:00:00.000Z",
        "NotBefore": "2016-08-10T16:00:00.000Z"
      }
    ]
  }
]

他に何か良いテストの方法を御存知の方がいたら教えてください。

まとめ

ちなみにクラスメソッドメンバーズにご加入いただきますと、EC2の予定されたイベントが確認され次第、EC2インスタンスの状態確認と日本語でのメール連絡を行っておりますので、このようなスクリプトの用意は不要です。

目新しさも無く、拙いスクリプトで恥ずかしい限りですが、備忘を兼ねてブログにしてみました。どこかの誰かのお役に立てば嬉しいです。