Lambdaってどんなサービスでどんな機能があるの？ ～AWS100本ノック～ 3/100

# はじめに

本記事では何かと利用することが多いLambdaについて、説明していきます。

# Lambdaとは？

- サーバーをプロビジョニングまたは管理することなくコードを実行できるイベント駆動型サービスです
- AWSでサーバーレスアプリーションを作りたいってなったら、必ずと言ってもいいほど出てきます

:::note info
:x: サーバーレス = `サーバが存在しない`
:o: サーバーレス = `サーバの管理をAWSが行う`
:::

では、Lambdaの各機能について紹介します

# 関数

Lambdaはまず関数を作るところから始まります。
関数は、Lambdaでコードを実行するために呼び出すことが出来る一機能になります。
作成し、任意のAWSサービスや処理から呼び出すことで処理を実行します、

関数は以下の3つから作成します。
- `一から作成`：一からコードを書いてLambda関数を実装する場合に選択します。基本はこちらを選択することになると思います
- `設計図の使用`：AWSが用意したサンプルコードをベースに実装する場合に選択します。Lambdaを初めて触る場合や、任意のイベント(S3へのアップロード起点でLambdaを実行、APIとしてLambdaを実行)のサンプルを見たい場合に利用します
- `コンテナイメージ`：ECRにPushしたDockerイメージをベースに実装する場合に選択します

## ランタイム

関数を作る際に、Lambdaの`実行環境`を選択します。これをランタイムと言います。
任意の言語とバージョンを選択すると、実装したコードをそのランタイムで実行できます。
これがLambdaの良いところであり、サーバーレスサービスと呼ばれる所以です。
※ちなみに、ランタイムの実態はLinuxのコンテナらしい :thinking: 

ランタイムとしては、現在以下が用意されています。
- Nodejs
- Python
- Java
- .NET
- Ruby
- Amazon Linux

:::note info
Amazon Linuxは、`カスタムランタイム`と呼ばれます。
PHPやGoなど上記のランタイムに存在しない言語を使いたい場合はこちらを選択します
:::

ランタイムで選択するバージョンですが、その言語が`LTSフェーズ`になると新しいバージョンのランタイムが提供されるようです。

またサポートの廃止についてですが、3つの段階に分かれています。

- 廃止
- 関数の作成をブロック
- 関数の更新をブロック

詳しく知りたい方は、[こちら](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html)をご確認ください。

## ハンドラ

Lambda関数を呼び出した際に`実行するメソッド`のことをハンドラと言います。
例えば、Node.jsですとデフォルトでindex.handlerとなっているのですがこれは、index.jsのexports.handlerを呼び出すということです

## アーキテクチャ

Lambdaが関数の実行に使用する`コンピュータプロセッサのタイプ`になります。
現在、`x86_64`と`arm64`が選択できますが、コスト効率が良いarm64を選ぶのが無難そうです

# ライフサイクル

Lambdaのメリットして、常時稼働するECSやEC2と違い必要な時に実行環境を立ち上げて処理を実行し、不要になったら停止することで`コストを抑えられる`という特徴があります。
実行環境の起動から停止におけるライフサイクルは、以下のようになります。

![ライフサイクル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/89124469-fa1d-e442-f1e5-79905cc9d00a.png)

