AWS CDK - Python Lambda Layer デプロイ

# 記事内容

- `@aws-cdk/aws-lambda-python-alpha`ライブラリを使ったLambda Layer作成
- `aws-cdk-lib/aws-lambda`ライブラリを使ったLambda Layer作成
- Lambdaに外部パッケージのインストール

# Layer内容

Layerに含める適当な外部パッケージ、関数実装を定義

## requirements.txt

```txt:requirements.txt
numpy==1.26.0
```

## common_layer.py

```python:common_layer.py
def print_message(msg):
    print(f"Message: {msg}")
```

# Layerを使用するLambda

上記Layerを使用する適当なLambda実装

```python
import common_layer
import numpy

def handler(event, context):
    print(f"numpy: {numpy.random.rand()}")
    common_layer.print_message("message-1")
```

# `@aws-cdk/aws-lambda-python-alpha`ライブラリを使ったLambda Layer作成

## 概要

こちらの方法はDockerが起動している必要がある
Docker上で自動的にLayerを作成（`python`フォルダにLayerに含めるパッケージインストール）される
そのため、Layerに含めるパッケージはローカルに残らない
また、Layerに含めるファイルを`python`フォルダに格納する必要はない
以下使用するフォルダ構成

```txt
 ├─ app/
 │   │
 │   ├ bin/
     │  └─ app.ts
     │
     └ lib/
        │
        ├ common-layer/
        │   │
        │   ├─ layers/
        │   │     ├─ common_layer.py
        │   │     └─ requirements.txt
        │   │
        │   └─ common-layer.ts
```

## CDK実装

```typescript:common-layer.ts
import * as path from 'node:path';

import * as pylambda from '@aws-cdk/aws-lambda-python-alpha';
import { Stack, StackProps } from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class CommonLayerStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const stackName = this.stackName;

    const commonLayerName = `${stackName}-CommonLayer`;
    const commonLayer = new pylambda.PythonLayerVersion(this, commonLayerName, {
      layerVersionName: commonLayerName,
      entry: path.join(__dirname, 'layers'),
      compatibleRuntimes: [lambda.Runtime.PYTHON_3_11],
    });
  }
}
```

# `aws-cdk-lib/aws-lambda`ライブラリを使ったLambda Layer作成

## 概要

こちらの方法は先ほどの方法とは異なり、自身でLayerに含めるパッケージをインストールする
また、Layerに含めるファイルを`python`フォルダに格納する必要がある
以下使用するフォルダ構成

```txt
 ├─ app/
 │   │
 │   ├ bin/
     │  └─ app.ts
     │
     └ lib/
        │
        ├ common-layer/
        │   │
        │   ├─ layers/
        │   │     ├─ python 
        │   │     │    └─ common_layer.py 
        │   │     │
        │   │     └─ requirements.txt
        │   │
        │   └─ common-layer.ts
```

## パッケージインストール

LambdaがAmazon Linux上で動作する関係上、Linux上でLayerに含めるパッケージをインストールする必要がある
筆者はWindows環境のため、WSL上のUbuntuを使用（パッケージによってはAmazon Linuxが必要になる可能性あり）
Ubuntuにpyenvをインストールし、Windows環境と同じPython環境を用意
VSCodeの場合、以下のようにcmdとwslの切替が容易に行うことが可能

![2023-10-21-16-45-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/493e20b3-9f1a-9fcb-0d12-e2dc9ea3a94a.png)


wslにて以下コマンドで`python`フォルダ以下にパッケージをインストール後、
cmdに切替てCDKデプロイを行った

```txt
pip install -r requirements.txt -t python/
```

## CDK実装

```typescript:common-layer.ts
import * as path from 'node:path';

import { Stack, StackProps } from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class CommonLayerStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const stackName = this.stackName;

    const commonLayerName = `${stackName}-CommonLayer`;
    const commonLayer = new lambda.LayerVersion(this, commonLayerName, {
      code: lambda.AssetCode.fromAsset(path.join(__dirname, 'layers')),
      compatibleRuntimes: [lambda.Runtime.PYTHON_3_11],
      layerVersionName: commonLayerName,
    });
  }
}
```

