[AWS Lambda] VPC와 서브넷 설정을 통한 인터넷 연결 (feat. 이메일 발송)

Hans·2023년 8월 24일
0

안녕하세요:) 신규 프로젝트에서 장고를 서버리스로 운영하기 위해 람다(lambda)를 처음 도입하면서 이메일 발송에 에러가 발생하여 해결했던 방법을 공유하고자 합니다. 아직 응애 주니어 개발자이기 때문에 네트워크 관련 에러를 많이 접해보지 않은 관계로 객관적인 정보전달을 하려 노력했지만 틀린 부분이 있을 수 있으니 코멘트와 양해를 부탁드립니다... ㅎㅎ;

배경과 에러 진단

이번 프로젝트에선 사용자와 관리자에게 이메일을 발송해야 하는 로직이 필요했습니다.
그래서 여느 때와 다름없이 장고의 메일 발송 함수를 사용하고 zappa를 통해 배포한 후 정상적으로 메일이 발송되는지 테스트를 했습니다.

#pseudocode
from django.core.mail import send_mail

def email_test():
    ...
    send_mail(
        subject="메일 테스트",
        ...
    ) 

해당 함수를 실행하자 무한 로딩 후 Endpoint request timed out 메시지와 함께 메일 발송이 되지 않았습니다.
엔드포인트 요청에서 시간 초과가 발생했다는 건 사용하는 SMTP의 서버 쪽 에러거나 우리 서버 에러라는 말인데 말이죠...🤔
저흰 네이버의 SMTP를 사용하려고 했기에 어느 쪽 서버의 에러일 가능성이 더 클지 생각해 본다면 당연히 우리 쪽 서버일 확률이 99.999999%라고 생각을 하고 진단 방향을 우리 쪽 서버로 바꾸고 후딱 백엔드 서버를 분석했습니다.

먼저 개발 환경에서 위 코드를 다시 실행해 보니 아주 정상적으로 발송되었습니다.

따라서 엔드포인트 에러이기 때문에 aws 아키텍쳐를 분석했고, 여기서 해결법을 찾을 수 있었습니다...!

VPC(Virtual Private Cloud)

먼저 VPC에 대한 설명은 여기서 확인하실 수 있습니다.
기본적으로 Zappa를 통해 배포할 경우 생성되는 서브넷의 경우 퍼블릭 서브넷입니다.

처음 Zappa를 통해 배포하고 나서 AWS VPC 콘솔에서 새로 생긴 VPC를 클릭하고 Resource map을 보시게 되면 zappa가 자동으로 생성한 서브넷 4개가 라우팅 테이블로 연결되어 있고, 이 라우팅 테이블은 인터넷 게이트웨이(igw)에 연결되어 있는 것을 확인하실 수 있습니다.
그래서 따로 설정하지 않아도 API통신과 어드민 페이지 접속을 원활하게 할 수 있었죠.
그럼 퍼블릭 서브넷이면 당연하게!!!!! 메일도 보내져야 되는 거 아닌가 !!!! 하고 약 2주일 동안 삽질을 해봤는데 결과는 VPC가 범인이었습니다.

Zappa로 배포하고 나서 대부분의 경우 RDS 혹은 보안 때문에, 기타 등등 여러 이유로 AWS Lambda 콘솔에서 해당 람다 함수 > 구성 > VPC에서 VPC 설정을 하셨을 겁니다. 저도 메일 테스트를 하기 전에 먼저 RDS를 사용하려고 VPC를 사전에 설정했거든요...

람다 함수에 VPC 적용 후 화면.png

혹?시?나? 해서 VPC를 연결을 하지 않고, 즉 zappa가 처음 배포할 때의 기본 값인 VPC가 없는 상태에서 메일 테스트를 진행해 봤습니다.

Zappa로 처음 배포하고 난 뒤 구성 > VPC의 화면

그리고 정말 놀랍게도 메일이 성공적으로 발송되었습니다...

범인 발견

에러의 원인은 찾았는데 해결을 어떻게 하지? 나 RDS 써야 되는데??? 하며 멘붕에 빠져 있을 때
스택 오버플로우에서 솔루션을 발견했습니다. 링크

