[Hands On] 서버리스 웹 애플리케이션 구축 가이드

서버리스란?

서버리스(serverless)란 단어만 보면, 서버(Server) + 리스(Less)의 합성어로 서버가 없다는 뜻처럼 보입니다. 하지만, 서버리스란 클라우드 공급 업체가 인프라를 관리하기 때문에 개발자가 서버를 관리할 필요 없이 애플리케이션을 빌드하고 실행할 수 있도록 하는 클라우드 컴퓨팅 모델입니다.

서버리스 장단점

장점

  • 개발자가 서버에 신경 쓸 필요가 없어 개발 생산성 향상 및 개발 시간 단축
  • 사용한 만큼만 지불(OnDemand)하여 비용 절감이 가능
  • 자동 스케일 업 및 스케일 다운 지원
  • 간단한 패키징 및 배포로 빠른 작업이 가능

단점

  • ColdStart에 대한 이슈
    • ColdStart란? 오랫동안 사용하지 않고 있다가 오랜만에 실행하게 되면 딜레이가 발생할 수 있음
  • 특정 클라우드 공급 업체(CSP)에 종속되어 다른 CSP로의 전환이 어려울 수 있음
  • 코드와 기능이 분산되어 있어 복잡한 테스트 및 디버깅 환경
  • 지속적으로 실행이 필요한 워크로드에 적용하면 기존보다 비용이 증가할 수 있음

서버리스 웹 애플리케이션 실습에서 사용되는 서비스

CloudFront

캐싱을 통해 사용자에게 좀 더 빠른 전송 속도를 제공함을 목적. CloudFront는 전 세계 이곳저곳에 Edge Server(Location)을 두고 Client에 가장 가까운 Edge Server를 찾아 Latency를 최소화시켜 빠른 데이터를 제공

이번 핸즈온에서 사용될 주요 기능

  • 엣지 로케이션에 원본 데이터 캐싱

S3

어디서나 원하는 양의 데이터를 저장하고 검색할 수 있도록 구축된 객체 스토리지. 최고 수준의 내구성, 가용성, 성능, 보안 및 거의 무제한의 확장성을 아주 저렴한 요금으로 제공하는 단순한 스토리지 서비스

이번 핸즈온에서 사용될 주요 기능

  • 웹사이트 호스팅

Lambda

서버를 프로비저닝하거나 관리할 필요없이 코드를 실행하기 해주는 서버리스 컴퓨팅 서비스

이번 핸즈온에서 사용할 주요 기능

  • Code 배포를 통한 웹페이지 구성

API Gateway

클라이언트 애플리케이션과 백엔드 서비스 간의 통신을 효율적으로 관리하고 보안을 유지하기 위한 중요한 도구

이번 핸즈온에서 사용할 주요 기능

  • 서버리스 백엔드 API 구축

DynamoDB

키-값 쌍으로 저장하며, 파티션 키와 정렬 키를 사용하여 데이터를 구성하는 NoSQL

이번 핸즈온에서 사용할 주요 기능

  • 파티션 키로 데이터를 구분하여 저장

실습 최종 구성도

실습 구성 환경은 다음과 같습니다.

  1. S3로 웹페이지를 호스팅하고 Cloudfront와 연결하여 웹페이지 로딩 시간을 단축
  2. 웹페이지에서 사용자가 값을 입력하면 API Gateway를 통해 Lambda를 호출
  3. Lambda 함수 코드를 통해 사용자가 입력한 값을 DynamoDB에 저장

1. S3 웹페이지 호스팅

1-1. S3 생성

1. 버킷 이름 > 리전 선택 > [모든 퍼블릭 액세스 차단] 상자 체크

2. 나머지는 기본 옵션 선택 후 만들기

1-2. 웹호스팅 파일 업로드

업로드 > 파일 추가 > 파일 업로드

웹호스팅 파일 코드

<!DOCTYPE html>
<html>
<head>
    <title>KICO Member</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }

        #submit-form {
            background-color: #fff;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }

        h1 {
            text-align: center;
        }

        label {
            font-weight: bold;
        }

        input[type="text"],
        input[type="number"],
        input[type="date"] {
            width: 90%;
            padding: 10px;
            margin-bottom: 10px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }

        input[type="text"]::placeholder,
        input[type="number"]::placeholder,
        input[type="date"]::placeholder {
            color: #999;
        }

        input[type="submit"] {
            background-color: #007bff;
            color: #fff;
            padding: 10px 15px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            transition: background-color 0.2s;
            width: 100%;
        }

        input[type="submit"]:hover {
            background-color: #0056b3;
        }

        #result {
            margin-top: 20px;
            text-align: center;
            font-weight: bold;
            /* submit 버튼 하단에 나오게 하기 */
            position: absolute;
            bottom: 250px;
        }
    </style>
