AWS CloudFront란?

  • AWS에서 제공하는 CDN(Content Delivery Network) 서비스입니다.
  • 전세계 AWS 각 리전의 엣지 로케이션에 리소스의 복사본을 미리 로드해놓고, 사용자들이 짧은 지연 시간에 파일을 받을 수 있도록 해줍니다.1

커스텀 도메인을 사용해서 Route53을 거쳐 HTTPS를 통해 정적 리소스를 호스팅하려면 S3만으로는 안 되기 때문에, CloudFront와 S3를 함께 이용하게 됩니다.2

이점

CloudFront를 S3와 함께 사용하면 다음과 같은 이점이 있습니다.

  • CDN을 통한 빠른 전송 속도
  • S3에 직접 액세스 하는 것보다 싼 요금
  • S3에 부하가 몰리지 않고 엣지 로케이션에 캐싱되어 부하 분산
  • 커스텀 도메인 사용 가능
    • 예: *.musma.net
  • HTTPS 사용 가능
    • 예: https://
  • CloudFront에서 제공하는 각종 통계, 보안 기능 사용 가능

S3의 정적 사이트 호스팅 기능을 사용하면 http로는 되는데 https는 안 됩니다. CloudFront를 https로 시작하는 커스텀 도메인 주소를 사용할 수 있을 뿐만 아니라 위와 같이 여러가지 이점이 있습니다.

이용 사례: React 애플리케이션 번들 배포

리액트 애플리케이션 번들(index.html와 js, css, images 등)을 S3에 올리고, CloudFront와 연동해서 CDN over HTTPS를 할 수 있습니다. 이때 *.musma.net 같이 커스텀 도메인을 CloudFront에 설정해서 사용할 수 있습니다.

실습

도메인 *.musma.net를 보유하고, Route53, Certificate Manager에 인증서가 올바르게 등록된 상태라고 가정합니다.3

이제부터 AWS Management Console 대신 AWS CLI를 통해 실습을 진행하겠습니다. 왜냐하면 관리 콘솔은 사람을 위한 인터페이스이지만 AWS CLI는 사람과 시스템(빌드 서버 혹은 프로비저닝 도구 등)이 같이 쓸 수 있어서, 나중에 코드로 인프라를 관리(IaC)하기 위해서는 관리 콘솔보다 AWS CLI에 익숙해지는 것이 좋기 때문입니다. 또 다른 이유는 화면 캡처가 번거롭기 때문에 (…)

1. S3 버킷 생성

아무 이름으로 지어도 되지만, 도메인 주소랑 연관된 것을 알아보기 쉽도록 도메인 이름으로 만들어줍니다.

$ aws s3 mb s3://example.musma.net

CloudFront를 이용함으로써 얻는 장점의 하나는 바로 S3 설정이 간단해진다는 것입니다. 그냥 버킷을 생성하고 파일만 올리면 되고, 나중에 CloudFront에 접근 허용만 해주면 됩니다.

2. CloudFront Origin Access Identity 생성

CloudFront에서 S3에 접근할 수 있도록 허용해줄 보안 주체를 생성합니다. 나중에 “이 보안 주체에 대해 버킷의 특정 권한을 허용한다.” 같은 보안 정책을 만드는데 사용합니다.

AWS CLI를 사용할 때, 입력할 파라미터가 복잡하면 보통 json 파일로 파라미터를 전달합니다. 이때 샘플 파일을 만들어서 필요한 부분을 수정해서 사용하면 됩니다.

AWS CLI의 명령어 순서는 aws [서비스] [커맨드] [...파라미터] 입니다.

$ aws cloudfront create-cloud-front-origin-access-identity --generate-cli-skeleton > create-cloud-front-origin-access-identity.json

해석: AWS CloudFront 서비스에서 Origin Access Identity를 생성할건데, 파라미터 json 샘플 파일(create-cloud-front-origin-access-identity.json)을 만들어라.

create-cloud-front-origin-access-identity.json 파일의 내용은 아래와 같습니다.

{
  "CloudFrontOriginAccessIdentityConfig": {
    "CallerReference": "",
    "Comment": ""
  }
}

아래와 같이 채워주시면 됩니다. 저는 default로 명명했습니다.

{
  "CloudFrontOriginAccessIdentityConfig": {
    "CallerReference": "default",
    "Comment": "default"
  }
}

CallerReference는 중복 생성 방지를 위해 API 호출시 ID를 주는 것이고, Comment가 실제로 사용되는 값입니다. Comment는 말 그대로 코멘트이기 때문에 알아보기 쉽게만 적어주면 됩니다.

그다음 create-cloud-front-origin-access-identity.json를 사용해서 실제로 Origin Access Identity를 생성합니다. (주의: 파일 이름 앞에 file:// 붙입니다.)

$ aws cloudfront create-cloud-front-origin-access-identity --cli-input-json file://create-cloud-front-origin-access-identity.json

그럼 Origin Access Identity가 생성된 결과가 JSON으로 나옵니다.

{
  "Location": "https://cloudfront.amazonaws.com/2018-11-05/origin-access-identity/cloudfront/EXXXXXXXXXXXXX",
  "ETag": "EYYYYYYYYYYYY",
  "CloudFrontOriginAccessIdentity": {
    "Id": "EXXXXXXXXXXXXX",
    "S3CanonicalUserId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "CloudFrontOriginAccessIdentityConfig": {
      "CallerReference": "default",
      "Comment": "default"
    }
  }
}

CloudFrontOriginAccessIdentity.IdCloudFrontOriginAccessIdentity.S3CanonicalUserId 부분을 기억하십시오. (예: EXXXXXXXXXXXXX)

