ALB をトリガーにして Lambda を実行する構築ハンズオン


この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので十分ご注意ください。

はじめに

ALB経由からLambdaにアクセスの挙動を確認する際に毎回手動でポチポチしている自分に嫌気がさしまして、一念発起。
CloudFormationで再現性のある構築を少しでも残していきたいと思いまして、簡単な構築ですがハンズオンしていきます。


構成図


ハンズオン

構築の流れ

1.VPC作成

2.Lambda作成

3.ALB作成

上記の順番で構築を行なっていきます。
最終的に下記画像がALBのDNSを入力することで、画面に表示されるようになります。

1.VPC作成

以前記載したブログCloudFormationを使ってVPC構築に沿って、VPCを構築していきます。

2.Lambda作成

ALBから呼び出されるLambdaを作成します。
コールドスタートの影響もあるため、デプロイ直後にALBから呼び出しをするとタイムアウトになったりするので、タイムアウト値を多め(180秒)に設定
Lambda関数にALBトリガーを追加するリソースベースポリシーは、次項の3.ALB作成で構築していきます。

AWSTemplateFormatVersion: '2010-09-09'
Description:
  Lambda Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Lambda Configuration"
        Parameters:
        - FunctionName
        - Description
        - Handler
        - MemorySize
        - Runtime
        - Timeout

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  FunctionName:
    Type: String
    Default: "cfn-lmd-inamura"
  Description:
    Type: String
    Default: "cfn-lmd-inamura"
  Handler:
    Type: String
    Default: "index.lambda_handler"
  MemorySize:
    Type: String
    Default: "128"
  Runtime:
    Type: String
    Default: "python3.9"
  Timeout:
    Type: String
    Default: "180"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          def lambda_handler(event, context):
              response = {
                  "statusCode": 200,
                  "statusDescription": "200 OK",
                  "isBase64Encoded": False,
                  "headers": {
                      "Content-Type": "text/html; charset=utf-8"
              }
              }

              response['body'] = """<html>
              <head>
              <title>sample-lambda</title>
              <style>
              html, body {
              margin: 0; padding: 0;
              font-family: arial; font-weight: 100; font-size: 1em;
              text-align: center;
              }
              </style>
              </head>
              <body>
              <p>Hellow sample-lambda!!</p>
              </body>
              </html>"""
              return response

      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${FunctionName}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: !Sub "${FunctionName}-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource:  !Sub "arn:${AWS::Partition}:logs:*:*:*"

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#                
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"

3.ALB作成

ALB-EC2とALB-Lambdaの記述方法が異なることを知らずにハマりました。公式ドキュメントの読みなしで解決。
そして、リソースベースポリシーとTargetGroupとListenerの構築順番でもハマりました。DependsOnを利用して解決。

AWSTemplateFormatVersion: "2010-09-09"
Description: 
  ALB Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "ALB Configuration"
        Parameters:
        - ALBName
        - Type
        - Scheme
        - IpAddressType
      - label:
          default: "ALB TargetGroup"
        Parameters:
        - TGName
        - TargetType
      - label:
          default: "ALB SecurityGroup"
        Parameters:
        - GroupName
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  ALBName:
    Type: String
    Default: "cfn-alb-inamura"
  Type:
    Type: String
    Default: "application"
  Scheme:
    Type: String
    Default: "internet-facing"
  IpAddressType:
    Type: String
    Default: "ipv4"
  TGName:
    Type: String
    Default: "cfn-tgg-inamura"
  TargetType:
    Type: String
    Default: "lambda"
  GroupName:
    Type: String
    Default: "cfn-sg-alb-inamura"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  ALB
# ------------------------------------------------------------#
  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Ref ALBName
      Type: !Ref Type
      Scheme: !Ref Scheme
      IpAddressType: !Ref IpAddressType
      Subnets: 
        - !ImportValue cfn-inamura-public-subneta
        - !ImportValue cfn-inamura-public-subnetc
      SecurityGroups: 
        - !Ref ALBSecurityGroup

  ListenerHTTP:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: LambdaInvokePermission
    Properties:
      Name: !Ref TGName
      TargetType: !Ref TargetType
      Targets:
        - Id: !ImportValue cfn-lmd-inamura-arn

# ------------------------------------------------------------#
#  ALB SG
# ------------------------------------------------------------#
  ALBSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
        GroupDescription: "ALB SG"
        GroupName: !Ref GroupName
        VpcId: !ImportValue cfn-inamura-vpc
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: 0.0.0.0/0

# ------------------------------------------------------------#
#  リソースベースポリシー
# ------------------------------------------------------------#
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !ImportValue cfn-lmd-inamura-arn
      Action: lambda:InvokeFunction
      Principal: elasticloadbalancing.amazonaws.com

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#    
Outputs:
  ALBURL:
    Description: ALB endpoint URL
    Value: !Join
        - ""
        - - http://
          - !GetAtt ALB.DNSName

挙動の確認

①構築したロードバランサーのDNS nameを確認する

②インターネットブラウザにDNS nameを入力して、Lambdaで設定した画面が表示される。
※コールドスタートの場合、体感で1~2分程度表示に時間がかかりました。


さいごに

気軽に始めましたがCloudFormationに慣れていないことや、そもそものALBにどんな設定値が必要なのかなど、調べてみると分かっていないことが多く構築に時間がかかりました。ただ今後ALBの設定を毎回スクラッチすることからは、少しだけ解放されたかと思うと悪くない時間でした。
途中で気づいたEC2での設定や、Lambdaを修繕中の際のエラーページの表示など、まだまだ検証していくこと多めなので手を動かして知見を増やせればと思います。

Last modified: 2022-11-23

Author