- `INIT`：
  - 初期化フェーズのことで、コールドスタートとも呼びます。コールドスタートについては[こちら](https://fintan.jp/page/8039/)がわかりやすかったです
  - 拡張機能・ランタイム・関数の初期化を行います。関数コードやレイヤーのダウンロード、ハンドラー外のコードの実行を行います
  - 一定頻度でLambda関数が実行される場合は、実行環境を使いまわすためInitフェーズは行われません
- `INVOKE`
  - Lambda関数のハンドラーを呼び出し、実装したコードを実行します
- `SHUTDOWN`
  - Lambda関数が一定時間呼び出されないとシャットダウンします
  - 定期的に呼びだされる場合はシャットダウンされません

リクエスト数に対してLambda実行環境が足りない場合は新しく実行環境を立ち上げます。
このあたりのライフサイクル管理やスケーリングは、Lambdaが全て自動で行ってくれるため、非常に便利 です:smile: 

ライフサイクルについて詳しく知りたい方は、[こちら](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtime-environment.html)をご確認ください。

# イベント

Lambdaイベント周りの設定を説明します

## トリガー
Lambda関数は、何かしらの`イベント`をもとに実行されますが、これをトリガーと言います。
トリガーには、大きく以下に分かれます。

- `AWSサービス`：3へのファイルアップロード、CodeCommitへのプッシュなどのAWSの何かしらのイベントをトリガーに実行します
- `サードパーティーサービス`：SalesForceなどのAWS以外のサードパーティーサービスの何かしらのイベントをトリガーに実行します
- `直接Lambda関数を呼び出し`：自分たちで作ったAPIからLambdaを呼び出すなどLambdaを任意のタイミングで直接実行します

また、設定したトリガーイベントが起きるとLambda関数が実行されますが、呼び出し方は以下の3つに分かれます。
なお、一部を除いて呼び出し方を選択することは出来ません。

- `同期呼び出し`
  - Lambda関数の処理が終わるのを待ち、処理結果のレスポンスを受け取ります
  - API Gatewayを使ったAPIとかがこれにあたります
- `非同期呼び出し`
  - Lambda関数の処理が終わるのを待ちません
  - 非同期呼び出しの場合は一度、`内部的に持っているキュー`にイベントが溜まり、順次Lambda関数を実行します
  - S3イベントによるトリガーや、EventBridgeによる定期実行とかがこれにあたります
- `ポーリング呼び出し`
  - 同期呼び出し/非同期呼び出しと違い、Lambdaから定期的にイベントを取りに行くやり方です
  - ポーリングはLambdaが自動的に行ってくれるため、自前で実装する必要はありません
  - SQS・Kinesis Streams・DynamoDBStreamsトリガーがこれにあたります

:::note info
API Gatewayでは、同期呼び出し・非同期呼び出しのどちらのAPIも作成することが出来ます
:::

## 送信先

関数の処理が成功/失敗した際に、イベント内容を任意の場所に送信できます。
なぜ、この機能があるのでしょう？

同期呼び出しの場合は、レスポンスで成功失敗を判断できますが、非同期/ポーリング呼び出しの場合はLambda関数が成功・失敗したかを受け取ることが出来ません。
ただ、場合によっては失敗したら管理者にメール通知したいなど要件があると思います。

そういった場合にこの機能を使えばLambda関数が`成功したら送信先A`に送信、`失敗したら送信先B`に送信というようなことが簡単にできます。

なお、非同期呼び出し/ポーリング呼び出しで設定できる条件と送信先が違います。
- `非同期呼び出し`
  - 条件：成功、失敗
  - 送信先：SNS、SQS、Lambda、EventBridge
- `ポーリング呼び出し`
  - 条件：失敗
  - 送信先：SNS、SQS

:::note info
送信先は任意設定のため、設定しなくてもいいし、失敗時だけ設定するなども可能です。
なお、同期呼び出しで設定しても何も起こらないので注意です。
:::

:::note info
非同期呼び出しに関しては、送信先以外にもDLQの設定がありますが、送信先と機能的には変わらない(DLQの後継が送信先)ので、送信先だけ設定しておけば問題ありません。
:::

# 設定

次に、Lambdaの基本設定を紹介します

## メモリ
- Lambda関数に割り当てるメモリを設定します
- 現在、128MB～10GBまで設定できます
- CPUは設定したメモリに比例して割り当てられます。例：1769MBの場合、1vCPU


## タイムアウト
- Lambda関数の処理の最大時間を設定します
- この設定以上に処理時間がかかった場合はLambda関数がタイムアウトします
- 1秒～15分まで設定できます
- この設定にInitフェーズは含まれませんが、Initフェーズは10秒以内に終了しないとタイムアウトします

## アクセス権限

基本的にLambda関数は、何かしらのサービスと連携する必要があります。そのためには、それらにアクセスする権限が必要です。
アクセス権限としては、以下の2つがあります

- `実行ロール`
   - `Lambdaが任意のリソースに`アクセスするための権限
   - デフォルトではLambda関数がログを出力するためにCloudWatchLogsの権限が付与されていてこれは必ず必要になりますが、それ以外は必要に応じて付与していきます。例：DynamoDBにデータを登録したい、S3へファイルをアップロードしたい
   - ポーリング呼び出しの場合は、Lambdaがトリガーのイベントを取りに行くので、対象のAWSリソースへのアクセス権限が菅らず必要になります
- `リソースベースのポリシーステートメント`
   - `Lambdaに任意のリソースが`アクセスする(lambda:InvokeFunctionする)ための権限
   - 同期/非同期呼び出しの場合はAWSリソースがLambda関数を呼び出すので、こちらが必ず必要になります

## 環境変数

こちらは開発者に馴染みがある環境変数と同様と考えてもらって問題ないです。
環境毎に違う値だったり、ハードコーディングしたくない値を環境変数に設定することで、コードで利用できます。

他のAWSサービスと同様、保存時に暗号化することもできます。

:::note alert
秘密情報を環境変数に設定するのはセキュリティ的に非推奨です。
パラメータストアやシークレットマネージャーから取得する形がより安全で推奨されています。
:::

:::note info
パラメータアストアやシークレットマネージャーから値を取得する処理を実行コードに記述するのではなく、Lambda Extensionを利用する方法が推奨されているようです。
関数のライフサイクル内で取得できるようになるので、関数実行時に都度取得する必要がなくなり、処理時間の短縮が見込めます。
:::


## エフェメラルストレージ
  - /tmp内に設定できる一時ストレージです
  - 現在、512MB～10GBまで設定できます

# 料金体系

ここまで紹介してきましたが、みなさん気になるのは料金ですよね。
どんなことに対して料金が発生するのでしょうか？
Lambda関数は主に以下の内容で料金が発生します

- `リクエスト回数`：Lambda関数を呼び出した回数
- `実行時間`：Lambda関数の処理の実行時間(初期化含む)
- `メモリ`：割り当てたメモリ

※他にも、データ転送量やエフェメラルストレージなどで課金されますが、とりあえず上記がメインになると思っていただければと思います。

# 開発周り

開発に関連する設定について紹介します

## レイヤー

Lambda関数を複数実装していると、以下のように思うことがあると思います。

- ログ出力やエラーハンドリングは全ての関数で利用するし、共通化したい
- DB操作やS3の操作はいろんなところから使いまわしたいから、共通化したい
- このライブラリは色々なところから使うから、管理を一元管理したい

こういった場合に、複数のLambda関数で同じ処理を実装しないように`共通化する仕組み`がLambdaでも提供されていて、これがレイヤーになります。
利用するライブラリや、任意の言語で実装した共通処理(ログ、例外処理)をzip圧縮したものをレイヤーとして定義することで、複数の関数から利用可能になります

:::note info
レイヤーは、1つの関数に最大5個まで含めることが出来ます
:::

## エイリアスとバージョン管理

関数を作成/更新すると未指定の場合は、バージョンは`$LATEST`として更新されます。
しかし、以下のようなケースがあると思います。

- 何か問題が発生した場合に、1つ前のバージョンにすぐに戻せるようにしたい
- 1つの関数で複数環境のLambdaを実行したい。開発環境用は常に$LATESTを見るが、本番環境は開発環境の検証が終わるまでは1つ前のバージョンにしておきたい

そういった場合に、関数のバージョンを発行することで、任意の関数バージョンを実行できるようになります。バージョンは`1`から連番で付与されます
※バージョンを発行した関数のARN：`arn:aws:lambda:ap-northeast-1:1234567890:function:test:1`

しかし、このままだとどの環境がどのバージョンを使っているかがわかにくいし、バージョンが変わると呼び出し元の変更が必要になります
そこでエイリアス機能によって、任意の名前とバージョンを紐づけることが出来ます。
ex: `エイリアス：dev、バージョン：$LATEST`、`エイリアス：prd、バージョン：3`

エイリアス名で関数を実行することが出来るため、バージョンが変わっても呼び出し元に変更の必要がなくなります
※proeというエイリアスを発行した関数バージョンのARN：`arn:aws:lambda:ap-northeast-1:1234567890:function:test:prod`

## SAM

サーバレスアプリケーションの開発とデプロイを支援するためのフレームワーク(CloudFormationの拡張機能)です。
サーバーレスAPIだと、API Gateway + Lambdaが基本になるかと思いますが、その構築を簡単に行うためのフレームワークだと思ってもらえればと思います。
Lambdaの機能ではないため、簡単な紹介になりますが、内容を知りたい方は[こちら](https://qiita.com/tech4anyone/items/7fc1e7118cf18c100093)をご確認ください

## メモリチューニング

メモリをスケールアップすると、関数の実行時間を短縮することが出来ますがその分料金がかさんでしまいます。
なので、メモリ設定と実行時間のバランスを見る必要があります。

簡単にメモリ設定の可視化行う方法として[AWS Lambda Power Tuning](https://docs.aws.amazon.com/ja_jp/lambda/latest/operatorguide/profile-functions.html)があります

# スケーリング/再試行

スケーリングと再試行周りの設定について紹介します

## 同時実行

Lambdaの実行環境であるインスタンスは、リクエスト数に応じてスケーリングしますが、無限に増えるわけではありません。
上限緩和申請が可能ですが、デフォルトでは1000になっています。
つまり、1000インスタンスまでしか増えないということです。

また、これはLambda関数あたりではなく、アカウント単位になります。
なので、Lambda関数が5個あった場合は、5個のLambda関数で1000個までしか同時に捌けないことになります。

1000を超えた場合にどうなるかは、呼び出し方によって違います。
- `同期呼び出し`：スロットリングエラーがレスポンスされます
- `非同期呼び出し`：設定したイベントの有効期間(1分～6時間)であれば、内部キューに保持し、順次処理されます。有効期間を過ぎるとイベントは破棄されます
- `ポーリング呼び出し`：非同期呼び出しと基本的に同じです

ある程度のリクエスト数が各Lambda関数で試算出来る場合は、Lambda関数毎に同時実行数上限を設定することもできます。
例えば、Lambda関数Aは100、Bは10、Cは500などです。

初期化フェーズの時間が長い場合は、事前に初期化フェーズを終わらせたLambda関数をアイドリング状態で準備しておくことが出来る`プロビジョニングされた同時実行`という機能もあります。
料金はその分かかりますが、検討の余地はありそうです

## 非同期呼び出し設定

非同期呼び出しの場合は、`イベントの有効期間`と`再試行回数`を設定できます。

- `再試行`
  - Lambda関数の処理が失敗した場合に再度Lambdaを実行する回数(0～2回)を設定できます
  - 自前でリトライ処理を実装しないでもこの設定で簡単にリトライ処理が実現できます
  - 設定箇所は違いますが、ポーリング呼び出しでも同様にリトライが可能です

- `イベントの有効期間`
  - 内部キューにイベントを保持する期間です
  - 有効期間を過ぎると、イベントは破棄されます


# その他

その他、設定についていくつか紹介します

## 関数URL

LambdaでAPIを作成する場合は、今までは前段にAPI Gatewayを作る必裕がありました。
しかし、この機能でLambda関数専用のHTTPSエンドポイントを発行出来るようになり、API GatewayなしでもAPIを作ることが出来ます。
※URLは、`https://<url-id>.lambda-url.<region>.on.aws`の形式で発行されます

認証方法としては以下の2つがあります
- AWS IAM：認証されたIAMユーザーとロールのみが、関数URLにリクエスト出来ます。ECSやEC2などから実行する場合に使いそうです
- NONE：認証なしでリクエストを行います。誰でも実行できてしまうので、Lambda関数内で認証処理を行うか、CloudFrontのOACを用いた制御を行う必要がありそうです。気になる方は[こちら](https://qiita.com/Kanahiro/items/85573c9ae724df435a6a)をご確認ください

:::note info
カスタムドメインを使いたい場合は、CloudFrontと紐づける必要があります
:::

## VPC Lambda

LambdaはVPCサービスではないため、普通に関数を作るとVPC内のリソースにアクセスすることが出来ません。しかし、VPC内に作成するRDSなどにリクエストを行いたいケースもあると思います。

その場合はVPC Lambdaを作成し、VPC内にLambdaを配置することによってVPC内のリソースにアクセスすることが出来ます。

:::note info
VPC Lambdaという名前から、EC2やECSを配置するVPC内にLambdaを作るように思いがちです（私もそう思ってました）
しかし、実際は利用者が作成したVPCではなく、Lambdaサービスによって所有・管理されているVPC内で実行されます。
[こちら](https://zenn.dev/shimi7o/articles/79fb5cb2175a6c)の記事が簡単でわかりやすかったです
:::

## RDSデータベース接続

VPC Lambdaで作成した関数と、VPC内に作成したRDSを接続するための設定です。
ある程度のリクエストが想定される場合は、RDS Proxyを設定する必要が出てくるかもしれませんが、今回は割愛します、
RDS Proxyについて知りたい方は、[こちら](https://www.sunnycloud.jp/column/20210502-01/)をご確認ください


# おわりに

今回はLambdaの機能について紹介しました。
Lambdaを使ってみたい、けどどんな機能があるかよくわからない方の参考になれば幸いです:bow: