Lambda + FastAPI + APIGateway をAWS CDK でデプロイ

## やること
タイトル通り、FastAPIをLambdaで使用してAPIを設計し、APIGatewayでデプロイするのをCDKで行う。

## 前提
- __OpenAPI__ を使ってAPIを設計
    - CDKではRestApiではなく、OpenAPI定義からAPIGatewayを作ることができる[SpecRestApi](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.SpecRestApi.html)を使う
- LanbdaをCDKでデプロイするところまでは既に記事があるのでそちらを参照してCDKを立ち上げる

    https://qiita.com/stake15/items/2616a568593d48e5bd16
    
    上の記事ではLambdaの設定部分が省略されているので、そこは公式ドキュメント参照。
    現時点では英語ばかりだしドキュメント複雑だし、わからなければ「Lambda CDK」とかで検索。
    
    https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html

## 開発環境
- Python 3.8
- AWS CLI

## FastAPIとMungum用のLambdaレイヤーを作成する準備
FastAPIとMungumをLambdaで使うためにはこれらの依存関係を直接Lambda環境にインストールさせる処理をしなければならない。
その手段としてLambdaレイヤーがあるので、レイヤーの材料となるdependencies.zipを作成する。

requirements.txtを作成
~~~text:requirements.txt
fastapi
mangum
~~~

依存関係をzipにまとめる
~~~
mkdir python
pip install -r requirements.txt -t python/python/lib/python3.10/site-packages/
cd python
zip -r ../dependencies.zip .
cd ..
rm -rf python
~~~

__この辺りの詳細や、上記コマンドを自動化させる方法は[別の記事](https://qiita.com/haruki-lo-shelon/items/72b7308be3bb6bbaad69)に記載__

## Fast APIを使う

Lambda関数のファイルを以下のように変更
~~~python:lambda/app.py
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()


@app.get("/sample", status_code=200)
async def root():
    return {"message": "Successfully API"}

handler = Mangum(app)
~~~

続いてlibディレクトリのCDKファイル
Lambdaレイヤーを追加している点にも注目

~~~typescript:lib/cdk-openapi-express-stack.ts
import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as signer from "aws-cdk-lib/aws-signer";
import { Construct } from "constructs";
import * as fs from "fs";
import * as path from "path";
import * as yaml from "yaml";

export class SrcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // deploy時の引数 or envファイル参照で行う
    const env_name = this.node.tryGetContext("env") ?? process.env.ENV_NAME;

    const signingProfile = new signer.SigningProfile(this, "SigningProfile", {
      platform: signer.Platform.AWS_LAMBDA_SHA384_ECDSA,
    });

    const codeSigningConfig = new lambda.CodeSigningConfig(
      this,
      "CodeSigningConfig",
      {
        signingProfiles: [signingProfile],
      }
    );

    // レイヤー作成
    const layer = new lambda.LayerVersion(this, "CustomLayer", {
      code: lambda.Code.fromAsset("dependencies.zip"),
      compatibleRuntimes: [lambda.Runtime.PYTHON_3_10],
      description: "A layer to hold the FastAPI and Mangum dependencies",
    });

    // Lambda関数の作成
    const fn = new lambda.Function(this, "handler", {
      codeSigningConfig,
      runtime: lambda.Runtime.PYTHON_3_10,
      handler: "app.handler",
      code: lambda.Code.fromAsset(path.join(__dirname, "../lambda")),
      layers: [layer], // レイヤーを設定
    });

    // SpecRestApiを使ったAPIGatewayの作成
    const swaggerYaml = yaml.parse(
      fs.readFileSync("./api/docs/openapi.yaml").toString()
    );

    for (const path in swaggerYaml.paths) {
      for (const method in swaggerYaml.paths[path]) {
        swaggerYaml.paths[path][method]["x-amazon-apigateway-integration"] = {
          uri: `arn:${cdk.Aws.PARTITION}:apigateway:${cdk.Aws.REGION}:lambda:path/2015-03-31/functions/${fn.functionArn}/invocations`,
          passthroughBehavior: "when_no_match",
          httpMethod: "POST",
          type: "aws_proxy",
        };
      }
    }

    const apigw = new apigateway.SpecRestApi(this, "RestApi", {
      apiDefinition: apigateway.ApiDefinition.fromInline(swaggerYaml),
    });

    fn.addPermission("LambdaPermisson", {
      principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
      action: "lambda:InvokeFunction",
      sourceArn: apigw.arnForExecuteApi(),
    });
  }
}

~~~

デプロイして終わり

## まとめ

FastAPIをLambdaで使用してAPIを設計し、APIGatewayでデプロイするのをCDKで行いました。
FastAPIを使えば簡単にAPIを設定できました。
また、Lambdaに必要な外部ライブラリはローカルにインストールしていても動作しないので、直接ライブラリファイルを含めてデプロイするか、本記事のようにレイヤーを作成するかなどの処理が必要です。
レイヤーの方がスマートな気がしたので今回はこの方法で実装しました。