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

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

------------

## 構成図
![](https://cloud5.jp/wp-content/uploads/2022/11/スクリーンショット-2022-11-23-17.12.11-640x569.png)

------------

## ハンズオン
### 構築の流れ
#### 1.VPC作成
#### 2.Lambda作成
#### 3.ALB作成
上記の順番で構築を行なっていきます。
最終的に下記画像がALBのDNSを入力することで、画面に表示されるようになります。
![](https://cloud5.jp/wp-content/uploads/2022/11/スクリーンショット-2022-11-23-16.12.15-640x103.png)

### 1.VPC作成
以前記載したブログ[CloudFormationを使ってVPC構築](https://cloud5.jp/cfn_vpc/)に沿って、VPCを構築していきます。

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

```yaml
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`を利用して解決。

```yaml
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`を確認する
![](https://cloud5.jp/wp-content/uploads/2022/11/スクリーンショット-2022-11-23-16.31.28-640x153.png)

②インターネットブラウザに`DNS name`を入力して、Lambdaで設定した画面が表示される。
※コールドスタートの場合、体感で1~2分程度表示に時間がかかりました。
![](https://cloud5.jp/wp-content/uploads/2022/11/スクリーンショット-2022-11-23-16.12.15-1-640x103.png)

------------

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