SES (Simple E-mail Service) 을 사용중인데 어느날 AWS측에서 메일이 왔다.

이메일 반송률 (Bounce rate)이 10%를 넘어간단다...


들어가서 확인해보니 



bounce rate를 5%를 밑으로 유지해야하는데 10%를 왔다갔다 하고 있었다.

지속적으로 5%이상이면 해당 SES를 사용할수 없게 막혀버리던가 계정자체가 막힐수도 있단다.


보통 해결방법을 찾아보니


SES (반송) -> SNS -> Lambda -> DynamoDB


이렇게 구성하여 다이나모DB에서 모은 반송메일주소 리스트를 가지고 발송시에 거르는 방법으로 가고있더라.

그래서 일단 해당 방법으로 나도 구현하기로 하여 시작하였다.



----------------------------------------------------------------------------------

SNS 주제 생성


먼저 SNS (Simple Notification Service)로 가서 주제를 만들어야한다.

(SES 에서 해당 주제를 설정해놓으면 반송시에 해당 SNS로 메세지가 들어간다)



위와 같이 SES-Bounce 라는 이름으로 주제를 만들었다. 

(나같은 경우에는 [버지니아 북부] 리전에서 SES를 만들었었는데 해당 SNS주제를 설정하려면 같은리전에 있어야 설정할 수 있어서

SNS 주제 생성도 버지니아 북부에서 하였다.)



-------------------------------------------------------------------------------------

SES 설정


SES로 다시 가서 해당 발신 이메일을 클릭 후 

Notifications -> Edit Configuration 으로 간뒤

SNS Topic Configuration -> Bounces 에서 위에서 만든 SNS 주제를 선택해준다.



여기 까지하면 해당 발신이메일로 이메일 발송 후 반송시 해당 SNS주제로 알림이 전달된다.

이제는 SNS를 이용해 람다에서 다이나모DB로 넣어주면 될듯하다.



-------------------------------------------------------------------------------------

DynamoDB 테이블 생성


Lambda 생성전에 먼저 DynamoDB 를 만들자.


AWS DynamoDB로 가서 테이블 만들기를 한 후

테이블 이름과 기본키를 정한다.


내 경우에는 ses_bounce 라는 이름으로 테이블을 만들었고,

파티션 키는 단순하게 email (문자열) 이라고 넣었다. bounce처리된 이메일주소를 키로 raw들을 쌓을 것이다.

(이렇게 이메일을 키로 해놓으면 중복 수집 생각할 필요없을듯 하여...)




나중에 파이썬에서 boto3로 데이터를 넣을거니 boto3클라이언트에 넣을 IAM을 미리생성하여

access_key와 secret_key를 발급해놓자.



-------------------------------------------------------------------------------------

Lambda 생성, 설정


이제 람다를 만들러가면 된다.

나는 파이썬으로 다이나모DB에 넣어주는 로직을 만들거기 때문에 python3.6 으로 선택하여 만들었다.

각자 맞는 언어로 알아서 만들면될듯. 참고로 DB도 나는 dynamoDB를 정하였지만, 

몽고디비, RDB등 알아서 정하면된다.





람다를 생성 후 앞에 위에서 만들었던 SNS를 달아주면된다.

(마찬가지로 내가 SEN, SNS가 전부 버지니아 북부에 만들어서 해당 람다에서 SNS를 잡기위해 람다도 버지니아 북부에 만들었다)




-------------------------------------------------------------------------------------

테스트 발송, 데이터 확인


나는 boto3로 bounce처리되는 이메일을 python으로 현재 시간과 함께 다이나모DB에 적재할 것인데

SNS에서 람다로 들어왔을때의 데이터 형태를 모르기 때문에 우선 테스트발송을 해보기로 하였다.


SES에서 [Send a test email] 이라는 항목이 있는데 해당 테스트발송 기능을 이용하여

bounce@simulator.amazonses.com

이쪽으로 메일을 보내게 되면 반송이 된다




발송 후 람다로 들어오는 데이터를 찍어보니 아래와 같이 데이터가 들어왔다.