When you add VPC configuration to a Lambda function, it can only access resources in that VPC. If a Lambda function needs to access both VPC resources and the public Internet, the VPC needs to have a Network Address Translation (NAT) instance inside the VPC.

그러니까 VPC도 사용하고 싶고, 인터넷 사용도 하고 싶으면 NAT를 써서 해결하라는 것이었습니다.
..........NAT가 뭔데?

NAT(Network Address Translation)

사실 VPC를 설정하지 않아도 람다와 RDS는 사용할 수 있습니다.
그럼에도 VPC 사용을 권장하는 이유는 크게 두 가지를 꼽을 수 있습니다.

  1. VPC 내 위치한 람다 함수와 RDS는 서로 프라이빗 IP를 사용하여 통신하기에 외부로부터 액세스를 차단할 수 있습니다. 당연하게도 우리의 소중한 DB를 외부에서 접근할 수 있게 한다는 건 상상만 해도 끔찍하쥬..
  2. 람다와 RDS가 같은 VPC 내에 존재하기에 데이터 전송 속도가 향상되고 네트워크 지연 시간이 감소됩니다.

이러한 이유로 대부분 RDS와 연동할 때 VPC를 설정하게 됩니다.

이때 VPC로 연결되어 있는 상황에서 RDS나 AWS 내 서비스가 외부 인터넷에 연결해야 할 경우 NAT를 사용해야만 합니다. NAT는 퍼블릭 서브넷과 프라이빗 서브넷이 같은 VPC 안에 있으면 서로 통신할 수 있다는 점을 이용해 퍼블릭 서브넷이 프라이빗 서브넷 대신 인터넷 접속을 해주는 서포터(?)라고 생각하면 됩니다.

Lambda는 VPC 내 리소스에만 In/Out Bound가 설정에 따라 허용되기 때문에 외부 인터넷의 경우 NAT를 설정해야 합니다. 스택 오버플로우 형님들 조언대로 NAT를 사용하기 위해 굳이굳이 2개의 서브넷은 프라이빗, 2개의 서브넷은 퍼블릭으로 설정한 뒤 메일을 전송해 보니 아주 성공적이었습니다.

그럼 어떻게 했는지 그 삽질의 과정을 공유하겠습니다!

VPC에 인터넷 연결하기

먼저 람다 함수에 위쪽에 업로드한 "VPC 적용 후 화면.png"처럼 여러분의 VPC가 람다 함수에 이미 적용되어 있다고 가정하겠습니다.

가장 먼저 해야 할 일은 NAT 생성입니다.

NAT 게이트웨이의 비용은 시간당 0.059달러가 부과됩니다. 따라서 30일을 기준으로 약 42.48달러의 요금이 부과되니 주의하세요!

VPC 콘솔에 접속 > NAT 게이트웨이 클릭 후 NAT 게이트웨이 생성을 클릭합니다.

이름은 자유롭게 정하시면 되고 서브넷은 ap-northeast-2a로 설정해줍니다.
그리고 탄력적 IP 할당을 클릭하시고 해당 IP로 설정한 후 NAT 게이트웨이 생성을 클릭해 생성해 줍시다.

그리고 다음으론 VPC 콘솔의 왼쪽 메뉴에서 라우팅 테이블을 클릭하고 라우팅테이블을 만들어 주세요.

저는 라우팅 테이블의 이름을 구분하기 쉽게 프라이빗과 퍼블릭으로 했습니다. 생성할 때 VPC 선택하는 건 zappa가 자동으로 만들어준 VPC를 선택하시면 됩니다!
생성하시고 나면 사진 속 저의 화면과 다른 점은 프라이빗, 퍼블릭 라우팅 테이블의 "명시적 서브넷 연결"이 비어 있을 겁니다. 이제 요놈들을 설정해 보겠습니다.

먼저 퍼블릭을 클릭 > 서브넷 연결 > 명시적 서브넷 연결의 서브넷 연결 편집 클릭해 줍시다.

그럼 선택 가능한 서브넷이 총 4개가 보일 겁니다.
어차피 2개씩 나눠서 프라이빗, 퍼블릭을 할 거라 임의로 두개를 정해도 되지만 저는 172.31.0.0/20과 172.31.16.0/20을 퍼블릭으로 설정하겠습니다.

