개발

AWS Lambda CloudFront SubRouting으로 버전별로 정적 호스팅 서빙하기

Padd60 2023. 10. 24. 23:17

이슈사항

클라우드 프론트를 사용해서 s3내 정적파일들을 서빙할때 등록한 도메인으로 접속시 루트 기본 객체로 지정한 index.html만 반환되고 하위 폴더로 서브라우팅(domain/**)로 접근하면 해당 객체를 찾을 수 없거나 권한 오류가 나서 오류 페이지를 보게 된다.

대안으로 서브라우팅 주소 뒤에 index.html을 붙혀 명시적으로 html 파일을 가져와 서빙하면 되지만

UX적으로 도메인에 파일 이름이 노출되는것은 매우 불쾌하고 혼란을 가중시킬 수 있다.

따라서 여러 버전을 하나의 도메인에서 관리, 접근하기 위해 클라우드 프론트에서 서브라우팅을 지원해 각 폴더 내 index.html을 자동 반환하게 구현해주어야 한다.

이를 위해 CloudFront와 같은 환경에서 동작할 수 있는 Lambda@Edge를 사용해 기존 원본 요청을 수정하여 위 문제를 해결할 수 있다.

 

해결과정

1. Lambda 함수 작성

const config = {
    suffix: '.html',
    appendToDirs: 'index.html',
    removeTrailingSlash: false,
};

const regexTrailingSlash = /.+\/$/; // e.g. "/some/" or "/some/page/" but not root "/"

export const handler = async (event) => {
    const { request } = event.Records[0].cf;
    const { uri } = request;
    const { suffix, appendToDirs, removeTrailingSlash } = config;

    // Regular expressions and other constants
    const regexTrailingSlash = /.+\/$/;

    // Append "index.html" to origin request
    if (appendToDirs && uri.match(regexTrailingSlash)) {
        request.uri = uri + appendToDirs;
        return request;
    }

    // If nothing matches, return request unchanged
    return request;
};

코드를 요약하자면 domain/ 또는 domain/**/ 와 같이 /로 끝나는 주소로 접근해서 원본요청을 할때

해당 request uri에 index.html을 붙혀 원본에 요청하게끔 변경해서 처리한다는 내용이다.

만일 /로 끝나지 않는 요청이라면 주어진 주소 그대로 원본에 요청하게 된다.

 

2. Lambda@Edge로 배포하기 및 클라우드 프론트 연동

lambda에서 트리거를 클라우드 프론트로 걸어주게 되면 @Edge로 배포해야한다고 설정이 뜰 것이다.

그때 클라우드 프론트 Distribution ID를 입력하여 주소를 찾아주고 연결시켜 배포하면 된다.

주의(권한 설정)

이때 lambda의 권한이 s3 및 클라우드 프론트 접근 권한이 없고 신뢰관계에서 edgelambda에 대한 Allow를 주지 않으면 오류가 발생해 배포되지 않으니 AWS IAM 메뉴에서 lambda 생성때 추가된 권한이나 새로운 권한 아래와 같이 추가해준다.

  • 권한
    • CloudFrontFullAccess
    • AmazonS3FullAccess
    • AWSLambdaExecute
    • AWSLambdaRole
  • 신뢰관계
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "edgelambda.amazonaws.com",
                    "lambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

 

배포 이후 클라우드 프론트의 동작 탭에 가서 편집을 누르고 하단에 원본요청에 해당 lambda가 잘 붙었는지 확인한다.

 

3. 불필요한 Lambda 호출을 막기 위한 클라우드 프론트 동작 설정

위처럼 설정이후 기본 동작에 걸려있는 원본요청으로 Lambda를 넣었을 경우 domain/**/로 들어갔을때 같은 뎁스의 원본의 모든 파일에 대한 Lambda가 실행되서 불필요한 파일에 대한 함수 실행이 일어나 호출 횟수가 예상치보다 더 나오게된다.

원래 이슈 해결로 index.html에 대한 내용만 적용하기로 했으니 동작에 가서 기본동작 외로 두개정도 동작을 추가해준다.

  1. 경로가 /*/로 들어온거에 대한 것만 Lamdba@Egde를 원본요청에 적용해 루트 이외의 서브라우팅에만 Lambda가 적용되게 변경해준다
  2. //.js, //.css, /assets/ 등의 index.html 외적으로 가져오게되는 자원에 대한 것은 기본 값으로 저장해 Lambda 호출이 안되게끔하고 우선순위를 제일 상단으로 올린다.

위까지 적용했다면 우선순위를 가진 동작들이 3개가 생성될 것이다.

변경 후에는 하위 폴더로 서브라우팅시 index.html 자원에 대한 요청 한번만 발생하고 이후 클라우드 프론트 캐싱되어 이후 무효화를 진행하지 않는 이상 기본 TTL(캐싱타임)이 지나지 않는다면 Lambda를 이용해 원본에 호출하지 않고 캐싱된 내용을 반환하여 호출 횟수가 증가하지 않게 된다.

 

4. 빌드된 index.html의 자원 경로 상대 경로로 변경하기

위와 같이 설정해도 js나 css를 가져올때 경로 문제가 생기거나 이상한 루트의 js, css를 가져오는 경우가 생길 것이다. 이는 빌드될때 대부분 루트 기반으로 자원을 /로 가져오기 때문에 발생하는 문제이다.

따라서 webpack 또는 vite 등의 번들어의 빌드 기본설정을 변경하거나 빌드된 파일에 index.html에서 해당 자원들의 절대경로에 ./로 변경하여 s3에 저장해야한다.

추가적으로 서브라우팅시 /를 후위에 붙혀주는 이유도 상대경로로 찾아서 js, css등의 연결 자원을 찾기 때문이다.

 

번들러 별 기본 build output 경로 설정

Webpack

webpack.config.js 수정

module.exports = {
...
output: {
    publicPath: './',
		...
  },
...
}

Vite

vite.config.ts 수정

export default defineConfig({
  base: './',
	...
});

 

비고

[AWS] Nextjs cloudfront S3 배포 시 html 확장자 제거 (feat. lambda 함수 이용)

 

[AWS] Nextjs cloudfront S3 배포 시 html 확장자 제거 (feat. lambda 함수 이용)

이번 글은 Nextjs로 AWS에 베포를 진행하는 과정 중 발생하였다. 처음에는 모든 페이지들이 정상적으로 작동하는 듯 싶었으나, 역시 그럴일이 없었다. Nextjs는 동적 라우팅 즉, Dynamic Loute를 지원한

2ham-s.tistory.com