LambdaとRDSの連携をCDKで簡単実装する

はじめに

先日、Lambda上でコンテナイメージを動かしRDSに接続するということをやりました。

miyohide.hatenablog.com

このときは手作業でぽちぽちやりましたが、環境設定が面倒くさかったのでCDKで実装してみました。

VPCを作る

まずはVPCを作成します。こんな感じで。ちょっとでも料金節約のために、natGatewaysの数は1に設定します。

    // VPCの作成
    const vpc = new Vpc(this, "MyVPC", {
      enableDnsHostnames: true,
      enableDnsSupport: true,
      maxAzs: 2,
      natGateways: 1,
      subnetConfiguration: [
        {
          name: "PublicSubnet",
          subnetType: SubnetType.PUBLIC,
          cidrMask: 24,
          mapPublicIpOnLaunch: true,
        },
        {
          name: "PrivateSubnet",
          subnetType: SubnetType.PRIVATE_WITH_EGRESS,
          cidrMask: 24,
        },
        {
          name: "DBSubnet",
          subnetType: SubnetType.PRIVATE_ISOLATED,
          cidrMask: 24,
        }
      ]
    });

こんな感じでリソースが作られます。

作ってみて思いましたが、name属性にSubnetはいらなかったかなと思います。自動生成する名前にもSubnetがつくので冗長でした。

Lambdaを作る

コンテナイメージを作るLambdaを作成します。コンテナイメージはECRにあるので、こんな感じで実装します。

    // ECRのリポジトリを指定する
    const repository = Repository.fromRepositoryName(this, "MyRepository", "my-ruby-app");

    // Lambda用のセキュリティグループを作成する
    const lambdaSecurityGroup = new SecurityGroup(this, "LambdaSecurityGroup", {
      vpc: vpc,
      description: "Lambda Security Group",
      allowAllOutbound: true,
    });

    // ECRにあるコンテナイメージを利用してLambda関数を作成する
    const lambda = new Function(this, "Lambda", {
      code: Code.fromEcrImage(repository, {
        tag: "latest",
      }),
      functionName: "my-ruby-app",
      runtime: Runtime.FROM_IMAGE,
      handler: Handler.FROM_IMAGE,
      timeout: cdk.Duration.seconds(30),
      vpc: vpc,
      vpcSubnets: {
        subnetType: SubnetType.PRIVATE_WITH_EGRESS,
      },
      securityGroups: [lambdaSecurityGroup],
    });

既存のECRはRepository.fromRepositoryNameで取得できるので、それを使い、Functioncode属性にてCode.fromEcrImageにて対象のイメージを指定します。

RDSを作る

RDSを作成します。こんな感じで実装します。

    // RDS用のセキュリティグループの作成
    const rdsSecurityGroup = new SecurityGroup(this, "RDSSecurityGroup", {
      vpc: vpc,
      description: "RDS Security Group",
      allowAllOutbound: true,
    });

    const dbSubnetGroup = new SubnetGroup(this, "MyDBSubnetGroup", {
      vpc: vpc,
      description: "My DB Subnet Group",
      vpcSubnets: {
        subnetType: SubnetType.PRIVATE_ISOLATED,
        onePerAz: true,
      }
    });

    // RDSインスタンスの作成と設定を行う。今回はPostgreSQLを使用しているため、
    // DatabaseInstanceEngine.POSTGRESを指定する。
    const rdsInstance = new DatabaseInstance(this, "MyRDSInstance", {
      engine: DatabaseInstanceEngine.POSTGRES,
      instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO),
      vpc: vpc,
      databaseName: "mypostgresdb",
      multiAz: false,
      subnetGroup: dbSubnetGroup,
      securityGroups: [rdsSecurityGroup],
    });

上記のCDK内でRDSのパスワードなどは指定していませんが、これらはSecret Managerに自動的に登録されます。

前回でのプログラムでは環境変数経由でユーザー名やパスワードの取得をしていましたが、Secret Managerから読み取らせる必要があります。実装についてはまた後日。

RDSの作成は、そこそこ時間がかかるのでのんびり待ちます。

セキュリティグループの設定

セキュリティグループの指定は以下の実装で適切なものが実装されます。

rdsInstance.connections.allowDefaultPortFrom(lambda, "Lambda to RDS");

おまけ

これでCDKで前回の環境を実装することができました。今回はテスト用だったので、一度動作確認をしたらcdk destroyで削除しようとしたのですが、実際の削除には30分ぐらい時間がかかりました。主な要因は、Lambdaが生成するネットワークインターフェースが20分ぐらい使っているという状況のまま消えなかったため。あまり気にせずにのんびり待ちます。