선택했다면 연결 저장을 눌러주세요!
마찬가지로 프라이빗도 같은 순서로 서브넷을 연결해야 하는데, 퍼블릭에서 설정하지 않은 172.31.32.0/20과 172.31.48.0/20을 선택하고 저장해 줍시다.
참고로 여러분과 저의 IPv4 CIDR은 다를 수 있습니다.

테이블 만들고 서브넷만 연결하면 모합니까... 라우팅 설정을 안 해줬는데!
퍼블릭 라우팅테이블을 클릭 > 라우팅 > 라우팅 편집을 클릭합니다.

그리고 대상은 0.0.0.0/0을 클릭하고, 대상은 인터넷 게이트웨이를 클릭한 후 zappa가 자동으로 만들어 둔 인터넷 게이트웨이를 사용합니다.

다 선택했다면 변경 사항 저장을 눌러주세요.

다음으론 프라이빗 라우팅 테이블을 클릭하여 똑같이 라우팅 > 라우팅 편집에서 라우팅 추가 클릭 후 대상은 0.0.0.0/0으로, 대상은 NAT 게이트웨이 클릭 후 앞서 생성한 NAT를 클릭한 후 변경 사항 저장을 클릭!

자 그럼 여러분의 서브넷들은 아주 예쁘게 퍼블릭 2개, 프라이빗 2개가 되었습니다!

하지만 이 사실을 아직 람다는 알지 못합니다... 왜냐하면 안 알려줬거든요.

람다를 프라이빗으로

AWS Lambda 콘솔에서 여러분의 함수를 클릭한 후 구성 > VPC > 편집을 눌러주세요.

기존에 서브넷을 어떻게 설정하셨는지 모르겠지만 전 서브넷 4개 모두 설정한 상태였습니다.
요놈들을 아~까 프라이빗 라우팅 테이블에서 서브넷 연결할 때 사용했던 서브넷으로만 체크해줍시다.
전 172.31.32.0/20과 172.31.48.0/20을 프라이빗으로 설정했었죠?

반드시!! 프라이빗 라우팅 테이블에 연결된 서브넷을 체크해 주세요.

그리고 저장을 눌러줍시다.

어?~~~

여러분의 람다는 이제 외부 인터넷과 연결이 아주 잘 될겁니다! (이정도면 어? 해도 되잖아)
메일도 테스트해 보시면 성공적으로 전송될 겁니다.
람다의 서브넷이 프라이빗인데 모든 통신이 잘 되는 이유는? NAT 덕분입니다 ㅎㅎㅎㅎㅎㅎㅎ

여담

이메일 발송이 되지 않아서 폭풍 구글링을 해보니 어디선가 lambda에선 Django의 내장 메일 전송 함수가 작동하지 않는다는 글을 봤습니다.
그래서 AWS SES를 사용하기 위해 위 과정에서 IAM 역할, 정책 추가, boto3의 메일 전송 함수를 사용했었습니다.

import boto3from botocore.exceptions 
import ClientErrorfrom django.conf 
import settings

def send_email(subject=None, recipients=None, html=None):
    SENDER = "example@naver.com"
    AWS_REGION = "ap-northeast-2"
    BODY_TEXT = (        
        "Amazon SES Test (Python)\r\n"        
        "This email was sent with Amazon SES using the "        
        "AWS SDK for Python (Boto)."    
    )
    CHARSET = "UTF-8"
    client = boto3.client(        
        "ses",        
        region_name=AWS_REGION,        
        aws_access_key_id=settings.SES_ACCESS_KEY_ID, 
        aws_secret_access_key=settings.SES_SECRET_ACCESS_KEY,    
    )
    response = client.send_email(
        ...
    )

물론 이 방법도 잘 작동했으나 이번 포스트를 작성하면서 Django의 내장 메일 전송 함수를 사용해 테스트 해봤는데 똑같이 잘 작동했습니다.
SES를 사용해야 하는 상황이라면 어쩔 수 없겠지만 그렇지 않다면 추가적인 세팅이 필요 없는 장고 함수를 사용하는 것을 추천드립니다.

끝까지 읽어주셔서 감사합니다 :)

profile
You miss 100% of the shots you don't take.

0개의 댓글