无服务器框架 - 预定义云前端分布的 Lambda@Edge 部署

Serverless Framework - Lambda@Edge Deployment for a predefined Cloud Front Distribution

我已经尝试了一天来配置自动化 lambda@Edge 以通过无服务器框架与分发关联,但效果不佳。

这是 documentation 他们说我们可以使用来自资源的预定义云前端分发,但没有显示如何?

这是我的 Resources.yml,其中包括 S3 存储桶并关联了两个分布的来源:

Resources:

ResourcesBucket:
    Type: AWS::S3::Bucket
    Properties:
        BucketName: ${self:custom.resourcesBucketName}
        AccessControl: Private
        CorsConfiguration:
            CorsRules:
            -   AllowedHeaders: ['*']
                AllowedMethods: ['PUT']
                AllowedOrigins: ['*']

ResourcesBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
        Bucket:
            Ref: ResourcesBucket
        PolicyDocument:
            Statement:
            # Read permission for CloudFront
            -   Action: s3:GetObject
                Effect: "Allow"
                Resource: 
                    Fn::Join: 
                        - ""
                        - 
                            - "arn:aws:s3:::"
                            - 
                                Ref: "ResourcesBucket"
                            - "/*"
                Principal:
                    CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId

CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
        CloudFrontOriginAccessIdentityConfig:
            Comment:
                Fn::Join: 
                    - ""
                    -
                            - "Identity for accessing CloudFront from S3 within stack "
                            - 
                                Ref: "AWS::StackName"
                            - ""
                # I can use this instead of Fn::Join !Sub 'Identity for accessing CloudFront from S3 within stack #{AWS::StackName}' Getting benefit of
                # serverless-pseudo-parameters plugin

# Cloudfront distro backed by ResourcesBucket
ResourcesCdnDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
        DistributionConfig:
            Origins:
                # S3 origin for private resources
                -   DomainName: !Sub '${self:custom.resourcesBucketName}.s3-${self:provider.region}.amazonaws.com'
                    Id: S3OriginPrivate
                    S3OriginConfig:
                        OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
                # S3 origin for public resources           
                -   DomainName: !Sub '${self:custom.resourcesBucketName}.s3-${self:provider.region}.amazonaws.com'
                    Id: S3OriginPublic
                    S3OriginConfig:
                        OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
            Enabled: true
            Comment: CDN for public and provate static content.
            DefaultRootObject: index.html
            HttpVersion: http2
            DefaultCacheBehavior:
                AllowedMethods:
                    - DELETE
                    - GET
                    - HEAD
                    - OPTIONS
                    - PATCH
                    - POST
                    - PUT
                Compress: true
                TargetOriginId: S3OriginPublic
                ForwardedValues:
                    QueryString: false
                    Headers:
                    - Origin
                    Cookies:
                        Forward: none
                ViewerProtocolPolicy: redirect-to-https
            CacheBehaviors:
                - 
                    PathPattern: 'private/*'
                    TargetOriginId: S3OriginPrivate
                    AllowedMethods:
                    - DELETE
                    - GET
                    - HEAD
                    - OPTIONS
                    - PATCH
                    - POST
                    - PUT
                    Compress: true
                    ForwardedValues:
                        QueryString: false
                        Headers:
                            - Origin
                        Cookies:
                            Forward: none
                    ViewerProtocolPolicy: redirect-to-https
                - 
                    PathPattern: 'public/*'
                    TargetOriginId: S3OriginPublic
                    AllowedMethods:
                    - DELETE
                    - GET
                    - HEAD
                    - OPTIONS
                    - PATCH
                    - POST
                    - PUT
                    Compress: true
                    ForwardedValues:
                        QueryString: false
                        Headers:
                            - Origin
                        Cookies:
                            Forward: none
                    ViewerProtocolPolicy: redirect-to-https

            PriceClass: PriceClass_200

