PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

AWS CDK を使ってみよう

こんにちは、SaaSプロダクト開発部の村山です。 業務でAWS CDKを使って環境構築を行う機会がありましたので 、今回はAWS CDKの紹介をしていきます。

AWS CDKとは?

TypeScript、JavaScript、Python、Java、C#/.Net、Goのいずれかのプログラミング言語を使用してAWSのインフラストラクチャを定義し、AWSにプロビジョニングできるフレームワークです。

記述したコードはCloudFormationのリソースとしてデプロイされます。

使い慣れたプログラミング言語で記述できるため、自由度が高い記述が可能です。

docs.aws.amazon.com

AWS CDKの構造

アプリケーション

アプリケーションは1つ以上のスタックのコンテナです。

スタック

コンストラクトを使ってAWS リソースを定義します。
デプロイ時にCloudFormationのスタックとしてプロビジョニングされるので、CloudFormationのクォータと制限が適用されます。

コンストラクト

コンストラクトはAWS CDKの基本的な構成要素(クラス)で、1つ以上のCloudFormationリソースと設定を表しています。
コンストラクトはAWSが提供するものの他に、独自のコンストラクトを作成したりサードパーティのデベロッパーが作成したコンストラクトを使用することもできます。

コンストラクトにはL1からL3まで3つのレベルがあります。

  • L1: Cfn(CloudFormation)リソース(CfnXxxという名前のコンストラクトはL1)
  • L2: AWSによってキュレートされたコンストラクトで、デフォルト値やベストプラクティスのセキュリティポリシーなどが含まれている
  • L3: 特定のサービスを構築するパターン(xxxPatternsという名前のコンストラクトはL3)

L1 → L3 に向かって抽象度が高くなっています。
基本はL2を使っていくことが多いと思います。L2やL3では作成できない場合は、L1を使って記述していきます。

docs.aws.amazon.com

やってみる

今回はサンプルとしてVPCとVPC Lambdaを作ってみたいと思います。

前提として

  • AWSアカウントの準備
  • AWS CLIのインストール
  • Node.js(14.15.0以降)のインストール

は済んでいるとします。

準備

AWS CDK CLIをインストールします。

$ npm install -g aws-cdk

この記事の執筆時点のCDK versionは2.134.0です

$ cdk --version
2.134.0 (build 265d769)

デプロイする予定のAWS環境のBootstrapを実行する必要があります。
これは後で行っても問題ないです。

$ cdk bootstrap aws://ACCOUNT-NUMBER/REGION

ディレクトリを作成してCDKアプリを初期化します。 今回はTypeScriptを使います。

$ mkdir cdk-sample
$ cd cdk-sample
$ cdk init app --language typescript

作成されたディレクトリとファイルはこのようになっています。

.
├── README.md
├── bin
│   └── cdk-sample.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── cdk-sample-stack.ts
├── node_modules
├── package-lock.json
├── package.json
├── test
│   └── cdk-sample.test.ts
└── tsconfig.json

実際にコードを書いていくスタックはlib/cdk-sample-stack.tsです。
エディタで開くとこうなっています。

lib/cdk-sample-stack.ts

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';

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

    // The code that defines your stack goes here

    // example resource
    // const queue = new sqs.Queue(this, 'CdkSampleQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

constructorの中にコードを記述していきます。
コードが長くなったり再利用をしたくなったりした場合は、メソッドを作ったり、スタックを分割したりして呼び出す形にすることもできます。
こういった柔軟な記述ができるところが、プログラミング言語で書ける利点ですね。

どのコンストラクトを使うかは公式のAPIリファレンスから探します。
ちなみにCDKのドキュメントはcdk docsコマンドを実行するとブラウザで開くことができます。

GitHubにサンプルコードもあるので、そちらも参考するといいと思います。 github.com

コンストラクタの記述は基本的に

第一引数にスコープ(コンストラクト)
第二引数にid
第三引数にコンストラクタのprops
という形になっています。

VPCの作成

まずはVPCを作成してみます。

ec2コンストラクトをインポート

lib/cdk-sample-stack.ts

import { aws_ec2 as ec2 } from "aws-cdk-lib";

VPCの作成

lib/cdk-sample-stack.ts

const vpc = new ec2.Vpc(this, "SampleVPC", {
  ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
  vpcName: "cdk-sample-vpc",
});

ec2.VpcコンストラクトはL2なので、特に指定しなくてもデフォルトでサブネットの作成などをやってくれます。
自分で指定したい場合は、API リファレンスを確認しながらpropsを記述します。 今回は、ipAddressesでCIDRを指定、vpcNameでVPCの名前を指定しています。

Tips: コンストラクトのインスタンスからは、コンストラクトを構成する要素を取得することができます。

例えばec2.Vpcコンストラクトで作成されたパブリックサブネットを取得するときは

vpc.publicSubnets

このようにします。

実際に何をやっているのか確認するために、CDKのコードからCloudFormationのテンプレートを出力できるcdk synthコマンドを実行してみます。

$ cdk synth

出力されたCloudFormationテンプレート

Resources:
  SampleVPC676AFAA6:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: cdk-sample-vpc
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/Resource
  SampleVPCPublicSubnet1SubnetFF189553:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      CidrBlock: 10.0.0.0/18
      MapPublicIpOnLaunch: true
      Tags:
        - Key: aws-cdk:subnet-name
          Value: Public
        - Key: aws-cdk:subnet-type
          Value: Public
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet1
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet1/Subnet
  SampleVPCPublicSubnet1RouteTableD74013E6:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet1
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet1/RouteTable
  SampleVPCPublicSubnet1RouteTableAssociation0E1E38CA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleVPCPublicSubnet1RouteTableD74013E6
      SubnetId:
        Ref: SampleVPCPublicSubnet1SubnetFF189553
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet1/RouteTableAssociation
  SampleVPCPublicSubnet1DefaultRouteD62243DA:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: SampleVPCIGW11D9AA06
      RouteTableId:
        Ref: SampleVPCPublicSubnet1RouteTableD74013E6
    DependsOn:
      - SampleVPCVPCGW9A31D44E
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet1/DefaultRoute
  SampleVPCPublicSubnet1EIP7C23471C:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet1
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet1/EIP
  SampleVPCPublicSubnet1NATGatewayB71B54D1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - SampleVPCPublicSubnet1EIP7C23471C
          - AllocationId
      SubnetId:
        Ref: SampleVPCPublicSubnet1SubnetFF189553
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet1
    DependsOn:
      - SampleVPCPublicSubnet1DefaultRouteD62243DA
      - SampleVPCPublicSubnet1RouteTableAssociation0E1E38CA
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet1/NATGateway
  SampleVPCPublicSubnet2SubnetAF202FA8:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      CidrBlock: 10.0.64.0/18
      MapPublicIpOnLaunch: true
      Tags:
        - Key: aws-cdk:subnet-name
          Value: Public
        - Key: aws-cdk:subnet-type
          Value: Public
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet2
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet2/Subnet
  SampleVPCPublicSubnet2RouteTable63C2EDC5:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet2
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet2/RouteTable
  SampleVPCPublicSubnet2RouteTableAssociation9C39EBDB:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleVPCPublicSubnet2RouteTable63C2EDC5
      SubnetId:
        Ref: SampleVPCPublicSubnet2SubnetAF202FA8
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet2/RouteTableAssociation
  SampleVPCPublicSubnet2DefaultRoute45289F69:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: SampleVPCIGW11D9AA06
      RouteTableId:
        Ref: SampleVPCPublicSubnet2RouteTable63C2EDC5
    DependsOn:
      - SampleVPCVPCGW9A31D44E
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet2/DefaultRoute
  SampleVPCPublicSubnet2EIPA80228F3:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet2
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet2/EIP
  SampleVPCPublicSubnet2NATGateway2775C8CB:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - SampleVPCPublicSubnet2EIPA80228F3
          - AllocationId
      SubnetId:
        Ref: SampleVPCPublicSubnet2SubnetAF202FA8
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PublicSubnet2
    DependsOn:
      - SampleVPCPublicSubnet2DefaultRoute45289F69
      - SampleVPCPublicSubnet2RouteTableAssociation9C39EBDB
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PublicSubnet2/NATGateway
  SampleVPCPrivateSubnet1SubnetB2AF079C:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      CidrBlock: 10.0.128.0/18
      MapPublicIpOnLaunch: false
      Tags:
        - Key: aws-cdk:subnet-name
          Value: Private
        - Key: aws-cdk:subnet-type
          Value: Private
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PrivateSubnet1
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet1/Subnet
  SampleVPCPrivateSubnet1RouteTable11FF53B5:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PrivateSubnet1
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet1/RouteTable
  SampleVPCPrivateSubnet1RouteTableAssociation42FCDD78:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleVPCPrivateSubnet1RouteTable11FF53B5
      SubnetId:
        Ref: SampleVPCPrivateSubnet1SubnetB2AF079C
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet1/RouteTableAssociation
  SampleVPCPrivateSubnet1DefaultRoute8F7F8183:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: SampleVPCPublicSubnet1NATGatewayB71B54D1
      RouteTableId:
        Ref: SampleVPCPrivateSubnet1RouteTable11FF53B5
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet1/DefaultRoute
  SampleVPCPrivateSubnet2Subnet1A9D7D61:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      CidrBlock: 10.0.192.0/18
      MapPublicIpOnLaunch: false
      Tags:
        - Key: aws-cdk:subnet-name
          Value: Private
        - Key: aws-cdk:subnet-type
          Value: Private
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PrivateSubnet2
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet2/Subnet
  SampleVPCPrivateSubnet2RouteTable8996254C:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: CdkSampleStack/SampleVPC/PrivateSubnet2
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet2/RouteTable
  SampleVPCPrivateSubnet2RouteTableAssociation13E6A7CE:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleVPCPrivateSubnet2RouteTable8996254C
      SubnetId:
        Ref: SampleVPCPrivateSubnet2Subnet1A9D7D61
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet2/RouteTableAssociation
  SampleVPCPrivateSubnet2DefaultRouteE89BC1AA:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: SampleVPCPublicSubnet2NATGateway2775C8CB
      RouteTableId:
        Ref: SampleVPCPrivateSubnet2RouteTable8996254C
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/PrivateSubnet2/DefaultRoute
  SampleVPCIGW11D9AA06:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: cdk-sample-vpc
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/IGW
  SampleVPCVPCGW9A31D44E:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId:
        Ref: SampleVPCIGW11D9AA06
      VpcId:
        Ref: SampleVPC676AFAA6
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/VPCGW
  SampleVPCRestrictDefaultSecurityGroupCustomResourceACB89147:
    Type: Custom::VpcRestrictDefaultSG
    Properties:
      ServiceToken:
        Fn::GetAtt:
          - CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E
          - Arn
      DefaultSecurityGroupId:
        Fn::GetAtt:
          - SampleVPC676AFAA6
          - DefaultSecurityGroup
      Account:
        Ref: AWS::AccountId
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
    Metadata:
      aws:cdk:path: CdkSampleStack/SampleVPC/RestrictDefaultSecurityGroupCustomResource/Default
  CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
      ManagedPolicyArns:
        - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: Inline
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:AuthorizeSecurityGroupIngress
                  - ec2:AuthorizeSecurityGroupEgress
                  - ec2:RevokeSecurityGroupIngress
                  - ec2:RevokeSecurityGroupEgress
                Resource:
                  - Fn::Join:
                      - ""
                      - - "arn:"
                        - Ref: AWS::Partition
                        - ":ec2:"
                        - Ref: AWS::Region
                        - ":"
                        - Ref: AWS::AccountId
                        - :security-group/
                        - Fn::GetAtt:
                            - SampleVPC676AFAA6
                            - DefaultSecurityGroup
    Metadata:
      aws:cdk:path: CdkSampleStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role
  CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket:
          Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}
        S3Key: 4b996a3e5a083d5c78c6f30a8571a94fb7ec557eecbe54dbc065faba0d9076e6.zip
      Timeout: 900
      MemorySize: 128
      Handler: __entrypoint__.handler
      Role:
        Fn::GetAtt:
          - CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0
          - Arn
      Runtime: nodejs18.x
      Description: Lambda function for removing all inbound/outbound rules from the VPC default security group
    DependsOn:
      - CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0
    Metadata:
      aws:cdk:path: CdkSampleStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler
      aws:asset:path: asset.4b996a3e5a083d5c78c6f30a8571a94fb7ec557eecbe54dbc065faba0d9076e6
      aws:asset:property: Code
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/21Qy24CMQz8Fu7ZlMeB9ohWqOKCoqXiWmWzhhp2HZQ4iyrEv9cRpbn0NOPxyB57rt8Wejqx11i57lz12Orbjq07K5E+wc31bX9xqj7Q3tTKpLZHt0stAWetsMYnhg/b9lD0oq1i9A4to6c/cybrjcmwtfxuGa72W5mAo9AyeEMMQfjT8EjyW61Yon4NQHxXDUSfgpO5KbIfSin7/m+Z4EfsICiJByxXH5GO2V976jCHvSvyHehTfBlnr3opjzpFxCokYhxANw/8AQ4DHeJEAQAA
    Metadata:
      aws:cdk:path: CdkSampleStack/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - af-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

サブネットやルートテーブルが一緒に作成されているのがわかると思います。

この時点で一度デプロイしてみましょう。

$ cdk deploy

実行が完了したらAWSコンソールを確認してみましょう。

指定したCIDRでVPCが作成されました。

Lambdaの作成

次は、Lambdaの作成です。

必要なコンストラクトをインポートします。

lib/cdk-sample-stack.ts

import { 
  aws_ec2 as ec2,
  aws_lambda as lambda,
  aws_lambda_nodejs as lambdaNodejs,
  Duration,
} from "aws-cdk-lib";
import { Runtime } from "aws-cdk-lib/aws-lambda";

続いて、LambdaとLambdaの関数URLの設定です。 先ほど作成したVPCをpropsに設定します。

lib/cdk-sample-stack.ts

const testFunc = new lambdaNodejs.NodejsFunctions(this, "test_func", {
  vpc: vpc,
  runtime: Runtime.NODEJS_20_X,
  functionName: "test_func",
  timeout: Duration.seconds(25),
  logRetention: 7,
});

testFunc.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.NONE,
});

固定のレスポンスを返す関数を作成します。
Lambdaのソースコードの指定の仕方はいろいろあるのですが、特に何も指定しない場合はlib/スタック名.関数名.tsのファイルが参照されます。

