해당 실습은 AWS에서 제공하는 Build a Serverless Web Applcation의 내용을 기반으로 작성되었습니다. https://aws.amazon.com/getting-started/hands-on/build-serverless-web-app-lambda-apigateway-s3-dynamodb-cognito/
해당 실습에서는 사용자가 Wild Rydes 에서 유니콘 탑승을 요청할 수 있도록 하는 간단한 서버리스 웹 애플리케이션을 생성합니다. 애플리케이션은 사용자에게 원하는 픽업 위치를 표시하기 위한 HTML 기반 사용자 인터페이스(UI)를 제공하고 RESTful 기반의 백엔드 서비스와 연계해서 최적의 유니콘을 배정합니다. 응용 프로그램은 사용자가 승차를 요청하기 전에 회원 가입 및 로그인할 수 있는 기능을 제공합니다.
Application Architecture
해당 애플리케이션은 AWS Lambda , Amazon API Gateway , Amazon DynamoDB , Amazon Cognito 및 AWS Amplify를 이용해서 구축될 예정입니다. Amplify는 HTML, CSS, JavaScript 및 사용자 브라우저에 로드되는 이미지 파일을 포함한 정적 웹 리소스의 지속적인 배포(Continuous Deployment) 및 호스팅을 제공합니다. 브라우저에서 실행되는 JavaScript는 Lambda 및 API Gateway를 사용하여 구축된 퍼블릭 백엔드 API를 통해서 데이터를 송수신합니다. Amazon Cognito는 사용자 관리 및 백엔드 API와의 인증 기능을 제공합니다. 마지막으로 DynamoDB에 애플리케이션 데이터를 저장합니다.
Modules
이 워크샵은 4개의 모듈로 나뉩니다. 각 모듈은 아키텍처를 구현하고 작업을 확인하는 데 도움이 되는 빌드 및 단계별 지침에 대한 시나리오를 설명합니다.
정적 웹 사이트 호스팅: 내장된 지속적 배포를 통해 웹 애플리케이션에 대한 정적 리소스를 호스팅하도록 AWS Amplify 구성
사용자 관리: Amazon Cognito 사용자 풀을 생성하여 사용자 계정 관리
서버리스 백엔드 구축: 웹 애플리케이션에 대한 요청을 처리하기 위한 Lambda 함수 구축
RESTful API 배포: Amazon API Gateway를 사용하여 이전 모듈에서 구축한 Lambda 함수를 RESTful API로 노출
Module 1: Static Web Hosting with Continuous Deployment
Overview
이 모듈에서는 웹 애플리케이션의 정적 리소스(프론트엔드)를 S3로 호스팅하도록 AWS Amplify를 구성합니다. Amplify는 풀스택 웹 애플리케이션의 지속적인 배포 및 호스팅을 위한 git 기반 워크플로우를 제공합니다. 후속 모듈에서는 JavaScript를 사용하여 이러한 페이지에 동적 기능을 추가하여 AWS Lambda 및 Amazon API Gateway로 구축된 RESTful API를 호출합니다
Architecture Overview
해당 모듈의 아키텍처는 매우 간단합니다. AWS Amplify 콘솔에서 HTML, CSS, JavaScript, 이미지 및 기타 파일을 포함한 모든 정적 웹 콘텐츠를 관리되며 별도의 웹서버를 구축 하지 않고 AWS Amplify 콘솔에서 제공하는 URL을 통해서 사이트에 접근할 수 있습니다.
Implementation
Select a Region
1. 리전을 Asia Pacific (Seoul) - ap-northeast-2로 변경
2. AWS 관리 콘솔에서 Cloud9 서비스로 이동
3. 이미 생성되어 있는 serverless-lab Cloud9 인스턴스를 선택하고 Open IDE 를 클릭해서 인스턴스를 구동
4. IDE가 구동되는데 짧게는 몇 초, 길게는 수분까지 걸릴 수 있으며 구동이 완료되면 다음 화면이 표시됩니다.
Create a Git repository
1. AWS Management Console에서 좌측 상단에 있는 [Services] 를 선택하고 검색창에서 CodeCommit을 검색하거나 [Developer Tools] 카테고리에 있는 [CodeCommit] 를 선택
2. [Create repository] 클릭
3. Repository name에 wildrydes-site 를 입력하고 [Create] 클릭
4. 리포지토리를 복제하는 명령어를 클립보드로 복사 - [Copy] 클릭
5. Cloud9 인스턴스의 터미널로 이동해서 이전 단계에서 복사한 명령어를 붙여넣고 실행하거나 아래의 명령어 실행
lab-user:~/environment $ git clone https://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/wildrydes-site
Cloning into 'wildrydes-site'...
warning: You appear to have cloned an empty repository.
lab-user:~/environment $
lab-user:~/environment/wildrydes-site (master) $ cd ~/environment/wildrydes-site
lab-user:~/environment/wildrydes-site (master) $ git add index.html
lab-user:~/environment/wildrydes-site (master) $ git commit -m "update title"
[master bfceb2f] update title
Committer: EC2 Default User <ec2-user@ip-10-0-82-242.ap-northeast-2.compute.internal>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:
git config --global user.name "Your Name"
git config --global user.email you@example.com
After doing this, you may fix the identity used for this commit with:
git commit --amend --reset-author
1 file changed, 1 insertion(+), 1 deletion(-)
lab-user:~/environment/wildrydes-site (master) $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 345 bytes | 345.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/wildrydes-site
0c18951..bfceb2f master -> master
lab-user:~/environment/wildrydes-site (master) $
4. Amplify 콘솔로 이동해서 변경분이 배포되고 있는지 확인
5. 사이트에 접속해서 타이틀이 변경되었는지 확인
Recap
해당 모듈에서는 Wild Rydes 비즈니스의 기반이 될 정적 웹사이트를 만들었습니다. AWS Amplify Console을 사용하면 지속적 통합 및 제공(CI/CD) 모델에 따라 정적 웹 사이트를 정말 쉽게 배포할 수 있습니다. 더 복잡한 Javascript 프레임워크 기반 응용 프로그램을 "구축"하는 기능이 있으며 브랜치 기반 배포, 사용자 지정 도메인 설정, 즉각 배포 및 암호 보호와 같은 기능이 있습니다.
Module 2: User Management
Overview
이 모듈에서는 사용자 계정을 관리를 위해 Amazon Cognito 사용자 풀을 생성합니다. 고객이 새 사용자로 등록하고, 이메일 주소를 검증하고, 사이트에 로그인할 수 있는 페이지도 배포할 예정입니다.
Architecture Overview
사용자가 웹사이트를 처음으로 방문하면 가정 먼저 새계정을 등록합니다. 해당 워크숍에서는 실습 목적상 이메일 주소와 비밀번호로만 계정 생성을 진행할 예정이지만 실제로 애플리케이션 구축시에는 추가 속성을 요구하도록 Amazon Cognito를 구성할 수 있습니다.
사용자가 계정 생성 요청을 하면 Amazon Cognito에서 사용자가 제공한 이메밀 주소로 인증 코드가 포함된 이메일을 보냅니다. 이메일 주소 검증을 위해 사용자는 사이트로 돌아가 이메일 주소와 인증 코드를 입력합니다. 테스트 용도로 임의의 이메일 주소를 사용하려는 경우 Amazon Cognito 콘솔을 사용하여 이메일 계정을 검증 할수도 있습니다.
사용자가 확인된 계정(이메일 확인 프로세스 사용 또는 콘솔을 통한 수동 확인 사용)을 갖고 나면 로그인할 수 있습니다. 사용자가 로그인할 때 사용자 이름(또는 이메일)과 비밀번호를 입력합니다. 그런 다음 JavaScript 코드에서 Amazon Cognito와 통신하고 SRP(Secure Remote Password) 프로토콜을 사용하여 인증하고 JWT(JSON Web Tokens) 을 발급 받습니다. JWT에는 사용자 ID에 대한 정보가 포함되어 있으며 다음 모듈에서 Amazon API Gateway로 구축한 RESTful API에 대해 인증하는 데 사용됩니다.
Implementation
Create an Amazon Cognito User Pool
1. AWS 관리 콘솔에서 Cognito 서비스로 이동
2. [Manae User Pools] 클릭
3. [Create a User Pool] 클릭
4. Pool name에 WildRydes을 입력하고 [Review defaults] 클릭
5. [Create pool] 클릭
6. Pool Id를 메모장에 복사
Add an App to Your User Pool
1. 왼쪽 패널 General settings 섹션 아래에 있는 App clients 선택
2. 사용할 이메일 주소와 비밀번호를 입력하고 LET'S RYDE 클릭 - 비밀번호는 8글자 이상으로 대문자 1개, 숫자 1개, 특수문자 1개가 필수
3. 이메일로 전송된 인증 코드 확인
4. 이메일 검증 페이지에서 이메일주소와 인증코드를 입력하고 VERIFY 클릭
5. 다음과 같은 팝업메시지가 나오면 이메일 검증이 정상으로 완료
6. 등록한 이메일 주소와 비밀번호를 입력하고 SIGN IN 클릭
7. 로그인이 완료되면 /ride.html 페이지로 연결이 되고 아래와 같이 메시지가 표시됨
Module 3: Serverless Service Backend
Overview
이 모듈에서는 AWS Lambda 및 Amazon DynamoDB를 사용하여 웹 애플리케이션의 비지니스 로직을 처리할 백엔드 프로세스를 구축합니다. 첫 번째 모듈에서 배포한 프론트엔드 웹 모듈을 통해서 사용자는 본인이 원하는 위치로 유니콘을 호출 할 수 있습니다. 이러한 요청을 수행하려면 브라우저에서 실행되는 JavaScript가 클라우드에서 실행되는 백엔드 서비스를 호출해야 합니다.
Architecture Overview
사용자가 유니콘을 요청할 때마다 호출되는 Lambda 함수를 구현합니다. 이 함수는 유니콘 무리중에서 하나의 유니콘을 선택하고 DynamoDB 테이블에 요청을 기록한 다음 발송되는 유니콘에 대한 세부 정보로 프론트엔드 애플리케이션에 응답합니다.
Lambda 함수는 Amazon API Gateway를 외부에 노출이 되고 브라우저에서 해당 API 엔드포인트를 호출합니다. 다음 모듈에서 프론트엔드와 백엔드의 연결을 구현하고 해당 모듈에서는 백엔드 API 구동에 대한 테스트만 진행합니다.
Implementation
Create an Amazon DynamoDB Table
1. AWS 관리 콘솔에서 Cognito 서비스로 이동
2. [Create Table] 클릭
3. Table name에 Rides을 입력하고 Partition key에는 RideId 입력하고 종류는 String으로 선택
3. 생성 옵션에는 Author from scratch를 선택, Function name에는 RequestUnicorn 을 입력, Runtime에는 Node.js 16.x를 선택하고 [Create function] 클릭
4. 코드에디터의 index.js에 있는 코드블록을 아래의 코드블록으로 교체하고 [Deploy] 클릭
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
const randomBytes = require('crypto').randomBytes;
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();
const fleet = [
{
Name: 'Bucephalus',
Color: 'Golden',
Gender: 'Male',
},
{
Name: 'Shadowfax',
Color: 'White',
Gender: 'Male',
},
{
Name: 'Rocinante',
Color: 'Yellow',
Gender: 'Female',
},
];
exports.handler = (event, context, callback) => {
if (!event.requestContext.authorizer) {
errorResponse('Authorization not configured', context.awsRequestId, callback);
return;
}
const rideId = toUrlString(randomBytes(16));
console.log('Received event (', rideId, '): ', event);
// Because we're using a Cognito User Pools authorizer, all of the claims
// included in the authentication token are provided in the request context.
// This includes the username as well as other attributes.
const username = event.requestContext.authorizer.claims['cognito:username'];
// The body field of the event in a proxy integration is a raw string.
// In order to extract meaningful values, we need to first parse this string
// into an object. A more robust implementation might inspect the Content-Type
// header first and use a different parsing strategy based on that value.
const requestBody = JSON.parse(event.body);
const pickupLocation = requestBody.PickupLocation;
const unicorn = findUnicorn(pickupLocation);
recordRide(rideId, username, unicorn).then(() => {
// You can use the callback function to provide a return value from your Node.js
// Lambda functions. The first parameter is used for failed invocations. The
// second parameter specifies the result data of the invocation.
// Because this Lambda function is called by an API Gateway proxy integration
// the result object must use the following structure.
callback(null, {
statusCode: 201,
body: JSON.stringify({
RideId: rideId,
Unicorn: unicorn,
UnicornName: unicorn.Name,
Eta: '30 seconds',
Rider: username,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}).catch((err) => {
console.error(err);
// If there is an error during processing, catch it and return
// from the Lambda function successfully. Specify a 500 HTTP status
// code and provide an error message in the body. This will provide a
// more meaningful error response to the end client.
errorResponse(err.message, context.awsRequestId, callback)
});
};
// This is where you would implement logic to find the optimal unicorn for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a unicorn at random.
function findUnicorn(pickupLocation) {
console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
return fleet[Math.floor(Math.random() * fleet.length)];
}
function recordRide(rideId, username, unicorn) {
return ddb.put({
TableName: 'Rides',
Item: {
RideId: rideId,
User: username,
Unicorn: unicorn,
UnicornName: unicorn.Name,
RequestTime: new Date().toISOString(),
},
}).promise();
}
function toUrlString(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
function errorResponse(errorMessage, awsRequestId, callback) {
callback(null, {
statusCode: 500,
body: JSON.stringify({
Error: errorMessage,
Reference: awsRequestId,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}
5. [Configuration] → [Permission] → Execution role 에서 RequestUnicorn-role-xxxx 를 선택
6. [Permissions] 섹션 오른쪽에 있는 [Add Permissions ] → [Attach policies] 클릭
해당 모듈에서는 Amazon API Gateway를 사용하여 이전 모듈에서 구축한 Lambda 함수를 RESTful API로 노출합니다. 이 API는 퍼블릭 인터넷을 통해서 접근이 가능하지만 이전 모듈에서 생성한 Amazon Cognito 사용자 풀을 통해서 인증 및 인가를 받은 사용자만 접근할수 있습니다. 해당 구성을 통해서 브라우저에서 실행되는 JavaScript로 API를 호출해서 정적으로 호스팅되는 웹 사이트를 동적 웹 응용 프로그램으로 전환할 수 있습니다.
Architecture Overview
위의 다이어그램은 해당 모듈에서 구축할 API Gateway 구성 요소를 이전에 구축한 기존 구성 요소와 연동하는 방법을 보여줍니다. 회색으로 표시된 항목은 이전 단계에서 이미 구현한 부분입니다.
첫 번째 모듈에서 배포한 정적 웹 사이트에는 이미 해당 모듈에서 구성할 API와 연동하기 위해서 구성된 페이지가 있습니다. /ride.html 페이지에는 유니콘 탑승 요청하기 위한 간단한 지도 기반 인터페이스가 있습니다. /signin.html 페이지를 사용하여 인증한 후 사용자는 지도의 한 지점을 클릭한 다음 오른쪽 상단 모서리에 있는 "Request Unicorn" 버튼을 클릭해서 배차를 요청하여 픽업 위치를 선택할 수 있습니다.
해당 모듈은 API의 클라우드 구성 요소를 구축하는데 필요한 단계에 중점을 둘 것이지만 해당 API를 호출하는 브라우저 코드가 작동하는 방식에 관심이 있다면 웹사이트의 ride.js 파일을 통해서 확인할 수 있습니다. 이 경우 애플리케이션은 jQuery의 ajax() 함수를 사용하여 API를 호출합니다.
Implementation
Create a New REST API
1. AWS 관리 콘솔에서 API Gateway 서비스로 이동
2. Choose an API type에서 REST API 카드에 있는 [Build] 를 클릭
3. Protocol에 REST, 생성옵션에 New API를 선택하고 API name에 WildRydes 를 입력하고 Endpoint Type에 Edge optimized를 선택하고 [Create API] 클릭
4. 생성된 API 설정화면 왼쪽 패널에서 [Authorizers] 선택하고 [+ Create New Authorizer] 클릭
5. Name에 WildRydes를 입력, Type에 Cognito를 선택, Cognito User Pool에서 지역은 ap-northeast-2로 그대로 두고 목록에서 WildRydes를 선택, Token Source에 Authorization을 입력하고 [Create] 클릭
6. 웹브라우저에서 배포한 사이트의 /ride.html 경로로 접속 - 로그인 화면이 보일 경우에는 이전 계정생성 단계에서 사용한 이메일과 비밀번호를 입력해서 로그인
7. 브라우저의 개발자 도구를 통해서 Authentication 토큰 값을 복사
8. 위에서 생성한 Authorizer 구성에서 [Test]를 클릭
9. Authorization Token에 웹페이지에서 확인한 토큰 값을 입력하고 [Test] 클릭
10. 응답코드가 200이고 Claims에 로그인한 사용자의 상세내용이 표시되면 정상
Create a new resource and method
1. API 설정화면 왼쪽 패널에서 [Resources] → [Actions] → [Create Resource] 선택
2. Resource Name에 ride를 입력하면 Resource Path에 ride가 자동으로 입력되고 Enable API Gateway CORS를 활성화하고 [Create Resource] 클릭
3. 위에서 생성한 /ride 리소스를 선택 → [Actions] → [Create Method] 선택