</head>
<body>
    <form id="submit-form">
        <h1>KICO Member</h1>
        <label for="name">Name:</label>
        <input type="text" id="name" name="name" required><br>

        <label for="age">Age:</label>
        <input type="number" id="age" name="age" required><br>

        <label for="dob">Date of Birth:</label>
        <input type="date" id="dob" name="dob" required><br>

        <input type="submit" value="Submit">
    </form>

    <div id="result"></div>

    <script>
        document.getElementById('submit-form').addEventListener('submit', function(e) {
            e.preventDefault();

            // 입력값을 JSON 형식으로 구성
            var formData = {
                name: document.getElementById('name').value,
                age: parseInt(document.getElementById('age').value),
                dob: document.getElementById('dob').value
            };

            // Lambda 함수 호출을 위한 API Gateway 엔드포인트 URL
            var apiUrl = 'API Gateway URL'; // 여기에 API Gateway URL을 넣으세요

            // Lambda 함수 호출
            fetch(apiUrl, {
                method: 'POST',
                body: JSON.stringify(formData)
            })
            .then(function(response) {
                return response.json();
            })
            .then(function(data) {
                document.getElementById('result').innerHTML = data.result;
            })
            .catch(function(error) {
                console.error('Error:', error);
            });
        });
    </script>
</body>
</html>

2. CloudFront 배포

2-1. CloudFront 설정

명시한 절차가 아닌 경우 default 옵션으로 진행하였습니다.

1.원본도메인(생성한 S3 선택)

2.원본 액세스 제어 설정(권장) – CloudFront에 대한 액세스만 허용되도록 설정

3.제어 설정 생성

4. 뷰어 프로토콜 정책 – Redirect HTTP to HTTPS

http로 접속시에 https로 리다이렉트

5. 보안 보호 비활성화

6. 기본값 루트 객체 – S3의 index.html 파일을 호스팅할 것이므로 index.html 입력

7. 생성

2-2. S3에 정책 구성

CloudFront를 배포를 하면 아래와 같이 [정책 복사]가 나오는데 복사 후 S3 권한 편집

2-3. CloudFront 도메인 접속

CloudFront 도메인으로 접속해 아래 페이지에서 값을 입력해도 오류가 발생한다. 웹페이지에 데이터를 입력하면 연결된 API Gateway URL을 통해 Lambda를 호출하는데 API URL을 넣지 않아 발생하는 오류다.

현재까지의 구성도

3. DynamoDB 생성

Partition Key(파티션 키)

  • 특정 데이터 항목을 식별하는데에 필요한 항목

Sort Key(정렬 키)

  • 파티션 키와 함께 사용되며, 동일한 파티션 키를 가진 항목들 간에만 정렬 키를 통해 정렬

예를 들어, 사용자 정보를 저장하는 테이블에서 파티션 키로 사용자ID를 저장하는 테이블에서 정렬 키로 사용자의 등록 날짜를 선택하면 동일한 사용자 ID를 가진 항목들이 등록 날짜를 기준으로 정렬됩니다.

1.테이블 생성

2.테이블의 기본 키 역할을 할 테이블 이름 과 파티션 키를 사용하여 DynamoDB 테이블을 생성합니다 . 여기서는 값을 입력한 시각을 사용하여 고유한 아이디가 생성되기 때문에 파티션 키는 id로 설정했습니다.

4. Lambda 생성

4-1. 함수 생성

함수를 생성하고 함수 작성 언어를 선택 후 Lambda에서 DynamoDB에 데이터를 저장할 수 있도록 권한 추가

1.새로작성

2.함수이름 설정

3. 코드 런타임 선택

4.AWS 정책 템플릿에서 새 역할 생성 > 역할 이름 설정 > DynamoDB 권한 추가

4-2. 코드 배포

코드 > 코드 작성 > 배포

Lambda 함수 코드

import json
import boto3
import datetime  # datetime 모듈 추가

# DynamoDB 클라이언트 생성
dynamodb = boto3.client('dynamodb')