现在我已经完成了有关 CloudFront 的所有设置,我只想在边缘添加一个 lambda 来验证我的私人内容(Id 为 S3OriginPrivate 的起源)。所以这是我的 serverless.yml 文件:

        service: mda-app-uploads
    
    plugins:
      - serverless-offline
      - serverless-pseudo-parameters
      - serverless-iam-roles-per-function
    
    custom:
      stage: ${opt:stage, self:provider.stage}
      resourcesBucketName: ${self:custom.stage}-mda-resources-bucket
    
    
        provider:
          name: aws
          runtime: nodejs12.x
          stage: ${opt:stage, 'dev'}
          region: us-east-1
          versionFunctions: true
        
        
        
        resources:
          - ${file(resources/s3-cloudfront.yml)}
          
        # functions:
        functions: 
          mdaAuthEdge:
            handler: mda-edge-auth.handler
            events:
              - cloudFront:
                  eventType: viewer-request
                  origin:
                    Id: S3OriginPrivate

部署时我遇到了这个问题:

TypeError: Cannot read property 'replace' of undefined

这说明这个id已经存在,无法像我想的那样被替换。我的主要重点是在边缘部署 lambda 并将其与无服务器框架内的云前端相关联,因此我进行了另一项尝试,将几乎所有内容添加到云形成资源中,并且仅依赖于无服务器框架来部署该功能,这里是我的 serverless.yml 和资源文件:

service: mda-app-uploads

plugins:
  - serverless-offline
  - serverless-pseudo-parameters
  - serverless-iam-roles-per-function

custom:
  stage: ${opt:stage, self:provider.stage}
  resourcesBucketName: ${self:custom.stage}-mda-resources-bucket


provider:
  name: aws
  runtime: nodejs12.x
  stage: ${opt:stage, 'dev'}
  region: us-east-1
  versionFunctions: true



resources:
  # Buckets
  - ${file(resources/s3-cloudfront.yml)}
  
# functions:
functions: 
  mdaAuthEdge:
    handler: mda-edge-auth.handler
    role: LambdaEdgeFunctionRole

资源:

