Back End/AWS

[AWS] CloudWatch 로그를 S3 로 보관하기 (Exporting CloudWatch logs to S3 Bucket)

DevPing9_ 2022. 11. 7. 11:20

 

CloudWatch, S3 그리고 S3 Glacier 의 가격

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 버킷에 옮긴다.

 

https://medium.com/dnx-labs/exporting-cloudwatch-logs-automatically-to-s3-with-a-lambda-function-80e1f7ea0187


 

Lambda Application

AWS 를 사용한다면 모두 필요로 하는 작업이기에 이미 선구자가 있었다.
제일 유명해보이는 TerraForm 의 GitHub 코드를 참조하여 작업한다.

 

 

GitHub - DNXLabs/terraform-aws-log-exporter: This module creates a lambda function that exports log groups on the AWS account an

This module creates a lambda function that exports log groups on the AWS account and region deployed. - GitHub - DNXLabs/terraform-aws-log-exporter: This module creates a lambda function that expor...

github.com

 

아래의 코드는 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] EventBridge 에서 Lambda 를 Trigger 하기

Rule 생성 EventBrdige -> Rules 에서 Rule 생성 Schedule Job 으로 이벤트를 발생시킬 것인지, Event Pattern 으로 이벤트를 발생시킬지 선택하고 생성한다. Target 은 당연히 Lambda 로 생성하면된다. Resource Based Pol

developer-ping9.tistory.com

 


AWS 리소스에 대한 권한

위의 코드를 담은 Lambda 함수를 만든다해도 리소스에 대한 권한문제로 실행이 되지 않는다.
아래와 같이 설정한다.

더 아래에 이미지 설명도 같이 첨부하니 천천히 따라하시면 된다.

 

 

저장할 S3 의 버킷 권한

저장할 S3 버킷을 만들고 해당 버킷에서
Permission 탭에서 Bucket Policy 부분을 아래와 같이 편집한다.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GET Bucket ACL",
            "Effect": "Allow",
            "Principal": {
                "Service": "logs.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            # 저장할 S3 의 ARN
            "Resource": "arn:aws:s3:::{$S3 버킷의 이름}"
        },
        {
            "Sid": "Put Log",
            "Effect": "Allow",
            "Principal": {
                "Service": "logs.amazonaws.com"
            },
            "Action": "s3:PutObject",
            # 저장할 S3 의 ARN의 하위리소스를 모두 허락
            "Resource": "arn:aws:s3:::{$S3 버킷의 이름}/*"
        }
    ]
}

 


실행할 Lambda 의  권한

작성된 Lambda -> Configuration -> Permssion 탭에서 Excution Role 을 클릭하여 
Permission Policy 에 아래의 JSON 을 추가한다.

"Resource" 에 대한 부분은 본인리소스의 ARN 으로 채워넣는다.
아래에 이미지 설명이 있다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            # Lambda 가 CloudWatch Logs 에 대한 수행권한
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:DescribeLogGroups",
                "logs:ListTagsLogGroup",
                "logs:CreateExportTask"
            ],
            # CloudWatch ARN
            "Resource": "arn:aws:logs:us-east-1:{$계정번호}:*"
        },
        {
            # Lambda 가 Systems Manger 에 대한 수행권한 (Systems Manager -> Application Management -> Parameter)
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameter",
                "ssm:PutParameter"
            ],
            # Systems Manager -> Application Management -> Parameter 의 ARN
            "Resource": "arn:aws:ssm:us-east-1:{$계정번호}:parameter/{$파라미터이름}"
        },
        {
            # Lambda 가 자기자신의 CloudWatch LogGroup 에 대한 수행권한
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                # Lambda 자기자신의 로그그룹 ARN
                "arn:aws:logs:us-east-1:{$계정번호}:log-group:/aws/lambda/{$람다함수이름}:*"
            ]
        },
        {
            # Lambda 가 S3 에 대한 수행권한
            "Effect": "Allow",
            "Action": [
                "s3:*",
                "s3-object-lambda:*"
            ],
            "Resource": "*"
        }
    ]
}

 

 

CloudWatch 의 ARN 과 LogGroup 의 ARN

검정으로 칠해진 것이 AWS 계정번호이며 
arn:aws:logs:us-east-1:{$계정번호}:* 는 us-east-1 리젼의 CloudWatch를 가르킨다.
arn:aws:logs:us-east-1:{$계정번호}:log-group:{$로그그룹명}:* 는 해당리젼의 로그그룹을 가르킨다. 

 

그외 ARN 에 대해서는 위와 같은 방식으로 찾아가면 된다.

 

Reference

 

Exporting Cloudwatch Logs automatically to S3 with a Lambda function

The missing feature of Cloudwatch Logs.

medium.com

 

 

AWS: Permissions for exporting logs from Cloudwatch to Amazon S3

I am trying to export logs from one of my CloudWatch log groups into Amazon S3, using AWS console. I followed the guide from AWS documentation but with little success. My organization does not allo...

stackoverflow.com

 

728x90