# DynamoDB 테이블 이름
table_name = '테이블 이름 입력'  # 여기에 테이블 이름을 넣으세요

# AWS Lambda 핸들러
def lambda_handler(event, context):
    # API Gateway에서 전달받은 JSON 데이터 파싱
    if event['body']:
        try:
            request_body = json.loads(event['body'])
        except json.JSONDecodeError as e:
            return {
                'statusCode': 400,
                'body': json.dumps({'error': 'Invalid JSON in request body'})
            }
    else:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': 'Request body is empty'})
        }

    # 현재 시간을 기반으로 고유한 ID 생성
    now = datetime.datetime.now()
    unique_id = now.strftime('%Y%m%d%H%M%S%f')  # YYYYMMDDHHMMSSffffff 형식

    # 입력값에서 '-'와 '.'을 제거
    dob = request_body['dob'].replace('-', '').replace('.', '')

    # DynamoDB에 저장할 데이터
    data = {
        'id': {'S': unique_id},
        'name': {'S': request_body['name']},
        'age': {'N': str(request_body['age'])},
        'dob': {'S': dob}
    }

    try:
        # DynamoDB에 데이터 저장
        response = dynamodb.put_item(
            TableName=table_name,
            Item=data
        )

        # 저장 성공 메시지
        result_message = "성공적으로 저장되었습니다."
        response_code = 200
    except Exception as e:
        # 오류 발생 시 메시지
        result_message = f"Error: {str(e)}"
        response_code = 500

    # Lambda 함수의 응답
    response_data = {
        'result': result_message
    }

    return {
        'statusCode': response_code,
        'headers': {'Access-Control-Allow-Origin': '*'},
        'body': json.dumps(response_data)
    }

5. API Gateway 생성

S3 버킷의 웹 사이트를 Lambda 기능과 통합하려면 API 게이트웨이가 필요합니다.

5-1. REST API 구축

1.프로토콜 선택 – REST

2.새 API 생성 – 새 API

3.설정 – API 이름 설정 및 엔드포인트 유형 [지역] 선택

4.API 생성

5-2. 메서드 생성

POST 메서드를 만듭니다. 통합 유형으로 Lambda 함수를 선택 하고 [Lambda 프록시 통합 사용]을 체크하고 방금 생성한 Lambda 함수를 선택합니다. 이후, Lambda 함수에 대한 권한을 추가하고 생성한 API 게이트웨이가 이전 단계에서 생성된 Lambda 함수에 대한 트리거로 추가되고, POST 메서드를 통해 데이터가 함수에 전달될 수 있습니다.

5-3. API 배포

생성한 API를 실제 사용할 수 있도록 설정하면 API Gateway URL이 생성된다

1.작업 > API 배포

2.배포스테이지 : 새 스테이지

3.스테이지 이름 설정

API 호출 URL 생성

6. 웹사이트를 API Gateway와 연결

웹사이트를 API 게이트웨이와 연결하려면 정적 웹사이트의 index.html 파일에 코드에 API 게이트웨이 URL을  입력 후 S3에 다시 업로드

예:

var apiUrl = 'https://abcdefg.execute-api.ap-northeast-2.amazonaws.com/test';

전체 구성이 제대로 작동하는지 테스트하려면 양식에 데이터를 입력하고 성공메세지가 뜬 것을 확인합니다. 이후, 웹사이트의 필드 값이 DynamoDB 테이블에 업데이트 되었는지 확인합니다.

6. 마무리

웹페이지에서 입력된 데이터가 API gateway를 통해 Lambda를 호출하여 DynamoDB에 저장되는 구조를 살펴보았습니다. 이렇게 구성한 서버리스 아키텍처는 클라우드 서비스를 효율적으로 활용하는 방법을 보여준 것입니다. 데이터 수집 및 저장 과정을 자동화하고 관리하기 위한 다양한 클라우드 서비스가 제공되므로 개발자는 복잡한 서버 인프라를 구축할 필요없이 빠르게 웹 애플리케이션을 만들 수 있습니다.

하지만, 일부 애플리케이션은 서버 기반 아키텍처가 더 적합할 수 있으며, 서버리스에서는 주의해야할 성능 및 비용 등과 같은 고려 사항이 있습니다. 서버리스를 선택하여 서비스할때는 장단점과 워크로드 환경을 신중하게 고려하여 최적의 결정을 내릴 것을 권장드립니다.

감사합니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다