AWS Lambdaが注目されています。
その理由は、スケーラビリティとコスト効率の良さです。
しかし、その真価を引き出すには非同期処理の理解が不可欠です。
この記事では、非同期処理の基本から、Lambda関数での実装方法、
さらには必要な権限付与に至るまでを、短く、分かりやすく解説します。
非同期処理とは?
非同期処理と言っても、いろいろな意味があります。
- マルチタスク処理
- 非同期I/O
- 非同期プログラミング
- 並列計算
それぞれを簡単に説明しておきます。
マルチタスク処理
マルチタスク処理は、さらに次のように分けることがあります。
- プロセス間の非同期実行
- スレッドの非同期実行
プロセス間の非同期実行
親プロセスが子プロセスを生成し、親プロセスは子プロセスの完了を待たずに自身の処理を継続します。
子プロセスは独立して並行して実行されます。
スレッドの非同期実行
単一のプロセス内で複数のスレッドを生成し、これらのスレッドが並行して実行されます。
スレッドはプロセスのリソースを共有するため、通信やデータ共有が容易です。
非同期I/O
非同期I/Oは、次のように分けられます。
- 非ブロッキングI/O
- イベント駆動I/O
非ブロッキングI/O
システムがI/O操作を即座に完了できない場合でも、プログラムの実行をブロックせずに即座に制御を返します。
実際のI/O操作はバックグラウンドで続行されます。
イベント駆動I/O
I/O操作の完了や外部からのイベント(例えば、ネットワークからのデータ受信)を待つ代わりに、
イベントが発生したときに実行されるコールバック関数を登録します。
非同期プログラミング
非同期プログラミングは、次のように分けることができます。
- プロミス/フューチャー
- コールバック関数
プロミス/フューチャー
処理の結果を表すオブジェクトを使用し、その処理が完了する前に次の処理を記述します。
結果が準備でき次第、それを利用してさらなる処理を行います。
コールバック関数
ある処理が終了した後に実行されるべき関数を事前に指定します。
その処理が非同期に実行される間、プログラムの他の部分が実行を続けられるようにします。
並列計算
複数の計算を同時に実行することで、タスクの完了時間を短縮します。
これは特に、データ処理や科学計算など、大量の計算が必要なアプリケーションで有効です。
Lambda関数における非同期処理
Lambda関数における非同期処理は、lambda_client.invoke関数を利用するケースとします。
正確には、InvocationType=’Event’と指定するケースになります。
なお、この場合の非同期処理は非同期I/Oと非同期プログラミングの両方の特徴を併せ持ちます。
まあ、それぞれの中間という感じです。
そして、このケースでは二つのLambda関数が登場します。
- 呼び出し元のLambda関数(InvokeProcessFunction)
- 呼び出されるLambda関数(ProcessDataFunction)
関数一覧で次のように確認できます。
それぞれのコードは、以下のようなモノになります。
InvokeProcessFunctionのlambda_function.py
import json import boto3 # Lambdaクライアントを作成 lambda_client = boto3.client('lambda') def lambda_handler(event, context): try: response = lambda_client.invoke( FunctionName='ProcessDataFunction', # 呼び出されるLambda関数の名前 InvocationType='Event', # 非同期実行を指示 Payload=json.dumps(event) # 呼び出される関数に渡すデータ ) print('非同期呼び出し成功') return { 'statusCode': 200, 'body': json.dumps('非同期呼び出し成功') } except Exception as e: print('非同期呼び出し失敗', e) return { 'statusCode': 500, 'body': json.dumps('非同期呼び出し失敗') }
ProcessDataFunctionのlambda_function.py
import json def lambda_handler(event, context): print('非同期Lambda関数が呼び出されました。') print('受け取ったイベント:', json.dumps(event)) # 必要な処理をここに書く return { 'statusCode': 200, 'body': json.dumps('処理完了') }
Lambda関数における非同期処理のための権限付与
InvokeProcessFunctionを実行すると、次の結果が表示されます。
エラーの詳細は、以下。
非同期呼び出し失敗 An error occurred (AccessDeniedException) when calling the Invoke operation:
非同期の呼び出しに失敗しているということです。
これは、権限がないために生じているエラーとなります。
具体的には、InvokeProcessFunctionにProcessDataFunctionを呼び出す権限がありません。
ということで、InvokeProcessFunctionに権限を付与します。
現状のアクセス権限は、以下。
InvokeProcessFunction関数の「設定→アクセス権限」で確認できます。
デフォルトでは、CloudWatchの権限だけが付与されています。
(※適用するロールによる部分はあります)
そして、アクションは以下。
では、IAMから権限を付与しましょう。
該当するロールの許可ポリシーにある「AWSLambdaBasicExecutionRole」をクリック。
現状では、CloudWatchのみですね。
「JSON」をクリックして、編集します。
今回追加したのは、選択されている箇所です。
「アカウントID」は、非表示にしています。
追加する内容は、以下のフォーマットです。
{ "Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:リージョン:アカウントID:function:Lambda関数名" }
今回は、関数名は指定しています。
以下のようにワイルドカードの設定も可能です。
arn:aws:lambda:リージョン:アカウントID:function:*
ただし、セキュリティ的にはその分だけ弱くなってしまいます。
あと、「Resource」部分は「関数のARN」をコピペすることができます。
これをコピペする方が、確実だと言えます。
JSONを編集したら、サービスが追加されていることを確認できます。
IAM上で確認できたら、Lambda関数でも確認しましょう。
「AWS Lambda」が増えています。
そして、アクションが追加したモノであることを確認できます。
ここまで設定できれば、OKです。
再度、InvokeProcessFunctionを実行します。
CloudWatchでも、呼び出し成功のログを確認できます。
本当に、Lambda上の権限は面倒です。
でも、セキュリティのことを考えたら、仕方がないのでしょうね。