Resources:

    LambdaEdgeFunctionRole:
        Type: "AWS::IAM::Role"
        Properties:
            Path: "/"
            ManagedPolicyArns:
                - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                -
                    Sid: "AllowLambdaServiceToAssumeRole"
                    Effect: "Allow"
                    Action: 
                        - "sts:AssumeRole"
                    Principal:
                        Service: 
                            - "lambda.amazonaws.com"
                            - "edgelambda.amazonaws.com"
    LambdaEdgeFunctionPolicy:
        Type: "AWS::IAM::Policy"
        Properties:
            PolicyName: MainEdgePolicy
            PolicyDocument:
                Version: "2012-10-17"
                Statement:
                    Effect: "Allow"
                    Action: 
                        - "lambda:GetFunction"
                        - "lambda:GetFunctionConfiguration"
                    Resource: !Ref MdaAuthAtEdgeLambdaFunction.Version #!Join [':', [!GetAtt MdaAuthAtEdgeLambdaFunction.Arn, '2']]
            Roles:
                - !Ref LambdaEdgeFunctionRole




    ResourcesBucket:
        Type: AWS::S3::Bucket
        Properties:
            BucketName: ${self:custom.resourcesBucketName}
            AccessControl: Private
            CorsConfiguration:
                CorsRules:
                -   AllowedHeaders: ['*']
                    AllowedMethods: ['PUT']
                    AllowedOrigins: ['*']

    ResourcesBucketPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
            Bucket:
                Ref: ResourcesBucket
            PolicyDocument:
                Statement:
                # Read permission for CloudFront
                -   Action: s3:GetObject
                    Effect: "Allow"
                    Resource: 
                        Fn::Join: 
                            - ""
                            - 
                                - "arn:aws:s3:::"
                                - 
                                    Ref: "ResourcesBucket"
                                - "/*"
                    Principal:
                        CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
    
    CloudFrontOriginAccessIdentity:
        Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
        Properties:
            CloudFrontOriginAccessIdentityConfig:
                Comment:
                    Fn::Join: 
                        - ""
                        -
                                - "Identity for accessing CloudFront from S3 within stack "
                                - 
                                    Ref: "AWS::StackName"
                                - ""
                    # I can use this instead of Fn::Join !Sub 'Identity for accessing CloudFront from S3 within stack #{AWS::StackName}' Getting benefit of
                    # serverless-pseudo-parameters plugin

    # Cloudfront distro backed by ResourcesBucket
    ResourcesCdnDistribution:
        Type: AWS::CloudFront::Distribution
        Properties:
            DistributionConfig:
                Origins:
                    # S3 origin for private resources
                    -   DomainName: !Sub '${self:custom.resourcesBucketName}.s3-${self:provider.region}.amazonaws.com'
                        Id: S3OriginPrivate
                        S3OriginConfig:
                            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
                    # S3 origin for public resources           
                    -   DomainName: !Sub '${self:custom.resourcesBucketName}.s3-${self:provider.region}.amazonaws.com'
                        Id: S3OriginPublic
                        S3OriginConfig:
                            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
                Enabled: true
                Comment: CDN for public and provate static content.
                DefaultRootObject: index.html
                HttpVersion: http2
                DefaultCacheBehavior:
                    AllowedMethods:
                        - DELETE
                        - GET
                        - HEAD
                        - OPTIONS
                        - PATCH
                        - POST
                        - PUT
                    Compress: true
                    TargetOriginId: S3OriginPublic
                    ForwardedValues:
                        QueryString: false
                        Headers:
                        - Origin
                        Cookies:
                            Forward: none
                    ViewerProtocolPolicy: redirect-to-https
                CacheBehaviors:
                    - 
                        PathPattern: 'private/*'
                        TargetOriginId: S3OriginPrivate
                        AllowedMethods:
                        - DELETE
                        - GET
                        - HEAD
                        - OPTIONS
                        - PATCH
                        - POST
                        - PUT
                        Compress: true
                        LambdaFunctionAssociations:
                            - 
                                EventType: origin-request
                                LambdaFunctionARN: !Ref MdaAuthEdgeLambdaFunction.Version
                                    #!Join [':', [!GetAtt MdaAuthAtEdgeLambdaFunction.Arn, '2']]
            #    arn:aws:lambda:eu-west-1:219511374676:function:mda-aws-functions-dev-authLambdaAtEdge:1
                        ForwardedValues:
                            QueryString: false
                            Headers:
                                - Origin
                            Cookies:
                                Forward: none
                        ViewerProtocolPolicy: redirect-to-https
                    - 
                        PathPattern: 'public/*'
                        TargetOriginId: S3OriginPublic
                        AllowedMethods:
                        - DELETE
                        - GET
                        - HEAD
                        - OPTIONS
                        - PATCH
                        - POST
                        - PUT
                        Compress: true
                        ForwardedValues:
                            QueryString: false
                            Headers:
                                - Origin
                            Cookies:
                                Forward: none
                        ViewerProtocolPolicy: redirect-to-https

                PriceClass: PriceClass_200

但是我遇到了很多与定义版本等相关的错误。我搜索、调试和调查了很多小时,但似乎很难配置。关于如何通过无服务器框架使 lambda 边缘与预定义的云前端一起工作的任何帮助?

使用无服务器框架这样做有点棘手,但我通过将云形成与无服务器框架相结合解决了这个问题。我在这里找到了另一个问题的答案,其中包含如何这样做的完整描述:

我不想在这里重复所有内容,而且我发现这个问题非常重要,很多人都没有具体的解决方案,所以如果您遇到任何问题,请告诉我。

方法是只在 serverless.yml 中创建函数,然后在云层中,您可以执行创建版本、角色和另一个函数的所有魔法,这将帮助您发布 arn 并使用它动态地。