lib/cdk-sample-stack.test_func.ts

export async function handler() {
  return {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: { message: "hello CDK" },
  };
}

再度デプロイします。

$ cdk deploy

関数URLが発行されたVPC Lambdaができました。

発行された関数URLにアクセスしてみます。

無事、レスポンスが帰ってきました。

最後に

最終的に完成したコードは以下になります。

lib/cdk-sample-stack.test_func.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import {
  aws_ec2 as ec2,
  aws_lambda as lambda,
  aws_lambda_nodejs as lambdaNodejs,
  Duration,
} from "aws-cdk-lib";
import { Runtime } from "aws-cdk-lib/aws-lambda";

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

    const vpc = new ec2.Vpc(this, "SampleVPC", {
      ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
      vpcName: "cdk-sample-vpc",
    });

    const testFunc = new lambdaNodejs.NodejsFunction(this, "test_func", {
      vpc: vpc,
      runtime: Runtime.NODEJS_20_X,
      functionName: "test_func",
      timeout: Duration.seconds(25),
      logRetention: 7,
    });

    testFunc.addFunctionUrl({
      authType: lambda.FunctionUrlAuthType.NONE,
    });
  }
}

使い慣れたプログラミング言語を使って、少ないコード量で記述できるのは魅力的ですよね。
スタックの分割など応用編は、機会があれば書きたいと思います。

最後まで読んでいただきありがとうございました。