Amazon Web Service
AWS에는 Lambda@Edge라는 기능이 있습니다. Lambda@Edge는 CloudFront (이하 CF)가 동작하는 엣지 서버에서 실행되는 Lambda 함수를 의미하며, CF로 인입되거나 CF의 오리진으로부터 오는 응답을 가로채서 사용자의 의도에 맞게 변경하는 역할을 해줍니다. 그리고 Lambda@Edge를 활용해서 할 수 있는 것 중에 이미지 리사이징이 있습니다. 이미지 리사이징에 대해서는 좋은 예제들과 많은 글들이 있으니 오늘 글에서는 Lambda@Edge를 통해 리사이징 된 이미지를 S3 원본에서 삭제하고, CF에서 cache invalidation 하는 방법에 대해서 이야기해 보려고 합니다.
본격적인 이야기를 하기 전에 Lambda@Edge의 동작 원리에 대해서 살펴보겠습니다. 아래는 AWS 공식 홈페이지에서 제공하는 Lambda@Edge의 동작 원리입니다.
그림에서 보이는 것처럼 Lambda@Edge가 동작하는 위치는 총 4개의 위치로 구분됩니다. 그리고 이미지 리사이징의 원리는 viewer request 위치에서 사용자의 이미지 요청 URL을 변경하고 S3 원본에서의 응답을 origin response에서 가로채서 리사이징과 관련된 작업을 합니다.
자세한 내용은 https://github.com/kimsejun2000/lambdaedgedims을 참고하시기 바랍니다. 자세한 예제와 함께 구성되어 있기 때문에 그냥 따라 하기만 해도 정상적으로 동작하는 리사이징을 볼 수 있습니다.
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
이 내용을 그림으로 표현한다면 아래와 같이 됩니다.
CF는 사용자의 요청 URL을 cached 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 코드 중 일부입니다.
중요한 부분은 aws_s3_bucket_notification입니다. S3 오리진에서 이미지가 지워지는 이벤트, 즉 s3:ObjectRemoved 이벤트가 발생하면 설정한 Lambda 함수를 실행시키도록 하는 부분입니다. 이를 통해서 우리는 사용자가 원본을 지우는 순간을 이벤트로 잡아낼 수 있고 Lambda 함수를 실행시키도록 할 수 있게 되었습니다. 그럼 실제 Lambda 함수에서는 어떤 작업들을 하면 될까요?
사용자의 이미지 삭제 이벤트를 받은 후 이미지 삭제용 Lambda 함수는 어떤 이미지가 삭제되었는지에 대한 정보를 받아서 해당 이미지에 대한 리사이징 이미지들이 저장되어 있을 디렉터리들을 지워주면 됩니다. 아래는 함수 코드 일부입니다.
이 함수는 https://kupczynski.info/2019/01/09/invalidate-cloudfront-with-lambda-s3.html 에서 사용한 Lambda 함수를 일부 수정한 함수입니다.
S3 오리진의 이미지를 삭제하는 건 간단합니다. S3로부터 전달받은 event를 파싱 해서 지워진 이미지가 어떤 이미지인지 확인하고 그에 따라 파생된 100x100, 200x200, 300x300 등의 키들을 delete_objects 메서드로 지워주면 됩니다.
여기서 100x100, 200x200, 300x300이 삭제 이벤트에 포함되면 더 이상 진행시키지 않고 함수를 종료시키는 부분을 눈여겨보셔야 합니다. 그렇지 않으면 이미지 삭제 Lambda가 삭제한 리사이징 된 이미지들에 대한 삭제 이벤트가 다시 날아와서 영원히 끝나지 않는 Lambda 함수를 보게 됩니다.
그리고 해당 이미지들의 CF에서의 invalidation 코드는 아래와 같습니다.
소스 코드의 일부분은 삭제했습니다. cf_distribution_id를 설정하는 부분 등등..
이렇게 Lambda 함수를 구성한 후 실제 테스트하게 되면 이미지 삭제 작업과 CF invalidation 작업은 성공했다고 나옵니다. 하지만 테스트해보면 S3 오리진에서는 삭제되었지만 CF에서는 삭제되지 않고 계속 캐싱된 이미지가 나오는 것을 확인할 수 있습니다. 왜 그럴까요?
잠깐 위로 다시 돌아가 보겠습니다. 앞에서 이야기했던 것처럼 viewer request는 CF로 인입된 URL을 리사이징 된 이미지의 URL로 변경해서 S3 오리진에 요청하고 그에 대한 캐시 키를 만듭니다.
하지만 이렇게 변경된 URL은 S3 오리진으로 요청하는 URL이고 실제 CF에서의 키가 되는 URL은 쿼리 스트링이 뒤에 붙은 /image/100x100/test.jpg?d=100 이 됩니다. 즉 CF에서 invalidation을 하기 위해서는 /image/100x100/test.jpg을 invalidation 하면 안 되고 /image/100x100/test.jpg? d=100을 invalidation 해야 합니다. 왜 그럴까요? 아래 코드는 이미지 리사이징에 사용되는 viewer request 함수의 일부분입니다.
하단을 보시면 viewer request가 변경한 값은 request 구조체의 uri 값입니다. request 구조체에는 querystring이라는 구조체도 있습니다. 이 값은 viewer request 함수 내에서 d라는 쿼리 스트링이 함께 인입되었는지 확인하기 위해 활용됩니다. 그리고 콜백으로 넘길 때의 request 구조체에 쿼리 스트링이 그대로 포함됩니다. CF는 request.uri와 request.querystring을 조합하여 캐시 키를 생성하기 때문에 리사이징 된 이미지의 캐시 키는 변경된 URL과 쿼리 스트링의 조합이 됩니다.
그래서 우리가 위에서 작성한 이미지 삭제를 위한 Lambda 함수 중 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://kupczynski.info/2019/01/09/invalidate-cloudfront-with-lambda-s3.html