採用はこちら!

Shinonome Tech Blog

株式会社Shinonomeの技術ブログ
5 min read

AWS CDKとローカルテスト戦略

AWS CDKのローカルテストについて調べてみたので紹介します。

こんにちは!今年度は業務でPMを担当しているTatsuroです(決して偉くなったわけではない)。
今回は、AWS CDKのローカルテストについて調べてみたので紹介します。
戦略といえるほどのものではないかもしれませんが・・・

https://github.com/tatsurou9003/lt_aws-cdk

AWSのサービスは基本的にコンソール上で開発できます。ただ、ポチポチして開発するのは何かダサいし怠い・・・ということでAWS CDKに手を出してIaC化するわけです。そうなると勿論デプロイする前にローカルでデバッグ/テストしたくなってくるわけですが、本記事ではLocalStackおよびSAMを通じたスタックテストについて御紹介しようと思います。

LocalStackとは

LocalStackは、コンテナまたはCI環境で実行されるクラウドサービスエミュレーターです。LocalStack を使用すると、リモートクラウドプロバイダーに接続せずに、AWSアプリケーションやLambdaを完全にローカルマシン上で実行できます。複雑なCDKアプリケーションやTerraform構成をテストしている場合でも、AWSサービスについて学び始めたばかりの場合でも、LocalStackはテストと開発のワークフローを高速化し、簡素化するのに役立ちます。LocalStackは、AWS Lambda、S3、DynamoDB、Kinesis、SQS、SNSなど、多くのAWSサービスをサポートします・・・とのこと。要するにローカルテストにうってつけということです。
では実際に動かしてみましょう。

Overview
LocalStack Documentation

//docker-compose.yml
version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566" # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559" # external services port range
    environment:
      - DEBUG=${DEBUG:-0}
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

まずはdocにしたがってLocalStackのコンテナを立ち上げます。

docker-compose up

コンテナ起動

npm install -g aws-cdk-local aws-cdk
pip install awscli-local

コンテナの起動を確認できたらAWS CLI、CDKとLocalStackを併せて使うために、awslocalとcdklocalをインストールしておきます。

cdklocal init -language python

ではcdklocalプロジェクトを作成します。

----------2024-05-14-6.43.01
lib/lambda_handler.pyを追加するとこのようなディレクトリ構成になるはず

// cdk_local/cdk_local_stack.py
from aws_cdk import (
    Stack,
    Duration,
    aws_lambda as lambda_,
    aws_lambda_event_sources as lambda_event_sources,
)
from constructs import Construct

function_timeout = 3
function_memory_size = 128

class CdkLocalStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_function = lambda_.Function(self, "HelloWorldFunction",
            code=lambda_.Code.from_asset("lib"),
            handler="lambda_handler.lambda_handler",
            runtime=lambda_.Runtime.PYTHON_3_8,
            timeout=Duration.minutes(function_timeout),
            memory_size=function_memory_size,
        )
        lambda_function.add_event_source(lambda_event_sources.ApiEventSource(
                method="get",
                path="/hello",
            )
        )
// lib/lambda_handler.py
import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "Welcome to PlayGround",
        }),
    }

簡単なAPI GatewayとLambdaのスタックを定義

cdklocal bootstrap

お馴染みのコマンドでIAMロール・バケット等のスタックを作成

cdklocal deploy

コンテナにデプロイします。

awslocal lambda list-functions
{
    "Functions": [
        {
            "FunctionName": "CdkLocalStack-HelloWorldFunction000000000",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:000000000000:function:CdkLocalStack-HelloWorldFunction0000000000",
            "Runtime": "python3.8",
            "Role": "arn:aws:iam::000000000000:role/CdkLocalStack-HelloWorldFunctionServic-000000",
            "Handler": "lambda_handler.lambda_handler",
            "CodeSize": 282,
            "Description": "",
            "Timeout": 180,
            "MemorySize": 128,
            "LastModified": "2024-05-13T16:23:48.267109+0000",
            "CodeSha256": "00000000000000000000000000",
            "Version": "$LATEST",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "000000000000",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            },
            "LoggingConfig": {
                "LogFormat": "Text",
                "LogGroup": "/aws/lambda/CdkLocalStack-HelloWorldFunction000000000000"
            }
        }
    ]
}

awslocalコマンドを使ってdeployされているlambda関数をリスト表示すると、先ほどのLambda関数を確認できました。では早速API Gatewayのエンドポイントにアクセスしてみましょう。と思ったけど・・・