{
  'Records': [{
    'EventSource': 'aws:sns',
    'EventVersion': '1.0',
    'EventSubscriptionArn': 'arn:aws:sns:us-east-비밀비밀',
    'Sns': {
      'Type': 'Notification',
      'MessageId': 'd297a622-비밀비밀',
      'TopicArn': 'arn:aws:sns:us-east-1:비밀비밀:SES-Bounce',
      'Subject': None,
      'Message': '{"notificationType":"Bounce","bounce":{"bounceType":"Permanent","bounceSubType":"General","bouncedRecipients":[{"emailAddress":"bounce@simulator.amazonses.com","action":"failed","status":"5.1.1","diagnosticCode":"smtp; 550 5.1.1 user unknown"}],"timestamp":"2018-11-15T02:36:34.200Z","feedbackId":"비밀비밀","remoteMtaIp":"비밀비밀","reportingMTA":"dsn; 비밀비밀"},"mail":{"timestamp":"2018-11-17T02:36:33.000Z","source":"비밀비밀@naver.com","sourceArn":"arn:aws:ses:us-east-1:비밀비밀","sourceIp":"비밀비밀","sendingAccountId":"비밀비밀","messageId":"비밀비밀","destination":["bounce@simulator.amazonses.com"]}}',
      'Timestamp': '2018-11-17T02:36:34.217Z',
      'SignatureVersion': '1',
      'Signature': '비밀비밀',
      'SigningCertUrl': '비밀비밀',
      'UnsubscribeUrl': '비밀비밀',
      'MessageAttributes': {}
    }
  }]
}


어쩄든 여기서 필요한 데이터는 Records 안에 Sns 안에 Message 인데

해당 Message를 파싱하여 bounce 안에 bouncedRecipients의 emailAddress가 궁극적으로 가져오고싶은 반송 이메일 주소가 되겠다!



-------------------------------------------------------------------------------------

Python 소스작성


위의 데이터가 람다로 들어온다고 생각하고 이제 소스를 짜면 되겠다.


난 귀찮으니 따로 로컬에 프로젝트 만들지 않고 

람다 코드 인라인 편집기에서 바로 작성하였다.






import logging
import json
import boto3
import os
import datetime

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ACCESS_KEY = os.environ.get('ACCESS_KEY')
SECRET_KEY = os.environ.get('SECRET_KEY')

def lambda_handler(event, context):
    try:
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        message = event['Records'][0]['Sns']['Message']
        
        bounce_list = json.loads(message)["bounce"]["bouncedRecipients"]
        
        if len(bounce_list) > 0:
            for bounce in bounce_list:
                if str(bounce["emailAddress"]) is not None and str(bounce["emailAddress"]) != "":
                    put_email(str(bounce["emailAddress"]), str(now))
    
    except Exception as e:
        logger.error('error : {}'.format(str(e)))
    
    return True


def put_email(email, now):
    result = "fail"
    dynamo_client = boto3.client('dynamodb', region_name='ap-northeast-2',
                               aws_access_key_id=ACCESS_KEY,
                               aws_secret_access_key=SECRET_KEY)

    response = dynamo_client.put_item(
        TableName='ses_bounce',
        Item = {
            "email": {
                "S": email
            },
            "date": {
                "S": str(now)
            }
        }
    )
    
    if "ResponseMetadata" in response:
        if "HTTPStatusCode" in response["ResponseMetadata"]:
            if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
                result = "success"
                
    logger.info('{} : {}, '.format(email, result))


딱히 어려움이 없는 소스이다...

데이터 들어오는거 파싱해서 emailAddress 추출한다음에 boto3를 이용하여 다이나모 디비에 넣어주면된다.

(boto3는 aws lambda python에 기본으로 들어있는듯하니 따로 모듈을 추가할 필요 없는듯 하다)


(다이나모쪽에 권한이 있는 IAM의 access_key와 secret_key를

람다 설정쪽의 [환경변수] 항목에 ACCESS_KEY, SECRET_KEY 라는 이름으로 각각 넣어놓으면 해당 키를 가져다가 boto3.client에서 설정한다)


 