3. CloudFront Distribution 생성

CloudFront Distribution은 실제로 CDN 호스팅을 할 구성을 설정하는 것입니다.

위에서 처럼 --generate-cli-skeleton을 해서 샘플 json 파일을 뽑아봅니다.

$ aws cloudfront create-distribution --generate-cli-skeleton > create-distribution.json

그리고 알맞게 수정합니다.

{
  "DistributionConfig": {
    "CallerReference": "example.musma.net",
    "Aliases": {
      "Quantity": 1,
      "Items": ["example.musma.net"]
    },
    "DefaultRootObject": "index.html",
    "Origins": {
      "Quantity": 1,
      "Items": [
        {
          "Id": "example.musma.net", // 간단하게 커스텀 도메인 이름으로 id를 지으면 됩니다.
          "DomainName": "example.musma.net.s3.ap-northeast-2.amazonaws.com", // S3 버킷 도메인
          "S3OriginConfig": {
            "OriginAccessIdentity": "origin-access-identity/cloudfront/EXXXXXXXXXXXXX" // 아까 생성했던 Origin Access Identity
          }
        }
      ]
    },
    "DefaultCacheBehavior": {
      "TargetOriginId": "example.musma.net",
      "ForwardedValues": {
        "QueryString": false,
        "Cookies": {
          "Forward": "none"
        }
      },
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0,
        "Items": []
      },
      "MinTTL": 0,
      "ViewerProtocolPolicy": "redirect-to-https",
      "AllowedMethods": {
        "Quantity": 2,
        "Items": ["GET", "HEAD"],
        "CachedMethods": {
          "Quantity": 2,
          "Items": ["GET", "HEAD"]
        }
      }
    },
    "Comment": "",
    "Enabled": true,
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": false,
      // Certificate Manager에 등록한 *.musma.net 도메인의 인증서의 ARN
      // (반드시 us-east-1 리전에서 생성한 것을 사용)
      "ACMCertificateArn": "arn:...",
      "SSLSupportMethod": "sni-only",
      "MinimumProtocolVersion": "TLSv1.1_2016"
    },
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "whitelist",
        "Quantity": 1,
        "Items": ["KR"]
      }
    }
  }
}

뭐가 뭔지… 정신 없죠? 지금 다 알 필요는 없고 모르면 기본값으로 두고 넘어가면 됩니다.
(대충 입력해도 심각하게 잘못되진 않습니다. ^^;)

이제 생성합니다.

$ aws cloudfront create-distribution --cli-input-json file://create-distribution.json

혹시 에러 메시지가 나오면 가이드 해주는대로 파일 내용을 고쳐서 다시 실행하면 됩니다.

4. S3 버킷 정책 수정

CloudFront에서 S3 버킷에 접근할 수 있도록 정책을 수정합니다.

아래와 같이 bucket-policy.json을 작성합니다.

{
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
          // 위에서 Origin Access Identity 생성할 때 얻은 S3CanonicalUserId
          "CanonicalUser": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example.musma.net/*"
    }
  ]
}

그리고

$ aws s3api put-bucket-policy --bucket example.musma.net --policy file://bucket-policy.json

5. 버킷에 파일 업로드 (동기화)

기존 버킷에 담긴 파일들은 삭제하고 로컬 디렉토리와 같은 내용으로 동기화합니다. 번들링한 결과물이 담긴 디렉토리, 예를 들어 create-react-app으로 만든 프로젝트에서는 build 디렉토리를 동기화하면 됩니다.

$ cd build/
$ aws s3 sync . s3://example.musma.net/ --delete

6. CloudFront 캐시 invalidation

CloudFront의 엣지 로케이션 캐시를 무효화하고 다시 S3에서 업데이트 된 파일을 가져오도록 invalidation을 생성합니다.

아래는 create-react-app 프로젝트의 번들 파일들을 CloudFront가 refresh하도록 합니다.

$ aws cloudfront create-invalidation --distribution-id EXXXXXXXXXXXX --paths / /index.html /service-worker.js /manifest.json /asset-manifest.json /favicon.jpg

결론

AWS Route53에 커스텀 도메인을 등록해서 https 프로토콜로 들어오는 액세스 요청을 통해 정적 리소스를 서비스하기 위해 CloudFront와 S3를 설정하는 방법을 알아보았습니다.

이 방법으로 S3 버킷으로 직접 요청을 받지 않고(S3 버킷 자체에는 퍼블릭 액세스가 전면 차단되어 외부 접근 권한이 없는 상태) CloudFront를 통해서만 파일에 접근할 수 있습니다.

이 방법이 사실상의 표준 사용법이라고 할 수 있습니다.

참고로 CloudFront는 정적이든 동적이든 가리지 않으며, 대상이 반드시 S3가 아니어도 됩니다. nginx와 같은 웹서버의 캐싱과 쿼리 스트링, HTTP 헤더 전달, 리버스 프록시 기능을 일부 갖고 있습니다. 위에서 실습한 내용을 바탕으로 필요에 따라 적절히 사용하시기 바랍니다.

감사합니다.


Reference


  1. 즉, 미국에서 요청하면 가까운 미국 리전의 엣지 로케이션에서, 한국에서 요청하면 서울 리전 내의 엣지 로케이션 서버에서 파일을 받게 됩니다. 

  2. S3의 정적 사이트 호스팅 기능은 HTTP 프로토콜에서만 가능합니다. 

  3. 다음에 AWS Certificate Manager에 대해서도 포스팅하겠습니다.