이번글은 회고보다는 이미지 리사이징하면서 만났던 무수한 에러들과 해결했던 부분을 정리하고자 한다.
(정리 안해놓으면 까먹을거 같아서..)
프로젝트 초기 구상 때부터 걸렸지만 외면하고 있던 부분이 있었다.
그것은 바로 매우매우 많은 이미지..
Next.js 로 이미지 리사이징 하는것도 좋고 아님 node.js 서버 하나 돌려서 이미지 리사이징 처리해도 좋은데
일단 스택을 React로 잡기도했고 이미지 리사이징 하려고 서버를 상시 돌린다는 것도 배보다 배꼽이 큰 느낌이라 다른 방법을 찾아봤다.
Cloudflare vs AWS cloudfront & lambda@edge
serverless 컨셉을 유지하면서 이미지 리사이징 처리하는 방법으로 둘중에 고민했는데 Cloudflare가 요새 뜨기도 하고 사용법도 훨씬 간편한데 AWS 인프라를 사용해보고 싶어서 AWS로 선택했다.
Cloudflare의 이미지 관련 내용을 잠깐 설명하면 Next.js 이미지 리사이징과 AWS cloudfront(cdn)를 합친걸로 생각하면 되는데 아래 예시를 참고하면 쉽게 이해될듯하다.
//Next.js 이미지 리사이징
import Image from 'next/image';
<Image
src="/path/to/your/image.jpg" // 이미지 경로
width={500} // 원하는 너비
height={300} // 원하는 높이
layout="responsive" // 레이아웃 옵션
quality={75} // 이미지 품질 (0-100)
/>
// cloudflare image 리사이징
// Docs: https://developers.cloudflare.com/images/url-format
export default function cloudflareLoader({ src, width, quality }) {
const params = [`width=${width}`, `quality=${quality || 75}`, 'format=auto']
return `https://example.com/cdn-cgi/image/${params.join(',')}/${src}`
}
요렇게 함수를 만들어서 호출하면 cloudflare 에 등록한
url(https://example.com/ 도메인 등록 해줘야 쓸수 있음!)
을 통해 이미지를 리사이징 할 수 있다
위 cloudflareLoader를 호출해 url로 요청을 하면 cdn이 해당 내용을 캐싱해서 일정 시간동안은 리사이징을 진행하지 않고 캐싱해온 콘텐츠를 가지고와서 리소스 낭비가 없다.
처음엔 그냥 next.js 더 써보고 싶은데 Next.js 로 마이그레이션 해버릴까.. 를 생각했지만 이번 리사이징 진행하면서 좀더 알아보니 편하게만 생각했던 next/image 리사이징은 그만한 대가가 있었다.
사이드 프로젝트로 next/image 사용하던 중에 본인 노트북이 다운된적이 있다.
좀 오래 쓰긴했어도 그정도는 아닌데.. 같은 작업중에 몇번 더 다운되길래 디버깅해봤다.
image 리사이징이 많이 들어간 컴포넌트가 랜더링 되는 순간 다운되길래 CPU 사용량을 측정해보니 순간적으로 100%를 찍고 내려오고 있었다.
..잉?
이미지 프로세싱은 CPU 리소스를 많이 사용한다. 리사이징은 각 픽셀에 대한 연산을 수행하는데,
1920x1080 해상도의 이미지는 200만 픽셀이 넘는다. 모든 픽셀에 연산을 수행하고 다시 픽셀을 새로운 크기에 맞게 재배치 하는 과정이 CPU 부하를 준다. JPEG, Webp 확장자 처럼 용량을 압축한 포맷들도 압축을 해제 후 리사이징을 진행하기 때문에 원본 px값의 연산이 고스란히 진행된다.
만약 AWS 프리티어 계정의 서버에 올린다고 하면
AWS EC2 ts.micro의 1CPU, 1GB RAM 인스턴스 사양으로 이미지 리사이징이 몇개만 진행되도 CPU가 금방 100% 되는걸 예상할 수 있고(본인 노트북 사양 4CPU, 8GB RAM) RAM 사용량도 금방 100%가 넘어갈듯하다.
이미지 프로세싱은 메모리에 원본 이미지를 로드하고 프로세싱 중의 buffer와 결과 값을 저장하는 등에 RAM 이 사용되는데 적당히 5MB PNG 파일로 계산해보자.
원본의 50%만 압축되었다고 가정해도 (2:1 ~ 5:1 압축률을 가진다)
압축해제 10MB( 5MB * 2 ) + 결과 값 10MB + (리사이징 중 Buffer 값) = 20MB 보다 크고
운영체제와 Next.js 서버 메모리 사용량을 제외하면 40개의 이미지 리사이징 만으로 RAM 사용량이 100%가 되버린다.
(그리고 Next서운 이야기.. )
이미지 최적화의 캐싱 관련해서 가비지 컬렉터가 정상적으로 작동하지 않아 메모리가 100%가 되어버린다?
등등의 next/image의 그림자를 보고나니 lambda 함수로 로컬 서버 외의 리소스를 사용하는 접근이 맞다고 생각했다.
AWS cloudfront & lambda@edge (feat.s3)
AWS 인프라를 통해 이미지 리사이징을 처리하는 과정을 아래 아키텍처와 같다
0. (리사이징할) 이미지를 s3에 업로드 한다.
1. 사용자가 cloudfront url을 통해 s3 에 업로드한 이미지를 요청한다
2. s3에 요청한다 (cloudfront에 리사이징한 이미지가 캐싱되어 있으면 바로 주고 과정은 끝남)
3. cloudfront에 설정해놓은 origin response 트리거로 lambda 함수가 호출된다
4. 요청한 url(&query) 로 이미지를 리사이징하고 리사이징된 콘텐츠는 cloudfront가 캐싱한다.
5. url을 요청한 사용자는 해당 이미지를 받아 본다.
그럼 아키텍처대로 구현해보자.
1. s3 버킷 만들기
처음에 리전 선택 잘못했다가 다시 만들었다. 버킷 사용자가 주로 위치한 지역과 가까운 리전을 선택해야 데이터 전송 시간을 줄일 수 있다. cloudfront로 데이터를 캐싱해서 주더라도 첫 요청은 s3에서 응답하기 때문에 사용자가 많은 곳으로 리전 선택을 해주도록 하자.
2. cloudfront 배포 생성하기
origin domain에 위에서 만든 s3를 설정한다.
-> cloudfront가 콘텐츠를 가져올 오리진(원본) 서버를 설정하는것.
s3 버킷, ec2 인스턴스 또는 다른 http 서버일 수 있고 origin 의 위치와 성격에 따라 성능에 영향을 미칠수 있다.
원본 엑세스 제어 설정을 하면 s3로의 직접 접근이 막히고 cloudfront를 통한 접근만 가능해진다.
create new OAC(Origin access control) 를 만들어준다
위의 과정은 cloudfront 의 OAC(원본 액세스 제어)로 s3 원본을 보호하게 해주는데 요청 응답 플로우는 다음과 같다.
1. 클라이언트가 HTTPS 요청을 Cloudfront로 보냄
2. Cloudfront 엣지 로케이션이 요청을 수신하고 요청 객체가 아직 캐시되지 않은 경우
OAC 서명 프로토콜을 사용해 요청에 서명.
3. S3는 요청에 응답 (올바른 서명이면 승인, 아니면 거부)
-> 위의 서명 요청을 선택하면 위 과정 중 2번에서 CloudFront IAM 서비스 보안 주체가 수신 요청 발생 시 Authorization 헤더에 서명한다. (관련 docs)
그리고 중간에 WAF 선택 란이 있는데 test용이면 비활성화 하면된다.
aws 설명에 혹해서 활성화했는데 웹 사이트 이용자가 거의 없는데 WAF비용만 한달에 $10 정도씩 나가서 비활성화했다..
-> WAF은 SQL 인젝션, XSS, 파일 포함 공격, 악성 봇 차단, DDoS 공격에 대한 방어를 해준다.
+ 좀더 알아보니 이런 방어를 cloudfront 이후에 해서 url 요청 자체는 모두 받아들인다. 예를들어 DDos 같은 경우 Origin에 대한 요청은 방지하지만 cloudfront 요청은 무수히 발생하고 WAF도 어쨌든 요청에 대한 deny response가 무수히 발생하는 상황이라 해당 요금이 과도하게 발생할 여지가있다.
추가적인 내용은 WAF 보안 관련해서 알아보다가 매우 도움이 되었던 블로그를 링크해놓겠다.
https://changmyeong.tistory.com/77
AWS CloudFront에서 CloudFlare로 이관한 후기
2023년 6월, 여러 게임의 편의 기능을 제공하며 유저를 조금씩 모아보겠다는 취지로 사이드 프로젝트를 개발하여 오픈했다. 개발 시간은 1.5일 정도 걸렸지만 오픈한지 한 달이 되던 때에 하루에 7
changmyeong.tistory.com
3 cloudfront 배포 후 해당 정책 s3 버킷 정책에 등록
해당 과정을 진행해야 cloudfront를 통해 s3 에 접근할 수 있다.
이렇게하면 cloudfront를 통해 s3 이미지에 접근할 수 있다.
4. cloudfront 캐시 정책 생성
쿼리 문자열에 따라 콘텐츠를 캐싱하기위해서는 cloudfront에 쿼리 문자열을 포함하도록 설정해야한다.
5. iam
-> iam (identity and access management) 리소스에 대한 엑세스를 관리하고 제어하는 서비스로 iam을 통해 aws 리소스에 대한 권한을 세밀하게 설정하고 관리할 수 있다. 우린 아래 방법 중 2) 역할을 가진 iam 이 필요하다.
1) 사용자에게 권한 부여
- aws 계정의 리소스에 액세스할 수 있는 개별 사용자를 생성할 수 있다. (사용자에게 필요한만큼 권한 부여)
- JSON 형식의 정책을 통해 사용자의 권한을 정의한다.
2) 역할 (role)
- 주로 ec2 인스턴스나 lambda 함수와 같은 aws 서비스가 다른 aws 리소스에 액세스 할 때 사용.
lambda 관련 권한 정책이 다양한데 그중 AWSLambdaExecute는 lambda 함수를 호출할 수 있는 권한을 포함하고, s3의 GET에 대한 액세스를하게 해준다. (관련 docs)
iam 생성 후 신뢰관계를 편집해서 lambdaedge 에 대한 권한을 추가해준다. (관련 docs)
이러면 lambda 함수 실행시 우리에게 필요한 iam이 완성되었다.
다음편 serverless 프레임워크로 lambda함수 배포하기
'aws' 카테고리의 다른 글
AWS cloudfront & lambda@edge 로 이미지 리사이징-2 (2) | 2024.08.29 |
---|