CloudFront DistributionからALB-Lambdaを呼び出す構築ハンズオン

## はじめに
##### 先日CloudFrontからAPIGateway経由でLambdaへの構築を行なったので、そしたらセットでALB経由でLambdaこ構築に関してもCFn化しておこうと思い立ち、さっそくハンズオンしていきます。

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

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

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

## ハンズオン
### 構築の流れ
#### 1.VPC作成
#### 2.Lambda作成
#### 3.ALB作成
#### 4.CloudFront作成
##### 上記の順番で構築を行なっていきます。

##### 最終的には、CloudFrontのディストリビューションドメインから、ALB経由でLambdaのメッセージ部分をダウンロードできるまでを行なっていきます。


-----------

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


### 2.Lambda作成
##### 特別な設定はせず`statusCode`が`200`を返す、Lambdaを構築します。
##### ALB経由でアクセスした際にダウンロードされる、`body`部分のメッセージを一部修正しています。

```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: "10"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import json

          def lambda_handler(event, context):
              # TODO implement
              print(event)
              return {
                  'statusCode': 200,
                  'body': json.dumps('Hello CloudFront-ALB!')
              }

      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"
                  - "logs:CreateLogGroup"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"

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

### 3.ALB作成
#####　3.1.この後作成するCloudFrontの`Custom-Header`を利用して、CloudFront経由でアクセスしたユーザにはLambdaへのアクセスを許可して、直接ALBへアクセスしたユーザには【ERROR】ページを表示するようにします。

```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:
        - TGName1
        - 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"
  TGName1:
    Type: String
    Default: "cfn-tgg1-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 TargetGroup1
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP

  ListenerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup1
      Conditions:
        - Field: path-pattern
          Values: 
            - '*'
        - Field: http-header
          HttpHeaderConfig:
            HttpHeaderName: Custom-Header
            Values: 
              - inamura
      ListenerArn: !Ref ListenerHTTP
      Priority: 1

  ListenerRule2:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: "fixed-response"
          FixedResponseConfig:
            ContentType: 'text/html'
            MessageBody: |
              <!DOCTYPE html>
              <html lang="ja">
              <head>
              <meta charset="UTF-8">
              <title>【ERROR】</title>
              </head>
              <body>
                <h1>CloudFront経由でアクセスください</h1>
              </body>
              </html>
            StatusCode: 503
      Conditions:
        - Field: path-pattern
          Values: 
            - '*'
      ListenerArn: !Ref ListenerHTTP
      Priority: 2

  TargetGroup1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: LambdaInvokePermission
    Properties:
      Name: !Ref TGName1
      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
  ALBDomain:
    Description: ALB Domain
    Value:  !GetAtt ALB.DNSName
    Export:
      Name: cfn-alb-dns
  HostedZoneID:
    Description: The ID of the Amazon Route 53 hosted zone associated with the load balancer.
    Value:  !GetAtt ALB.CanonicalHostedZoneID
    Export:
      Name: cfn-alb-id
```

#### 3.2.CFnのタブ『出力』から、下記赤枠部分の『ALBURL』を押下する
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-11-9.19.29-640x157.png)
##### 3.3.下記画面に遷移する
##### `Custom-Header`が付与されていないアクセスのためALBで【ERROR】ページに遷移されてしまう
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-11-9.19.43-640x174.png)

### 4.CloudFront作成
##### 3.で構築したALBをオリジンとして、`Custom-Header`を設定しています。

```yaml

AWSTemplateFormatVersion: '2010-09-09'
Description:
  CloudFront Create
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  CloudFront
# ------------------------------------------------------------#
  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: "Create CloudFront with CloudFormation"
        PriceClass: PrinceClass_All
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
          Compress: true
          TargetOriginId: !ImportValue cfn-alb-id
          ViewerProtocolPolicy: allow-all
        Enabled: true
        HttpVersion: http2and3
        Origins:
          - CustomOriginConfig:
              HTTPSPort: '80'
              OriginProtocolPolicy: http-only
            DomainName: !ImportValue cfn-alb-dns
            Id: !ImportValue cfn-alb-id
            OriginCustomHeaders: 
            - HeaderName: Custom-Header
              HeaderValue: inamura
        PriceClass: PriceClass_All
# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  DomainName:
    Value: !GetAtt Distribution.DomainName
    Export:
      Name: cfn-clf-dns
```

### 挙動の確認
#### ①構築されたCloudFrontからドメイン名を確認する
##### オリジンにはALBのDNSが設定されていることも確認できる
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-11-9.39.41-640x79.png)

#### ②ブラウザにドメインを入力するとダウンロードが開始される
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-11-9.37.13.png)

#### ③ダウンロードされた資材を確認して、Lambdaの`body`部分のメッセージを確認することができる
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-11-9.44.40.png)

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

## さいごに
##### CloudFrontからALB間を、今回は`Custom-Header`で設定しましたが、今時で言ったらSGにCloudFrontのマネージドプレフィックスを設定などの方法が良かったかもしれません。
##### さっそく次の作るものが見つかりましたが、残り少ない今年に捉われることもなく、時間を見つけては検証して、自分の作れるものを増やしていきたいと思います！