覚え書き。どうしても対外IPを固定したLambdaが必要だったので作った。

  • 実際に動いた構成をブログ用にまとめたものなので、そのままだと動かないかも。
  • 具体的な設定値を乗せたいので、あまりパラメータに出してない。必要ならパラメータにする。
  • タグは適当。とりあえず、タグをつけられる箇所だということを示すためにつけてる。

以下の記事を参考にさせていただきました。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: "example"

Parameters:
  ServiceName:
    Description: "サービス名"
    Type: String
  
Resources:
  #========================================
  # IAM
  #========================================

  # Lambda実行ロール
  MyVpcLambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
  
  # Lambda実行ロールのポリシー
  MyVpcLambdaExecutionPolicy:
    Type: "AWS::IAM::Policy"
    DependsOn: MyVpcLambdaExecutionRole
    Properties:
      PolicyName: !Ref VpcLambdaExecutionPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
            Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
          - Effect: "Allow"
            Action:
              - "ec2:CreateNetworkInterface"
              - "ec2:DescribeNetworkInterfaces"
              - "ec2:DetachNetworkInterface"
              - "ec2:DeleteNetworkInterface"
            Resource: "*"
      Roles:
        - !Ref MyVpcLambdaExecutionRole


  #========================================
  # Lambda
  #========================================

  ## ここでは AWS::Serverless::Function ではなく AWS::Lambda::Function で作成してるけど、
  ## AWS::Serverless::Function でも VpcConfig の書き方は同じ

  # VPC Lambda
  MyLambdaFunction:
    Type: AWS::Lambda::Function
    DependsOn: MyVpcLambdaExecutionPolicy
    Properties:
      # ここではとりあえずNode.jsのHelloWorldを書いてます。
      FunctionName: !Ref LambdaFunctionName
      Code:
        ZipFile: |
          exports.handler = async (event) => {
            const response = {
              statusCode: 200,
              body: JSON.stringify('Hello from Lambda!'),
            };
            return response;
          };
      Handler: "index.handler"
      Runtime: "nodejs12.x"
      Timeout: 30
      MemorySize: 128
      Role: !GetAtt MyVpcLambdaExecutionRole.Arn
      VpcConfig:
        SecurityGroupIds:
          - !Ref MyLambdaSecurityGroup
        SubnetIds:
          # マルチAZならここを増やす
          - !Ref MyPrivateSubnet1
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName


  #========================================
  # VPC
  #========================================

  # VPC (ここでは 192.168.0.0/16)
  MyVpc:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "192.168.0.0/16"
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName
  
  # Lambda用のセキュリティグループ
  MyLambdaSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: !Ref MyVpc
      GroupName: "MyLambdaSecurityGroupName"
      GroupDescription: "VPC Lambda"
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName

  # インターネットゲートウェイ
  MyInternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName
  
  # インターネットゲートウェイをVPCにアタッチ
  MyInternetGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      VpcId: !Ref MyVpc
      InternetGatewayId: !Ref MyInternetGateway
  
  # ついでにDynamoDBへのVPCエンドポイントをプライベートサブネットに作成 (必要なら)
  MyVPCEndpointDynamoDB:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      VpcId: !Ref MyVpc
      VpcEndpointType: "Gateway"
      ServiceName: "com.amazonaws.ap-northeast-1.dynamodb"
      RouteTableIds:
        # プライベートサブネットの数だけ指定 (マルチAZならここを増やす)
        - !Ref MyPrivateRouteTable1


  #----------------------------------------------
  # マルチAZにする場合はここから下を増やす
  # (ここでは ap-northeast-1a)

  # パブリックサブネット (ここでは 192.168.1.0/24)
  MyPublicSubnet1: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: "192.168.1.0/24"
      VpcId: !Ref MyVpc 
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName

  # プライベートサブネット (ここでは 192.168.2.0/24)
  MyPrivateSubnet1: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: "192.168.2.0/24"
      VpcId: !Ref MyVpc
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName

  # NATゲートウェイ
  MyNATGateway1: 
    Type: "AWS::EC2::NatGateway"
    Properties: 
      AllocationId: !GetAtt MyNATGateway1Ip.AllocationId 
      SubnetId: !Ref MyPublicSubnet1
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName
  MyNATGateway1Ip: 
    Type: "AWS::EC2::EIP"
    Properties: 
      Domain: "vpc"

  # パブリックサブネットのルートテーブル ...
  MyPublicRouteTable1:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref MyVpc
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName
  
  ## ... IGWにルーティング
  MyPublicRoute1: 
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref MyPublicRouteTable1
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref MyInternetGateway

  ## ... ルートテーブルをサブネットに紐づけ
  MyPublicSubnet1RouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref MyPublicSubnet1
      RouteTableId: !Ref MyPublicRouteTable1

  ## プライベートサブネットのルートテーブル ...
  MyPrivateRouteTable1:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref MyVpc
      Tags:
        - Key: "ServiceName"
          Value: !Ref ServiceName
  
  ## ... NATゲートウェイにルーティング
  MyPrivateRoute1: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref MyPrivateRouteTable1 
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref MyNATGateway1

  # ... ルートテーブルをサブネットに紐づけ
  MyPrivateSubnet1RouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref MyPrivateSubnet1 
      RouteTableId: !Ref MyPrivateRouteTable1