AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装してみた

2023.07.31

こんにちは、CX 事業本部 Delivery 部の若槻です。

今回は、AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装する方法を確認してみます。

使用したのは Amazon Lambda Python Library です。現在は Alpha 版となります。

このライブラリの PythonFunction を使うことにより、NodejsFunctionと同様に、モジュールのパッケージングをよしなにやってくれます。

試してみた

AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装してみます。

プロジェクト作成

CDK プロジェクトを初回作成します。言語は TypeScript です。(Python ではありません)

mkdir cdk_demo_project
cd cdk_demo_project
cdk init --language typescript

Amazon Lambda Python Library 導入

Amazon Lambda Python Library をインストールします。

npm i  -D @aws-cdk/aws-lambda-python-alpha

Lambda 関数および CDK コード

Lambda 関数を配置するディレクトリを作成します。

mkdir src & mkdir src/lambda & mkdir src/lambda/hello
touch src/lambda/hello/index.py

次の内容の Lambda 関数を作成します。

src/lambda/hello/index.py

def handler(event, context):
    return {
        'statusCode': 200,
        'body': 'Hello, CDK!'
    }

PythonFunction を使って Lambda 関数を CDK コードで実装します。

lib/cdk_demo_project-stack.ts

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

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

    new PythonFunction(this, 'helloFunction', {
      functionName: 'helloFunction',
      runtime: aws_lambda.Runtime.PYTHON_3_11,
      entry: 'src/lambda/hello',
      handler: 'handler',
    });
  }
}

デプロイ

さてここから CDK Deploy により Lambda 関数をデプロイしていくのですが、PythonFunction で Lambda 関数をビルドする上で必要な Docker 周りでかなりの試行錯誤があったので、その記録を残しておきます。

CDK Deploy をしようとすると Synth でエラーとなりました。ここで Docker の起動が必要であることに気が付きます。

$ cdk deploy                                
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2
`).map((line,idx)=>`${idx===0?firstLine:padding}${line}`)};const reason=proc.signal!=null?`signal ${proc.signal}`:`status ${proc.status}`,command=[prog,...args.map(arg=>/[^a-z0-9_-]/i.test(arg)?JSON.stringify(arg):arg)].join(" ");throw new Error([`${prog} exited with ${reason}`,...prependLines("--> STDOUT:  ",proc.stdout)??[],...prependLines("--> STDERR:  ",proc.stderr)??[],`--> Command: ${command}`].join(`
                                                                                                                                                                                                                                            ^