curl -X GET http://localhost:4566/restapis/{apiID}/test/_user_request_/hello

ドキュメントを見ると、apiIdなるものが必要らしいです。

awslocal apigateway get-rest-apis
{
    "items": [
        {
            "id": "apiID",  // こいつだ!
            "name": "CdkLocalStackHelloWorldFunction:ApiEventSource",
            "createdDate": "2024-05-14T01:23:53+09:00",
            "apiKeySource": "HEADER",
            "endpointConfiguration": {
                "types": [
                    "EDGE"
                ]
            },
            "tags": {
                "aws:cloudformation:logical-id": "CdkLocalStackHelloWorldFunctionApiEventSource",
                "aws:cloudformation:stack-name": "CdkLocalStack",
                "aws:cloudformation:stack-id": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/CdkLocalStack/000000"
            },
            "disableExecuteApiEndpoint": false,
            "rootResourceId": "00000000"
        }
    ]
{"message": "Welcome to PlayGround"}%

先ほどのエンドポイントを叩くと・・・出てきました。
お次はSAM CLIでやってみましょう。

SAMとは

AWS SAMテンプレートは、サーバーレスアプリケーション用のIaCの定義に最適化された簡潔な構文を提供します。AWS CloudFormationの拡張機能として、SAMテンプレートをCloudFormationに直接デプロイできます。これにより、AWSでの広範なIaCサポートの恩恵を受けることができます。SAM CLIは、SAMの機能をすぐに使えるようにするデベロッパーツールです。これを使用すると、サーバーレスアプリケーションをすばやく作成、開発、デプロイできます。とのこと。早い話がLambda・API Gateway・dynamoDBのような構成に特化したCloudFormationということですね。
こいつの良いところは、CDKで作成したテンプレートを与えてやると、ローカルでLambda・API Gatewayを動かせるところです。
では実際に動かしてみましょう。

AWS サーバーレスアプリケーションモデル - アマゾン ウェブ サービス
AWS でサーバーレスアプリケーションを構築、デプロイ、提供、共有する方法を簡素化します。
AWS SAM CLIのインストール - AWS Serverless Application Model
このセクションでは、macOS、Windows、および Linux に AWS SAM CLI をインストールする方法について説明します。

まずはSAM CLIをインストールしましょう。GUIで出来ます。

sam local invoke HelloWorldFunction --no-event -t ./cdk.out/CdkLocalStack.template.json

sam local invokeでDockerコンテナが立ち上がり、直接Lambda関数を呼び出せます。

Invoking lambda_handler.lambda_handler (python3.8)                                                              
Local image is up-to-date                                                                                       
Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.                                               
                                                                                                                
Mounting                                                                                                        
/Users/tatsurom/Documents/aws-cdk-lt/cdk_local/cdk.out/asset.000000000000000 as /var/task:ro,delegated, inside runtime container                                               
START RequestId: 78a999e2-47c3-4eac-b494-f9fdc01b148a Version: $LATEST
END RequestId: ###############
REPORT RequestId: #############  Init Duration: 0.69 ms  Duration: 500.30 ms     Billed Duration: 501 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"Welcome to PlayGround\"}"}

上手く呼び出せました。お次はAPI Gatewayを通して呼び出してみましょう。

sam local start-api -t ./cdk.out/CdkLocalStack.template.json

sam local start-apiでローカルにAPIが立ち上がります。

curl -X GET http://127.0.0.1:3000/hello 
{"message": "Welcome to PlayGround"}%   

返ってきました。

まとめ

いかがだったでしょうか。今回は、LocalStack, SAMを使ってAWSのサービスをローカルでテストしてみました。所感としては、サーバレス構成の場合は圧倒的にSAM CLIが楽チンだと感じました。LocalStackは汎用的なツールで、課金すると扱えるサービスが増えたり、より容易に扱えるようになるみたいです。

----------2024-05-14-7.53.36
少し話は逸れますが、先日コミュニティ内でLT会を開催して登壇しました(この記事の内容も入ってた)。
30人近くの前で1時間半ぐらい喋って大変疲れました(どこがLightning Talkやねんという声は置いておいて)。
もっとコミュニティ内でAWSに興味を持ってくれる学生が増えたらいいなと思います。
それではまた次回の記事でお会いしましょう、さようなら👋