작성 후 게시하여 위에서 했던 방법대로 테스트 발송을 해보도록 한다.


-------------------------------------------------------------------------------------

결과


테스트 발송을 해보거나 실제로 반영 후에 다이나모디비를 확인해보면




위 사진처럼 데이터가 쌓이게 된다.

발송 로직에서 해당 데이터를 긁어와 예외처리 해주면 ses bounce rate가 점차 줄어들것이다.


'공부 > Aws' 카테고리의 다른 글

boto3 s3 remove deletemarker  (7) 2018.05.22
AWS cli s3 덮어쓰기  (0) 2018.04.27
AWS cli s3 upload  (0) 2018.04.24
AWS cli config  (0) 2018.04.24
AWS s3 listObjects all  (1) 2018.04.12

s3에서 버저닝 설정을 해놓게 되면 파일을 삭제시 완전히 삭제되는게 아니다.

해당 파일을 최신버전에 deletemarker가 생성되며

그 파일의 최신버전이 deletemarker일때 해당파일은 삭제상태가 되는것이다.


그러므로 삭제했던 파일도 최신버전의 deletemarker를 지우게되면 다시 이전 버전으로 살아나는 것이다.



위 사진처럼 파일을 삭제하면 딜리트마커가 쌓인다.

다른 경우로 덮어쓰게되면 삭제마커가 아닌 새로운 object가 이전 버전위에 쌓이는 것이다.


그리고 그 새로쌓인 마커가 최신버전이 되면서 해당 마커의 파일이 노출된다.


//////////////////////////////////


나는 버저닝 해놓았던 s3에서 실수로 어떤 폴더를 삭제하게 되었는데

해당 폴더를 살리기 위해 콘솔에서 딜리트마커를 지우던중 너무 귀찮아서(일일이 하나하나 클릭후에 삭제를 해줘야함)

boto3를 이용해 내가 살리고싶은 파일들을 되돌리는 python 소스를 짜게되었다.


import boto3.s3

bucket_name = "bucket_name_hahaha"
path = "2018/05/"
#삭제한 날짜 gmt +0 로 해주세용
date = '2018-05-11 06:42'

class recovery:

    def start(self):
        print("=" * 80 + "\nstart recovery run!!!\n" + "=" * 80)

        self.client = boto3.client('s3', aws_access_key_id="엑세스키 입력하세용", aws_secret_access_key="시크릿키 입력하세용", region_name='ap-northeast-2')
        self.do()
        print("=" * 80 + "\nstart recovery end!!!\n" + "=" * 80)

    def do(self):
        response = self.client.list_object_versions(
            Bucket=bucket_name,
            EncodingType='url',
            KeyMarker='',
            MaxKeys=1000,
            Prefix=path
        )
        print(response)

        contents = response['DeleteMarkers']
        versionIds = []

        for version in contents:
            if str(version["LastModified"]).find(date) == 0 and version["IsLatest"] is True and str(version["Key"]).endswith("/") is not True:
                versionIds.append({'VersionId':version["VersionId"], "Key":version["Key"]})
                print(version["Key"])

        print("갯수 = " , len(versionIds) , "개")

        #삭제 하기
        response = self.client.delete_objects(
            Bucket=bucket_name,
            Delete={
                'Objects': versionIds,
                'Quiet': True
            }
        )


이렇게 bucket_name에는 s3버킷 이름을 써주면되고, path에는 복구하고싶은 경로를 설정하면되고 복구하고싶은 파일들이 지워진 날짜를 입력하면된다.

(스트링 비교이고 gmt +0기준이라 잘생각해서 써주면됨)


boto3.client에 엑세스키와 시크릿키, 리전을 입력한후에 실행하면

해당 경로에서 해당 날짜시간에 삭제한(deletemarker)를 찾아 그 마커가 라스트버전이면(삭제상태) 해당 마커를 지워서

파일을 이전 버전으로 되돌려준다.



'공부 > Aws' 카테고리의 다른 글

AWS SES Bounce 처리  (0) 2018.11.17
AWS cli s3 덮어쓰기  (0) 2018.04.27
AWS cli s3 upload  (0) 2018.04.24
AWS cli config  (0) 2018.04.24
AWS s3 listObjects all  (1) 2018.04.12

