【AWS CDK v2】创建固定IP的Lambda方法

在某些需要从Lambda访问受IP限制的服务器的情况下,或者在需要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

完整的工作

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时,您还可以一并创建相关联的子网和EIP等资源。

在这里,除了默认的资源外,通过使用选项,我们分别对两个可用区创建了公共子网和私有子网。

    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,
        },
      ],
    });

安全组

创建一个与Lambda相关联的安全组(官方参考文档在此)。
将安全组的vpc指定为刚刚创建的那个。

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

Lambda – fā yā

最后创建将放置在VPC中的Lambda(可参考官方参考资料)。

将先前创建的安全组传递给securityGroups,将subnetType设置为PRIVATE_WITH_NAT的子网传递给vpcSubnets,通过vpc的selectSubnets方法。由于Lambda函数需要访问互联网,还需要将allowPublicSubnet设置为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,
    });
  }

请确认固定IP是否已设定。

您可以在AWS控制台的EC2 -> 网络和安全 -> 弹性IP下找到已创建的EIP。

由于此次将maxAzs设置为2,因此将创建两个EIP。如果需要访问具有访问限制的服务器等,请确保允许此IP地址。

请注意:默认情况下,每个AWS账户在每个区域只能创建最多5个弹性IP地址。参考:弹性IP地址

mosaic_20220928161244.png

总结

设置VPC以固定Lambda的IP地址,感觉需要进行详细的配置。但事实上,VPC会很好地创建所需的资源,所以实现起来意外地简单。

请注意,每个账户最多只能创建5个EIP,因此如果要使用多个环境,可能需要为每个环境分别设立账户来进行相应的处理。

我认为受到IP限制的访问很常见,如果这篇文章能对您有所帮助,我将感到幸运。

请查阅相关资料。

使用AWS CDK时尝试设置Lambda函数URL(函数URL)。(使用L1 Constract)从VPC关联给Lambda添加固定IP。

额外的东西

Lambda的实现细节

Lambda的实现是通过TypeScript,并使用axios来执行API。
以下示例可发送简单的POST请求。

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}`);
  }
};
广告
将在 10 秒后关闭
bannerAds