s3にJSON読み書きを行うaws lambda に docker imageを cdkでデプロイしたメモ

[前回](https://qiita.com/hibohiboo/items/85b192ec15572c3dbffd)は代替ベースイメージからの作成を試した。
今回は、公式イメージを使ってS3へつないでファイルの読み書きを行うことをテストする。
[ソース](https://github.com/hibohiboo/aws-cdk-v2/tree/master/python-lambda-project)

## ディレクトリイメージ
```
.
├─cdk
│ ├── bin
│ │   └── python-docker-lambda-s3.ts
│ ├── lib
│ │   └── python-docker-lambda-s3-stack.ts
│ └ package.json
└─python-lambda-project
  ├── s3Data
  │   └── test.json
  └─ s3
      ├── Dockerfile
      ├── lambda_function.py # lambdaで実行したいコード
      └── requirements.txt # 使用するライブラリ
```

## Hello world
久しぶりなのでまずはHello Worldを行った。
[ソース](https://github.com/hibohiboo/aws-cdk-v2/tree/53476b3921256b8846fa279822d7192bec9a49fb/python-lambda-project)

```python-lambda-project/s3/lambda_function.py
import sys
import os
def handler(event, context):
    return 'Hello ' + os.environ.get("TEST", "default") +' from AWS Lambda using Python' + sys.version + '!'
```

```docker:python-lambda-project/s3/Dockerfile
FROM public.ecr.aws/lambda/python:3.11
COPY requirements.txt ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt
COPY lambda_function.py ${LAMBDA_TASK_ROOT}
CMD [ "lambda_function.handler" ]
```

```python-lambda-project/s3/lambda_function.py
import sys
import os
def handler(event, context):
    return 'Hello ' + os.environ.get("TEST", "default") +' from AWS Lambda using Python' + sys.version + '!'
```

```ts:cdk/lib/python-docker-lambda-s3-stack.ts
import { Aspects, Duration, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

interface PythonDockerLambdaS3StackProps extends StackProps {}

export class PythonDockerLambdaS3Stack extends Stack {
  constructor(scope: Construct, id: string, props: PythonDockerLambdaS3StackProps) {
    super(scope, id, props);

    new lambda.DockerImageFunction(this,'AssetFunction',
      {
        code: lambda.DockerImageCode.fromImageAsset('../python-lambda-project/s3/'),
        environment: { TEST: 'test' }
      },
    );
  }
}
```

```ts:cdk/bin/python-docker-lambda-s3.bin
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { PythonDockerLambdaS3Stack } from '../lib/python-docker-lambda-s3-stack';

const env = {account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION};
const app = new cdk.App();
new PythonDockerLambdaS3Stack(app, 'PythonDockerLambdaS3Stack', { env });
```

```json:cdk/package.json
{
  "name": "cdk",
  "scripts": {
    "docker-lambda-s3": "cdk -a \"npx ts-node --prefer-ts-exts bin/python-docker-lambda-s3.ts\" deploy PythonDockerLambdaS3Stack --profile produser"
  }
}
```

デプロイ

```bash
cd cdk && npm run docker-lambda-s3
```

### 実行結果

AWSのコンソール上から、テスト実行。
実行結果は以下。

```
"Hello test from AWS Lambda using Python3.11.6 (main, Nov 16 2023, 11:06:00) [GCC 7.3.1 20180712 (Red Hat 7.3.1-17)]!"
```

## S3に読み書きできるようにする。
アップロードするJSONは以下にした。

```json:python-lambda-project/s3Data/test.json
{
  "test": "test data",
  "version": 1.1,
  "descritpion": "テスト用データ",
  "other": null
}

```

### CDK修正

```diff:cdk/lib/python-docker-lambda-s3-stack.ts
import {Aspects,Duration,RemovalPolicy,Stack,StackProps,Tag,Tags,} from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import {BlockPublicAccess,Bucket,BucketAccessControl,} from 'aws-cdk-lib/aws-s3';
import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment';
import { Construct } from 'constructs';

interface PythonDockerLambdaS3StackProps extends StackProps {
+  projectDirectory: string;
}

export class PythonDockerLambdaS3Stack extends Stack {
  constructor(scope: Construct,id: string,props: PythonDockerLambdaS3StackProps,) {
    super(scope, id, props);
+    const bucket = new Bucket(this, 'Bucket', {
+      removalPolicy: RemovalPolicy.DESTROY,
+      autoDeleteObjects: true,
+    });
+    new BucketDeployment(this, 'DeployFiles', {
+      destinationBucket: bucket,
+      sources: [Source.asset(`${props.projectDirectory}/s3Data/`)],
+    });

    const helloImageFunction = new lambda.DockerImageFunction(this,'AssetFunction',
      {
        code: lambda.DockerImageCode.fromImageAsset(
          `${props.projectDirectory}/s3/`,
        ),
+       environment: { S3_BUCKET_NAME: bucket.bucketName },
      },
    );
+    bucket.grantReadWrite(helloImageFunction);
  }
}

```

```diff:cdk/bin/python-docker-lambda-s3.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { PythonDockerLambdaS3Stack } from '../lib/python-docker-lambda-s3-stack';

const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};
const app = new cdk.App();
new PythonDockerLambdaS3Stack(app, 'PythonDockerLambdaS3Stack', {
  env,
+  projectDirectory: '../python-lambda-project',
});
```

### コード修正

```py:python-lambda-project/s3/lambda_function.py
import sys
import os
import json
import boto3

def handler(event, context):
    bucket_name = os.environ.get("S3_BUCKET_NAME")
    s3_client = boto3.client('s3')
    s3_object = s3_client.get_object(Bucket=bucket_name, Key='test.json')
    json_data = s3_object['Body'].read()
    data = json.loads(json_data)
    data['test'] = 'world'
    s3_client.put_object(Bucket=bucket_name, Key='test2.json', Body=json.dumps(data))
    return 'Hello ' + data['test']
```

```text:python-lambda-project/s3/requirements.txt
boto3[crt]

```

### 実行結果
AWSのコンソールからlambdaをテスト実行。
S3に`test2.json`が作成されていることを確認。
ダウンロードすると下記になる。
文字コードの問題で、日本語が変換されてしまった模様。
今回は、ファイルの読み書きを確認のため、この問題の解決まではしない。

```json
{
  "test": "world",
  "version": 1.1,
  "descritpion": "\u30c6\u30b9\u30c8\u7528\u30c7\u30fc\u30bf",
  "other": null
}
```

## 余談
pythoのバージョンが上がって、参照するライブラリのパスや優先順位が少し変わったもよう。

version|ランタイム|pip|優先順位
--|--|--|--
3.11|`/var/lang/lib/python3.11/site-packages`|`/var/lang/lib/python3.11/site-packages`|`/var/runtime`より`/var/lang/lib`を優先
3.7-3.10|`/var/runtime`|`/var/lang/lib/python3.x/site-packages`|`/var/lang/lib`より`/var/runtime`を優先

## 参考

[公式 - python-image](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html#python-image-bas)
[CDK - DockerImageFunction](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.DockerImageFunction.html)
[boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html)
[Python(boto3)でS3にデータをファイル保存せず直接アップロードする方法](https://dev.classmethod.jp/articles/upload-json-directry-to-s3-with-python-boto3/)
[boto3でファイルの読み書き](https://tech.unifa-e.com/entry/2021/09/22/131140)
[Boto3のS3オブジェクトの読み書き](https://dev.classmethod.jp/articles/boto3-s3-object-put-get/)