[AWS] CloudFront Function 도입기

김상현·2025년 1월 27일
1

문제&해결

목록 보기
3/3
post-thumbnail

"나는 다 필요 없고 CloudFront Function 사용 방법이 궁금해!"에 해당되시는 분들은 'CloudFront Functions 생성 방법'으로 이동!

배경

기존의 ERP 시스템은 클라이언트가 업로드한 파일을 WAS(Web Application Server)의 로컬 디스크에 저장하고 관리하는 방식으로 운영되어 왔습니다.

그러나, 시스템이 다년간 운영되면서 파일이 지속적으로 누적되었고, 결과적으로 WAS의 로컬 디스크 사용량이 전체 용량의 90%에 도달하게 되었습니다.

디스크 용량 문제를 해결하기 위해 먼저 로컬 디스크에 저장된 데이터 중 임시 파일이나 더 이상 사용되지 않는 파일을 제거하여 가용 용량을 확보했습니다. 이를 통해 디스크 사용률을 90%에서 60%로 낮추는 데 성공했지만, 문제의 근본적인 해결책이 아닌 임시방편에 불과했습니다.

결과적으로, 파일 관리 방식을 포함한 시스템 구조 전반을 재검토하여 근본적인 해결 방안을 모색해야 한다는 결론에 도달했고, 파일을 관리하는 주체를 WAS에서 S3로 이관하게 되었습니다.


문제상황

WAS에 저장된 파일을 S3로 이관한 이후, S3에 저장된 파일을 CloudFront를 통해 조회하는 과정에서 예상치 못한 문제가 발생했습니다. 브라우저는 기본적으로 PDF, JPG, Text 파일과 같은 형식을 자동으로 렌더링하도록 설계되어 있지만, 파일이 암호화된 경우에는 브라우저에서 해당 파일을 렌더링하지 못하는 문제가 발생했습니다.

브라우저에서는 파일이 암호화되어 렌더링되지 않는 경우에도, 사용자가 마우스 우클릭 메뉴에서 '다른 이름으로 저장...'을 선택하면, 해당 파일을 다운로드할 수 있습니다. 다운로드된 파일은 브라우저를 거치지 않고, 로컬 환경에서 열 수 있기 때문에 암호화된 파일이라도 문제없이 확인할 수 있습니다.

그러나, 이 방식은 '파일을 직접 열어야 하는 상황'에서의 임시적인 해결책으로 작동하지만, 사용자 경험 측면에서 불편함을 유발할 수 있다고 판단하였고, 반드시 해결해야하는 과제로 우선순위를 책정하였습니다.

이러한 문제를 해결하기 위해서는 CloudFront를 통해 파일을 조회할 때, 브라우저에 렌더링되는 방식이 아니라 즉시 다운로드되도록 파일 관리 방식을 변경해야만 했습니다.


문제 해결 방법

방법 #1

첫 번째로 시도한 방법은 WAS에서 CloudFront를 통해 파일을 직접 다운로드하여, 다시 클라이언트에게 제공하는 방식이었습니다. 그러나, 이 방식은 WAS의 파일 I/O 작업을 발생시킵니다. 특히, 대용량 파일, 예를 들어 엑셀 파일을 WAS를 거쳐 클라이언트에게 전달하는 경우, WAS의 메모리 가용성에 큰 부담을 주게 됩니다.

따라서, 해당 방법은 성능 저하와 자원 고갈을 초래할 수 있기 때문에, 최적의 해결책이 아니라고 판단하여 후순위로 미루게 되었습니다.

방법 #2

두 번째로 시도한 방법은 CloudFront에서 파일을 요청할 때, 응답 헤더 필드에 'Content-Disposition: attachment' 값을 추가하는 방식이었습니다.

해결 방법을 설명하기 앞서, 응답 헤더 필드를 추가하는 것만으로 파일을 즉시 다운로드가 가능하다는 것을 설명하기 위해선 브라우저의 파일 처리 방식을 이해할 필요가 있습니다.

브라우저의 파일 처리 방식

브라우저는 기본적으로 파일의 MIME 타입(Content-Type)을 기반으로 파일을 처리합니다.

