본문 바로가기

AWS

[CloudFront] S3 Object 변경 시 Cloudfront Invalidation 자동으로 수행하기

반응형

1. 개요

보통 정적 컨텐츠를 처리하기 위해 CloudFront와 S3를 연동하는 경우가 많다.

 

이럴 때 Bucket object를 변경하면 CloudFront의 Edge Location은 이를 알아차리지 못하는데 이럴 때 자동으로 변경된 Object를 자동으로 Invalidation 해주는 방법에 대해서 알아보자.

 

 

2. 방법 소개

1. 서버 혹은 사용자가 Bucket의 Image를 변경한다.

2. S3 Bucket에 ObjectCreation 이벤트가 발생 시 Lambda 함수를 Trigger 할 수 있다.

3. Lambda 함수를 Event를 전달받아 어떤 Object가 생성/변경되었는지 확인할 수 있다.

4. Object가 새로 생성되는 이벤트는 무시하며, Object가 변경되는 이벤트일 경우에만 CloudFront Invalidation을 수행한다.

5. 서버 혹은 사용자는 CloudFront에 객체를 요청 시 새로 변경된 이미지를 요청할 수 있게 된다.

 

 

핵심 내용은 다음과 같다.

- Bucket Object Event

- Bucket Versioning

- CloudFront Invalidation

- Boto3 (사실 핵심은 아니고... bash를 써도 되고 nodejs sdk를 써도 되고... api만 호출하면 되니 아무거나 써도 상관없음)

 

2.1. 사전 조건

- S3 Bucket에 Versioning(버전 관리)가 되어 있어야 한다.

 

 

3. 구현

Lambda는 Serverless Framework를 통해 구현하였다. 언어는 Python을 사용하였다.

 

import json
import boto3
from time import time

# S3 CDN Distribution Id
s3_distribution_id = "XXXXXXXXXXX"

s3_client = boto3.client('s3')
cf_client = boto3.client('cloudfront')

def main(event, context):
    
    for record in event['Records']:
        bucket_name = record['s3']['bucket']['name']
        object_key = record['s3']['object']['key']
        print(f"Bucket Name : {bucket_name} , Key : {object_key}")
    
        # Check Version Object Count
        object_versions = s3_client.list_object_versions(Bucket=bucket_name, Prefix=object_key)['Versions']
    
        if len(object_versions) > 1:
            print(f"Object Versiong 개수가 {len(object_versions)}개입니다. CF Invalidation 수행합니다.")
            
            cf_response = cf_client.create_invalidation(
                DistributionId=s3_distribution_id,
                InvalidationBatch={
                    'Paths': {
                        'Quantity': 1,
                        'Items': [
                            "/" + object_key
                            ]
                    },
                    'CallerReference': str(time()).replace(".", "")
                })
            
            print(f"CF Invalidation에 대한 상태 코드는 {cf_response['ResponseMetadata']['HTTPStatusCode']}입니다.")
            
            if cf_response['ResponseMetadata']['HTTPStatusCode'] == 201:
                for version in object_versions:
                    if not version['IsLatest']:
                        print(f"This version is old version. Delete this. {version['VersionId']}")
                      
                        s3_response = s3_client.delete_object(
                            Bucket=bucket_name,
                            Key=object_key,
                            VersionId=version['VersionId']
                            )
                      
                        print(f"S3 Object 제거에 대한 상태 코드는 {s3_response['ResponseMetadata']['HTTPStatusCode']}입니다.")
                  
                    else:
                        print(f"This version is latest version. Keep this. {version['VersionId']}")
    
    return {
        'statusCode': 200,
        'body': 'Lambda Finished !!'
    }

 

 

간략히 설명하자면 S3:ObjectCreated:* 이벤트가 발생 시 Lambda를 트리거하게 되는데,

 

람다는 이벤트를 받아 어떤 Object인지 확인하고 Version 개수를 체크하여 현재 Object가 2개 이상이면 Old Version의 객체가 있는 것이니 객체 변경으로 간주하여 CloudFront Invalidation을 수행해준다.

이후 오래된 객체들은 Lambda에서 바로 삭제해줘버린다. Lifecycle을 적용해서 삭제할수도 있지만 필자는 그냥 이렇게 오래된 객체를 삭제해버렸다.

 

(참고로 Lambda에는 S3 객체를 조회하고 CloudFront Invalidation을 수행할 수 있는 권한이 있어야 한다.)

 

 

Object Event는 필자의 경우 Serverless.yml에서 설정해주었다.

Web UI에서도 당연히 설정할 수 있는 내용이기에 알아서 찾은 뒤 설정해주면 된다.

 

 

설정하게 될 경우 Bucket Object가 변경될 경우에만 CloudFront Invalidation이 특정 객체에만 수행됨을 확인할 수 있다.

 

Lambda 로그를 보면 오래된 객체만 제거하고 최신 이미지 객체만 유지하는것 또한 확인할 수 있다.

 

 

 

이런식의 CF Invalidation을 수행하는 Lambda를 1개 만들어두면 나중에 Bucket의 Event만 연결해주면 중앙에서 Lambda로 CF Invalidation을 관리할 수 있게 된다.

 

이러한 것들을 개별 버킷마다 별도 로직을 작성하는게 아니라 중앙에서 관리한다면 유지보수를 하는 수고 또한 줄어들 것으로 생각된다.

 

 

별 내용은 없고 필요한 분들은 코드 정도 참고하면 금방 실무에 적용하실 수 있을 것이다.

반응형