Aws cli s3로 덮어쓰기 업로드를 하려고하는데 아래와 같이

aws s3 sync /Users/jojo/Downloads/test s3://bucket_name/test --acl public-read

했지만 업로드에 실패했다.


내가 하려던 것은 똑같은 폴더와 안에있는 똑같은 파일들을 그대로 다시 s3에 업로드 하려는게 목적이었는데


sync로는 덮어쓰기가 안되는듯 하여 찾아보니

cp (카피)로 하면 된다고 한다.

그래서 copy로 시도해보았는데 그래도 안되었다.


aws s3 cp /Users/jojo/Downloads/test s3://bucket_name/test --acl public-read

위와 같이 하였는데 실패했다. 디렉토리 카피라 안된다고 한다.


예를들어

aws s3 cp /Users/jojo/Downloads/test/haha.jpg s3://bucket_name/test --acl public-read

이렇게 파일자체를 하나하나 덮어쓰는건 되더라.


그래도 분명히 디렉토리를 카피 업로드하는게 있을거라 생각하고 찾았는데 역시 있었다.


aws s3 cp /Users/jojo/Downloads/test s3://bucket_name/test --acl public-read --recursive


위에서 처럼 --recursive 옵션을 붙여주니 디렉토리 업로드가 잘되었다.

'공부 > Aws' 카테고리의 다른 글

AWS SES Bounce 처리  (0) 2018.11.17
boto3 s3 remove deletemarker  (7) 2018.05.22
AWS cli s3 upload  (0) 2018.04.24
AWS cli config  (0) 2018.04.24
AWS s3 listObjects all  (1) 2018.04.12

aws s3 웹 콘솔에서 대용량이나 많은 수의 파일을 업로드하기에는 많이 불편한점이 있다.

중간에 끈키기도 하며, 엄청 버벅인다.


그래서 할 수 있는 방법으로는
aws cli를 로컬에 설정한 후 s3 sync를 이용해 업로드하는 방법이 있다.


■ aws cli 설정 법
http://isntyet.tistory.com/124


cli설정이 끝났다면 아래와 같이 명령어와 해당하는 경로를 입력해주면 업로드가 된다.

aws s3 sync [로컬경로] s3://[버킷이름]/[업로드 경로] --acl public-read
ex)
aws s3 sync /Users/jojo/Downloads/test s3://thisismybucket/test --acl public-read


위 테스트와 같이 입력하게되면 로컬의 /Users/jojo/Downloads/test 의 폴더가

s3의 thisismybucket 버킷에 test라는 폴더로 업로드 된다.


window에서도 마찬가지로

c:\test 이런식의 경로를 입력해주면 된다.

'공부 > Aws' 카테고리의 다른 글

AWS SES Bounce 처리  (0) 2018.11.17
boto3 s3 remove deletemarker  (7) 2018.05.22
AWS cli s3 덮어쓰기  (0) 2018.04.27
AWS cli config  (0) 2018.04.24
AWS s3 listObjects all  (1) 2018.04.12

aws s3에 많은 용량의 파일을 올려야하는데 s3 web 콘솔에서 한번에 올리자니 중간에 멈추거나

용량이 너무 많아 업로드가 안될때가 있다.


이럴때는 aws cli s3를 이용하면 되는데 해당 글에서는 우선 cli를 설정하는 방법을 살펴보겠다.


■ mac에서 하는법

brew install awscli

으로 homebrew를 이용해 바로 설치해버리자.

그런다음

aws configure


을 실행하면 입력라인이 하나 둘 나온다.


AWS Access Key ID [None]: 

AWS Secret Access Key [None]: 

Default region name [None]: 

Default output format [None]:


위와 같은 입력라인들이 나오는데 aws IAM에서 설정한 것을 해당 라인에 맞게 입력하면된다.
– AWS Access Key ID [None]: 액세스 키 입력
– AWS Secret Access Key [None]: 시크릿 키 입력
– Default Region name [None]: 리전입력, 난 Seoul 리전이라  ap-northeast-2 입력
– Default output format [None]: ex) json, text, table


