초보 개발자

S3, Lambda, Cloud Watch $7,000달러 과금 및 예방 대책 본문

AWS

S3, Lambda, Cloud Watch $7,000달러 과금 및 예방 대책

taehyeki 2023. 4. 21. 15:59

Lambda를 사용하는 목적

S3를 트리거로 사용하여 웹에서 이미지를 업로드할 때 원본 이미지를 네 가지 크기로 변환하는 것입니다.

 


기대 효과

웹 사이트에서 원본(고화질)을 그대로 로드할 필요가 없어져 더 빠른 로딩이 가능해진다는 것입니다.


Lambda의 소스 코드 내용

업로드된 파일이 이미지인지 확인하고, 버킷 이름이 "origin"인지 확인한 후, 조건에 맞는 경우 Small, Medium, Large, Thumb 크기로 변환하여 업로드합니다. 이 작업에 대한 정보는 Cloudwatch에 기록됩니다.
업로드된 파일이 이미지인지 확인하고, 버킷 이름이 "origin"인지 확인한 후, 조건에 맞지 않는 경우 Cloudwatch 로그에만 기록됩니다.


문제점은 S3 Trigger를 특정 폴더만 설정해야 했는데, 모든 버킷에 설정되어 있어 문제가 발생했다는 것입니다. 이것을 인지하지 못하고 이미지 수만 개가 로컬에서 사이즈로 변환되어 업로드되었습니다. 그 결과 수십만 회의 Lambda가 실행되었습니다.

발생 시간 (타임존)

약 2023/04/13 08:35 UTC ~ 2023/04/17 04:40 UTC
금요일에 실행되어 월요일에 발견하여 삭제했습니다.


피해

Lambda는 318,963,413초, $5,316.07이 청구되었고,
S3의 API 요청은 1,294,187,450회로 $478.85와 257,053,754회로 $1,208.15가 청구되었으며, 

CloudWatch의 데이터 페이로드는 937GB로 $712가 청구되었습니다.

예방 대책

이러한 문제가 다시 발생하지 않도록 예방 조치를 철저히 세우고 Lambda를 사용하려고 합니다. 먼저, AWS Cost Management 도구를 사용하여 청구 금액이 일정 금액을 초과하지 않도록 결정했습니다. 두 번째로, 버킷에 대한 PutObject 권한을 웹 사이트 도메인 이름을 가진 엔드포인트에만 허용하도록 설정하여 로컬에서 이러한 작업이 금지되도록합니다.
(IAM을 악의적으로 탈취하여이러한 상황이 발생할 수 있기 때문입니다.) 마지막으로 CloudWatch를 사용하여 비정상적인 트리거나 Lambda의 발생을 감지하고, S3 트리거를 비활성화하는 기능을 추가합니다.

 

비정상적인 트리거를 감시, S3 트리거를 비활성화하는 기능을 추가

더보기

S3트리거를 사용해서 업로드 될 때마다 이미지를 변환시키는 람다를 만들었다.

하지만 S3트리거가 무한히 반복되는 문제가 생겨 이를 방지하기 위한 기능을 만드려고 한다.

두 가지 방법을 사용했다.

 

1. EventBridge사용 ( 비 효율적 )

먼저 첫번 째는 eventBridge를 통해 5분마다 실행되는 이벤트를 생성하여 람다함수 ( s3 trigger가 특정 수 이상 작동하면 s3 trigger를 제거하는 코드 )를 실행시킨다. 구조도는 아래와 같다.

cloud watch matrics의 람다 함수 중 invocations라는 것이 있는데, 이를 사용하여 s3trigger의 작동회수를 구할 수 있다. ( 다만 이 람다함수가 여러 트리거에서 사용하면 제대로 확인이 불가 할 수 있음 ) 이를 이용해서 아래와 같은 코드의 람다함수를 작성해보자.

 

2. cloud watch alarm 사용

두 번째 방법은 cloud watch alarm을 사용하는 것이다. 알람을 통해 lambda함수의 invocations를 감시한 뒤에 해당 지표에 이상이 생기면. SNS에 통지를 보내고, 알림 메일보내는 기능과 S3 trigger를 제거하는 람다를 그 SNS의 구독시키면 된다.

alarm 경보 -> sns -> 메일보내기, s3 trigger 제거 이렇게 되는 것이다.

import boto3
import os
def lambda_handler(event, context):
    s3_client = boto3.client('s3')
    
    bucket_name = os.environ['bucket_name']
    function_name = os.environ['lambda_name']
    
    response = s3_client.get_bucket_notification_configuration(Bucket=bucket_name)
    
    configurations = response.get('LambdaFunctionConfigurations', [])
    
    # Remove the configuration for the specified function name
    configurations = [c for c in configurations if c['LambdaFunctionArn'] != function_name]
    
    # Update the notification configuration for the bucket
    s3_client.put_bucket_notification_configuration(
        Bucket=bucket_name,
        NotificationConfiguration={
            'LambdaFunctionConfigurations': configurations
        }
    )
    print("detected unnormal access to s3 trigger")
    return ''

 

 

비용 감면

트리거에 대해 정확하게 확인할 수 없었던 것이 문제이며, 이렇게까지 비용이 발생할 것으로 생각하지 못했습니다. 우리의 실수이지만, 오류를 명확히 인식하고 있으나, 막대한 비용이 발생했습니다. 3일 동안 10년치의 시간이 실행되어 1.5억 회의 요청이 전송되었다는 것은 정상적인 트래픽이 아닌 것으로 생각됩니다. 이를 AWS 결제 대행사에 문의를 보냈고,

 

약 2달에 걸쳐 AWS측에서 온 질문( 전체적인 경위, 정확히 문제가 된 서비스,예방 대책 수립) 들을 대응하면서 결과적으로는 $7174.94의 비용을 면제 받을 수 있었다.

 

트리거 라는 것이 정말 편리한 기능이지만 이렇게 까지 위험할 수 있다는 것은 인지하지 못하고 있었다.

면제를 받게 되서 정말 다행이며, 앞으로 비용이 발생할 수 있는 부분에 대해서는 안전장치를 꼭 만들어 두어야 겠다는 생각을 하였다.