brunch
매거진 Journey To AWS

Lambda@Edge와 CF Invalidation

Amazon Web Service

by 강진우

AWS에는 Lambda@Edge라는 기능이 있습니다. Lambda@EdgeCloudFront (이하 CF)가 동작하는 엣지 서버에서 실행되는 Lambda 함수를 의미하며, CF로 인입되거나 CF의 오리진으로부터 오는 응답을 가로채서 사용자의 의도에 맞게 변경하는 역할을 해줍니다. 그리고 Lambda@Edge를 활용해서 할 수 있는 것 중에 이미지 리사이징이 있습니다. 이미지 리사이징에 대해서는 좋은 예제들과 많은 글들이 있으니 오늘 글에서는 Lambda@Edge를 통해 리사이징 된 이미지를 S3 원본에서 삭제하고, CF에서 cache invalidation 하는 방법에 대해서 이야기해 보려고 합니다.


Lambda@Edge의 동작 원리


본격적인 이야기를 하기 전에 Lambda@Edge의 동작 원리에 대해서 살펴보겠습니다. 아래는 AWS 공식 홈페이지에서 제공하는 Lambda@Edge의 동작 원리입니다.

스크린샷 2019-06-16 오후 9.14.07.png Lambda@Edge의 동작원리

그림에서 보이는 것처럼 Lambda@Edge가 동작하는 위치는 총 4개의 위치로 구분됩니다. 그리고 이미지 리사이징의 원리는 viewer request 위치에서 사용자의 이미지 요청 URL을 변경하고 S3 원본에서의 응답을 origin response에서 가로채서 리사이징과 관련된 작업을 합니다.

자세한 내용은 https://github.com/kimsejun2000/lambdaedgedims을 참고하시기 바랍니다. 자세한 예제와 함께 구성되어 있기 때문에 그냥 따라 하기만 해도 정상적으로 동작하는 리사이징을 볼 수 있습니다.

viewer request


Lambda@Edge가 동작하는 위치 중에 viewer request 부분을 조금 더 자세히 살펴보겠습니다. AWS 공식 문서에는 아래와 같이 정의되어 있습니다.

CloudFront Viewer Request – The function executes when CloudFront receives a request from a viewer and before it checks to see whether the requested object is in the edge cache

이 내용을 그림으로 표현한다면 아래와 같이 됩니다.

KakaoTalk_Photo_2019-06-16-21-24-15.jpeg viewer request의 위치

CF는 사용자의 요청 URLcached image를 찾는 키로 사용합니다. 만약 사용자가 /image/test.jpg라고 입력했다면 오리진으로부터 test.jpg을 찾아서 사용자에게 응답해주고 /image/test.jpg라는 키를 이용해서 해당 이미지를 저장합니다. 그리고 viewer request는 위 그림에서처럼 요청 URL을 키로 변환하는 과정에 개입하여 동작합니다. 즉, 이미지 리사이징 예제에서 본 것처럼 /image/test.jpg?d=100으로 요청을 하게 된다면 viewer request가 이 URL/image/100x100/test.jpg라는 URL로 바꾸게 되고 CF는 이렇게 바뀐 URL을 키 값으로 사용하게 됩니다.

하지만 정말 키 값이 /image/100x100/test.jpg일까요? 그렇지 않습니다. 이에 대해서는 뒤에서 이야기하겠습니다.

리사이징 된 이미지의 삭제


이렇게 Lambda@Edge를 통해 썸네일 이미지 등 여러 크기의 이미지들을 on-the-fly 형태로 사용자가 원하는 순간에 생성해서 서비스할 수 있습니다. 하지만 사용자가 원본 이미지를 지우게 된다면 어떨까요? Lambda@Edge를 통해 생성된 리사이징 이미지들 역시 삭제해야 합니다. 그렇지 않고 원본 이미지만 삭제된다면 사용자의 의도와는 다르게 삭제된 이미지들의 여러 버전들이 남아서 계속 접근 가능하게 될 겁니다.

그리고 삭제 작업 역시 S3 이벤트를 통해서 Lambda를 실행시킬 수 있고 이를 통해 자동화할 수 있습니다.

아래는 삭제를 위한 Lambda 함수의 Terraform 코드 중 일부입니다.

스크린샷 2019-06-16 오후 9.33.44.png Lambda 함수 설정 코드 중 일부

중요한 부분은 aws_s3_bucket_notification입니다. S3 오리진에서 이미지가 지워지는 이벤트, 즉 s3:ObjectRemoved 이벤트가 발생하면 설정한 Lambda 함수를 실행시키도록 하는 부분입니다. 이를 통해서 우리는 사용자가 원본을 지우는 순간을 이벤트로 잡아낼 수 있고 Lambda 함수를 실행시키도록 할 수 있게 되었습니다. 그럼 실제 Lambda 함수에서는 어떤 작업들을 하면 될까요?


이미지 삭제를 위한 Lambda 함수