예를 들어, 해당 파일이 PDF 파일(Content-Type: application/pdf)일 경우 크롬 내장 PDF 뷰어를 사용해 바로 렌더링합니다. 그러나, Excel 파일(Content-Type: image/svg+xml)일 경우 파일을 브라우저에서 렌더링 하지 않고 즉시 다운로드 받게 됩니다.

💾 브라우저, 파일 처리 방식

No확장자파일 MIME결과
1JPEG (.jpg, .jpeg)Content-Type: image/jpeg브라우저 렌더링
2PNG (.png)Content-Type: image/png브라우저 렌더링
3PDF (.pdf)Content-Type: application/pdf브라우저 렌더링
4TEXT (.txt)Content-Type: text/plain브라우저 렌더링
5HTML (.html, .htm)Content-Type: text/html브라우저 렌더링
6PowerPoint (.pptx)Content-Type: image/svg+xml파일 다운로드

Content-Disposition

그러나, 응답 헤더 필드에 Content-Disposition 값이 추가된다면 결과는 달라집니다!

응답 헤더 필드에 Content-Disposition: attachment 값이 추가된다면 파일의 MIME 타입과 무관하게 무조건 파일을 즉시 다운로드하게 됩니다.

📄 As a response header for the main body

HTTP 구문의 첫번째 파라미터는 inline (기본값, 웹 페이지 안에서 또는 웹 페이지로 나타남) 또는 attachment (반드시 다운로드 받아야 하며 대부분의 브라우저는 'Save as'(새이름으로저장)창을 보여주고 filename 파라미터들이 존재한다면 그 이름을 새이름으로 미리 채워줌)입니다.

Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

정리하면, CloudFront에 파일을 요청할 때, 응답 헤더 필드에 Content-Disposition: attachment 값을 추가하면, 파일을 즉시 다운로드 받을 수 있게 됩니다. 이 방법은 방법#1과 다르게 WAS의 파일 I/O 작업을 발생시키지 않아 더욱 효과적인 방법이라고 볼 수 있습니다.


CloudFront Function 도입

문제 발생 배경과 다양한 해결 시도를 거쳐, 드디어 CloudFront Function을 활용해 문제를 해결한 과정에 대해 설명하겠습니다.

해결 방법을 짧게 정리하면, 클라이언트가 CloudFront에 파일을 요청할 때 쿼리 스트링 값(download=true)이 존재할 경우, 응답 헤더에 'Content-Disposition: attachment'를 추가하여, 브라우저가 파일을 즉시 다운로드하도록 유도하였습니다.

CloudFront Function

AWS 공식 문서에 따르면 CloudFront Function의 정의는 다음과 같습니다.

CloudFront Functions를 사용하면 대규모, 지연에 민감한 CDN 사용자 지정을 위해 JavaScript로 가벼운 함수를 작성할 수 있습니다.
함수는 CloudFront를 통해 흐르는 요청 및 응답을 조작하고, 기본 인증 및 권한 부여를 수행하고, 엣지에서 HTTP 응답을 생성하는 등의 작업을 수행할 수 있습니다.
CloudFront Functions 런타임 환경은 밀리초 미만의 시작 시간을 제공하고, 초당 수백만 개의 요청을 처리할 수 있도록 즉시 확장되며, 매우 안전합니다.
CloudFront Functions는 CloudFront의 기본 기능이므로 CloudFront 내에서 코드를 완전히 빌드, 테스트 및 배포할 수 있습니다.
CloudFront 함수를 CloudFront 배포와 연결하면 CloudFront가 CloudFront 엣지 위치에서 요청과 응답을 가로채서 함수로 전달합니다. 다음 이벤트가 발생하면 CloudFront Functions를 호출할 수 있습니다.

  • CloudFront가 뷰어로부터 요청을 수신하는 경우(뷰어 요청)
  • CloudFront가 뷰어에게 응답을 반환하기 전(뷰어 응답)

정리하면, CloudFront Functions는 CloudFront에 요청을 보내면 해당 요청을 CloudFront 엣지 위치에서 가로채서 함수로 요청 값을 가공하는 작업을 수행할 수 있습니다. 이를 통해 Response Header 필드에 값을 추가한다면, 어떤 파일이든 즉시 다운로드할 수 있습니다.

