AWS Lambda と仲直りしてみる
はじめに
……はい。
仕事で AWS と関わるようになり早1年が経とうとしているが、未だに使いこなせていないサービスが多い。
AWS Lambda もそのうちのひとつだが、そう遠くない将来、サーバレス案件の比重が増えることが分かっており、なんとかキャッチアップしないといけないと危機感を募らせるようになった。
そこで、Lambda と仲良くなるまでの過程の話を、ここに書き残していきたい。
もともとは 1つの記事に「マネジメントコンソール上で Lambda を作って動かす」→「Cloud9 + AWS SAM CLI で IaC」→「CodeCommit + CodeDeploy + CodePipeline で CI/CD」あたりをまとめて書きたかったが、話があまりにも長くなりすぎたので分割することにした。
今日は、できるだけミニマルなサンプルを通じて、「マネジメントコンソール上で Lambda を作って動かす」レッスンに取り組む話に焦点を絞る。
【練習1】 UUID を生成する Lambda 関数を手作りしてみよう
最低限、いったい何が起こるのかを理解するために、SAM CLI といった便利な自動化ツールの手を借りず、手動で一から Lambda 関数を作成してみよう。
※ いきなり SAM を使いたい方はコチラ:
tercel-tech.hatenablog.com
章題にもある通り、UUID を生成するだけの簡単な Lambda 関数を作ってみることにする。
ちなみに UUID とは絶対に重複しないといわれる便利な乱数である。
関数の作成
AWS マネジメントコンソールで「Lambda」を検索し、ページ右上の「関数の作成」ボタンを押す。
関数名に適当な名前をつけ(関数名には半角英数字、-、_のみ使用可)、ランタイムには「Python 3.8」を選択し、ページ右下の「関数の作成」ボタンを押す。
ランタイムのサポートポリシー
下図に示すように、Lambda 関数を作成する画面では、ランタイム(開発言語*1)をいくつかの候補から選択できる。
ランタイムを選択する際には、情報の豊富さや AWS Lambda のランタイムサポートにも注目しよう。
ランタイムサポートポリシーによると、Node.js 10系は今年の7月末には非推奨となり(フェーズ1)、さらに8月末には当該ランタイムを使用する Lambda 関数の更新ができなくなる(フェーズ2)とされている。
名前 | OS | フェーズ1開始 | フェーズ2開始 |
---|---|---|---|
Python 2.7 | Amazon Linux | 2021/7 /15 | 2021/9/30 |
Ruby 2.5 | Amazon Linux | 2021/7/30 | 2021/8/30 |
Node.js 10.x | Amazon Linux 2 | 2021/7/30 | 2021/8/30 |
Node.js 8.10 | Amazon Linux | 2020/3/6 | |
Node.js 6.10 | Amazon Linux | 2019/8/12 | |
Node.js 4.3 Edge | Amazon Linux | 2019/4/30 | |
Node.js 4.3 | Amazon Linux | 2020/3/6 | |
Node.js 0.10 | Amazon Linux | 2016/10/31 | |
.NET Core 2.0 | Amazon Linux | 2019/5/30 | |
.NET Core 1.0 | Amazon Linux | 2019/7/30 |
ご覧の通り Node.js はそこそこ短命なので、売り物に組み込む際はアップデートを見据えた運用体制を確保できるかどうかがポイントとなる。
Lambda 関数の基本形 (Python 3.8)
しばらく待つと、関数が正常に作成された旨のメッセージが表示される。
このままページの中程までスクロールすると、lambda_function.py
というファイルが作られ、Hello World 的なサンプルコードが予めセットされていることに気付く。
これが、すべての始まりであり基本形でもある。
import json def lambda_handler(event, context): # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
もう少し一般化すると、Python 3.8 における Lambda 関数の基本形は、event
と context
という 2つの引数をとる以下の形をした関数である。
def xxx_handler(event, context): # ... 主処理... return 戻り値
ここで、2つの引数がなにものであるか気になったので簡単に触れておく。 気にならないようであれば無視して次節までスキップして構わない。
第1引数の event
は、いわゆるイベントパラメータである。
たとえば、REST API として Lambda が実行される場合は、HTTP 要求電文に含まれるもろもろのパラメータが event
を通して引き継がれてくる。
event
には、Lambda 関数を呼び出す側がセットした JSON がそのまま丸ごと入っているため*2、言い換えると event
がどのような値を持つかは、Lambda 関数の実行契機となるイベント次第となる。
第2引数の context
は Lambda 関数自身のメタ情報なので、当面の間あまり意識しなくてよろしい。
context
がどのようなプロパティを持っているかについては、公式ドキュメント「Python の AWS Lambda context オブジェクト - AWS Lambda」に詳説があるが、自分自身の関数名や ARN が取得できたりメモリサイズが取得できたりするくらいである。
戻り値も、イベントによって異なる。
REST API ならば HTTP の応答電文を返す必要があるが、単なる定期実行バッチならば 0 か 9 かも知れない。
Lambda 関数のコーディング
表示されている lambda_function.py
を以下のように書き換えてみよう。
uuid
標準ライブラリからバージョン4 の UUID を生成し、戻り値にセットするだけの簡単な改修である。
import json import uuid def lambda_handler(event, context): id = str(uuid.uuid4()) return { 'statusCode': 200, 'body': json.dumps({ 'id': id }) }
Changes not deployed というバッジが表示されるので、「Deploy」ボタンを押す。
デプロイに成功すると「Deploy」ボタンが非活性になり、Changes deployed という緑色のバッジが表示される。
動作確認
デプロイした Lambda 関数の動作確認をするため、オレンジ色の「Test」ボタンを押そう。
すると、「テストイベントの設定」画面が現れ、動作確認用のパラメータを JSON 形式で設定できる。
ここに設定した内容は、Lambda 関数をテスト実行した際に event
引数にまとめて引き渡されてくる。 ただし今回はパラメータを参照しないため、特に何も変更せず、適当なイベント名を入力してページ右下の「作成」ボタンを押そう。
この状態で、再び「Test」ボタンを押すと、Lambda 関数がテスト実行される。
しばらく待つと、戻り値や Lambda 関数の実行ログなどが確認できる(標準出力に吐いた文字列などもログとして出力される)。
実行結果を見ると、056839e5-552a-44fc-9275-8331a5103386
という、どう考えても被りそうにない UUID が返却されていることが分かる。
𝗥𝗲𝘀𝗽𝗼𝗻𝘀𝗲 { "statusCode": 200, "body": "{\"id\": \"056839e5-552a-44fc-9275-8331a5103386\"}" } (以下略)
ちなみにログは、CloudWatch Logs からも確認できる。
Lambda 関数の権限について
ここまでの結果からも分かるとおり、この Lambda 関数は、CloudWatch Logs に対する書き込み権限をもったロール (AWSLambdaBasicExecutionRole
) の下で実行されていたことになる。
Lambda 関数を作成する際に、裏でロールが自動的に作成されていたのである。 この正体を確認するには、実際に Lambda 関数の「設定」→「アクセス権限」を選択すればよい。
DynamoDB や S3 といったリソースと連携する必要がある場合は、Lambda に対してアクセス権を追加する必要がある。
次の練習で実践してみよう。
【練習2】 UUID を DynamoDB に登録してみよう
続いて、生成した UUID を DynamoDB に蓄積するように改修してみよう。
DynamoDB は AWS の NoSQL データベースである。 データベース操作と聞いて身構える必要はない。
DynamoDB テーブルの作成
マネジメントコンソールから「DynamoDB」を検索しよう。
ここで、「テーブルの作成」ボタンを押す。
もし DynamoDB のトップページに「テーブルの作成」ボタンが表示されない場合は、左側のメニューから「テーブル」→「テーブルの作成」ボタンの順にクリックする。
テーブル名を適当に入力、プライマリキーは簡単のため「id
」と入力し、型は「文字列」のままページ右下の「作成」ボタンを押す。
ここで入力したテーブル名は後で使うので覚えておこう。
Lambda 関数の改修
以下のコードに書き換えて、「Deploy」ボタンを押す。
import json import uuid import boto3 dynamodb = boto3.resource('dynamodb') # DynamoDB オブジェクト def lambda_handler(event, context): id = str(uuid.uuid4()) table = dynamodb.Table('potato-db') # 先ほど作成した potato-db テーブル table.put_item(Item = { 'id': id }) # id を書き込む return { 'statusCode': 200, 'body': json.dumps({ 'id': id }) }
先ほどと同じ要領で Deploy → Test 実行すると、権限不足で AccessDeniedException が発生してしまう。
これは、前述のとおり、Lambda 関数ができることがせいぜい CloudWatch Logs へのログの書き出しくらいだからである。
そこで、次節では DynamoDB の読み書きができるよう、権限を追加してみよう。
アクセス権の編集
再び「設定」タブをクリックし、左側のメニューから「アクセス権限」を選択、表示されている「実行ロール」のリンクをクリックしよう。
すると当該ロールの編集ページが開くので、「ポリシーをアタッチします」ボタンをクリックする。
「AmazonDynamoDBFullAccess」という権限を選択し、右下の「ポリシーのアタッチ」ボタンをクリックする。
成功すると、「ポリシー AmazonDynamoDBFullAccess がアタッチされました」というメッセージが表示される。
一応、この画面から AmazonDynamoDBFullAccess がいかなるポリシーかを確認することができる。 単なる DynamoDB への書き込み権限を超えてそこそこえぐい設定が連なっている。
もしも仕事で使うなら、 PoC とはいえもっと権限を絞るべきだろう。
動作確認
再び、Lambda 関数に戻り、「Test」ボタンを押す。 プログラムは変更せず、権限を編集しただけなので、再 Deploy は不要である。
うまくいくと、DynamoDB に登録した UUID が戻り値に表示される。
では、本当に DynamoDB に登録されているかどうかを確認するため、マネジメントコンソールで「DynamoDB」を検索し、左側のメニューから「テーブル」→(テーブル名)ラジオボタン →「項目」タブの順にクリックしよう。
先ほど表示された UUID が DynamoDB の id
に追加されていることが確認できるだろう。
ここまでのまとめと課題
実を言うとここまでの手順は、かなりユーザ体験(開発者体験?)が悪い。
まず、マネジメントコンソールをあちこち往ったり来たりしなければならず、思考が分断されがちである。
今回は極めて簡単な題材にも拘らず、Lambda と DynamoDB と IAM を切り替えながら作業を進める必要があった。 Lambda から参照するために DynamoDB のテーブル名を確認しに行ったり、権限を確認するため IAM へのリンクに飛んだりといった具合である。
あちこちタブを開いているうちに、自分が何をしようとしているのか忘れて作業漏れが発生したら怖い。
次に、ソースコードの変更管理のしようがない点も気になる。
Web のフォーム上でソースコードを直接編集する方法では、いつ・誰が・なんの目的をもって Lambda を書き換えたかが、後になってトレースできないのである。
そして、最も大きな問題は、リソースが散らかることである。
Lambda と DynamoDB を連携させるため、DynamoDB に設定したテーブル名を Lambda に転記するシーンがあったが、あれはかなりイケてない。
もしも開発の中盤でうっかり DynamoDB の命名にスペルミスが見つかり、急遽設定を変更したとしよう。 関連する夥しい Lambda の横展開修正が怖い。
自分の意思で作った Lambda や DynamoDB はまだしも、暗黙のうちに作られた IAM ロールやポリシーが人知れず溢れかえっていく未来が見える。
開発が大規模化すればするほど、管理できないモノたちが猖獗を極めるだろう。
だいぶ辛辣ですな。
もしも Lambda の開発方法がコレしかないなら、とうに心が砕け散っていたと思う。
ふむー。
今日の記事は、AWS SAM の話の前置きに過ぎないのだ。