CloudWatch 는 단순 저장말고도 Metrix 를 이용해 경보등의 서비스를 생성할 수 있으므로 당연히 CloudWatch Log 서비스의 가격이 S3 보다 비싸다.
또, S3 가 S3 Glacier 보다 가격이 비싸다. (액세스 속도 차이)
그렇다면 장애사후분석등 로그를 꼭 보관해야하는 상황에서는 Alarm 등의 서비스를 이용하기 위해 7일 정도로 CloudWatch 에 보관하고 CloudWatch 의 로그를 S3 로 옮겨 1~2달을 보관하고 이후에는 S3 Glacier 로 옮겨 Archive 하는 것이 비용적으로 가장 효율적일 것이다.
그런데 문제점은 CloudWatch -> S3 로 옮기는 버튼은 AWS 콘솔에 존재하지만 주기적으로 옮기는 UI 는 AWS 콘솔에 존재하지 않는다는 것이다.
즉, S3 Client 로 자체 어플리케이션을 만들어야한다는 것을 의미한다. 이 포스팅에서는 CloudWatch 에서 S3 로 로그를 옮기는 것 어플리케이션을 구축하는 것에 대해서만 설명합니다.
CloudWatch to S3
전체적인 개요는 아래의 이미지와 같다. Lambda 에 어플리케이션을 등록시키고 EventBridge 를 이용해 주기적으로 Lambda 를 트리거한다. 그리고 Trigger 된 Lambda 는 지정한 CloudWatch Log Group 의 로그를 S3 버킷에 옮긴다.
Lambda Application
AWS 를 사용한다면 모두 필요로 하는 작업이기에 이미 선구자가 있었다. 제일 유명해보이는 TerraForm 의 GitHub 코드를 참조하여 작업한다.
아래의 코드는 CloudWatch Log 를 S3 로 Exporting 하는 작업이며 SSM(AWS Systems Manager) 를 활용하여 해당 작업이 1일(특정기간)안에 수행이 되었는지 판단하여 수행이 되었다면 실행시키지 않는 코드이다.
import boto3
import os
from pprint import pprint
import time
logs = boto3.client('logs')
ssm = boto3.client('ssm')
def lambda_handler(event, context):
extra_args = {}
log_groups = []
log_groups_to_export = []
if 'S3_BUCKET' not in os.environ:
print("Error: S3_BUCKET not defined")
return
print("--> S3_BUCKET=%s" % os.environ["S3_BUCKET"])
while True:
response = logs.describe_log_groups(**extra_args)
log_groups = log_groups + response['logGroups']
if not 'nextToken' in response:
break
extra_args['nextToken'] = response['nextToken']
for log_group in log_groups:
response = logs.list_tags_log_group(logGroupName=log_group['logGroupName'])
log_group_tags = response['tags']
if 'ExportToS3' in log_group_tags and log_group_tags['ExportToS3'] == 'true':
log_groups_to_export.append(log_group['logGroupName'])
for log_group_name in log_groups_to_export:
ssm_parameter_name = ("/log-exporter-last-export/%s" % log_group_name).replace("//", "/")
try:
ssm_response = ssm.get_parameter(Name=ssm_parameter_name)
ssm_value = ssm_response['Parameter']['Value']
except ssm.exceptions.ParameterNotFound:
ssm_value = "0"
export_to_time = int(round(time.time() * 1000))
print("--> Exporting %s to %s" % (log_group_name, os.environ['S3_BUCKET']))
if export_to_time - int(ssm_value) < (24 * 60 * 60 * 1000):
# Haven't been 24hrs from the last export of this log group
print(" Skipped until 24hrs from last export is completed")
continue
try:
response = logs.create_export_task(
logGroupName=log_group_name,
fromTime=int(ssm_value),
to=export_to_time,
destination=os.environ['S3_BUCKET'],
destinationPrefix=log_group_name.strip("/")
)
print(" Task created: %s" % response['taskId'])
time.sleep(5)
except logs.exceptions.LimitExceededException:
print(" Need to wait until all tasks are finished (LimitExceededException). Continuing later...")
return
except Exception as e:
print(" Error exporting %s: %s" % (log_group_name, getattr(e, 'message', repr(e))))
continue
ssm_response = ssm.put_parameter(
Name=ssm_parameter_name,
Type="String",
Value=str(export_to_time),
Overwrite=True)
return ssm_response
그리고 Lambda -> Configuration -> Environment Variables 탭에서 환경변수인 S3_BUCKET (S3 버킷명) 을 설정한다.
EventBridge 설정
포스팅이 방대해질 수 있으니 아래의 링크로 대체한다.
AWS 리소스에 대한 권한
위의 코드를 담은 Lambda 함수를 만든다해도 리소스에 대한 권한문제로 실행이 되지 않는다. 아래와 같이 설정한다.
더 아래에 이미지 설명도 같이 첨부하니 천천히 따라하시면 된다.
저장할 S3 의 버킷 권한
저장할 S3 버킷을 만들고 해당 버킷에서 Permission 탭에서 Bucket Policy 부분을 아래와 같이 편집한다.