졸업 프로젝트를 진행하면서 원래는 스프링부트를 사용해서 개발하려고 했으나 음정 측정및 옥타브로 계산하는 과정을 실시간으로 프론트엔드에서 구현하기 때문에 구현할 api가 예상보다 많이 줄었습니다. 따라서 스프링프레임워크는 해당 프로젝트에 비해 너무 리소스가 많이 필요로 하는 기술이였기때문에 차라리 AWS RDS, Lambda와 API Gateway를 사용하여 서버리스 구성하는 것으로 변경했습니다.
저희 졸업 프로젝트의 백엔드 아키텍처는 다음과 같습니다. 굉장히 단순한 구조를 가지고 있습니다,,,ㅎㅎ
대략적인 흐름은 API Gateway로 rest api 요청이 들어오면 요청이 트리거가 되어 Lambda함수에 작성된 내용을 수행하고 RDS에 저장된 데이터들을 호출해오는 과정입니다.
구현 방법 설명에 앞서서 사용하는 기술들에 대한 설명을 하겠습니다.
: Lambda는 AWS에서 제공하는 서버리스 컴퓨팅 플랫폼입니다.
서버리스란, 서버가 없다는 뜻이 아니고 개발자가 서버의 존재를 신경쓸 필요가 없다는 뜻입니다. 서버가 잘 돌아가고 있는지, 개수와 사양한 적당한지 등등 신경쓸 필요없이 사용자는 오직 코드에만 집중할 수 있으니 무척 편합니다.
단점
API Gateway란 규모에 상관없이 API 생성, 유지 관리, 모니터링과 보호를 할 수 있게 해주는 서비스입니다.
말 그대로 Client에서 server로 통신할 때 사용하는 많은 api들의 대문(게이트웨이)또는 통로와 같은 역할을 한다고 보면 됩다.API Gateway를 이용하면 통합적으로 엔드포인트와 REST API를 관리할 수 있으며
API 게이트웨이를 등록해주면, 모든 클라이언트는 각 서비스의 엔드포인트 대신 API Gateway로 요청을 전달하여 관리가 용이해집니다. 사용자가 설정한 라우팅 설정에 따라 각 엔드포인트로 클라이언트를 대리하여 요청하고 응답을 받으면 다시 클라이언트에게 전달하는 프록시(proxy) 역할을 하기 때문입니다. 후에 프록시에 관한 내용이 나오기 때문에 기억해주세요!
RDS 콘솔창에 들어가서 좌측 메뉴의 데이터베이스 -> 데이터베이스 생성으로 들어갑니다. 표준생성, mysql을 선택하고 사용자의 이름과 암호를 만들어주어 데이터베이스를 생성합니다.
초기 데이터베이스 이름, 마스터 사용자 이름, 암호, rds 엔드포인트, DB 인스턴스 식별자는 코드로 데이터베이스에 접글할 때 사용하므로 기억해둡니다
또한 VPC, 서브넷, VPC 보안 그룹의 값은 lambda가 rds에 접근할 수 있게 설정할 때 사용하므로 기억해둡니다.
람다 권한 설정하기
람다와 rds가 VPC 내부에서 통신하기 때문에 람다 함수의 구성에 권한에 들어가서 실행 역할 편집을 눌러줍니다.
권한 정책에서 권한 추가를 눌러 준다음에 AWSLambdaVPCAccessExecutionRole를 추가 해줍니다.
AWSLambdaVPCAccessExecutionRole
: VPC 내부 통신을 위한 권한 설정입니다
VPC 설정
VPC는 위에서 생성한 RDS의 VPC와 동일하게 만들어 주면 됩니다.
환경변수 설정
mysql zip 파일 업로드
데이터베이스에 접근하기 위해서는 pymysql 라이브러리가 필요하기 때문에 pymysql을 설치한 후 .zip 파일로 압축하여 람다 함수에 올려야 합니다.
Lambda에서 기본적으로 제공하는 라이브러리가 아닌 이상, 필요한 라이브러리를 zip파일 형태로 업로드하거나 layer를 쌓아서 사용해야 합니다.
pip install --target lambda-rds pymysql --no-user
import os
import sys
import pymysql
import logging
import json
host = os.environ["DB_HOST"]
user = os.environ["DB_USERNAME"]
password = os.environ["DB_PASSWORD"]
db = os.environ["DB_NAME"]
logger = logging.getLogger()
logger.setLevel(logging.INFO)
try:
conn = pymysql.connect(host=host, user=user, passwd=password, db=db, connect_timeout=5)
except pymysql.MySQLError as e:
logger.error("연결 실패!")
logger.error(e)
sys.exit()
우선 pymysql을 사용해서 데이터 베이스와 연결을 해줍니다.
def lambda_handler(event, context):
if event["httpMethod"] == "GET": # GET 요청일 때 실행
high = event['queryStringParameters']['high'] # Qeuryparameter 받아오기
low = event['queryStringParameters']['low']
cur = conn.cursor() # 데이터베이스 연결
sql_string = f"select singer, song, highest_pitch, lowest_pitch, youtube_url, youtube_image, youtube_listen_url \
from music where highest_pitch <= {high} and lowest_pitch >= {low} \
order by highest_pitch desc, lowest_pitch asc;"
# sql문
cur.execute(sql_string)
rows = cur.fetchall() # sql문 실행 후 결과 받아오기
result = []
logger.info(rows)
for row in rows:
result.append({
"singer": row[0],
"song": row[1],
"high": row[2],
"low": row[3],
"image": row[5],
"url" : {
"listen" : row[4],
"sing" : row[6]
}
})
return {
"statusCode": 200,
"body": json.dumps(result),
}
이제 마지막으로 클라이언트가 들어오는 API Gateway를 설정줍니다.
요청이 Endpoint로 들어오면 트리거인 lambda가 작동하게 됩니다.
이중에서 rest api를 만들것이기 때문에 rest api를 선택해주고 아래 사진처럼 api gateway를 생성해주면 됩니다.
그후에는 원하는 리소스와 메서드를 생성해줍니다.
이때 주의해야 할점은 메서드를 생성할때 위에 작성한 람다와 연결을 해줘야 하고 꼭 Lambda 프록시 통합을 활성화해야 됩니다.
Lambda 프록시 통합은 단일 API 메서드의 설정을 통해 API를 구축하기 위한 간단하고 강력하며 민첩한 메커니즘입니다. Lambda 프록시 통합을 사용하면 클라이언트가 백엔드에서 단일 Lambda 함수를 호출할 수 있습니다. 이 함수는 다른 Lambda 함수를 호출하는 등 다른 AWS 서비스의 많은 리소스 또는 기능에 액세스합니다.
Lambda 프록시 통합에서 클라이언트가 API 요청을 제출하면 API Gateway는 통합된 Lambda 함수로 이벤트 객체를 전달합니다.
AWS 공식문서 참조
이후에 스테이지를 생성하여 배포를 하면 api 완성입니다! 스테이지를 통해서 cloudwatch와 연동하여 로그를 추적하거나 배포기록 등 다양한 기능을 볼수 있습니다.
이후에 포스트맨으로 테스트를 해보면 아래와 같이 api가 잘 요청 되는것을 볼 수 있습니다.
포스트맨에서는 잘 실행되지만 프론트앤드인 리액트에서 api를 호출하면 CORS 에러가 발생했습니다.
브라우저에서는 보안적인 이유로 cross-origin HTTP 요청들을 제한합니다. 그래서 cross-origin 요청을 하려면 서버의 동의가 필요합니다. 만약 서버가 동의한다면 브라우저에서는 요청을 허락하고, 동의하지 않는다면 브라우저에서 거절합니다.
이러한 허락을 구하고 거절하는 메커니즘을 HTTP-header를 이용해서 가능한데, 이를 CORS(Cross-Origin Resource Sharing)라고 부릅니다.
그래서 브라우저에서 cross-origin 요청을 안전하게 할 수 있도록 하는 메커니즘입니다.
api gateway에서 리소스를 선택하면 CORS 활성화라는 버튼이 나옵니다.
위와 같이 하면 보통을 해결이 되지만 저 같은 경우에는 Lambda 프록시 통합을 활성화 했기 때문에 대신 백엔드에서 해당 CORS 헤더를 반환해야 합니다(예: Access-Control-Allow-Origin).
백엔드에서 CORS 헤더를 반환해 보겠습니다. 아까 Lambda 함수를 작성했던 곳에서
def returnHeaders(self=""):
return {
"Access-Control-Allow-Headers":
"Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token",
"Access-Control-Allow-Methods":
"DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT",
"Access-Control-Allow-Origin":
"*"
}
...
return {
"statusCode": 200,
"body": json.dumps(result),
"headers": returnHeaders(),
}
위 코드를 작성하여 returnHeaders() 함수를 통해 CORS에 필요한 헤더를 같이 반환해 줍니다. 이렇게 하면 쉽게 CORS 에러를 해결 할 수 있습니다.