読者です 読者をやめる 読者になる 読者になる

Miscellaneous notes

主に技術的な雑記的な

AWS Lambdaのソースコードを覗いて動作原理を知る試み

この記事は、Serverless Advent Calendar 2016の14日目の記事です。

qiita.com

昨日は工藤さん(level69)による、「Serverless Meetup Sapporoで話てきた」でした。
工藤さんは元々北海道出身という縁もあることから、先日のServerless Meetup Sapporoに自腹で遠征してくれました。本当にありがとうございます。

serverless.connpass.com

本題

さて、本題ですが、最近はApache (元IBM) Openwiskを筆頭に、IronFunctionsなど、OSSなFaaS実装が増えてきていますが、ふと「Lambdaはどうなってんだろーなー。あ、ていうかLambdaもPythonなら、スクリプト言語だからある程度まではソースコード読めるんでは?」と思い立ったので、じゃあやってみましょうという試みです。

まずはFunctionの呼び出し元を知る

Functionの中でスタックトレースを出力すれば、Functionの呼び出し元が分かるはずです。Lambdaのソースコードの旅はここから始まる!ということで、出力してみましょう。

以下のようなFunctionをアップロードして実行します。

import traceback

def lambda_handler(event, context):
     traceback.print_stack()

結果を見てみましょう。

$ lamvery invoke {}
START RequestId: ed0dbfd9-c134-11e6-92b1-ab90b641e77e Version: $LATEST
  File "/var/runtime/awslambda/bootstrap.py", line 426, in <module>
    main()
  File "/var/runtime/awslambda/bootstrap.py", line 423, in main
    handle_event_request(request_handler, invokeid, event_body, context_objs, invoked_function_arn)
  File "/var/runtime/awslambda/bootstrap.py", line 243, in handle_event_request
    result = request_handler(json_input, context)
  File "/Users/marcy/project/lambda-source/lambda_function.py", line 4, in lambda_handler
END RequestId: ed0dbfd9-c134-11e6-92b1-ab90b641e77e
REPORT RequestId: ed0dbfd9-c134-11e6-92b1-ab90b641e77e  Duration: 0.55 ms   Billed Duration: 100 ms     Memory Size: 128 MB Max Memory Used: 15 MB  

/var/runtime/awslambda/bootstrap.py がLambda Function実行時の真の起点となるソースのようです。

起点のスクリプトをダウンロードしてみる

では、起点のスクリプトが分かったので取得してみましょう。Lambda:invokeFunctionAPIで得られる標準出力の内容は一定の長さを超えると切られてしまうので、S3に入れてみることにします。

以下のようなFunctionをアップロードして実行します。

import boto3

s3 = boto3.resource('s3')

def lambda_handler(event, context):
    s3.Object('marcy-terui', 'bootstrap.py').put(
        Body=open('/var/runtime/awslambda/bootstrap.py', 'rb'))

起点のスクリプトを読んでみる

さて、S3に入ったファイルを落として読んでみましょう。すると、まず冒頭でPythonの標準モジュールではないものをimportしています。特にimport前にsys.path.append 等を行っている記述はないので、同じディレクトリ内にあるはず。

同じディレクトリ内のファイルを全て取得する

以下のようなFunctionをアップロードして実行します。

import boto3
import os
s3 = boto3.resource('s3')

def lambda_handler(event, context):
    for f in os.listdir('/var/runtime/awslambda/'):
        s3.Object('marcy-terui', f).put(
            Body=open('/var/runtime/awslambda/{}'.format(f), 'rb'))

これで全て取得できました。

f:id:FumblePerson:20161214115851p:plain

え?ソースコードを見せろって?

はい。おっしゃる通りで、ソースコードを見ながらあーでもないこーでもないっていうのをやりたかったんですが、ソースコードに思いっきり Copyright (c) 2013 Amazon. All rights reserved. って書いてあるので、勝手に公開したら怒られかねないなと思いとどまりました。。。

気になる方は是非上記方法を試して見てください。一部 so 化されていますが、大部分は普通のPythonコードです。まだ全てをちゃんと読み込んでないですが、起動されたLambdaプロセスが呼び出しを待ち受ける方法が1つではないことが分かったりして、中々面白い発見があると思いますよ。何か最適化のヒントとなる情報などが読み取れたりするかも?(これくらいは言っても怒られないはず?)

簡単ですが、以上です

本当は、以下のようなのをある程度完成させてお披露目したかったんですが、Serverless Meetup SapporoやYAPC::Hokkaidoの諸々やら仕事がわりと立て込んでたりで進捗ダメです・・・年内には目処を付けたい。。。

とりあえず、こちらからは以上です