S3 웹 앱 및 API 게이트웨이를 위한 단일 CloudFront 배포

이 글은 S3에서 관리되는 웹 응용 프로그램과 백엔드 API를 단일 Amazon CloudFront 버전으로 어떻게 사용하는지 개괄적으로 보여 준다.

내가 이루고 싶은 결과:
  • website.com 내 웹 응용 프로그램 불러오기
  • website.com/non-existent-page 나에게 인성화된 404페이지
  • 를 줄 것이다
  • website.com/api/* 내 백엔드 API로 라우팅
  • website.com/api/non-existent-endpoint는 인간성이 좋은 404페이지가 아닌 기계의 우호적인 오류 응답을 백엔드에서 되돌려준다
  • 나는 Amazon API GatewayV2 을 이 예시의 백엔드로 사용할 것이지만, 원칙은 그 어떠한 다른 백엔드에도 적용되어야 한다.

    카탈로그

  • Why
  • Summary
  • Architecture

  • Step by Step Tutorial Using CDK
  • CDK Setup
  • API Gateway with a Lambda Backend
  • S3 Bucket and CloudFront Distribution
  • Add API Gateway as Another CloudFront Origin
  • Lambda@Edge for Handling Redirects
  • Test It Out
  • Key Take Aways
  • Alternatives
  • 왜?


    웹 응용 프로그램과 API를 위해 별도의 버전을 사용할 수 있을 때 왜 하나의 CloudFront 버전을 동시에 사용해야 합니까?E, g. 왜 나는 website.comapi.website.com만 사용할 수 없습니까?
    요컨대 자역은 관리하기 어려울 수도 있고, 일부 조직에서는 새로운 자역을 요청하기도 어려울 수도 있다.
    하위 도메인 관리가 어려울 수 있는 하나의 예는 서로 다른 환경이다.아래 하위 영역dev.website.com을 가진 개발 환경이 있다고 가정하십시오.APIapi.dev.website.com입니까, 아니면 dev.api.website.com입니까?개별 SSL 인증서를 생성하시겠습니까?당신의 CORS 규칙은요?CSP는요?코드 라이브러리에서 이 점을 어떻게 관리하시겠습니까?
    ...나는 영원히 계속할 수 있다.
    따라서 일부는 /api/만 사용하고 여기까지를 고려할 수도 있다.이 게시물은 그 사람들을 위해 쓴 것이다.

    총결산


    기본적으로 우리는 경로 모델을 바탕으로 여러 출처에서 CloudFront 서비스를 제공할 것이다.이 경우 Cloudfront는 모든 요청/api/*을 API 게이트웨이로 전송하고 다른 모든 요청을 S3로 전송합니다.이 하나만으로도 결과 1, 3, 4를 실현할 수 있다.
    그러나 만약 누군가가 접근을 시도한다면/non-existent-page S3에서 "NoSuchKey"오류를 얻을 것입니다. 이것은 우리의 인성화된 404 오류가 아닙니다.

    CloudFront 사용자 정의 오류 구성을 사용하여 모든 404 오류를 덮어쓰고 색인을 만드는 것이 어떻습니까?html 아니면 전용 404페이지?불행하게도 CloudFront는 현재 모든 원본의 사용자 정의 오류 설정을 설정할 능력이 없기 때문에 우리의 경우 백엔드 API 오류 응답을 덮어씁니다. (기본적으로 우리가 해결하고자 하는 문제의 핵심입니다.)
    따라서 사용자 정의 오류 설정은 불가능합니다. 우리는...
    그래, 네가 알아맞혔으니,Lambda@edge.

    저희가 사용할 수 있어요.Lambda@EdgeS3 원점으로 가는 /non-existent-page 루트의 경우 404 응답을 인덱스로 다시 지정합니다.html 또는 전용 404페이지.

    건축학


    아주 직접적이야. 우리 하나만 필요해.Lambda@Edge우리의 S3 기원과 분포 사이.

    CDK 단계별 자습서 사용


    다음은 CDK를 사용한 구축 작업 예제의 단계별 강좌입니다.
    이것은 제가 로컬에 설치한 내용입니다. 참고:
    $ node --version
    v14.13.1
    $ yarn --version # you can use npm
    1.22.5
    $ cdk --version
    1.67.0 (build 2b4dd71)
    $ docker --version # used by CDK to compile typescript lambdas
    Docker version 19.03.13, build 4484c46d9d
    
    또한 여기에서 모든 소스 코드와 배포 작업의 예에 대한 설명을 찾을 수 있습니다.

    회사 명 / 블로그 예시


    S3 bucket 및 API 게이트웨이 백엔드에서 관리되는 웹 응용 프로그램의 단일 Amazon CloudFront 배포 예


    CDK 설정


    typescript CDK 프로젝트를 만들고 종속 항목을 설치합니다.
    $ cdk init app --language typescript
    ...
    $ yarn add \
        @aws-cdk/aws-cloudfront \
        @aws-cdk/aws-apigatewayv2 \
        @aws-cdk/aws-s3 \
        @aws-cdk/aws-s3-deployment \
        @aws-cdk/aws-lambda \
        @aws-cdk/aws-lambda-nodejs \
        @aws-cdk/aws-iam
    ...
    $ yarn add --dev --exact [email protected] # for compiling typescript lambdas
    

    Lambda 백엔드가 있는 API 게이트웨이


    CDK 응용 프로그램의lib 폴더에 backend라는 폴더를 만들고 200으로 되돌아오는 위조 lambda 함수(index.ts를 놓습니다.
    export const handler = async (event: any): Promise<any> => {
      return {
        statusCode: 200,
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ key: "Machine friendly hello world" }),
      };
    };
    
    lib/{your-stack-name}.ts 파일에 필요한 가져오기를 추가합니다.
    import * as cdk from "@aws-cdk/core";
    import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs";
    import { Runtime } from "@aws-cdk/aws-lambda";
    import * as apigatewayv2 from "@aws-cdk/aws-apigatewayv2";
    
    이제 다음 코드를 CDK 스택에 추가하여 lambda 함수를 만들고 API GatewayV2와 통합할 수 있습니다.
    const httpApi = new apigatewayv2.HttpApi(this, "MyApiGateway");
    
    const helloWorldLambda = new NodejsFunction(this, "HelloWorldLambda", {
      entry: `${__dirname}/backend/index.ts`,
      handler: "handler",
      runtime: Runtime.NODEJS_12_X,
    });
    
    const lambdaIntegration = new apigatewayv2.LambdaProxyIntegration({
      handler: helloWorldLambda,
    });
    
    httpApi.addRoutes({
      path: "/api/helloworld", // You must include the `/api/` since CloudFront will not truncate it
      methods: [apigatewayv2.HttpMethod.GET],
      integration: lambdaIntegration,
    });
    

    3.S3 Bucket 및 CloudFront 배포


    CDK 응용 프로그램의 lib 폴더에 frontend라는 폴더를 만들고 index.html 파일을 만듭니다. html 내용을 포함합니다.
    <html>
      <body>
        Hello world
      </body>
    </html>
    
    lib/{your-stack-name}.ts 파일에서 다음 CDK 종속성을 가져옵니다.
    import * as cloudfront from "@aws-cdk/aws-cloudfront";
    import * as s3 from "@aws-cdk/aws-s3";
    import * as iam from "@aws-cdk/aws-iam";
    import { Duration } from "@aws-cdk/core";
    import * as s3deploy from "@aws-cdk/aws-s3-deployment";
    
    이제 개인 S3 bucket, CloudFront 릴리스와 중간의 모든 것(IAMs, OAIs 등)을 사용하여 표준 정적 사이트를 만듭니다.
    const cloudfrontOAI = new cloudfront.OriginAccessIdentity(
      this,
      "cloudfrontOAI",
      {
        comment: `Allows CloudFront access to S3 bucket`,
      }
    );
    
    const websiteBucket = new s3.Bucket(this, "S3BucketForWebsiteContent", {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      cors: [
        {
          allowedOrigins: ["*"],
          allowedMethods: [s3.HttpMethods.GET],
          maxAge: 3000,
        },
      ],
    });
    
    // uploads index.html to s3 bucket
    new s3deploy.BucketDeployment(this, "DeployWebsite", {
      sources: [s3deploy.Source.asset(`${__dirname}/frontend`)], // folder containing your html files
      destinationBucket: websiteBucket,
    });
    
    websiteBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        sid: "Grant Cloudfront Origin Access Identity access to S3 bucket",
        actions: ["s3:GetObject"],
        resources: [websiteBucket.bucketArn + "/*"],
        principals: [cloudfrontOAI.grantPrincipal],
      })
    );
    
    const cloudfrontDistribution = new cloudfront.CloudFrontWebDistribution(
      this,
      "MyDistribution",
      {
        comment: "CDN for Web App",
        defaultRootObject: "index.html",
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
        originConfigs: [
          {
            s3OriginSource: {
              s3BucketSource: websiteBucket,
              originAccessIdentity: cloudfrontOAI,
            },
            behaviors: [
              {
                compress: true,
                isDefaultBehavior: true,
                defaultTtl: Duration.seconds(0),
                allowedMethods:
                  cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
              },
            ],
          },
        ],
      }
    );
    

    다른 CloudFront 소스로 API 게이트웨이 추가


    API 게이트웨이를 경로 모드/api/*의 다른 소스로 CloudFront 배포에 추가합니다/api/*의 경로 일치 우선순위를 위해 API 게이트웨이가 S3 소스 위에 있는지 확인합니다).
    originConfigs: [
      {
        // make sure your backend origin is first in the originConfigs list so it takes precedence over the S3 origin
        customOriginSource: {
          domainName: `${httpApi.httpApiId}.execute-api.${this.region}.amazonaws.com`,
        },
        behaviors: [
          {
            pathPattern: "/api/*", // CloudFront will forward `/api/*` to the backend so make sure all your routes are prepended with `/api/`
            allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL,
            defaultTtl: Duration.seconds(0),
            forwardedValues: {
              queryString: true,
              headers: ["Authorization"], // By default CloudFront will not forward any headers through so if your API needs authentication make sure you forward auth headers across
            },
          },
        ],
      },
      {
        s3OriginSource: {
          s3BucketSource: websiteBucket,
          originAccessIdentity: cloudfrontOAI,
        },
        behaviors: [
          {
            compress: true,
            isDefaultBehavior: true,
            defaultTtl: Duration.seconds(0),
            allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
          },
        ],
      },
    ];
    

    리디렉션 처리를 위한 Lambda 테두리


    참고: 생성된 경우Lambda@Edge, 삭제하는 데 시간이 좀 걸릴 수 있습니다.
    CDK 응용 프로그램의lib 폴더에서 redirect 라는 폴더를 만들고 index.ts 파일을 만듭니다. 여기에는 다음과 같은 내용이 포함됩니다.Lambda@Edge):
    "use strict";
    
    exports.handler = (event: any, context: any, callback: any) => {
      const response = event.Records[0].cf.response;
      const request = event.Records[0].cf.request;
    
      /**
       * This function updates the HTTP status code in the response to 302, to redirect to another
       * path (cache behavior) that has a different origin configured. Note the following:
       * 1. The function is triggered in an origin response
       * 2. The response status from the origin server is an error status code (4xx or 5xx)
       */
    
      if (response.status == 404) {
        const redirect_path = `/`; //redirects back to root so to index.html
    
        response.status = 302;
        response.statusDescription = "Found";
    
        /* Drop the body, as it is not required for redirects */
        response.body = "";
        response.headers["location"] = [{ key: "Location", value: redirect_path }];
      }
    
      callback(null, response);
    };
    
    이제 CDK에 Lambda 리소스를 추가합니다(CloudFront에서 리소스를 배포하기 전에 나중에 배포에서 이 Lambda를 참조하므로).
    const redirectLambda = new NodejsFunction(this, "redirectLambda", {
      entry: `${__dirname}/redirect/index.ts`,
      handler: "handler",
      runtime: Runtime.NODEJS_12_X,
    });
    
    마지막으로 연락드릴게요.Lambda@Edge우리의 S3 출처:
    {
        s3OriginSource: {
            s3BucketSource: websiteBucket,
            originAccessIdentity: cloudfrontOAI,
        },
        behaviors: [
            {
                compress: true,
                isDefaultBehavior: true,
                defaultTtl: Duration.seconds(0),
                allowedMethods:
                cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
                lambdaFunctionAssociations: [
                    {
                        lambdaFunction: redirectLambda.currentVersion,
                        eventType: LambdaEdgeEventType.ORIGIN_RESPONSE,
                    },
                ],
            },
        ],
    },
    

    테스트 해봐.


    cloudfront URL을 사용하여 다음 노드에 액세스하십시오./ Hello World 인덱스로 돌아가야 합니다.html 페이지
    /api/helloworld 우리에게 기계의 우호적인 소식을 돌려주어야 한다
    /api/non-existent-endpoint 백엔드 API에서 처리해야 함

    마지막으로 /non-existent-page 우리를 색인으로 다시 지정해야 합니다.html, 이렇게 하면 우리는 우리의 웹 응용 프로그램에서 그것을 처리할 수 있다

    주요 배달

  • CloudFront 기반 경로 모드를 사용하여 여러 소스에서 서비스 제공
  • CloudFront는 전체 경로를 원점으로 전송하므로 API 경로가 CloudFront
  • 에 설정된 경로와 동일한지 확인하십시오.
  • 기본적으로 CloudFront는 어떤 헤드도 API에 전달하지 않으므로 어떤 헤드를 전달하는지 명확히 하십시오
  • API 오류 응답을 덮어쓰기 때문에 CloudFront 사용자 정의 오류 구성을 사용하지 마십시오
  • 사용Lambda@EdgeS3에서 NoSuchKey 오류 404개로 재정의
  • 선택


    하면, 만약, 만약...Lambda@Edge입맛에 맞지 않으므로 다음 대안을 모색해야 할 수도 있습니다.
  • 공공 S3 버킷 정적 사이트를 재정의한 세 번째 출처로 사용
  • API 게이트웨이 또는 ALB
  • 로부터 S3 제공
    도움이 됐으면 좋겠어요!
    다음 섹션에서 피드백과 질문을 환영합니다:)

    좋은 웹페이지 즐겨찾기