这是我的 Serverless.yml:

    service: mda-app-uploads
    
    plugins:
      - serverless-offline
      - serverless-pseudo-parameters
      - serverless-iam-roles-per-function
      - serverless-bundle
    
    
    custom:
      stage: ${opt:stage, self:provider.stage}
      resourcesBucketName: ${self:custom.stage}-mda-resources-bucket
      resourcesStages:
        prod: prod
        dev: dev
      resourcesStage: ${self:custom.resourcesStages.${self:custom.stage}, self:custom.resourcesStages.dev}
    
    
    provider:
      name: aws
      runtime: nodejs12.x
      stage: ${opt:stage, 'dev'}
      region: us-east-1
      versionFunctions: true
    
    functions: 
      oauthEdge:
        handler: src/mda-edge-auth.handler
        role: LambdaEdgeFunctionRole
        memorySize: 128
        timeout: 5
    
    
    resources:
      - ${file(resources/s3-cloudfront.yml)}

这是我的 resources/s3-cloudfront.yml:

Resources:

    AuthEdgeLambdaVersion:
        Type: Custom::LatestLambdaVersion
        Properties:
            ServiceToken: !GetAtt PublishLambdaVersion.Arn
            FunctionName: !Ref OauthEdgeLambdaFunction
            Nonce: "Test"

    PublishLambdaVersion:
        Type: AWS::Lambda::Function
        Properties:
            Handler: index.handler
            Runtime: nodejs12.x
            Role: !GetAtt PublishLambdaVersionRole.Arn
            Code:
                ZipFile: |
                    const {Lambda} = require('aws-sdk')
                    const {send, SUCCESS, FAILED} = require('cfn-response')
                    const lambda = new Lambda()
                    exports.handler = (event, context) => {
                        const {RequestType, ResourceProperties: {FunctionName}} = event
                        if (RequestType == 'Delete') return send(event, context, SUCCESS)
                        lambda.publishVersion({FunctionName}, (err, {FunctionArn}) => {
                        err
                            ? send(event, context, FAILED, err)
                            : send(event, context, SUCCESS, {FunctionArn})
                        })
                    }

    PublishLambdaVersionRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: '2012-10-17'
                Statement:
                - Effect: Allow
                  Principal:
                    Service: lambda.amazonaws.com
                  Action: sts:AssumeRole
            ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
            Policies:
            - PolicyName: PublishVersion
              PolicyDocument:
                Version: '2012-10-17'
                Statement:
                - Effect: Allow
                  Action: lambda:PublishVersion
                  Resource: '*'

    LambdaEdgeFunctionRole:
        Type: "AWS::IAM::Role"
        Properties:
            Path: "/"
            ManagedPolicyArns:
                - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                -
                    Sid: "AllowLambdaServiceToAssumeRole"
                    Effect: "Allow"
                    Action: 
                        - "sts:AssumeRole"
                    Principal:
                        Service: 
                            - "lambda.amazonaws.com"
                            - "edgelambda.amazonaws.com"
    LambdaEdgeFunctionPolicy:
        Type: "AWS::IAM::Policy"
        Properties:
            PolicyName: MainEdgePolicy
            PolicyDocument:
                Version: "2012-10-17"
                Statement:
                    Effect: "Allow"
                    Action: 
                        - "lambda:GetFunction"
                        - "lambda:GetFunctionConfiguration"
                    Resource: !GetAtt AuthEdgeLambdaVersion.FunctionArn
            Roles:
                - !Ref LambdaEdgeFunctionRole


    ResourcesBucket:
        Type: AWS::S3::Bucket
        Properties:
            BucketName: ${self:custom.resourcesBucketName}
            AccessControl: Private
            CorsConfiguration:
                CorsRules:
                -   AllowedHeaders: ['*']
                    AllowedMethods: ['PUT']
                    AllowedOrigins: ['*']

    ResourcesBucketPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
            Bucket:
                Ref: ResourcesBucket
            PolicyDocument:
                Statement:
                # Read permission for CloudFront
                -   Action: s3:GetObject
                    Effect: "Allow"
                    Resource: 
                        Fn::Join: 
                            - ""
                            - 
                                - "arn:aws:s3:::"
                                - 
                                    Ref: "ResourcesBucket"
                                - "/*"
                    Principal:
                        CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
                -   Action: s3:PutObject
                    Effect: "Allow"
                    Resource: 
                        Fn::Join: 
                            - ""
                            - 
                                - "arn:aws:s3:::"
                                - 
                                    Ref: "ResourcesBucket"
                                - "/*"
                    Principal:
                        AWS: !GetAtt LambdaEdgeFunctionRole.Arn

                -   Action: s3:GetObject
                    Effect: "Allow"
                    Resource: 
                        Fn::Join: 
                            - ""
                            - 
                                - "arn:aws:s3:::"
                                - 
                                    Ref: "ResourcesBucket"
                                - "/*"
                    Principal:
                        AWS: !GetAtt LambdaEdgeFunctionRole.Arn

    
    CloudFrontOriginAccessIdentity:
        Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
        Properties:
            CloudFrontOriginAccessIdentityConfig:
                Comment:
                    Fn::Join: 
                        - ""
                        -
                            - "Identity for accessing CloudFront from S3 within stack "
                            - 
                                Ref: "AWS::StackName"
                            - ""


    # Cloudfront distro backed by ResourcesBucket
    ResourcesCdnDistribution:
        Type: AWS::CloudFront::Distribution
        Properties:
            DistributionConfig:
                Origins:
                    # S3 origin for private resources
                    -   DomainName: !Sub '${self:custom.resourcesBucketName}.s3.amazonaws.com'
                        Id: S3OriginPrivate
                        S3OriginConfig:
                            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
                    # S3 origin for public resources           
                    -   DomainName: !Sub '${self:custom.resourcesBucketName}.s3.amazonaws.com'
                        Id: S3OriginPublic
                        S3OriginConfig:
                            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
                Enabled: true
                Comment: CDN for public and provate static content.
                DefaultRootObject: index.html
                HttpVersion: http2
                DefaultCacheBehavior:
                    AllowedMethods:
                        - DELETE
                        - GET
                        - HEAD
                        - OPTIONS
                        - PATCH
                        - POST
                        - PUT
                    Compress: true
                    TargetOriginId: S3OriginPublic
                    ForwardedValues:
                        QueryString: false
                        Headers:
                        - Origin
                        Cookies:
                            Forward: none
                    ViewerProtocolPolicy: redirect-to-https
                CacheBehaviors:
                    - 
                        PathPattern: 'private/*'
                        TargetOriginId: S3OriginPrivate
                        AllowedMethods:
                        - DELETE
                        - GET
                        - HEAD
                        - OPTIONS
                        - PATCH
                        - POST
                        - PUT
                        Compress: true
                        LambdaFunctionAssociations:
                            - 
                                EventType: viewer-request
                                LambdaFunctionARN: !GetAtt AuthEdgeLambdaVersion.FunctionArn
                        ForwardedValues:
                            QueryString: false
                            Headers:
                                - Origin
                            Cookies:
                                Forward: none
                        ViewerProtocolPolicy: redirect-to-https
                    - 
                        PathPattern: 'public/*'
                        TargetOriginId: S3OriginPublic
                        AllowedMethods:
                        - DELETE
                        - GET
                        - HEAD
                        - OPTIONS
                        - PATCH
                        - POST
                        - PUT
                        Compress: true
                        ForwardedValues:
                            QueryString: false
                            Headers:
                                - Origin
                            Cookies:
                                Forward: none
                        ViewerProtocolPolicy: redirect-to-https

                PriceClass: PriceClass_200

但是你会在我的其他问题的答案中找到完整的描述。