(#이전회차) 웹 애플리케이션 환경 AWS 리소스로 구성
서버리스 기술은 자동 크기 조정, 기본 제공 고가용성 및 종량제 결제 모델을 제공하여 민첩성을 개선하고 비용을 최적화합니다. 또한 이러한 기술을 사용하면 용량 프로비저닝 및 패치 적용과 같은 인프라 관리 작업이 필요하지 않으므로 고객을 위한 코드를 작성하는 데 집중할 수 있습니다.
Node.js 런타임을 사용하는 람다 함수를 생성해보자
AWS Lambda - 함수 생성 - 새로 작성
아래와 같이 작성하고, 함수를 생성한다.
생성한 람다 함수를 확인해보자
코드 - index.mjs 에 람다 함수의 소스 코드를 확인할 수 있다.
export const handler = async (event) => {
console.log(event)
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
템플릿은 람다 함수에서 사용하는 서비스들의 자원의 이름을 알려준다. Hello world 로 설정한 후에 저장하자.
생성된 테스트 이벤트가 체크된 것을 확인하고 Test 버튼을 누르자.
람다 함수는 CloudWatch에서도 결과를 확인 가능하다.
만약 외부에서 람다 함수가 실행된다면 사용자는 이를 CloudWatch 콘솔에서 결과를 확인해야 한다.
코드 - 모니터링 - CloudWatch Logs 보기에서 확인해보자
람다 함수에 대한 3번의 테스트 로그가 남아있다.
import json
def lambda_handler(event, context):
# TODO implement
print("홀리몰리과카몰리로보카폴리모노폴리")
print("이 모든것은 제프 베이조스가 세상을 지배하기 위해 만든 매트리스 세계이다 ")
print(event)
print(context)
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
코드를 수정했으면, DEPOLY 후 아래와 같이 테스트 이벤트를 구성 후 저장한다
테스트 버튼을 눌러 테스트하고, 결과를 확인해보자.
람다 함수의 시작점이다.
위에서 생성한 두 람다함수의 코드 소스를 확인해보자.
함수 생성 시, 지정한 런타임에 따라 소스코드 작성 언어를 선택한다.
람다 함수를 생성하자.
파이썬 언어를 사용한다.
람다 함수의 역할을 정의해보자.
{
"Records": [
{
"eventVersion": "2.0",
"eventSource": "aws:s3",
"awsRegion": "us-east-1",
"eventTime": "1970-01-01T00:00:00.000Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "EXAMPLE"
},
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"responseElements": {
"x-amz-request-id": "EXAMPLE123456789",
"x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "testConfigRule",
"bucket": {
"name": "example-bucket", ⇐ 버킷 이름
"ownerIdentity": {
"principalId": "EXAMPLE"
},
"arn": "arn:aws:s3:::example-bucket"
},
"object": {
"key": "test%2Fkey", ⇐ 파일(객체) 이름
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901"
}
}
}
]
}
import boto3 # 파이썬에서 Amazon Web Services(AWS)를 사용하기 위한 공식 SDK(소프트웨어 개발 키트)
import os
import sys, uuid ## 난수 생성모듈 / Universally Unique Identifier의 약자로, 고유 식별자를 생성하는 데 사용되는 모듈
from urllib.parse import unquote_plus
from PIL import Image
import PIL.Image
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html
s3_client = boto3.client('s3') # S3 객체를 가져온다
def resize_image(image_path, resized_path):
with Image.open(image_path) as image:
image.thumbnail(tuple(n/2 for n in image.size))
image.save(resized_path)
def lambda_handler(event, context):
for record in event['Records']: # 람다 함수 테스트 시 사용하는 템플릿의 요소를 가져온다. 해당 템플릿의 이벤트 JSON 중 Records 를 가져온다
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key'] # test%2Fkey : test/key 값을 URL 인코딩 한 값
tmpkey = key.replace('/', '') # path/file 형식을 pathfile 형식으로 변경한다.
# S3 버킷으로부터 파일을 가져와 저장할 경로와 / 썸네일 이미지를 저장할 경로를 지정한다.
download_path = '/tmp/{}{}'.format(uuid.uuid1(),tmpkey) # /tmp/uuidpathfile
upload_path = '/tmp/resized-{}'.format(tmpkey)
#S3 버킷으로부터 파일을 다운로드
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/download_file.html
s3_client.download_file(bucket, key, download_path)
# 다운로드한 파일의 크기를 반으로 축소한다
resize_image(download_path, upload_path)
# 썸네일 이미지를 S3 버킷에 업로드
# 다운로드버킷이름-resized 이름의 버킷에 원본 파일명과 동일한 파일명으로 업로드
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/upload_file.html
s3_client.upload_file(upload_path, '{}-resized'.format(bucket), key)
소스코드를 람다함수 소스코드에 옮기고, 테스트한다. (테스트 이벤트 구성 - s3-put 템플릿 사용)
람다 함수 실행 시, 소스코드에서 사용하는 PIL 모듈이 존재하지 않아 오류가 발생한다.
⇒ 파이썬 기본 라이브러리와 AWS SDK(여기에서는 boto3)를 제외한 외부 라이브러리는 직접 추가해 줘야 함
=> AWS 에서 PIL 모듈을 지원하지 안 하는걸까? 이걸 어떻게 해결할 수 있을까? 찾아보니 람다 함수 파일에 PIL(파이썬 이미지 라이브러리)를 재구현한 Pillow 모듈을 사용한다. 그러나 이 방식은 Pillow 모듈의 함수 사용을 소스코드에 적용해야 하고, 다른 모듈과의 의존성이 존재할 수 있지 않을까? => 람다 함수 런타임 버전과 사용 모듈 버전 호환 체크로 해결.
따라서 람다 실행환경과 동일한 환경에서 PIL 모듈을 빌드 후 람다 계층(레이어)로 등록한다.
FORBIIDEN 오류가 발생한다. 람다 실행에 있어 권한이 필요하다.
S3 버킷에서 파일을 가져올 때 권한이 없어서 오류가 발생한다.
람다 함수 역할에 S3 버킷을 읽고 쓰는 권한을 추가한다.
S3 FULL Access 를 주어준다
이제 해당 람다 함수에는 권한 정책이 추가되었다.
테스트를 위해 람다 레이어 버킷에 이미지를 하나 등록한다.
S3 버킷에 파일을 추가 후, 버킷 이름과 객체 이름(키)를 변경한다.
그리고 새로운 테스트 이벤트를 생성해 Depoly 및 테스트한다.
이번에는 파일 업로드 실패 오류가 발생했다.
썸네일을 저장할 S3 버킷이 존재하지 않아 발생하는 오류이다.
다시 TEST 를 실행하고 결과를 확인해보자
이상없이 결과가 나왔다.
썸네일을 저장한 S3도 확인해보자
S3 버킷에 대해 발생하는 모든 객체 발생 시(트리거 조건) 해당 람다 함수를 실행하겠다.
아래의 확인문은 트리거로 인해 발생하는 재귀적 호출에 대해 인지하고 있겠다는 의미( 무한반복으로 인해 발생한 내용은 트리거 생성자의 책임)
이제 생성한 트리거를 확인해보자
새로 이미지를 S3 버킷(lambda-layer-bucket-015)에 올려보자
이제 썸네일이 저장되는 버킷(lambda-layer-bucket-015-resized) 에서 확인해보자.
썸네일이 저장되었고, 크기가 조금 줄어든 것을 확인 가능하다.
이렇게 작동하는 것을 확인하기 위해 CloudWatch로 확인해보자.
로그 스트림에 해당 로그를 확인 가능하다.
C:\aws> mkdir hello-lambda
C:\aws> cd hello-lambda
C:\aws\hello-lambda> pip install pymysql --target .
C:\aws\hello-lambda>code .
import pymysql
def lambda_handler(event):
ret = []
db = pymysql.connect(host='연결하고자하는RDS엔드포인트주소', user='root', db='sampledb', password='password', charset='utf8')
curs = db.cursor()
sql = "select * from emp"
curs.execute(sql)
rows = curs.fetchall()
for e in rows:
temp = {'empno':e[0],'name':e[1],'department':e[2],'phone':e[3] }
ret.append(temp)
db.commit()
db.close()
return ret
tar -acf ..\lambda_function.zip .
- 압축 대상 파일 위치 : 현재 경로의 상위 경로에 lambda_function.zip 이름으로 압축 파일 생성
- 압축 대상 : 현재 경로의 모든 것( . )을 압축
tar -acf ..\lambda_function.zip .
소스 코드 - 파일 업로드에서 생성한 zip 파일을 다시 업로드 한다.
해당 함수를 아래와 같이 테스트해보자 (hello world 템플릿 사용)
import pymysql
def lambda_handler(event, context):
ret = []
db = pymysql.connect(host='연결하고자하는RDS엔드포인트주소', user='root', db='sampledb', password='password', charset='utf8')
curs = db.cursor()
sql = "select * from emp"
curs.execute(sql)
rows = curs.fetchall()
for e in rows:
temp = {'empno':e[0],'name':e[1],'department':e[2],'phone':e[3] }
ret.append(temp)
db.commit()
db.close()
return ret
기존에서는 lambda_handler(event)였다. 아래 사진처럼 변경한다. (파이썬에서는 아규먼트 2개 사용)
다시 파일을 압축시켜 올려 다시 확인해보자.
이번에는 람다 함수 실행 대기 시간 오류가 발생한다. 3초가 조금 넘자 람다 함수가 에러를 발생시킨 것이다. 해당 대기 시간을 늘려보자.
람다 함수 - 구성 -일반 구성 - 편집 - 기존 : 3초 -> 1분 -> 테스트 재실행
다시 에러코드가 나왔다. 해당 내용을 확인해보면, 람다 함수가 RDS와의 연결이 되지 않아 발생한 오류이다.
람다 함수는 RDS와 같은 VPC에 속해 있다. 따라서 람다 함수의 VPC를 설정해준다.
람다 함수 - VPC - 편집 - 아래와 같이 추가해준다.
그러나 권한 오류가 발생한다
람다 함수에 네트워크 인터페이스 생성 권한이 없어서 그런 것이다.
새로운 창에서 람다 함수로 들어간다
람다 함수에 해당 권한을 추가해주자.
람다 함수에 구성 - 권한 - 실행 역할 이름 클릭
권한 추가 - 정책 연결에서 AWSLambdaVPCAccessExecutionRole 추가한다.
다시 람다 VPC 편집 창으로 돌아가 저장을 눌러주면 권한이 추가된다.
이제 RDS에서 람다 함수를 연결 추가해주자.
RDS 프록시 기능을 해제 후 설정한다.
왜 테스트를 하면 데이터를 응답으로 가져올까? EC2, RDS 와 연관지어 생각해보자
이전에서는 람다 함수를 내부 (AWS 웹 콘솔)에서만 호출 가능했다.
API 게이트웨이를 생성해 외부 사용자가 람다 함수를 호출, 실행 할 수 있도록 설정해보자.
AWS 웹 콘솔에서 REST-API - REST API 생성
생성된 REST API 에서 리소스 생성을 눌러 리소스를 추가한다.
메서드를 추가한다
REST API 에서는 메서드 유형이 (GET, DELETE, PUT, 등이 존재) GET 으로 설정해 추가해준다.
아래 그림에서 선택한(밑줄) 람다 함수가 아닌, 그 위의 람다 함수를 추가했다.
REST API에 리소스와 메소드도 지정한 것을 확인 가능하다
테스트에 매개변수를 넣지 않고 확인해 보자.
해당 테스트 결과를 자세히 확인해보자
응답 본문 : 람다 함수의 응답
응답 헤더 : 람다 함수의 응답 헤더
로그 : REST API가 테스트를 수행하며 발생한 로그이다. 해당 로그에는 REST API와 람다 로그가 섞여있다.
스테이지가 생성되었다.
API Gateway의 엔드포인트로 접근해보자.
해당 엔드포인트에서 /emp 를 추가하면 이는 리소스를 의미한다.
합쳐진 URL은 브라우저로부터 HTTP GET 방식으로 요청된다.
해당 요청을 받은 API GateWay가 데이터를 가져와 보여준다.
import { useEffect, useState } from 'react';
import './App.css';
import axios from 'axios';
function App() {
// 상태변수를 정의
const [emps, setEmp] = useState([]);
// 화면이 최초로 출력되었을 때(=마운트될 때) 동작을 정의
useEffect(() => {
// axios.get('http://localhost:80/emp01')
// 플라스크 서버가 동작하고 있는 EC2 인스턴스의 퍼블릭 주소로 변경
axios.get('https://elzsq6rd82.execute-api.ap-southeast-1.amazonaws.com/prod/emp')
.then(res => {
console.log(res); // 응답 내용을 콘솔에 출력
setEmp(res.data); // 응답 내용 중 data 항목을 상태변수 emps에 설정
})
.catch(err => console.log(err));
}, []);
return (
<table>
<tr>
<th>사번</th>
<th>이름</th>
<th>부서</th>
<th>전화번호</th>
</tr>
{ // 상태변수의 내용을 화면에 출력
emps.map(emp =>
<tr>
<td>{emp.empno}</td>
<td>{emp.name}</td>
<td>{emp.department}</td>
<td>{emp.phone}</td>
</tr>
)
}
</table>
);
}
export default App;
#리액트 파이썬 코드 있는 곳으로 이동. (C:\aws\hello-react\src 나는 이곳에 있었다.) 해당 위치로 CMD 이동
C:\aws\hello-react>python -m venv venv # 가상 환경 설정
C:\aws\hello-react>cd \venv\Scripts>npm start
C:\aws\hello-react\venv\Scripts>activate # 가상 터미널 실행
(venv) C:\aws\hello-react\venv\Scripts>npm start
CORS 오류가 발생해 데이터를 가져오지 못한다.
API Gateway 에서 CORS 관련 설정을 해서 해결해보자.
API Gateway - API - 리소스 - > /emp 에서 CORS 활성화한다.
이때, 스테이지에서 CORS 활성화가 아니다!!!!
접근 제어 허용 메소드에서 GET 이 발생하면, 오리진이 ( * )으로부터 온 데이터의 CORS를 활성화 시켜준다.
해당 메소드에 대해 다시 배포해준다.
리액트 코드를 이제 로컬이 아닌 S3로 옮겨보자.
리액트 코드 빌드
C:\aws\hello-react> npm run build