여기서 확인해야할 포인트가 하나 더 있습니다. 바로 뷰어 요청(Viewer Request)뷰어 응답(Viewer Response)입니다.

- CloudFront가 뷰어로부터 요청을 수신하는 경우(뷰어 요청)
- CloudFront가 뷰어에게 응답을 반환하기 전(뷰어 응답)

뷰어 요청(Viewer Request)뷰어 응답(Viewer Response)을 간단하게 설명하면 다음과 같습니다.

  • 뷰어 요청(Viewer Request): CloudFront의 캐시가 확인되기 전에 모든 요청에서 실행
  • 뷰어 응답(Viewer Response): 오리진 또는 캐시에서 응답이 수신된 후 모든 요청에서 실행

Lambda@Edge는 Viewer Request, Viewer Response, Origin Request, Origin Response 모든 곳에 트리거를 연결할 수 있지만, CloudFront Functions는 Viewer Request, Viewer Response 단계에서만 트리거를 연결할 수 있습니다.

🤔 Lambda@Edge VS CloudFront Functions?

여기서 'Lambda@Edge가 범용성이 더 좋으니까 CloudFront Function 대신 사용하면 되는거 아닌가?'라는 의문점이 생길 수 있습니다. 이를 이해하기 위해서 Lambda@Edge와 CloudFront Functions의 차이점을 간단하게 설명해보겠습니다.

AWS 공식 문서의 내용을 인용하면 Lambda@Edge와 CloudFront Functions의 차이점은 다음과 같습니다.

CloudFront Functions는 다음과 같은 사용 사례의 단기 실행 경량 함수에 적합합니다.

  • 캐시 키 정규화: HTTP 요청 속성(헤더, 쿼리 문자열, 쿠키 및 URL 경로)을 변환하여 캐시 적중률을 개선할 수 있도록 최적의 캐시 키를 생성합니다.
  • 헤더 조작: 요청 또는 응답에서 HTTP 헤더를 삽입, 수정 또는 삭제합니다. 예를 들어 모든 요청에 True-Client-IP 헤더를 추가할 수 있습니다.
  • URL 리디렉션 또는 다시 쓰기: 요청 정보에 따라 뷰어를 다른 페이지로 리디렉션하거나 한 경로에서 다른 경로로 모든 요청을 다시 씁니다.
  • 요청 권한 부여: 권한 부여 헤더 또는 다른 요청 메타데이터를 검사하여 JSON 웹 토큰(JWT)과 같은 해시된 권한 부여 토큰을 검증합니다.

Lambda@Edge는 다음과 같은 사용 사례에 적합합니다.

  • 완료하는 데 몇 밀리초 이상이 걸리는 함수
  • 조정 가능한 CPU 또는 메모리가 필요한 함수
  • 서드 파티 라이브러리(다른 AWS 서비스와의 통합을 위한 AWS SDK 포함)에 의존하는 함수
  • 처리에 외부 서비스를 사용하기 위해 네트워크 액세스가 필요한 함수
  • 파일 시스템 액세스 또는 HTTP 요청 본문에 대한 액세스가 필요한 함수

Lambda@Edge와 CloudFront Functions 간단 비교

정리하면, Lambda@Edge는 복잡한 작업을 비동기로 처리하는 것에 특화되어 있습니다. 이에 반해, CloudFront Functions는 간단한 작업을 빠르게 처리하는 것에 특화되어 있는 것을 알 수 있습니다.

현재 요구사항은 파일 즉시 다운로드를 위한 헤더 조작이 필요한 상황이기 때문에 빠르고 간단하게 처리할 수 있는 CloudFront Functions이 Lambda@Edge를 사용하는 것보다 더 적합하다고 판단하였습니다.

CloudFront Functions 생성 방법

이제 진짜 진짜 CloudFront Functions의 사용법을 알려드리겠습니다. 이에 앞서 S3 생성 및 CloudFront 연결은 이미 구성되었다는 것을 전제로 시작하도록 하겠습니다. 만약 S3 생성과 CloudFront 연결이 되어있지 않다면 CloudFront와 S3연결 블로그를 참고해주시면 감사하겠습니다.

CloudFront Functions 생성 및 연동 과정

[1] AWS 콘솔에 로그인 후, CloudFront에 서비스에 접속하여 좌측 네비게이션 바의 '함수' 탭을 클릭한다.

