AWS Lambda(Python3 boto3)をローカル環境で構築してからアップしよう


はじめに

AWS Lambda(Python3)を構築する際、直接Lambda関数の画面で作業してもいいのですが、デバッグしづらいし、エディタは使いづらい。
その為、ローカル環境で一度作成してから、AWS Lambdaへアップする方法を記載します。

→2017/12/02追記
AWSアップデートされCould9なんてものが追加されたし、AWS Lambdaエディタ機能も追加されたので、今後はブラウザ上だけで充分かもね(´・ω・`)


環境

各インストール方法はぐぐってね。
OSはWindows10 Proにて確認。

事前にAWS AMIアカウントから「アクセスキー」、「シークレットアクセスキー」を発行後
コマンドプロンプト「aws configure」で登録してね。

$ aws configure
aws_access_key_id = アクセスキー
aws_secret_access_key = シークレットアクセスキー
region = ap-northeast-1
output = json

ATOMアドオン

  • atom-beautify スクリプト整形用※
  • atom-runner Python実行用★必須
  • autocomplete-python Python補完用※
  • file-icons アイコンが変わるよ
  • highlight-selected 選択文字列ハイライト表示※
  • japanese-menu ATOM日本語化※
  • minimap  スクロールバー※
  • python-autopep8 Python補完用
  • show-ideographic-space 全角スペース表示用※
  • symbols-tree-view 関数、変数一覧表示
  • web-search ATOMからWeb検索用

atom-runnerはATOM上でPythonを実行するのに必須。※印はお勧めアドオンです。


動作確認(Python3コード)

以下コードをコピーし、適当なファイル名+拡張子(.py)で作成後、「ALT+R」でPythonを実行。
稼働中のEC2インスタンスIDとTag名(サーバー名)を取得します。

import boto3
import json

ec2c = boto3.client('ec2')

##############################################
#起動しているインスタンスID取得
##############################################
def func_GetInstanceId():

    instanceid_List = []
    retec2c = ec2c.describe_instances(
        Filters=[
            {'Name':'instance-state-name','Values': ['running']}
        ]
    )
    for reservation in retec2c['Reservations']:
      for instanceid in reservation['Instances']:
        instanceid_List.append(instanceid['InstanceId'])

    return instanceid_List
##############################################
#インスタンスIDからタグ名取得
#引数:インスタンスID(i-1234567890ABCDEFG) 
#戻り値:タグ名 Win2016_SV、未設定の場合は「サーバー名未設定」
##############################################
def func_RET_ServerTagName(SET_InstanceId):

    TagName = ec2c.describe_tags(
        Filters=[
        {'Name': 'resource-id', 'Values': [SET_InstanceId]}
        ,{'Name': 'tag-key', 'Values': ['Name']}
        ])
    try:
        RET_ServerTagName=TagName['Tags'][0]['Value']
    except Exception as e:
        RET_ServerTagName="サーバー名未設定"
    return RET_ServerTagName

################################
# MAIN
################################
#def lambda_handler(event, context):
if __name__ == "__main__":

    #起動中のEC2インスタンスIDを取得
    insid_list=func_GetInstanceId()

    for insid in insid_list:
        #インスタンスIDからTag名(サーバー名)を取得    
        ins_name=func_RET_ServerTagName(insid)
        print(ins_name + "(" + insid + ")")

出力例

以下インスタンスIDは適当に書き換えています

Win2016SV1(i-123456789ABCDEFG)
サーバー名未設定(i-123456789)
Windows2012SV2(i-0123456789abcd)

AWS Lambdaにアップする際に修正する箇所

if __name__ == "__main__":

をコメントアウトし

def lambda_handler(event, context):

を有効にしましょう。

#def lambda_handler(event, context):
if __name__ == "__main__":

def lambda_handler(event, context):
#if __name__ == "__main__":

あとはAWS Lambdaにコピペして動作を確認。

時刻取得その1

時刻を取得する際、ローカル環境ではOSの時刻を取得する為、JSTで取得できますが
Lambda上はGMTで取得する為、+9時間する必要がありますよ。

import datetime

#Lambda上で実行する場合
#def lambda_handler(event, context):

#ローカルで実行する場合
if __name__ == "__main__":

    #ローカルで実行する場合
    d = datetime.datetime.now()

    #Lambda上で実行する場合
    #d = datetime.datetime.now() + datetime.timedelta(hours=9)

    print("d時間:" + str(d))

時刻取得その2

もしくは環境変数キー名「TZ」値「Asia/Tokyo」を追加すればJSTで取得する為、上記の様に+9時間しなくても大丈夫。

グローバル変数で時刻を取得すると・・・だれかが死ぬ

日付時刻を取得する際、関数内で取得せず、グローバル変数として取得した場合、
AWS Lambda上2回目の動作以降に同じ時間を取得するはめになりますので注意。

※障害例
時刻取得処理をトリガーにて9:00開始、10分毎に実行した際、
9:00に実行した場合は、初回に実行する為「9:00」と正確に取得しますが、次回9:10に実行した際にも「9:00」と取得される。9:20以降もずーっと「9:00」で取得されてしまう。

またこれがLambdaテスト実行時には再利用されないため障害は発生せず、トリガーで定期的に実行すると発生するやっかいなヤツ。

・2回目以降のLambdaを再利用している為発生する模様。
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-introduction.html

時刻取得(正)

import datetime

def lambda_handler(event, context):
    d = datetime.datetime.now() + datetime.timedelta(hours=9) # ◎関数内の為毎回取得します。
    print("d時間:" + str(d))

時刻取得(誤)

import datetime

d = datetime.datetime.now() + datetime.timedelta(hours=9) # ×グローバル変数で取得しちゃダメ

def lambda_handler(event, context):
    print("d時間:" + str(d))

以上