# Lambdaに外部パッケージのインストール

Lambdaも同様に `@aws-cdk/aws-lambda-python-alpha` , `aws-cdk-lib/aws-lambda` どちらかのライブラリを使用して定義する

## `@aws-cdk/aws-lambda-python-alpha`ライブラリを使ったLambda作成

LambdaのPythonファイルを格納したフォルダ内に
`requirements.txt`、`poetry.lock`等を含めればDocker上で自動的にパッケージをインストールしてくれる
（Layerでも`poetry.lock`を試したが上手くいかなかった）
以下使用するフォルダ構成

```txt
 ├─ app/
 │   │
 │   ├ bin/
     │  └─ app.ts
     │
     └ lib/
        │
        ├ functions/
        │   │
        │   ├─ handlers/
        │   │     └─ sample-function/
        │   │           ├─ index.py
        │   │           └─ requirements.txt
        │   │
        │   └─ functions.ts
```

### CDK実装

```typescript:functions.ts
import * as path from 'node:path';
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha';

export class FunctionsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const stackName = this.stackName;

    const functionName = `${stackName}-SampleFunction`;
    new PythonFunction(this, functionName, {
      functionName: functionName,
      runtime: lambda.Runtime.PYTHON_3_11,
      entry: path.join(__dirname, 'handlers/sample-function'),
      handler: 'handler',
    });
  }
}
```

### デプロイ結果

以下、`requirements.txt`に`pytz`を指定してデプロイしたLambdaのファイル構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/979107ef-e213-1f7a-babf-361e90e20ad9.png)

## `aws-cdk-lib/aws-lambda`ライブラリを使ったLambda作成

`@aws-cdk/aws-lambda-python-alpha`のデプロイ結果から、`python`フォルダ内にパッケージをインストールする必要はないが、
gitignoreでの管理上、`python`フォルダ内にパッケージをインストールするよう以下のフォルダ構成とした
（pythonフォルダ以下は、index.pyを除いてgit管理対象外）

```txt
 ├─ app/
 │   │
 │   ├ bin/
     │  └─ app.ts
     │
     └ lib/
        │
        ├ functions/
        │   │
        │   ├─ handlers/
        │   │     └─ sample-function/
        │   │           ├─ python/
        │   │           │     └─ index.py
        │   │           └─ requirements.txt
        │   │
        │   └─ functions.ts
```

### CDK実装

```typescript:functions.ts
import * as path from 'node:path';
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class FunctionsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const stackName = this.stackName;

    const functionName = `${stackName}-SampleFunction`;
    new lambda.Function(this, functionName, {
      code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/sample-function/python')),
      handler: 'index.handler',
      runtime: lambda.Runtime.PYTHON_3_11,
      functionName: functionName,
    });

  }
}
```

### パッケージインストール

パッケージのインストールはLayer時と同様にWLS上で実施する

# Lambda Layer サイズ

LambdaにアタッチできるLayerの合計サイズには上限（262144000 bytes）があり、超えると以下のようにエラーとなる。

```
Resource handler returned message: "Layers consume more than the available size of 262144000 bytes (Service: Lambda, Status Code: 400, Request ID: xxxx-49792a728f0e)"
```

Layerに含めていた外部パッケージの一部をLambda側に移動したが、
以下のようにエラーが変わり、Layer含めたLambdaのサイズに対して上限が設定されている

```text
Resource handler returned message: "Function code combined with layers exceeds the maximum allowed size of 262144000 bytes. The actual size is 270712450 bytes. (Service: Lambda, Status Code: 400, Request ID: )"
```

# 参考

- [Lambda 関数にパッケージを追加](https://dev.classmethod.jp/articles/building-python-lambda-function-with-aws-cdk-typescrip/#toc-6)
- [【AWS CDK】Pythonで動くLambdaレイヤーをCDKでデプロイする方法](https://qiita.com/nkserveren26/items/ab6850020966da123c36)
- [cdkでLambda Layerを作成するときは、zip化しないでOK](https://qiita.com/yust0724/items/2eadab67c2e0c866c5e8)
- [Create python lambda layer using CDK](https://stackoverflow.com/questions/73060461/create-python-lambda-layer-using-cdk)