사용자의 이미지 삭제 이벤트를 받은 후 이미지 삭제용 Lambda 함수는 어떤 이미지가 삭제되었는지에 대한 정보를 받아서 해당 이미지에 대한 리사이징 이미지들이 저장되어 있을 디렉터리들을 지워주면 됩니다. 아래는 함수 코드 일부입니다.

이 함수는 https://kupczynski.info/2019/01/09/invalidate-cloudfront-with-lambda-s3.html 에서 사용한 Lambda 함수를 일부 수정한 함수입니다.
스크린샷 2019-06-16 오후 9.37.43.png 이미지 삭제를 위한 Lambda 함수의 일부

S3 오리진의 이미지를 삭제하는 건 간단합니다. S3로부터 전달받은 event를 파싱 해서 지워진 이미지가 어떤 이미지인지 확인하고 그에 따라 파생된 100x100, 200x200, 300x300 등의 키들을 delete_objects 메서드로 지워주면 됩니다.

여기서 100x100, 200x200, 300x300이 삭제 이벤트에 포함되면 더 이상 진행시키지 않고 함수를 종료시키는 부분을 눈여겨보셔야 합니다. 그렇지 않으면 이미지 삭제 Lambda가 삭제한 리사이징 된 이미지들에 대한 삭제 이벤트가 다시 날아와서 영원히 끝나지 않는 Lambda 함수를 보게 됩니다.

그리고 해당 이미지들의 CF에서의 invalidation 코드는 아래와 같습니다.

소스 코드의 일부분은 삭제했습니다. cf_distribution_id를 설정하는 부분 등등..
스크린샷 2019-06-16 오후 9.41.32.png CF cache invalidation

이렇게 Lambda 함수를 구성한 후 실제 테스트하게 되면 이미지 삭제 작업과 CF invalidation 작업은 성공했다고 나옵니다. 하지만 테스트해보면 S3 오리진에서는 삭제되었지만 CF에서는 삭제되지 않고 계속 캐싱된 이미지가 나오는 것을 확인할 수 있습니다. 왜 그럴까요?


리사이징 된 이미지의 CF에서의 키


잠깐 위로 다시 돌아가 보겠습니다. 앞에서 이야기했던 것처럼 viewer requestCF로 인입된 URL을 리사이징 된 이미지의 URL로 변경해서 S3 오리진에 요청하고 그에 대한 캐시 키를 만듭니다.

KakaoTalk_Photo_2019-06-16-21-46-34.jpeg viewer request의 URL 변조

하지만 이렇게 변경된 URLS3 오리진으로 요청하는 URL이고 실제 CF에서의 키가 되는 URL은 쿼리 스트링이 뒤에 붙은 /image/100x100/test.jpg?d=100 이 됩니다. 즉 CF에서 invalidation을 하기 위해서는 /image/100x100/test.jpginvalidation 하면 안 되고 /image/100x100/test.jpg? d=100을 invalidation 해야 합니다. 왜 그럴까요? 아래 코드는 이미지 리사이징에 사용되는 viewer request 함수의 일부분입니다.

스크린샷 2019-06-16 오후 9.48.42.png viewer request 함수의 일부

하단을 보시면 viewer request가 변경한 값은 request 구조체의 uri 값입니다. request 구조체에는 querystring이라는 구조체도 있습니다. 이 값은 viewer request 함수 내에서 d라는 쿼리 스트링이 함께 인입되었는지 확인하기 위해 활용됩니다. 그리고 콜백으로 넘길 때의 request 구조체에 쿼리 스트링이 그대로 포함됩니다. CFrequest.urirequest.querystring을 조합하여 캐시 키를 생성하기 때문에 리사이징 된 이미지의 캐시 키는 변경된 URL과 쿼리 스트링의 조합이 됩니다.


CF Invalidation


그래서 우리가 위에서 작성한 이미지 삭제를 위한 Lambda 함수 중 CF invalidation 부분은 아래와 같이 수정되어야 합니다.

스크린샷 2019-06-16 오후 9.52.26.png CF invalidation 부분 코드 수정

각각에 맞게 ?d=100, ?d=200, ?d=300 으로 정의해 줘도 되겠지만 그보다는 *를 통해서 지워주는 게 더 확실하고 향후 손이 덜 가는 방법이 됩니다. 쿼리 스트링의 숫자가 바뀌게 된다면 이 값도 바꿔줘야 하기 때문입니다.


마치며


CF는 인입된 URL을 바탕으로 캐시 키를 생성하며, 이때 사용되는 캐시 키에는 URL 뿐 아니라 쿼리 스트링도 포함됩니다.

따라서 Lambda@Edge나 혹은 다른 방법을 통해서 CF와 관련된 작업을 했을 경우에는 cache invalidation을 할 때 주의해서 진행해야 합니다. 이에 대해서는 AWS에 잘 정의된 자료가 있습니다. (https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html) 더 자세한 정보는 이곳에서 확인하시기 바랍니다.


참고 자료


https://aws.amazon.com/ko/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/


https://kupczynski.info/2019/01/09/invalidate-cloudfront-with-lambda-s3.html


keyword
매거진의 이전글Terraform의 remote_state 활용하기