입력을 마친후 

aws configure list
라고 입력하면 설정이 잘 입력되었는지 확인 할 수 있다.



■ window에서 하는법

아래 주소에서 

http://aws.amazon.com/ko/cli

32bit 또는 64bit 중 해당하는 파일을 다운받고 실행한다.


그런다음 cmd창에서 mac에서와 같이

aws configure

입력하여 똑같이 설정하면 된다.

'공부 > Aws' 카테고리의 다른 글

AWS SES Bounce 처리  (0) 2018.11.17
boto3 s3 remove deletemarker  (7) 2018.05.22
AWS cli s3 덮어쓰기  (0) 2018.04.27
AWS cli s3 upload  (0) 2018.04.24
AWS s3 listObjects all  (1) 2018.04.12

aws s3에서 php로 해당 경로에 있는 파일, 폴더를 리스트를 가져올때 아래와 같이 하면되는데..


public function getListObject($bucketName, $prefix, $delimiter, $marker){
	$response = $this->S3->listObjects(array(
		'Bucket' => $bucketName,
		'Delimiter' => $delimiter,
		'EncodingType' => 'url',
		'Marker' => $marker,
		'Prefix' => $prefix
	));

	return $response;
}

하지만 한번 listObjects를 칠때 max는 1000개다..

내가 불러올 경로의 파일, 폴더 갯수는 3000천개가 넘는데 이렇게되면 1000개를 제외한 나머지 2000여개는 짤리게된다.


한번에 전체다 불러오기 위해 아래와같이 소스를 짰다.


public function getAllListObject($bucketName, $prefix, $delimiter, $marker){

        $response = $this->S3->listObjects(array(
            'Bucket' => $bucketName,
            'Delimiter' => $delimiter,
            'EncodingType' => 'url',
            'Marker' => $marker,
            'Prefix' => $prefix
        ));

        if ((!isset($response["CommonPrefixes"])) || (!is_array($response["CommonPrefixes"]))) {
            $response["CommonPrefixes"] = array();
        }

        if ((!isset($response["Contents"])) || (!is_array($response["Contents"]))) {
            $response["Contents"] = array();
        }

        while(true) {
            if ($response["IsTruncated"]) {
                $tmp = $this->S3->listObjects(array(
                    'Bucket' => $bucketName,
                    'Delimiter' => $delimiter,
                    'EncodingType' => 'url',
                    'Marker' => $response["NextMarker"],
                    'Prefix' => $prefix
                ));

                if ((isset($tmp["CommonPrefixes"])) && (is_array($tmp["CommonPrefixes"]))) {
                    $response["CommonPrefixes"] = array_merge($response["CommonPrefixes"], $tmp["CommonPrefixes"]);
                }

                if ((isset($tmp["Contents"])) && (is_array($tmp["Contents"]))) {
                    $response["Contents"] = array_merge($response["Contents"], $tmp["Contents"]);
                }

                $response["IsTruncated"] = $tmp["IsTruncated"];
                $response["NextMarker"] = $tmp["NextMarker"];

            } else {
                break;
            }
        }

        return $response;
}


위와 같이 만들고 사용해서 프린트해보면 폴더는 $response["CommonPrefixes"]  이곳에
파일들은 $response["Contents"] 이곳에 배열로 저장되어 있는것을 확인 할 수 있다.


# bucket 에는 버킷이름

Delimiter 에는 그냥 '/' 를 넣었고

Marker는 파일경로인데 파일들은 쭉 나열했을때 어느 파일 후 부터 가져올것인지 정하는거고

Prefix 는 상위경로(어디서 가져올 것인지)이다.

'공부 > Aws' 카테고리의 다른 글

AWS SES Bounce 처리  (0) 2018.11.17
boto3 s3 remove deletemarker  (7) 2018.05.22
AWS cli s3 덮어쓰기  (0) 2018.04.27
AWS cli s3 upload  (0) 2018.04.24
AWS cli config  (0) 2018.04.24

+ Recent posts