3
2

More than 1 year has passed since last update.

【AWS CDK v2】IPを固定化したLambdaの作成方法

Posted at

IP制限がかかっているサーバーにLambdaからアクセスする必要がある場合など、LambdaのIP固定化が必要な際にAWS CDK v2でどのように実装するかを記載します。

構成

VPCを作成し、その中でLambdaとEIPを紐付けてIPを固定化し、インターネットにアクセスする構成です。
Lambdaの中身はTypescriptで実装し、axiosを利用してAPIを実行します。

archtecture.drawio.png

環境

cdk --version
2.21.1 (build a6ee543)

node_modules/.bin/tsc --v
Version 4.8.3

完成系

vpc-lambda-stack.ts
import { Duration, Stack, StackProps } from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";

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

    // VPCの作成
    const vpc = new ec2.Vpc(this, "vpc", {
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "PublicSubnet",
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: "PrivateSubnet",
          subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
        },
      ],
    });

    // セキュリティグループの作成
    const securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
      vpc: vpc,
      allowAllOutbound: true,
    });

    // Lambdaの作成
    new nodejs.NodejsFunction(this, "VpcLambdaFunction", {
      entry: "src/api-test/handler/handler.ts",
      runtime: lambda.Runtime.NODEJS_16_X,
      timeout: Duration.seconds(10),
      vpc: vpc,
      securityGroups: [securityGroup],
      vpcSubnets: vpc.selectSubnets({
        subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
      }),
      allowPublicSubnet: true,
    });
  }
}

VPC

まずは基盤となるVPCを作成します(公式リファレンスはこちら)。
このVPCを作成する際にサブネットEIPなどの関連するリソースも一緒に作成できます。

ここではデフォルトのリソース以外に、オプションを利用することで2つのアベイラビリティゾーンに対してパプリックサブネットプライベートサブネットをそれぞれ作成しています。

    const vpc = new ec2.Vpc(this, "vpc", {
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "PublicSubnet",
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: "PrivateSubnet",
          subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
        },
      ],
    });

Security Group

次にLambdaに紐づけるセキュリティーグループを作成します(公式リファレンスはこちら)。
セキュリティーグループのvpcには先ほど作成したものを指定します。

    // セキュリティグループの作成
    const securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
      vpc: vpc,
      allowAllOutbound: true,
    });

Lambda

最後にVPCに配置するLambdaを作成します(公式リファレンスはこちら)。

securityGroupsには先ほど作成したものを、vpcSubnetsにはvpcのselectSubnetsメソッドによってsubnetTypeがPRIVATE_WITH_NATになっているものを渡します。
Lambda関数はインターネットにアクセスするため、allowPublicSubnettrueにする設定も必要です。

    // Lambdaの作成
    new nodejs.NodejsFunction(this, "VpcLambdaFunction", {
      entry: "src/api-test/handler/handler.ts",
      runtime: lambda.Runtime.NODEJS_16_X,
      timeout: Duration.seconds(10),
      vpc: vpc,
      securityGroups: [securityGroup],
      vpcSubnets: vpc.selectSubnets({
        subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
      }),
      allowPublicSubnet: true,
    });
  }

固定化されたIPの確認

作成されたEIPはAWSコンソールのEC2 -> ネットワーク&セキュリティ -> Elastic IPから確認できます。

今回はmaxAzsを2に指定しているため、EIPも2つ作成されます。アクセス制限のあるサーバーなどにアクセスする場合はこちらのIPアドレスに対する許可が必要です。

※注意:デフォルトでは、すべてのAWSアカウントで1リージョンあたり5つのElastic IPアドレスまでしか作成できない制限があります。参考:Elastic IP アドレス

mosaic_20220928161244.png

まとめ

LambdaのIPを固定化させるにはVPCを細かく設定しないといけないと思いきや、必要なリソースはVPCが良い感じに作成してくれるため、意外と簡単に実装が可能なことがわかりました。

ただし、1アカウントに作成できるEIPは5個までの制限があるため、複数環境を利用する場合は環境毎にアカウントを分けるなどの対応が必要かと思います。

IP制限のあるアクセスは何かと多いと思うので、この記事が参考になれば幸いです。

参考資料

【AWS/Lambda】lambdaに固定IPをつける/VPCに所属させる
AWS CDKを使ってLambda 関数URL(Function URLs)を設定してみた。(L1 Constractで)

おまけ

Lambdaの中身の実装

Lambdaの中身はTypeScriptで実装し、axiosを利用してAPIを実行します。
以下のサンプルでは簡単なPOSTリクエストを送信できます。

handler.ts
import { APIGatewayProxyEvent } from "aws-lambda";
import axios from "axios";
import * as log from "lambda-log";

/**
 * apiを実行するLambda
 * @param event
 * @returns
 */
export const handler = async (event: APIGatewayProxyEvent) => {
  log.info("event row data", { event });

  const endpoint = "https://example.com/";
  const headers = {
    "Content-Type": "application/json",
  };
  const body = {
    id: "XXXXXXXXXX",
  };

  // APIを実行
  try {
    return await axios
      .post(endpoint, body, {
        headers: headers,
      })
      .then((response) => {
        log.info("raw response data", { data: response.data });
      })
      .catch(async (reason) => {
        log.error("Executing api failed.", { reason });
        throw reason;
      });
  } catch (e) {
    log.error(`Error executing api, reason=${(e as Error).message}`);
  }
};
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2