[2] 우측 상단의 '함수 생성' 버튼을 클릭한다.

[3] 함수의 이름과 설명을 작성하고, Runtime은 'cloudfront-js-2.0'을 선택한 후 '함수 생성' 버튼을 클릭한다.

[4] 함수 코드를 작성한다. 코드 작성을 위한 자세한 내용은 AWS, CloudFront Functions 이벤트 구조를 참고하여 작성한다. 코드 작성이 완료되면, '변경 사항 저장' 버튼을 클릭한다.

만약, handler의 파라미터인 event의 구조를 알고 싶다면 CloudFront 함수 이벤트 구조를 참고

🧑‍💻 Functions 코드

function handler(event) {
    var response = event.response;
    var queryParams = event.request.querystring;
    var value_ = "";
    
    // parse query string
    var parsedParams = {}
    for(var key in queryParams) {
        parsedParams[key] = queryParams[key].value;
    }
    
    // 파일 즉시 다운로드
    if(parsedParams.download && parsedParams.download.toLowerCase() === 'true') {
        value_ += "attachment;";
    }
    
    // 파일 이름 설정
    if(parsedParams.filename) {
        value_ += ` filename=${parsedParams['filename']}`;
    }
    
    // content-disposition 헤더 추가
    response.headers['content-disposition'] = { value: value_ };
    
    return response;
}
}

[5] 성공적으로 코드가 저장되면 상단에 초록색 바와 함께 'functions이(가) 성공적으로 업데이트되었습니다.'라는 문구를 확인할 수 있다. 확인 문구를 확인했다면, '게시 Unpublished' 탭을 클릭한다.

[6] 우측 하단에 존재하는 '함수 게시' 버튼을 클릭한다.

[7] 하단에 '관련 배포' 컴포넌트가 추가된다. 여기서 '연결 추가' 버튼을 클릭한다.

[8] '배포' 탭에서는 해당 함수를 연결할 CloudFront 이름을 선택한다. '이벤트 유형'은 요구사항에 따라 Viewer response 혹은 Viewer request를 선택한다. '캐시 동작'은 구체적인 규정이 없다면 Default(*)로 선택한다.

만약 캐시 동작 정책을 연결하고 싶다면, 관리형 캐시 정책 사용을 참고!

모든 설정이 끝났다면, 우측 하단의 '연결 추가' 버튼을 클릭한다.

[9] 성공적으로 배포가 완료되면 상단에 초록새 바와 함께 'functions이(가) 성공적으로 게시되었습니다.' 라는 문구와 'functions이(가) CloudFront의 기본 캐시 동작과 성공적으로 연결되었습니다.' 라는 문구를 확인할 수 있다.
해당 문구들을 확인했다면, CloudFront Function이 성공적으로 게시된 것이다.


마무리

CloudFront Function을 활용한 파일 관리 시스템의 전환은 WAS의 파일 I/O에 대한 부담을 줄이고, 파일 관리에 대한 책임을 Cloud 환경으로 이관했다는 점에서 많은 이점을 제공했습니다. 앞으로 모든 파일 관리는 S3에서 처리할 수 있게 되었습니다. 더불어, WAS의 메모리 관련 이슈가 생기는 것을 방지할 수 있게 되었습니다.

물론, 파일 관리와 관련된 모든 권한이 Cloud로 이전되었기 때문에 앞으로 파일 관련 이슈가 생긴다면 해당 이슈를 해결하기 위해 또 다른 기능을 추가하거나, 기존의 기능을 변경 혹은 제거해야할지도 모릅니다. 그러나, 파일 다운로드를 효율적으로 다루기 위해서 CloudFront Function을 도입하는 것이 적합하다고 판단했고, 이러한 다양한 시행착오들은 결과적으로 시스템이 개선되어 가는 과정이라고 생각합니다.

유사한 문제를 경험하고 고민중이신 개발자 동료들에게 이 글이 도움이 되길 바랍니다. 감사합니다 😄

profile
목적 있는 글쓰기

1개의 댓글

comment-user-thumbnail
2025년 2월 4일

Deep Drive 구우우욷입니다. 시간 많이 들였겠다ㅋㅋ

답글 달기