Error: docker exited with status 1
--> Command: docker build -t cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd --platform "linux/amd64" --build-arg "IMAGE=public.ecr.aws/sam/build-python3.11" "/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib"
    at dockerExec (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2:237)
    at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4124)
    at new Bundling (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:100:39)
    at Function.bundle (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:61:44)
    at new PythonFunction (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/function.ts:75:22)
    at new CdkDemoProjectStack (/Users/wakatsuki.ryuta/projects/cdk_demo_project/lib/cdk_demo_project-stack.ts:9:5)
    at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cdk_demo_project/bin/cdk_demo_project-stack.ts:6:1)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._compile (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

Subprocess exited with error 1

Docker を起動させます。

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

するとエラーが変わりました。

$ cdk deploy
[+] Building 0.3s (3/3) FINISHED                                                                                                                                                                                 
 => [internal] load build definition from Dockerfile                                                                                                                                                        0.0s
 => => transferring dockerfile: 1.28kB                                                                                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                                                             0.0s
 => ERROR [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest                                                                                                                           0.3s
------
 > [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest:
------
failed to solve with frontend dockerfile.v0: failed to create LLB definition: failed to do request: Head "https://public.ecr.aws/v2/sam/build-python3.11/manifests/latest": Failed to lookup host: public.ecr.aws
/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2
`).map((line,idx)=>`${idx===0?firstLine:padding}${line}`)};const reason=proc.signal!=null?`signal ${proc.signal}`:`status ${proc.status}`,command=[prog,...args.map(arg=>/[^a-z0-9_-]/i.test(arg)?JSON.stringify(arg):arg)].join(" ");throw new Error([`${prog} exited with ${reason}`,...prependLines("--> STDOUT:  ",proc.stdout)??[],...prependLines("--> STDERR:  ",proc.stderr)??[],`--> Command: ${command}`].join(`
                                                                                                                                                                                                                                            ^
Error: docker exited with status 1
--> Command: docker build -t cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd --platform "linux/amd64" --build-arg "IMAGE=public.ecr.aws/sam/build-python3.11" "/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib"
    at dockerExec (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2:237)
    at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4124)
    at new Bundling (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:100:39)
    at Function.bundle (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:61:44)
    at new PythonFunction (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/function.ts:75:22)
    at new CdkDemoProjectStack (/Users/wakatsuki.ryuta/projects/cdk_demo_project/lib/cdk_demo_project-stack.ts:9:5)
    at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cdk_demo_project/bin/cdk_demo_project-stack.ts:6:1)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._compile (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

Subprocess exited with error 1

ここで Docker Desktop のバージョンが古いことに気が付いたのでアップグレードします。するとさらにエラーが変わりました。

$ cdk deploy
[+] Building 2.2s (4/4) FINISHED                                                                                                                                                                                 
 => [internal] load build definition from Dockerfile                                                                                                                                                        0.0s
 => => transferring dockerfile: 37B                                                                                                                                                                         0.0s
 => [internal] load .dockerignore                                                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                                                             0.0s
 => ERROR [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest                                                                                                                           2.1s
 => [auth] aws:: sam/build-python3.11:pull token for public.ecr.aws                                                                                                                                         0.0s
------
 > [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest:
------
failed to solve with frontend dockerfile.v0: failed to create LLB definition: unexpected status code [manifests latest]: 403 Forbidden
/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2
`).map((line,idx)=>`${idx===0?firstLine:padding}${line}`)};const reason=proc.signal!=null?`signal ${proc.signal}`:`status ${proc.status}`,command=[prog,...args.map(arg=>/[^a-z0-9_-]/i.test(arg)?JSON.stringify(arg):arg)].join(" ");throw new Error([`${prog} exited with ${reason}`,...prependLines("--> STDOUT:  ",proc.stdout)??[],...prependLines("--> STDERR:  ",proc.stderr)??[],`--> Command: ${command}`].join(`
                                                                                                                                                                                                                                            ^
Error: docker exited with status 1
--> Command: docker build -t cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd --platform "linux/amd64" --build-arg "IMAGE=public.ecr.aws/sam/build-python3.11" "/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib"
    at dockerExec (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2:237)
    at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4124)
    at new Bundling (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:100:39)
    at Function.bundle (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:61:44)
    at new PythonFunction (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/function.ts:75:22)
    at new CdkDemoProjectStack (/Users/wakatsuki.ryuta/projects/cdk_demo_project/lib/cdk_demo_project-stack.ts:9:5)
    at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cdk_demo_project/bin/cdk_demo_project-stack.ts:6:1)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._compile (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

Subprocess exited with error 1

このエラーは下記が参考になりました。

Docker で ECR にログインをします。

aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws

すると Synth が通り、デプロイまで行えました。

$ cdk deploy                                                                                                        
[+] Building 135.5s (7/7) FINISHED                                                                                                                                                          docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                                                                                                        0.0s
 => => transferring dockerfile: 1.28kB                                                                                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                                                             0.0s
 => [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest                                                                                                                                 2.4s
 => [auth] aws:: sam/build-python3.11:pull token for public.ecr.aws                                                                                                                                         0.0s
 => [1/2] FROM public.ecr.aws/sam/build-python3.11@sha256:ad08c7e702a7c2b5c13c60de6180ca4bc97be781a72063a618784596cbda65c6                                                                                 32.9s
 => => resolve public.ecr.aws/sam/build-python3.11@sha256:ad08c7e702a7c2b5c13c60de6180ca4bc97be781a72063a618784596cbda65c6                                                                                  0.0s
 => => sha256:10f153b3bd74382059f5423e00bb91447ae450a3b566ac8dd34b9072ac49dcf8 2.64kB / 2.64kB                                                                                                              0.0s
 => => sha256:29a91f87d93088b3ae82542459d827edd30a0bd0d1c2709f2e53982c258cb8b6 87.36kB / 87.36kB                                                                                                            0.2s
 => => sha256:877e2bfc6ba89a3fc51d12d92799e17d3ec8afefbf7cc868de9e35d078b8faa9 417B / 417B                                                                                                                  0.3s
 => => sha256:ad08c7e702a7c2b5c13c60de6180ca4bc97be781a72063a618784596cbda65c6 772B / 772B                                                                                                                  0.0s
 => => sha256:79a77e7c1be9a2c4f77ead609e8d8b7162377bb6905b2a244c7964d74d8c8762 2.51MB / 2.51MB                                                                                                              1.4s
 => => sha256:0e9b5ea721abee27f3e34c651182ec82103b32067ef6858f5d432d6b10c10099 7.04kB / 7.04kB                                                                                                              0.0s
 => => extracting sha256:29a91f87d93088b3ae82542459d827edd30a0bd0d1c2709f2e53982c258cb8b6                                                                                                                   0.0s
 => => sha256:c091401fd8a0aa9d9e34453038b9e08154298a521f3f460a3f0bb2b946e4629d 128.93MB / 128.93MB                                                                                                          6.5s
 => => extracting sha256:877e2bfc6ba89a3fc51d12d92799e17d3ec8afefbf7cc868de9e35d078b8faa9                                                                                                                   0.0s
 => => sha256:2e3380a3fb2644cc0f902d3001f7d8235102e976431959b3ecdcd7d9b947378f 15.31kB / 15.31kB                                                                                                            0.7s
 => => sha256:8ceec474a49056b91a037e8617c75feac54d4f014219d0ab05cfb0854f06f171 224.66MB / 224.66MB                                                                                                         13.6s
 => => extracting sha256:79a77e7c1be9a2c4f77ead609e8d8b7162377bb6905b2a244c7964d74d8c8762                                                                                                                   0.0s
 => => sha256:4b67a36e95c8b60c0373e05feeba88d4875956eac55167b880a8c838cc6f0e14 55.82MB / 55.82MB                                                                                                           11.9s
 => => sha256:0344a919057170918f8b5889a6c39857e0776683651910a7769bcb0cc283dc73 79.85MB / 79.85MB                                                                                                           15.0s
 => => extracting sha256:c091401fd8a0aa9d9e34453038b9e08154298a521f3f460a3f0bb2b946e4629d                                                                                                                   5.5s
 => => sha256:fcf86f09cae9b81c5101817003e642f3262b3787a61f1bff6f0ac052032940e7 205.46kB / 205.46kB                                                                                                         12.2s
 => => sha256:1ac678504b71b45f9cfa05f7a25ae9510e1c2cb432fdcac3a59d58c94c7ad10e 103.92kB / 103.92kB                                                                                                         12.4s
 => => extracting sha256:2e3380a3fb2644cc0f902d3001f7d8235102e976431959b3ecdcd7d9b947378f                                                                                                                   0.0s
 => => extracting sha256:8ceec474a49056b91a037e8617c75feac54d4f014219d0ab05cfb0854f06f171                                                                                                                  10.6s
 => => extracting sha256:4b67a36e95c8b60c0373e05feeba88d4875956eac55167b880a8c838cc6f0e14                                                                                                                   2.3s
 => => extracting sha256:0344a919057170918f8b5889a6c39857e0776683651910a7769bcb0cc283dc73                                                                                                                   4.7s
 => => extracting sha256:fcf86f09cae9b81c5101817003e642f3262b3787a61f1bff6f0ac052032940e7                                                                                                                   0.0s
 => => extracting sha256:1ac678504b71b45f9cfa05f7a25ae9510e1c2cb432fdcac3a59d58c94c7ad10e                                                                                                                   0.0s
 => [2/2] RUN     python -m venv /usr/app/venv &&     mkdir /tmp/pip-cache &&     chmod -R 777 /tmp/pip-cache &&     pip install --upgrade pip &&     mkdir /tmp/poetry-cache &&     chmod -R 777 /tmp/po  99.7s
 => exporting to image                                                                                                                                                                                      0.5s
 => => exporting layers                                                                                                                                                                                     0.5s
 => => writing image sha256:392b68bf00a6d9cee956f0a5322492f8853b9f35d6f8678fbc47591504c80a38                                                                                                                0.0s 
 => => naming to docker.io/library/cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd                                                                                                     0.0s 
                                                                                                                                                                                                                 
What's Next?                                                                                                                                                                                                     
  View summary of image vulnerabilities and recommendations → docker scout quickview
Bundling asset CdkDemoProjectStack/helloFunction/Code/Stage...
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
sending incremental file list
index.py

sent 217 bytes  received 35 bytes  504.00 bytes/sec
total size is 105  speedup is 0.42

✨  Synthesis time: 139.39s

CdkDemoProjectStack:  start: Building 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region
CdkDemoProjectStack:  success: Built 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region
CdkDemoProjectStack:  start: Building 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region
CdkDemoProjectStack:  success: Built 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region
CdkDemoProjectStack:  start: Publishing 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region
CdkDemoProjectStack:  start: Publishing 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region
CdkDemoProjectStack:  success: Published 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region
CdkDemoProjectStack:  success: Published 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region
CdkDemoProjectStack: deploying... [1/1]
CdkDemoProjectStack: creating CloudFormation changeset...

 ✅  CdkDemoProjectStack

✨  Deployment time: 32.56s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/CdkDemoProjectStack/00c2aac0-2abc-11ee-85b9-0a99c3b7c389

✨  Total time: 171.95s

デプロイした Lambda 関数を実行すると正常に動作していますね。

$ aws lambda invoke \
  --function-name helloFunction \
  response.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
$ cat response.json
{"statusCode": 200, "body": "Hello, CDK!"}

Lambda 関数にパッケージを追加

PythonFunction で実装する Lambda 関数にパッケージを追加したい場合は、entry パスに

  • requirements.txt
  • Pipfile
  • poetry.lock

のいずれかを含めれば、よしなにパッケージングをしてデプロイしてくれます。

今回は Poetry を使ってみます。pytz をインストールします。

cd src/lambda/hello
poetry init
poetry add pytz

すると src/lambda/hello/poetry.lock が作成されます。また同ディレクトリに pyproject.toml も作成されますが、これは Git で管理不要なので.gitignoreに追記しておきましょう。

Lambda 関数を pytz を使うように修正して、CDK Deploy します。

src/lambda/hello/index.py

from datetime import datetime
from pytz import timezone

def handler(event, context):
    tokyo_dt = datetime.now(timezone('Asia/Tokyo'))

    return {
        'statusCode': 200,
        'body': tokyo_dt.strftime('%Y-%m-%d %H:%M:%S')
    }

デプロイした Lambda 関数を実行すると、pytz が使用された結果が返ってきています。良さそうですね。

$ aws lambda invoke \
  --function-name helloFunction \
  response.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
$ cat response.json
{"statusCode": 200, "body": "2023-07-31 23:13:09"}

ちなみにパッケージングのその他の方法として同ライブラリの PythonLayerVersion も使用可能です。こちらは Lambda 関数のコードとは別にレイヤーとしてパッケージングされます。

おわりに

AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装してみました。

Python や Docker を使うのは久しぶりで四苦八苦しましたが、なんとかデプロイまで行えました。参考になれば幸いです。

参考

以上