

# 예제 애플리케이션 및 패턴 시작하기
<a name="example-apps"></a>

아래의 리소스를 사용하여 몇 가지 일반적인 Lambda 사용 사례를 구현하는 서버리스 앱을 빠르게 생성하고 배포할 수 있습니다. 각 앱 예제에 대해 AWS Management Console을 사용하여 리소스를 수동으로 생성하고 구성하거나 AWS Serverless Application Model을 통해 IaC를 사용하여 리소스를 배포하는 방법에 대한 지침을 제공합니다. 각 앱에 대한 개별 AWS 리소스를 구성하는 방법에 대해 자세히 알아보려면 콘솔 지침을 따르거나 AWS SAM을 사용하여 프로덕션 환경에서와 마찬가지로 리소스를 빠르게 배포하세요.

## 파일 처리
<a name="examples-apps-file"></a>
+ **[PDF 암호화 애플리케이션](file-processing-app.md)**: Amazon Simple Storage Service 버킷에 PDF 파일을 업로드하면 PDF 파일을 암호화한 후 다른 버킷에 저장하는 서버리스 애플리케이션이 생성됩니다. 이는 업로드 시 민감한 문서를 보호하는 데 유용합니다.
+ **[이미지 분석 애플리케이션](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-example-s3.html)**: Amazon Rekognition을 사용하여 이미지에서 텍스트를 추출하는 서버리스 애플리케이션을 생성합니다. 이 애플리케이션은 문서 처리, 콘텐츠 조정, 자동 이미지 분석에 유용합니다.

## 데이터베이스 통합
<a name="examples-apps-database"></a>
+ **[대기열-데이터베이스 애플리케이션](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-lambda-tutorial.html)**: 사용자 등록을 처리하고 주문 제출을 처리하는 데 유용한 대기열 메시지를 Amazon RDS 데이터베이스에 쓰는 서버리스 애플리케이션을 생성합니다.
+ **[데이터베이스 이벤트 핸들러](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-example-ddb.html)**: 감사 로깅, 데이터 복제, 자동화된 워크플로에 유용한 Amazon DynamoDB 테이블 변경 사항에 응답하는 서버리스 애플리케이션을 생성합니다.

## 예약된 작업
<a name="examples-apps-scheduled"></a>
+ **[데이터베이스 유지 관리 애플리케이션](scheduled-task-app.md)**: 자동화된 데이터베이스 유지 관리 및 데이터 수명 주기 관리에 유용한 cron 일정을 사용하여 Amazon DynamoDB 테이블에서 12개월이 지난 항목을 자동으로 삭제하는 서버리스 애플리케이션을 생성합니다.
+ **[Lambda 함수에 대한 EventBridge 예약된 규칙 생성](https://docs.aws.amazon.com/eventbridge/latest/userguide/run-lambda-schedule.html)**: EventBridge의 규칙에 예약된 표현식을 사용하여 지정된 일정에 따라 Lambda 함수를 트리거합니다. 이 형식은 cron 구문을 사용하며 1분 단위로 설정할 수 있습니다.

## 장기 실행 워크플로
<a name="examples-apps-workflows"></a>
+ **[주문 처리 애플리케이션](order-processing-app.md)**: 결제 처리, 재고 확인, 배송 조정 등 복잡한 주문 이행을 처리하는 지속성 함수를 사용하여 서버리스 애플리케이션을 생성합니다. 이 예제에서는 상태를 유지하면서 장기간 실행할 수 있는 워크플로를 빌드하는 방법을 보여줍니다.

## 추가 리소스
<a name="examples-apps-additional-resources"></a>

다음 리소스를 사용하여 Lambda 및 서버리스 애플리케이션 개발을 자세히 살펴보세요.
+ **[Serverless Land](https://serverlessland.com/)**: 서버리스 앱을 빌드할 수 있는 바로 사용 가능한 패턴 라이브러리입니다. 이를 통해 개발자는 Lambda, API Gateway, EventBridge 같은 AWS 서비스를 사용하여 애플리케이션을 더 빠르게 생성할 수 있습니다. 해당 사이트에서는 사전 구축된 솔루션과 모범 사례를 제공하므로 서버리스 시스템을 더 쉽게 개발할 수 있습니다.
+ **[Lambda 샘플 애플리케이션](https://docs.aws.amazon.com/lambda/latest/dg/lambda-samples.html)**: 이 가이드의 GitHub 리포지토리에서 사용할 수 있는 애플리케이션입니다. 이러한 샘플에서는 다양한 언어 및 AWS 서비스를 사용하는 경우를 보여줍니다. 각 샘플 애플리케이션에는 간편한 배포 및 정리를 위한 스크립트 및 지원 리소스가 포함되어 있습니다.
+ **[AWS SDK를 사용하는 Lambda 코드 예제](https://docs.aws.amazon.com/lambda/latest/dg/service_code_examples.html)**: Lambda를 AWS 소프트웨어 개발 키트(SDK)와 함께 사용하는 방법을 보여주는 예제입니다. 이러한 예제에는 기본 사항, 조치, 시나리오, AWS 커뮤니티 기여가 포함됩니다. 예제에서는 필수 작업, 개별 서비스 함수, 여러 함수 또는 AWS 서비스를 사용하는 특정 태스크를 다룹니다.

# 서버리스 파일 처리 앱 만들기
<a name="file-processing-app"></a>

Lambda의 가장 일반적인 사용 사례 중 하나는 파일 처리 작업을 수행하는 것입니다. 예를 들어, Lambda 함수를 사용하여 HTML 파일 또는 이미지로부터 PDF 파일을 자동으로 생성하거나, 사용자가 이미지를 업로드할 때 썸네일을 생성할 수 있습니다.

이 예에서는 Amazon Simple Storage Service (Amazon S3) 버킷에 PDF 파일이 업로드되면 자동으로 암호화하는 앱을 생성합니다. 이 앱을 구현하려면 다음 리소스를 생성합니다.
+ 사용자가 PDF 파일을 업로드할 S3 버킷
+ 업로드된 파일을 읽고 암호화된 암호 보호 버전을 생성하는 Python의 Lambda 함수
+ Lambda가 암호화된 파일을 저장할 두 번째 S3 버킷

또한 AWS Identity and Access Management(IAM) 정책을 생성하여 Lambda 함수에 S3 버킷에서 읽기 및 쓰기 작업을 수행할 권한을 부여합니다.

![\[\]](http://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images/ExampleApps/file_process_resources.png)


**작은 정보**  
Lambda를 처음 사용할 경우 이 예제 앱을 생성하기 전에 [첫 번째 Lambda 함수 생성](getting-started.md) 튜토리얼을 시작하는 것이 좋습니다.

AWS Management Console 또는 AWS Command Line Interface(AWS CLI)(을)를 사용하여 리소스를 생성하고 구성하여 앱을 수동으로 배포할 수 있습니다. AWS Serverless Application Model(AWS SAM)(을)를 사용하여 앱을 배포할 수도 있습니다. AWS SAM(은)는 코드형 인프라(IaC) 도구입니다. IaC를 사용하면 리소스를 수동으로 생성하지 않고 코드로 정의한 다음 자동으로 배포할 수 있습니다.

이 예제 앱을 배포하기 전에 IaC와 함께 Lambda를 사용하는 방법에 대해 자세히 알아보려면 [코드형 인프라(IaC)와 함께 Lambda 사용](foundation-iac.md) 섹션을 참조하세요.

## Lambda 함수 소스 코드 파일 생성
<a name="file-processing-app-download"></a>

프로젝트 디렉터리에 다음 파일을 생성합니다.
+ `lambda_function.py` - 파일 암호화를 수행하는 Lambda 함수의 Python 함수 코드
+ `requirements.txt` - Python 함수 코드에 필요한 종속성을 정의하는 매니페스트 파일

다음 섹션을 확장하여 코드를 확인하고 각 파일의 역할에 대해 자세히 알아보세요. 로컬 컴퓨터에서 파일을 생성하려면 아래 코드를 복사하여 붙여넣거나, [ aws-lambda-developer-guide GitHub repo](https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/file-processing-python)에서 파일을 다운로드합니다.

### Python 함수 코드
<a name="file-processing-app-function-code"></a>

다음 코드를 복사하여 새로운 `lambda_function.py` 파일에 붙여 넣습니다.

```
from pypdf import PdfReader, PdfWriter
import uuid
import os
from urllib.parse import unquote_plus
import boto3

# Create the S3 client to download and upload objects from S3
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    # Iterate over the S3 event object and get the key for all uploaded files
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = unquote_plus(record['s3']['object']['key']) # Decode the S3 object key to remove any URL-encoded characters
        download_path = f'/tmp/{uuid.uuid4()}.pdf' # Create a path in the Lambda tmp directory to save the file to 
        upload_path = f'/tmp/converted-{uuid.uuid4()}.pdf' # Create another path to save the encrypted file to
        
        # If the file is a PDF, encrypt it and upload it to the destination S3 bucket
        if key.lower().endswith('.pdf'):
            s3_client.download_file(bucket, key, download_path)
            encrypt_pdf(download_path, upload_path)
            encrypted_key = add_encrypted_suffix(key)
            s3_client.upload_file(upload_path, f'{bucket}-encrypted', encrypted_key)

# Define the function to encrypt the PDF file with a password
def encrypt_pdf(file_path, encrypted_file_path):
    reader = PdfReader(file_path)
    writer = PdfWriter()
    
    for page in reader.pages:
        writer.add_page(page)

    # Add a password to the new PDF
    writer.encrypt("my-secret-password")

    # Save the new PDF to a file
    with open(encrypted_file_path, "wb") as file:
        writer.write(file)

# Define a function to add a suffix to the original filename after encryption
def add_encrypted_suffix(original_key):
    filename, extension = original_key.rsplit('.', 1)
    return f'{filename}_encrypted.{extension}'
```

**참고**  
이 예제 코드에서는 암호화된 파일(`my-secret-password`)의 비밀번호가 함수 코드에 하드코딩되어 있습니다. 프로덕션 애플리케이션에서는 함수 코드에 암호와 같은 민감한 정보를 저장하지 마십시오. 대신 [AWS Secrets Manager 보안 암호를 생성](https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html)한 다음 [AWS Parameters and Secrets Lambda 확장](with-secrets-manager.md)을 사용하여 Lambda 함수에서 자격 증명을 검색합니다.

Python 함수 코드에는 다음의 3가지 함수가 포함됩니다. 즉, 함수가 간접 호출될 때 Lambda가 실행하는 [핸들러 함수](python-handler.md) 및 PDF 암호화를 수행하기 위해 핸들러가 간접 호출하는 `add_encrypted_suffix` 및 `encrypt_pdf`라는 이름의 2가지 개별 함수가 포함됩니다.

함수가 Amazon S3에 의해 간접 호출되면 Lambda는 간접 호출을 일으킨 이벤트에 대한 세부 정보가 포함된 JSON *형식의* 이벤트 인수를 함수로 전달합니다. 이 경우 정보에는 S3 버킷의 이름과 업로드된 파일의 객체 키가 포함됩니다. Amazon S3의 이벤트 객체 형식에 대한 자세한 내용은 [Lambda를 사용하여 Amazon S3 이벤트 알림 처리](with-s3.md) 섹션을 참조하세요.

그러면 함수는 AWS SDK for Python (Boto3)(을)를 사용하여 이벤트 객체에 지정된 PDF 파일을 로컬 임시 스토리지 디렉터리로 다운로드한 후 [https://pypi.org/project/pypdf/](https://pypi.org/project/pypdf/) 라이브러리를 사용하여 암호화합니다.

마지막으로 함수는 Boto3 SDK를 사용하여 암호화된 파일을 S3 대상 버킷에 저장합니다.

### `requirements.txt` 매니페스트 파일
<a name="file-processing-app-dependencies"></a>

다음 코드를 복사하여 새로운 `requirements.txt` 파일에 붙여 넣습니다.

```
boto3
pypdf
```

이 예제의 함수 코드에는 표준 Python 라이브러리의 일부가 아닌 두 개의 종속성, 즉 Python용 SDK(Boto3)와 함수가 PDF 암호화를 수행하는 데 사용하는 `pypdf` 패키지 두 개만 포함합니다.

**참고**  
Python용 SDK(Boto3) 버전이 Lambda 런타임의 일부로 포함되어 있으므로 함수의 배포 패키지에 Boto3를 추가하지 않고도 코드를 실행할 수 있습니다. 그러나 함수의 종속 항목을 완전히 제어하고 버전 불일치와 관련된 문제를 방지하기 위해 Python의 모범 사례는 함수의 배포 패키지에 모든 함수 종속성을 포함합니다. 자세한 내용은 [Python의 런타임 종속 항목](python-package.md#python-package-dependencies) 섹션을 참조하세요.

## 앱 배포
<a name="file-processing-app-deploy"></a>

수동으로 또는 AWS SAM(을)를 사용하여 이 예제 앱의 리소스를 만들고 배포할 수 있습니다. 프로덕션 환경에서는 수동 프로세스를 사용하지 않고 전체 서버리스 애플리케이션을 빠르고 반복적으로 배포하는 AWS SAM 등의 IaC 도구를 사용할 것을 권장합니다.

### 리소스 수동 배포
<a name="file-processing-app-deploy-manual"></a>

앱을 수동으로 배포하려면:
+ 소스 및 대상 Amazon S3 버킷 만들기
+ PDF 파일을 암호화하고 암호화된 버전을 S3 버킷에 저장하는 Lambda 함수를 생성합니다.
+ 객체가 원본 버킷에 업로드될 때 함수를 간접적으로 간접 호출하는 Lambda 트리거를 구성합니다.

시작하기 전에 빌드 머신에 [Python](https://www.python.org/downloads/)이 설치되어 있는지 확인합니다.

#### 2개의 S3 버킷 생성
<a name="file-processing-app-deploy-manual-create-buckets"></a>

먼저 2개의 S3 버킷을 생성합니다. 첫 번째 버킷은 PDF 파일을 업로드할 원본 버킷입니다. 두 번째 버킷은 Lambda에서 함수를 간접적으로 간접 호출할 때 암호화된 파일을 저장하는 데 사용됩니다.

------
#### [ Console ]

**S3 버킷 생성(콘솔)**

1. Amazon S3 콘솔의 [범용 버킷](https://console.aws.amazon.com/s3/buckets) 페이지를 엽니다.

1. 지리적 위치와 가장 가까운 AWS 리전을 선택합니다. 화면 상단의 드롭다운 목록을 사용하여 리전을 변경할 수 있습니다.  
![\[\]](http://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images/console_region_select.png)

1. **버킷 생성**을 선택합니다.

1. [**일반 구성(General configuration)**]에서 다음을 수행합니다.

   1. **버킷 유형**에서 **범용**을 선택했는지 확인합니다.

   1. **버킷 이름**에 Amazon S3 [버킷 이름 지정 규칙](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)을 충족하는 전역적으로 고유한 이름을 입력합니다. 버킷 이름은 소문자, 숫자, 점(.) 및 하이픈(-)만 포함할 수 있습니다.

1. 다른 모든 옵션을 기본값으로 두고 **버킷 생성**을 선택합니다.

1. 1\$14단계를 반복하여 대상 버킷을 생성합니다. **버킷 이름**에 `amzn-s3-demo-bucket-encrypted`를 입력합니다. 여기서 `amzn-s3-demo-bucket`은 방금 생성한 원본 버킷의 이름입니다.

------
#### [ AWS CLI ]

시작하기 전에 빌드 머신에 [AWS CLI이 설치](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)되어 있는지 확인합니다.

**Amazon S3 버킷을 생성하려면(AWS CLI)**

1. 다음 CLI 명령을 실행하여 원본 버킷을 생성합니다. 버킷에 대해 선택하는 이름은 전역적으로 고유해야 하며 Amazon S3 [버킷 이름 지정 규칙](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)을 따라야 합니다. 이름에는 소문자, 숫자, 점(.), 하이픈(-)만 사용할 수 있습니다. `region` 및 `LocationConstraint`의 경우 지리적 위치와 가장 가까운 [AWS 리전](https://docs.aws.amazon.com/general/latest/gr/lambda-service.html)을 선택하세요.

   ```
   aws s3api create-bucket --bucket amzn-s3-demo-bucket --region us-east-2 \
   --create-bucket-configuration LocationConstraint=us-east-2
   ```

   자습서의 뒷부분에서 원본 버킷과 동일한 AWS 리전에 Lambda 함수를 생성해야 하므로 선택한 리전을 기록해 둡니다.

1. 다음 명령을 실행하여 대상 버킷을 생성합니다. 버킷 이름에는 `amzn-s3-demo-bucket-encrypted`를 사용해야 합니다. 여기서 `amzn-s3-demo-bucket`은 1단계에서 생성한 원본 버킷의 이름입니다. `region` 및 `LocationConstraint`의 경우 원본 버킷을 만들 때 사용한 것과 동일한 AWS 리전을 선택합니다.

   ```
   aws s3api create-bucket --bucket amzn-s3-demo-bucket-encrypted --region us-east-2 \
   --create-bucket-configuration LocationConstraint=us-east-2
   ```

------

#### 실행 역할 만들기
<a name="file-processing-app-deploy-manual-create-execution-role"></a>

실행 역할은 AWS 서비스 및 리소스에 액세스할 수 있는 권한을 Lambda 함수에 부여하는 IAM 역할입니다. 함수에 Amazon S3에 대한 읽기 및 쓰기 액세스 권한을 부여하려면 [AWS관리형 정책을](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies)`AmazonS3FullAccess` 연결합니다.

------
#### [ Console ]

**실행 역할 생성 및 `AmazonS3FullAccess` 관리형 정책 연결(콘솔)**

1. IAM 콘솔에서 [역할(Roles)](https://console.aws.amazon.com/iam/home/roles) 페이지를 엽니다.

1. **역할 생성**을 선택합니다.

1. **신뢰할 수 있는 엔터티 유형**으로 **AWS 서비스**를 선택하고 **사용 사례**로 **Lambda**를 선택합니다.

1. **다음**을 선택합니다.

1. 다음을 수행하여 `AmazonS3FullAccess` 관리형 정책을 추가합니다.

   1. **권한 정책** 검색 상자에 **AmazonS3FullAccess**를 입력합니다.

   1. 정책 옆의 확인란을 선택합니다.

   1. **다음**을 선택합니다.

1. **역할 세부 정보**의 **역할 이름**에 **LambdaS3Role**을 입력합니다.

1. **역할 생성**을 선택합니다.

------
#### [ AWS CLI ]

**실행 역할 생성 및 `AmazonS3FullAccess` 관리형 정책 연결(AWS CLI)**

1. 다음 JSON을 `trust-policy.json`이라는 파일로 저장합니다. 이 신뢰 정책을 통해 Lambda는 서비스 보안 주체에 `lambda.amazonaws.com` 권한을 제공하여 AWS Security Token Service(AWS STS) `AssumeRole` 작업을 호출하기 위해 역할의 권한을 사용할 수 있습니다.  
****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "Service": "lambda.amazonaws.com"
         },
         "Action": "sts:AssumeRole"
       }
     ]
   }
   ```

1. JSON 신뢰 정책 문서를 저장한 디렉터리에서 다음 CLI 명령을 실행하여 실행 역할을 생성합니다.

   ```
   aws iam create-role --role-name LambdaS3Role --assume-role-policy-document file://trust-policy.json
   ```

1. `AmazonS3FullAccess` 관리형 정책을 연결하려면 다음 CLI 명령을 실행합니다.

   ```
   aws iam attach-role-policy --role-name LambdaS3Role --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
   ```

------

#### 함수 배포 패키지 생성
<a name="file-processing-app-deploy-manual-create-function-package"></a>

함수를 생성하려면 함수 코드와 해당 종속 항목을 포함하는 *배포 패키지*를 생성합니다. 이 애플리케이션의 경우, 함수 코드는 PDF 암호화를 위해 별도의 라이브러리를 사용합니다.

**배포 패키지를 만드는 방법**

1. 이전에 GitHub에서 만들거나 다운로드한 `lambda_function.py` 및 `requirements.txt` 파일이 들어 있는 프로젝트 디렉터리로 이동하여 `package`의 이름으로 새 디렉터리를 생성합니다.

1. 다음 명령어를 실행하여 `requirements.txt` 파일에 지정된 종속성을 `package` 디렉터리의 디렉터리에 설치합니다.

   ```
   pip install -r requirements.txt --target ./package/
   ```

1. 애플리케이션 코드와 해당 종속 항목을 포함한 .zip 파일 만들기 Linux 또는 MacOS에서 명령줄 인터페이스에서 다음 명령을 실행합니다.

   ```
   cd package
   zip -r ../lambda_function.zip .
   cd ..
   zip lambda_function.zip lambda_function.py
   ```

    Windows에서는 선호하는 zip 도구를 사용하여 `lambda_function.zip` 파일을 생성합니다. `lambda_function.py` 파일과 종속 항목이 포함된 폴더가 모두 .zip 파일의 루트에 있는지 확인합니다.

Python 가상 환경을 사용하여 배포 패키지를 생성할 수도 있습니다. [Python Lambda 함수에 대한 .zip 파일 아카이브 작업](python-package.md) 섹션을 참조하세요

#### Lambda 함수 생성
<a name="file-processing-app-deploy-manual-createfunction"></a>

이제 이전 단계에서 생성한 배포 패키지를 사용하여 Lambda 함수를 배포합니다.

------
#### [ Console ]

**Lambda 함수 생성(콘솔)**

콘솔을 사용하여 Lambda 함수를 생성하려면 먼저 일부 ‘Hello world’ 코드가 포함된 기본 함수를 생성합니다. 그런 다음 이전 단계에서 만든 .zip 파일을 업로드하여 이 코드를 고유한 함수 코드로 바꿉니다.

대용량 PDF 파일을 암호화할 때 함수의 타임아웃이 발생하지 않도록 하기 위해 함수의 메모리 및 타임아웃 설정을 구성합니다. 함수의 로그 형식도 JSON으로 설정합니다. 제공된 테스트 스크립트를 사용할 때는 CloudWatch Logs에서 함수의 호출 상태를 읽어 호출 성공 여부를 확인할 수 있도록 JSON 형식의 로그를 구성해야 합니다.

1. Lambda 콘솔의 [함수 페이지](https://console.aws.amazon.com/lambda/home#/functions)를 엽니다.

1. S3 버킷을 생성한 동일한 AWS 리전에서 작업 중인지 확인합니다. 화면 상단의 드롭다운 목록을 사용하여 리전을 변경할 수 있습니다.  
![\[\]](http://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images/console_region_select.png)

1. **함수 생성**을 선택합니다.

1. **새로 작성**을 선택합니다.

1. **기본 정보**에서 다음과 같이 합니다.

   1. [**함수 이름(Function name)**]에 `EncryptPDF`을 입력합니다.

   1. **런타임**에서 **Python 3.12**를 선택합니다.

   1. **아키텍처**에서는 **x86\$164**를 선택합니다.

1. 다음을 수행하여 이전 단계에서 생성한 실행 역할을 연결합니다.

   1. **기본 실행 역할 변경(Change default execution role)** 섹션을 펼칩니다.

   1. **기존 역할 사용**을 선택합니다.

   1. **기존 역할**에서 역할(`LambdaS3Role`)을 선택합니다.

1. **함수 생성**을 선택합니다.

**함수 코드를 업로드하려면(콘솔)**

1. **코드 소스** 창에서 **에서 업로드**를 선택합니다.

1. **.zip 파일**을 선택합니다.

1. **업로드**를 선택합니다.

1. 파일 선택기에서 .zip 파일을 선택하고 **열기**를 선택합니다.

1. **저장**을 선택합니다.

**함수 메모리 및 타임아웃 구성(콘솔)**

1. 함수의 **구성** 탭을 선택합니다.

1. **일반 구성** 창에서 **편집**을 선택합니다.

1. **메모리**를 256MB로 설정하고 **타임아웃**을 15초로 설정합니다.

1. **저장**을 선택합니다.

**로그 형식 구성하기(콘솔)**

1. 함수의 **구성** 탭을 선택합니다.

1. **모니터링 및 운영 도구**를 선택합니다.

1. **로깅 구성** 창에서 **편집**을 선택합니다.

1. **로깅 구성**의 경우 **JSON**을 선택합니다.

1. **저장**을 선택합니다.

------
#### [ AWS CLI ]

**함수를 생성하려면(AWS CLI)**
+ `lambda_function.zip` 파일이 들어 있는 디렉터리에서 다음 명령을 실행합니다. `region` 파라미터의 경우 S3 버킷을 생성한 리전으로 `us-east-2`를 대체하세요.

  ```
  aws lambda create-function --function-name EncryptPDF \
  --zip-file fileb://lambda_function.zip --handler lambda_function.lambda_handler \
  --runtime python3.12 --timeout 15 --memory-size 256 \
  --role arn:aws:iam::123456789012:role/LambdaS3Role --region us-east-2 \
  --logging-config LogFormat=JSON
  ```

------

#### 함수를 간접적으로 간접 호출하도록 Amazon S3 구성
<a name="file-processing-app-deploy-manual-configure-s3-trigger"></a>

원본 버킷에 이미지를 업로드할 때 Lambda 함수를 실행하려면 함수에 대한 트리거를 구성해야 합니다. 콘솔 또는 AWS CLI를 사용하여 Amazon S3 트리거를 구성할 수 있습니다.

**중요**  
이 절차는 버킷에 객체가 생성될 때마다 함수를 간접 호출하도록 S3 버킷을 구성합니다. 원본 버킷에서만 이를 구성해야 합니다. Lambda 함수가 함수를 간접적으로 간접 호출하는 동일한 버킷에 객체를 생성하는 경우 함수를 [루프에서 연속적으로 간접 호출](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)할 수 있습니다. 이로 인해 예상치 못한 요금이 AWS 계정에 청구될 수 있습니다.

------
#### [ Console ]

**Amazon S3 트리거를 구성하려면(콘솔)**

1. Lambda 콘솔의 [함수 페이지](https://console.aws.amazon.com/lambda/home#/functions)를 열고 함수(`EncryptPDF`)를 선택합니다.

1. **트리거 추가**를 선택합니다.

1. **S3**를 선택합니다.

1. **버킷**에서 원본 버킷을 선택합니다.

1. **이벤트 유형**에서 **모든 객체 생성 이벤트**를 선택합니다.

1. **재귀 호출**에서 확인란을 선택하여 입력 및 출력에 동일한 S3 버킷 사용이 권장되지 않음을 확인합니다. Serverless Land의 [Recursive patterns that cause run-away Lambda functions](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)를 읽고 Lambda의 재귀 호출 패턴에 대해 자세히 알아볼 수 있습니다.

1. **추가**를 선택합니다.

   Lambda 콘솔을 사용하여 트리거를 생성하면 Lambda는 선택한 서비스에 함수 간접 호출 권한을 부여하는 [리소스 기반 정책](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html)을 자동으로 생성합니다.

------
#### [ AWS CLI ]

**Amazon S3 트리거를 구성하려면(AWS CLI)**

1. 파일을 추가할 때 Amazon S3 소스 버킷이 함수를 간접적으로 호출할 수 있도록 허용하는 [리소스 기반 정책](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html)을 함수에 추가합니다. 리소스 기반 정책 설명은 함수를 간접적으로 간접 호출할 수 있는 다른 AWS 서비스 권한을 부여합니다. Amazon S3에 함수 간접 호출 권한을 부여하려면 다음 CLI 명령을 실행합니다. `source-account` 파라미터를 자신의 AWS 계정 ID로 바꾸고 고유한 원본 버킷 이름을 사용해야 합니다.

   ```
   aws lambda add-permission --function-name EncryptPDF \
   --principal s3.amazonaws.com --statement-id s3invoke --action "lambda:InvokeFunction" \
   --source-arn arn:aws:s3:::amzn-s3-demo-bucket \
   --source-account 123456789012
   ```

   이 명령으로 정의하는 정책은 원본 버킷에서 작업이 발생할 때만 Amazon S3가 함수를 간접적으로 간접 호출하도록 허용합니다.
**참고**  
S3 버킷 이름은 전역적으로 고유하지만 리소스 기반 정책을 사용할 때는 버킷이 반드시 계정에 속하도록 지정하는 것이 가장 좋습니다. 버킷을 삭제하면 다른 AWS 계정가 동일한 Amazon 리소스 이름(ARN)으로 버킷을 생성할 수 있기 때문입니다.

1. 다음 JSON을 `notification.json`이라는 파일로 저장합니다. 이 JSON을 원본 버킷에 적용하면 JSON이 새 객체가 추가될 때마다 Lambda 함수에 알림을 보내도록 버킷을 구성합니다. Lambda 함수 ARN의 AWS 계정 번호와 AWS 리전을 자신의 계정 번호와 리전으로 바꿉니다.

   ```
   {
   "LambdaFunctionConfigurations": [
       {
         "Id": "EncryptPDFEventConfiguration",
         "LambdaFunctionArn": "arn:aws:lambda:us-east-2:123456789012:function:EncryptPDF",
         "Events": [ "s3:ObjectCreated:Put" ]
       }
     ]
   }
   ```

1. 다음 CLI 명령을 실행하여 원본 버킷에 생성한 JSON 파일의 알림 설정을 적용합니다. `amzn-s3-demo-bucket`을 원본 버킷의 이름으로 바꿉니다.

   ```
   aws s3api put-bucket-notification-configuration --bucket amzn-s3-demo-bucket \
   --notification-configuration file://notification.json
   ```

   `put-bucket-notification-configuration` 명령 및 `notification-configuration` 옵션에 대한 자세한 내용은 *AWS CLI 명령 참조*의 [put-bucket-notification-configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-notification-configuration.html)을 참조하세요.

------

### AWS SAM(을)를 사용하여 리소스 만들기
<a name="file-processing-app-deploy-sam"></a>

시작하기 전에 빌드 머신에 [Docker](https://docs.docker.com/get-docker/)와 [최신 버전의 AWS SAMCLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)가 설치되어 있는지 확인합니다.

1. 프로젝트 디렉터리에서 다음 코드를 `template.yaml`이라는 새 파일에 붙여넣습니다. 자리 표시자 버킷 이름을 다음과 같이 바꿉니다.
   + 소스 버킷의 경우 `amzn-s3-demo-bucket`을 [S3 버킷 이름 지정 규칙](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)을 준수하는 이름으로 바꿉니다.
   + 대상 버킷의 경우 `amzn-s3-demo-bucket-encrypted`(을)를 `<source-bucket-name>-encrypted`(으)로 바꾸십시오. 여기서 `<source-bucket>`(은)는 원본 버킷으로 선택한 이름입니다.

   ```
   AWSTemplateFormatVersion: '2010-09-09'
   Transform: AWS::Serverless-2016-10-31
   
   Resources:
     EncryptPDFFunction:
       Type: AWS::Serverless::Function
       Properties:
         FunctionName: EncryptPDF
         Architectures: [x86_64]
         CodeUri: ./
         Handler: lambda_function.lambda_handler
         Runtime: python3.12
         Timeout: 15
         MemorySize: 256
         LoggingConfig:
           LogFormat: JSON
         Policies:
           - AmazonS3FullAccess
         Events:
           S3Event:
             Type: S3
             Properties:
               Bucket: !Ref PDFSourceBucket
               Events: s3:ObjectCreated:*
   
     PDFSourceBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: amzn-s3-demo-bucket
   
     EncryptedPDFBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: amzn-s3-demo-bucket-encrypted
   ```

   AWS SAM 템플릿은 앱용으로 만드는 리소스를 정의합니다. 이 예제에서 템플릿은 `AWS::Serverless::Function` 유형을 사용하여 Lambda 함수를 정의하고 `AWS::S3::Bucket` 유형을 사용하여 S3 버킷 두 개를 정의합니다. 템플릿에 지정된 버킷 이름은 플레이스홀더입니다. AWS SAM을 사용하여 앱을 배포하기 전에, 템플릿을 편집하여 [S3 버킷 이름 지정 규칙](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)을 충족하는 전역적으로 고유한 이름을 가진 버킷의 이름으로 변경해야 합니다. 이 단계에 대해서는 [AWS SAM(을)를 사용하여 리소스 만들기](#file-processing-app-deploy-sam)에서 자세히 설명합니다.

   Lambda 함수 리소스의 정의는 `S3Event` 이벤트 속성을 사용하여 함수에 대한 트리거를 구성합니다. 이 트리거를 사용하면 소스 버킷에서 객체가 생성될 때마다 함수가 간접 호출됩니다.

   또한 함수 정의는 함수의 [실행](lambda-intro-execution-role.md) 역할에 연결할 AWS Identity and Access Management(IAM) 정책을 지정합니다. [AWS관리형 정책](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies)`AmazonS3FullAccess`은 Amazon S3에 객체를 읽고 쓰는 데 필요한 권한을 함수에 제공합니다.

1. `template.yaml`, `lambda_function.py`, `requirements.txt` 파일을 저장한 디렉터리에서 다음 명령을 실행합니다.

   ```
   sam build --use-container
   ```

   이 명령은 응용 프로그램의 빌드 아티팩트를 수집하여 배포에 적합한 형식과 위치에 배치합니다. `--use-container` 옵션을 지정하면 Lambda와 유사한 Docker 컨테이너 내에 함수를 빌드합니다. 여기서는 이 옵션을 사용하므로, 빌드가 작동하기 위해 로컬 컴퓨터에 Python 3.12가 설치되어 있지 않아도 됩니다.

   빌드 프로세스 중, AWS SAM(은)는 템플릿의 `CodeUri` 속성으로 지정한 위치에서 Lambda 함수 코드를 찾습니다. 이 경우에는 현재 디렉토리를 위치(`./`)로 지정했습니다.

   `requirements.txt` 파일이 있는 경우, AWS SAM(은)는 해당 파일을 사용하여 지정된 종속성을 수집합니다. AWS SAM(은)는 기본적으로 함수 코드 및 종속성이 포함된 .zip 배포 패키지 패키지를 생성합니다. [PackageType](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-packagetype) 속성을 사용하여 함수를 컨테이너 이미지로 배포하도록 선택할 수도 있습니다.

1. 애플리케이션을 배포하고 AWS SAM 템플릿에 지정된 Lambda 및 Amazon S3 리소스를 생성하려면 다음 명령을 실행합니다.

   ```
   sam deploy --guided
   ```

   `--guided` 플래그를 사용하면 배포 AWS SAM 프로세스를 안내하는 메시지가 표시됩니다. 이 배포의 경우 Enter를 눌러 기본 옵션을 수락합니다.

배포 프로세스 중에 AWS SAM은 다음 리소스를 AWS 계정 위치에 생성합니다.
+ CloudFormation [스택](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html#cfn-concepts-stacks)의 이름은 `sam-app`입니다.
+ `EncryptPDF` 이름을 가진 Lambda 함수입니다.
+ `template.yaml` AWS SAM 템플릿 파일을 편집할 때 선택한 이름을 가진 S3 버킷 2개
+ `sam-app-EncryptPDFFunctionRole-2qGaapHFWOQ8` 이름 형식을 사용하는 함수의 IAM 실행 역할

AWS SAM(이)가 리소스 생성을 마치면 다음 메시지가 표시됩니다.

```
Successfully created/updated stack - sam-app in us-east-2
```

## 앱 테스트
<a name="file-processing-app-test"></a>

앱을 테스트하려면 원본 버킷에 PDF 파일을 업로드하고 Lambda가 대상 버킷에 파일의 암호화된 버전을 생성하는지 확인합니다. 이 예시에서는 콘솔이 또는 AWS CLI(을)를 사용하거나 제공된 테스트 스크립트를 사용하여 수동으로 테스트할 수 있습니다.

프로덕션 애플리케이션의 경우 단위 테스트와 같은 기존 테스트 방법 및 기법을 사용하여 Lambda 함수 코드가 올바르게 작동하는지 확인할 수 있습니다. 또한 제공된 테스트 스크립트와 같은 테스트를 수행하여 실제 클라우드 기반 리소스와의 통합 테스트를 수행하는 것이 가장 좋습니다. 클라우드에서의 통합 테스트를 통해 인프라가 올바르게 배포되었으며 예상대로 서로 다른 서비스 간에 이벤트가 전달되는지 확인할 수 있습니다. 자세한 내용은 [서버리스 함수 및 애플리케이션을 테스트하는 방법](testing-guide.md)를 참조하세요.

### 앱 수동으로 테스트하기
<a name="file-processing-app-test-manual"></a>

Amazon S3 소스 버킷에 PDF 파일을 추가하여 함수를 수동으로 테스트할 수 있습니다. 원본 버킷에 파일을 추가하면 Lambda 함수가 자동으로 간접 호출되고 대상 버킷에 파일의 암호화된 버전을 저장해야 합니다.

------
#### [ Console ]

**파일을 업로드하여 앱 테스트하기(콘솔)**

1. S3 버킷에 PDF 파일을 업로드하려면 다음을 수행합니다.

   1. Amazon S3 콘솔의 [버킷](https://console.aws.amazon.com/s3/buckets) 페이지를 열고 원본 버킷을 선택합니다.

   1. **업로드**를 선택합니다.

   1. **파일 추가**를 선택하고 파일 선택기를 사용하여 업로드하려는 이미지 파일을 선택합니다.

   1. **열기**를 선택한 후 **업로드**를 선택합니다.

1. 다음을 수행하여 Lambda가 PDF 파일의 암호화된 버전을 대상 버킷에 저장했는지 확인합니다.

   1. Amazon S3 콘솔의 [버킷](https://console.aws.amazon.com/s3/buckets) 페이지로 돌아가서 대상 버킷을 선택합니다.

   1. 이제 **객체** 창에 이름 형식(`filename_encrypted.pdf`)(`filename.pdf`은 원본 버킷에 업로드한 파일 이름을 의미)의 파일이 표시됩니다. 암호화된 PDF를 다운로드하려면 파일을 선택한 다음 **다운로드**를 선택합니다.

   1. Lambda 함수가 (`my-secret-password`)(으)로 보호한 암호를 사용하여 다운로드한 파일을 열 수 있는지 확인합니다.

------
#### [ AWS CLI ]

**파일을 업로드하여 앱 테스트하기(AWS CLI)**

1. 업로드하려는 PDF 파일이 포함된 디렉터리에서 다음 CLI 명령을 실행합니다. `--bucket` 파라미터를 원본 버킷의 이름으로 바꿉니다. `--key` 및 `--body` 파라미터에는 테스트 파일 이름을 사용합니다.

   ```
   aws s3api put-object --bucket amzn-s3-demo-bucket --key test.pdf --body ./test.pdf
   ```

1. 함수가 파일의 암호화된 버전을 생성하여 대상 S3 버킷에 저장했는지 확인합니다. 다음 CLI 명령을 실행하여 `amzn-s3-demo-bucket-encrypted`를 대상 버킷 이름으로 바꿉니다.

   ```
   aws s3api list-objects-v2 --bucket amzn-s3-demo-bucket-encrypted
   ```

   함수가 성공적으로 실행되면 다음과 유사한 출력이 표시됩니다. 대상 버킷에는 `<your_test_file>_encrypted.pdf` 이름 형식의 파일이 포함되어야 합니다. 여기서 `<your_test_file>`(은)는 업로드한 파일 이름입니다.

   ```
   {
       "Contents": [
           {
               "Key": "test_encrypted.pdf",
               "LastModified": "2023-06-07T00:15:50+00:00",
               "ETag": "\"7781a43e765a8301713f533d70968a1e\"",
               "Size": 2763,
               "StorageClass": "STANDARD"
           }
       ]
   }
   ```

1. Lambda가 대상 버킷에 저장한 파일을 다운로드하려면 다음 CLI 명령을 실행합니다. `--bucket` 파라미터를 대상 버킷의 이름으로 바꿉니다. `--key` 파라미터에는 `<your_test_file>_encrypted.pdf` 파일 이름을 사용합니다. 여기서 `<your_test_file>`(은)는 업로드한 테스트 파일의 이름입니다.

   ```
   aws s3api get-object --bucket amzn-s3-demo-bucket-encrypted --key test_encrypted.pdf my_encrypted_file.pdf
   ```

   이 명령은 파일을 현재 디렉터리에 다운로드하고 `my_encrypted_file.pdf`(으)로 저장합니다.

1. Lambda 함수가 (`my-secret-password`)(으)로 보호한 암호를 사용하여 다운로드한 파일을 열 수 있는지 확인합니다.

------

### 자동 스크립트로 앱을 테스트하기
<a name="file-processing-app-test-auto"></a>

프로젝트 디렉터리에 다음 파일을 생성합니다.
+ `test_pdf_encrypt.py` - 애플리케이션을 자동으로 테스트하는 데 사용할 수 있는 테스트 스크립트
+ `pytest.ini` - 테스트 스크립트의 구성 파일

다음 섹션을 확장하여 코드를 확인하고 각 파일의 역할에 대해 자세히 알아보세요.

#### 자동 테스트 스크립트
<a name="file-processing-app-test-script"></a>

다음 코드를 복사하여 새로운 `test_pdf_encrypt.py` 파일에 붙여 넣습니다. 자리 표시자 버킷 이름을 바꾸어야 합니다.
+ `test_source_bucket_available` 함수에서 `amzn-s3-demo-bucket`의 값을 원본 버킷의 이름으로 바꿉니다.
+ `test_encrypted_file_in_bucket` 함수에서 `amzn-s3-demo-bucket-encrypted`의 값을 `source-bucket-encrypted`으로 바꿉니다. 여기서 `source-bucket>`(은)는 원본 버킷의 이름입니다.
+ `cleanup` 함수에서 `amzn-s3-demo-bucket`을 소스 버킷의 이름으로 바꾸고, `amzn-s3-demo-bucket-encrypted`를 대상 버킷의 이름으로 바꿉니다.

```
import boto3
import json
import pytest
import time
import os

@pytest.fixture
def lambda_client():
    return boto3.client('lambda')
    
@pytest.fixture
def s3_client():
    return boto3.client('s3')

@pytest.fixture
def logs_client():
    return boto3.client('logs')

@pytest.fixture(scope='session')
def cleanup():
    # Create a new S3 client for cleanup
    s3_client = boto3.client('s3')

    yield
    # Cleanup code will be executed after all tests have finished

    # Delete test.pdf from the source bucket
    source_bucket = 'amzn-s3-demo-bucket'
    source_file_key = 'test.pdf'
    s3_client.delete_object(Bucket=source_bucket, Key=source_file_key)
    print(f"\nDeleted {source_file_key} from {source_bucket}")

    # Delete test_encrypted.pdf from the destination bucket
    destination_bucket = 'amzn-s3-demo-bucket-encrypted'
    destination_file_key = 'test_encrypted.pdf'
    s3_client.delete_object(Bucket=destination_bucket, Key=destination_file_key)
    print(f"Deleted {destination_file_key} from {destination_bucket}")
        

@pytest.mark.order(1)
def test_source_bucket_available(s3_client):
    s3_bucket_name = 'amzn-s3-demo-bucket'
    file_name = 'test.pdf'
    file_path = os.path.join(os.path.dirname(__file__), file_name)

    file_uploaded = False
    try:
        s3_client.upload_file(file_path, s3_bucket_name, file_name)
        file_uploaded = True
    except:
        print("Error: couldn't upload file")

    assert file_uploaded, "Could not upload file to S3 bucket"

    

@pytest.mark.order(2)
def test_lambda_invoked(logs_client):

    # Wait for a few seconds to make sure the logs are available
    time.sleep(5)

    # Get the latest log stream for the specified log group
    log_streams = logs_client.describe_log_streams(
        logGroupName='/aws/lambda/EncryptPDF',
        orderBy='LastEventTime',
        descending=True,
        limit=1
    )

    latest_log_stream_name = log_streams['logStreams'][0]['logStreamName']

    # Retrieve the log events from the latest log stream
    log_events = logs_client.get_log_events(
        logGroupName='/aws/lambda/EncryptPDF',
        logStreamName=latest_log_stream_name
    )

    success_found = False
    for event in log_events['events']:
        message = json.loads(event['message'])
        status = message.get('record', {}).get('status')
        if status == 'success':
            success_found = True
            break

    assert success_found, "Lambda function execution did not report 'success' status in logs."

@pytest.mark.order(3)
def test_encrypted_file_in_bucket(s3_client):
    # Specify the destination S3 bucket and the expected converted file key
    destination_bucket = 'amzn-s3-demo-bucket-encrypted'
    converted_file_key = 'test_encrypted.pdf'

    try:
        # Attempt to retrieve the metadata of the converted file from the destination S3 bucket
        s3_client.head_object(Bucket=destination_bucket, Key=converted_file_key)
    except s3_client.exceptions.ClientError as e:
        # If the file is not found, the test will fail
        pytest.fail(f"Converted file '{converted_file_key}' not found in the destination bucket: {str(e)}")

def test_cleanup(cleanup):
    # This test uses the cleanup fixture and will be executed last
    pass
```

자동화된 테스트 스크립트는 세 가지 테스트 함수를 실행하여 앱이 올바르게 작동하는지 확인합니다.
+ 테스트(`test_source_bucket_available`)에서는 테스트 PDF 파일을 버킷에 업로드하여 원본 버킷이 성공적으로 생성되었는지 확인합니다.
+ 테스트(`test_lambda_invoked`)에서는 함수에 대한 최신 CloudWatch Logs 로그 스트림을 조사하여 테스트 파일을 업로드했을 때 Lambda 함수가 실행되고 성공을 보고했는지 확인합니다.
+ 테스트(`test_encrypted_file_in_bucket`)는 대상 버킷에 암호화된 `test_encrypted.pdf` 파일이 들어 있는지 확인합니다.

이러한 모든 테스트가 실행된 후 스크립트는 추가 정리 단계를 실행하여 원본 및 대상 버킷 모두에서 `test.pdf` 및 `test_encrypted.pdf` 파일을 삭제합니다.

AWS SAM 템플릿과 마찬가지로 지정된 버킷 이름은 플레이스홀더입니다. 테스트를 실행하기 전에 앱의 실제 버킷 이름을 사용하여 이 파일을 편집해야 합니다. 이 단계에 대해서는 [자동 스크립트로 앱을 테스트하기](#file-processing-app-test-auto)에서 자세히 설명합니다.

#### 테스트 스크립트 구성 파일
<a name="file-processing-app-test-config"></a>

다음 코드를 복사하여 새로운 `pytest.ini` 파일에 붙여 넣습니다.

```
[pytest]
markers =
    order: specify test execution order
```

이는 `test_pdf_encrypt.py` 스크립트의 테스트 실행 순서를 지정하는 데 필요합니다.

테스트를 실행하려면 다음을 수행합니다.

1. `pytest` 모듈이 로컬 환경에 설치되어 있는지 확인합니다. 다음 명령을 실행하여 `pytest`(을)를 설치합니다.

   ```
   pip install pytest
   ```

1. `test_pdf_encrypt.py` 및 `pytest.ini` 파일이 들어 있는 디렉터리에 `test.pdf`라는 이름이 지정된 PDF 파일을 저장합니다.

1. 터미널 또는 쉘 프로그램을 열고 테스트 파일이 포함된 디렉터리에서 다음 명령을 실행합니다.

   ```
   pytest -s -v
   ```

   테스트가 완료되면 다음이 출력됩니다.

   ```
   ============================================================== test session starts =========================================================
   platform linux -- Python 3.12.2, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
   cachedir: .pytest_cache
   hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/pdf_encrypt_app/.hypothesis/examples')
   Test order randomisation NOT enabled. Enable with --random-order or --random-order-bucket=<bucket_type>
   rootdir: /home/pdf_encrypt_app, configfile: pytest.ini
   plugins: anyio-3.7.1, hypothesis-6.70.0, localserver-0.7.1, random-order-1.1.0
   collected 4 items
   
   test_pdf_encrypt.py::test_source_bucket_available PASSED
   test_pdf_encrypt.py::test_lambda_invoked PASSED
   test_pdf_encrypt.py::test_encrypted_file_in_bucket PASSED
   test_pdf_encrypt.py::test_cleanup PASSED
   Deleted test.pdf from amzn-s3-demo-bucket
   Deleted test_encrypted.pdf from amzn-s3-demo-bucket-encrypted
   
   
   =============================================================== 4 passed in 7.32s ==========================================================
   ```

## 다음 단계
<a name="file-processing-app-next-steps"></a>

이제 이 예제 앱을 만들었 봤으니, 제공된 코드를 기반으로 다른 유형의 파일 처리 응용 프로그램을 만들 수 있습니다. `lambda_function.py` 파일의 코드를 수정하여 사용 사례에 맞는 파일 처리 로직을 구현하십시오.

대부분의 일반적인 파일 처리 사용 사례는 이미지 처리를 포함됩니다. Python을 사용할 때 [필로우](https://pypi.org/project/pillow/)와 같은 가장 널리 사용되는 이미지 처리 라이브러리에는 일반적으로 C 또는 C\$1\$1 구성 요소가 포함됩니다. 함수의 배포 패키지가 Lambda 실행 환경과 호환되도록 하려면 올바른 소스 배포 바이너리를 사용하는 것이 중요합니다.

AWS SAM(으)로 리소스를 배포할 때는 배포 패키지에 올바른 소스 배포를 포함하기 위해 몇 가지 추가 단계를 거쳐야 합니다. AWS SAM(은)는 빌드 머신과 다른 플랫폼에 대한 종속성을 설치하지 않으므로 빌드 머신이 Lambda 실행 환경과 다른 운영 체제 또는 아키텍처를 사용하는 경우 `requirements.txt` 파일에 올바른 소스 배포(`.whl`파일)를 지정해도 작동하지 않습니다. 대신 다음 중 하나를 수행하십시오.
+ `sam build`(을)를 실행 시 `--use-container` 옵션을 사용하십시오. 이 옵션을 지정하면 AWS SAM(은)는 Lambda 실행 환경과 호환되는 컨테이너 기본 이미지를 다운로드하고 해당 이미지를 사용하여 Docker 컨테이너에 함수의 배포 패키지를 빌드합니다. 자세한 내용은 [제공된 컨테이너 내에 Lambda 함수 빌드하기](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-build.html#using-sam-cli-build-options-container)를 확인하십시오.
+ 올바른 소스 배포 바이너리를 사용하여 함수의 .zip 배포 패키지를 직접 빌드하고 AWS SAM 템플릿에서 `CodeUri`(으)로 지정한 디렉터리에 .zip 파일을 저장합니다. 바이너리 배포를 사용하여 Python용 .zip 배포 패키지를 빌드하는 방법에 대한 자세한 내용은 [종속 항목이 있는 .zip 배포 패키지 생성](python-package.md#python-package-create-dependencies) 및 [네이티브 라이브러리로 .zip 배포 패키지 생성](python-package.md#python-package-native-libraries)(을)를 참조하십시오.

# 예약된 데이터베이스 유지 관리를 수행하는 앱 생성
<a name="scheduled-task-app"></a>

AWS Lambda를 사용하여 자동화된 시스템 백업, 파일 변환, 유지 관리 태스크와 같은 예약된 프로세스를 대체할 수 있습니다. 이 예제에서는 오래된 항목을 삭제하여 DynamoDB 테이블에서 정기적으로 예약된 유지 관리를 수행하는 서버리스 애플리케이션을 생성합니다. 앱은 EventBridge 스케줄러를 사용하여 cron 일정에 따라 Lambda 함수를 간접 호출합니다. 함수가 간접 호출되면 테이블에서 1년 이상 된 항목을 쿼리하여 삭제합니다. 함수는 삭제된 각 항목을 CloudWatch Logs에 기록합니다.

예제를 구현하려면 먼저 DynamoDB 테이블을 생성하고 쿼리할 함수에 대한 테스트 데이터로 채웁니다. 그런 다음 EventBridge 스케줄러 트리거와 테이블에서 항목을 읽고 삭제할 수 있는 권한을 함수에 부여하는 IAM 실행 역할이 있는 Python Lambda 함수를 생성합니다.

![\[\]](http://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images/ExampleApps/cron_app.png)


**작은 정보**  
Lambda를 처음 사용하는 경우 이 예제 앱을 생성하기 전에 [첫 번째 Lambda 함수 생성](getting-started.md) 자습서를 완료하는 것이 좋습니다.

AWS Management Console을 사용하여 리소스를 생성하고 구성하여 앱을 수동으로 배포할 수 있습니다. AWS Serverless Application Model(AWS SAM)(을)를 사용하여 앱을 배포할 수도 있습니다. AWS SAM(은)는 코드형 인프라(IaC) 도구입니다. IaC를 사용하면 리소스를 수동으로 생성하지 않고 코드로 정의한 다음 자동으로 배포할 수 있습니다.

이 예제 앱을 배포하기 전에 IaC와 함께 Lambda를 사용하는 방법에 대해 자세히 알아보려면 [코드형 인프라(IaC)와 함께 Lambda 사용](foundation-iac.md) 섹션을 참조하세요.

## 사전 조건
<a name="scheduled-task-app-prereqs"></a>

예제 앱을 생성하기 전에 필수 명령줄 도구와 프로그램이 설치되어 있는지 확인하세요.
+ **Python**

  앱을 테스트하기 위해 생성한 DynamoDB 테이블을 채우기 위해 이 예제에서는 Python 스크립트와 CSV 파일을 사용하여 테이블에 데이터를 씁니다. 머신에 Python 버전 3.8 이상이 설치되어 있는지 확인하세요.
+ **AWS SAM CLI**

  AWS SAM을 사용하여 DynamoDB 테이블을 생성하고 예제 앱을 배포하려면 AWS SAM CLI를 설치해야 합니다. *AWS SAM 사용 설명서*의 [설치 지침](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)을 따르세요.
+ **AWS CLI**

  제공된 Python 스크립트를 사용하여 테스트 테이블을 채우려면 AWS CLI를 설치하고 구성해야 합니다. 이는 스크립트에서 AWS Identity and Access Management(IAM) 자격 증명에 액세스해야 하는 AWS SDK for Python (Boto3)을 사용하기 때문입니다. 또한 AWS SAM을 사용하여 리소스를 배포하려면 AWS CLI가 설치되어 있어야 합니다. *AWS Command Line Interface 사용 설명서*의 [설치 지침](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)에 따라 CLI를 설치합니다.
+ **Docker**

  AWS SAM을 사용하여 앱을 배포하려면 Docker 또한 빌드 머신에 설치되어 있어야 합니다. Docker 설명서 웹사이트의 [Docker 엔진 설치](https://docs.docker.com/engine/install/)에 있는 지침을 따르세요.

## 예제 앱 파일 다운로드
<a name="scheduled-task-app-download"></a>

예제 데이터베이스와 예약된 유지 관리 앱을 생성하려면 프로젝트 디렉터리에 다음 파일을 생성해야 합니다.

**예제 데이터베이스 파일**
+ `template.yaml` - DynamoDB 테이블을 생성하는 데 사용할 수 있는 AWS SAM 템플릿
+ `sample_data.csv` - 테이블에 로드할 샘플 데이터가 포함된 CSV 파일
+ `load_sample_data.py` - CSV 파일의 데이터를 테이블에 쓰는 Python 스크립트

**예약된 유지 관리 앱 파일**
+ `lambda_function.py` - 데이터베이스 유지 관리를 수행하는 Lambda 함수에 대한 Python 함수 코드
+ `requirements.txt` - Python 함수 코드에 필요한 종속성을 정의하는 매니페스트 파일
+ `template.yaml` - 앱을 배포하는 데 사용할 수 있는 AWS SAM 템플릿

**테스트 파일**
+ `test_app.py` - 테이블을 스캔하고 1년이 지난 모든 레코드를 출력하여 함수의 성공적인 작동을 확인하는 Python 스크립트

다음 섹션을 확장하여 코드를 확인하고 앱의 생성과 테스트에서 각 파일의 역할에 대해 자세히 알아보세요. 로컬 머신에 파일을 생성하려면 아래 코드를 복사하여 붙여넣으세요.

### AWS SAM 템플릿(예: DynamoDB 테이블)
<a name="scheduled-task-app-table-yaml"></a>

다음 코드를 복사하여 새로운 `template.yaml` 파일에 붙여 넣습니다.

```
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for DynamoDB Table with Order_number as Partition Key and Date as Sort Key

Resources:
  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      TableName: MyOrderTable
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: Order_number
          AttributeType: S
        - AttributeName: Date
          AttributeType: S
      KeySchema:
        - AttributeName: Order_number
          KeyType: HASH
        - AttributeName: Date
          KeyType: RANGE
      SSESpecification:
        SSEEnabled: true
      GlobalSecondaryIndexes:
        - IndexName: Date-index
          KeySchema:
            - AttributeName: Date
              KeyType: HASH
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true

Outputs:
  TableName:
    Description: DynamoDB Table Name
    Value: !Ref MyDynamoDBTable
  TableArn:
    Description: DynamoDB Table ARN
    Value: !GetAtt MyDynamoDBTable.Arn
```

**참고**  
AWS SAM 템플릿은 `template.yaml`의 표준 이름 지정 규칙을 사용합니다. 이 예제에는 두 개의 템플릿 파일이 있습니다. 하나는 예제 데이터베이스를 생성하는 데 사용되고, 다른 하나는 앱 자체를 생성하는 데 사용됩니다. 프로젝트 폴더에 있는 별도의 하위 디렉터리에 저장합니다.

AWS SAM 템플릿은 앱을 테스트하기 위해 생성하는 DynamoDB 테이블 리소스를 정의합니다. 이 테이블은 `Order_number`의 프라이머리 키와 `Date`의 정렬 키를 사용합니다. Lambda 함수가 날짜별로 직접 항목을 찾을 수 있도록 `Date-index`라는 [전역 보조 인덱스](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html)도 정의합니다.

`AWS::DynamoDB::Table` 리소스를 사용하여 DynamoDB 테이블을 생성하고 구성하는 방법에 대해 자세히 알아보려면 *AWS CloudFormation 사용 설명서*의 [AWS::DynamoDB::Table](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html)을 참조하세요.

### 샘플 데이터베이스 데이터 파일
<a name="scheduled-task-app-csv-file"></a>

다음 코드를 복사하여 새로운 `sample_data.csv` 파일에 붙여 넣습니다.

```
Date,Order_number,CustomerName,ProductID,Quantity,TotalAmount
2023-09-01,ORD001,Alejandro Rosalez,PROD123,2,199.98
2023-09-01,ORD002,Akua Mansa,PROD456,1,49.99
2023-09-02,ORD003,Ana Carolina Silva,PROD789,3,149.97
2023-09-03,ORD004,Arnav Desai,PROD123,1,99.99
2023-10-01,ORD005,Carlos Salazar,PROD456,2,99.98
2023-10-02,ORD006,Diego Ramirez,PROD789,1,49.99
2023-10-03,ORD007,Efua Owusu,PROD123,4,399.96
2023-10-04,ORD008,John Stiles,PROD456,2,99.98
2023-10-05,ORD009,Jorge Souza,PROD789,3,149.97
2023-10-06,ORD010,Kwaku Mensah,PROD123,1,99.99
2023-11-01,ORD011,Li Juan,PROD456,5,249.95
2023-11-02,ORD012,Marcia Oliveria,PROD789,2,99.98
2023-11-03,ORD013,Maria Garcia,PROD123,3,299.97
2023-11-04,ORD014,Martha Rivera,PROD456,1,49.99
2023-11-05,ORD015,Mary Major,PROD789,4,199.96
2023-12-01,ORD016,Mateo Jackson,PROD123,2,199.99
2023-12-02,ORD017,Nikki Wolf,PROD456,3,149.97
2023-12-03,ORD018,Pat Candella,PROD789,1,49.99
2023-12-04,ORD019,Paulo Santos,PROD123,5,499.95
2023-12-05,ORD020,Richard Roe,PROD456,2,99.98
2024-01-01,ORD021,Saanvi Sarkar,PROD789,3,149.97
2024-01-02,ORD022,Shirley Rodriguez,PROD123,1,99.99
2024-01-03,ORD023,Sofia Martinez,PROD456,4,199.96
2024-01-04,ORD024,Terry Whitlock,PROD789,2,99.98
2024-01-05,ORD025,Wang Xiulan,PROD123,3,299.97
```

이 파일에는 DynamoDB 테이블을 표준 쉼표로 구분된 값(CSV) 형식으로 채우기 위한 몇 가지 예제 테스트 데이터가 포함되어 있습니다.

### 샘플 데이터를 로드하기 위한 Python 스크립트
<a name="scheduled-task-app-load-script"></a>

다음 코드를 복사하여 새로운 `load_sample_data.py` 파일에 붙여 넣습니다.

```
import boto3
import csv
from decimal import Decimal

# Initialize the DynamoDB client
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('MyOrderTable') 
print("DDB client initialized.")

def load_data_from_csv(filename):
    with open(filename, 'r') as file:
        csv_reader = csv.DictReader(file)
        for row in csv_reader:
            item = {
                'Order_number': row['Order_number'],
                'Date': row['Date'],
                'CustomerName': row['CustomerName'],
                'ProductID': row['ProductID'],
                'Quantity': int(row['Quantity']),
                'TotalAmount': Decimal(str(row['TotalAmount']))
            }
            table.put_item(Item=item)
            print(f"Added item: {item['Order_number']} - {item['Date']}")

if __name__ == "__main__":
    load_data_from_csv('sample_data.csv')
    print("Data loading completed.")
```

이 Python 스크립트는 먼저 AWS SDK for Python (Boto3)을 사용하여 DynamoDB 테이블에 대한 연결을 생성합니다. 그런 다음 예제–데이터 CSV 파일의 각 행을 반복하고, 해당 행에서 항목을 생성하고, boto3 SDK를 사용하여 항목을 DynamoDB 테이블에 씁니다.

### Python 함수 코드
<a name="scheduled-task-app-function-code"></a>

다음 코드를 복사하여 새로운 `lambda_function.py` 파일에 붙여 넣습니다.

```
import boto3
from datetime import datetime, timedelta
from boto3.dynamodb.conditions import Key, Attr
import logging

logger = logging.getLogger()
logger.setLevel("INFO")

def lambda_handler(event, context):
    # Initialize the DynamoDB client
    dynamodb = boto3.resource('dynamodb')
    
    # Specify the table name
    table_name = 'MyOrderTable'
    table = dynamodb.Table(table_name)
    
    # Get today's date
    today = datetime.now()
    
    # Calculate the date one year ago
    one_year_ago = (today - timedelta(days=365)).strftime('%Y-%m-%d')
    
    # Scan the table using a global secondary index
    response = table.scan(
        IndexName='Date-index',
        FilterExpression='#date < :one_year_ago',
        ExpressionAttributeNames={
            '#date': 'Date'
        },
        ExpressionAttributeValues={
            ':one_year_ago': one_year_ago
        }
    )
    
     # Delete old items
    with table.batch_writer() as batch:
        for item in response['Items']:
            Order_number = item['Order_number']
            batch.delete_item(
                Key={
                    'Order_number': Order_number,
                    'Date': item['Date']
                }
            )
            logger.info(f'deleted order number {Order_number}')
    
    # Check if there are more items to scan
    while 'LastEvaluatedKey' in response:
        response = table.scan(
            IndexName='DateIndex',
            FilterExpression='#date < :one_year_ago',
            ExpressionAttributeNames={
                '#date': 'Date'
            },
            ExpressionAttributeValues={
                ':one_year_ago': one_year_ago
            },
            ExclusiveStartKey=response['LastEvaluatedKey']
        )
        
        # Delete old items
        with table.batch_writer() as batch:
            for item in response['Items']:
                batch.delete_item(
                    Key={
                        'Order_number': item['Order_number'],
                        'Date': item['Date']
                    }
                )
    
    return {
        'statusCode': 200,
        'body': 'Cleanup completed successfully'
    }
```

Python 함수 코드에는 함수가 간접 호출될 때 Lambda가 실행하는 [핸들러 함수](python-handler.md)(`lambda_handler`)가 포함되어 있습니다.

EventBridge 스케줄러에서 함수를 간접 호출하면 AWS SDK for Python (Boto3)을 사용하여 예약된 유지 관리 태스크를 수행할 DynamoDB 테이블에 대한 연결을 생성합니다. 그런 다음 Python `datetime` 라이브러리를 사용하여 1년 전 날짜를 계산한 다음 테이블에서 이보다 오래된 항목을 스캔하고 삭제합니다.

DynamoDB 쿼리 및 스캔 작업의 응답은 최대 1MB로 크기가 제한됩니다. 응답이 1MB보다 큰 경우 DynamoDB는 데이터에 페이지를 매기고 응답에서 `LastEvaluatedKey` 요소를 반환합니다. 함수가 테이블의 모든 레코드를 처리할 수 있도록 해당 키가 있는지 확인하고 전체 테이블이 스캔될 때까지 마지막으로 평가된 위치에서 테이블 스캔을 계속 수행합니다.

### `requirements.txt` 매니페스트 파일
<a name="scheduled-task-app-dependencies"></a>

다음 코드를 복사하여 새로운 `requirements.txt` 파일에 붙여 넣습니다.

```
boto3
```

이 예제에서 함수 코드에는 표준 Python 라이브러리에 포함되지 않은 종속성이 하나만 있습니다. 바로 함수가 DynamoDB 테이블에서 항목을 스캔하고 삭제하는 데 사용하는 Python용 SDK(Boto3)입니다.

**참고**  
Python용 SDK(Boto3) 버전이 Lambda 런타임의 일부로 포함되어 있으므로 함수의 배포 패키지에 Boto3를 추가하지 않고도 코드를 실행할 수 있습니다. 그러나 함수의 종속 항목을 완전히 제어하고 버전 불일치와 관련된 문제를 방지하기 위해 Python의 모범 사례는 함수의 배포 패키지에 모든 함수 종속성을 포함합니다. 자세한 내용은 [Python의 런타임 종속 항목](python-package.md#python-package-dependencies) 섹션을 참조하세요.

### AWS SAM 템플릿(예약된 유지 관리 앱)
<a name="scheduled-task-app-table-yaml"></a>

다음 코드를 복사하여 새로운 `template.yaml` 파일에 붙여 넣습니다.

```
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for Lambda function and EventBridge Scheduler rule

Resources:
  MyLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: ScheduledDBMaintenance
      CodeUri: ./
      Handler: lambda_function.lambda_handler
      Runtime: python3.11
      Architectures:
        - x86_64
      Events:
        ScheduleEvent:
          Type: ScheduleV2
          Properties:
            ScheduleExpression: cron(0 3 1 * ? *)
            Description: Run on the first day of every month at 03:00 AM
      Policies:
        - CloudWatchLogsFullAccess
        - Statement:
            - Effect: Allow
              Action:
                - dynamodb:Scan
                - dynamodb:BatchWriteItem
              Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/MyOrderTable'

  LambdaLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${MyLambdaFunction}
      RetentionInDays: 30

Outputs:
  LambdaFunctionName:
    Description: Lambda Function Name
    Value: !Ref MyLambdaFunction
  LambdaFunctionArn:
    Description: Lambda Function ARN
    Value: !GetAtt MyLambdaFunction.Arn
```

**참고**  
AWS SAM 템플릿은 `template.yaml`의 표준 이름 지정 규칙을 사용합니다. 이 예제에는 두 개의 템플릿 파일이 있습니다. 하나는 예제 데이터베이스를 생성하는 데 사용되고, 다른 하나는 앱 자체를 생성하는 데 사용됩니다. 프로젝트 폴더에 있는 별도의 하위 디렉터리에 저장합니다.

AWS SAM 템플릿은 앱의 리소스를 정의합니다. `AWS::Serverless::Function` 리소스를 사용하여 Lambda 함수를 정의합니다. EventBridge 스케줄러 일정과 Lambda 함수를 간접 호출하는 트리거는 이 리소스의 `Events` 속성을 `ScheduleV2` 유형으로 사용하여 생성됩니다. AWS SAM 템플릿에서 EventBridge 스케줄러 일정을 정의하는 방법에 대해 자세히 알아보려면 *AWS Serverless Application Model 개발자 가이드*의 [ScheduleV2](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-schedulev2.html)를 참조하세요.

Lambda 함수와 EventBridge 스케줄러 일정 외에도 삭제된 항목의 레코드를 전송할 함수에 대한 CloudWatch 로그 그룹도 정의합니다.

### 테스트 스크립트
<a name="scheduled-task-app-test-script"></a>

다음 코드를 복사하여 새로운 `test_app.py` 파일에 붙여 넣습니다.

```
import boto3
from datetime import datetime, timedelta
import json

# Initialize the DynamoDB client
dynamodb = boto3.resource('dynamodb')

# Specify your table name
table_name = 'YourTableName'
table = dynamodb.Table(table_name)

# Get the current date
current_date = datetime.now()

# Calculate the date one year ago
one_year_ago = current_date - timedelta(days=365)

# Convert the date to string format (assuming the date in DynamoDB is stored as a string)
one_year_ago_str = one_year_ago.strftime('%Y-%m-%d')

# Scan the table
response = table.scan(
    FilterExpression='#date < :one_year_ago',
    ExpressionAttributeNames={
        '#date': 'Date'
    },
    ExpressionAttributeValues={
        ':one_year_ago': one_year_ago_str
    }
)

# Process the results
old_records = response['Items']

# Continue scanning if we have more items (pagination)
while 'LastEvaluatedKey' in response:
    response = table.scan(
        FilterExpression='#date < :one_year_ago',
        ExpressionAttributeNames={
            '#date': 'Date'
        },
        ExpressionAttributeValues={
            ':one_year_ago': one_year_ago_str
        },
        ExclusiveStartKey=response['LastEvaluatedKey']
    )
    old_records.extend(response['Items'])

for record in old_records:
    print(json.dumps(record))

# The total number of old records should be zero.
print(f"Total number of old records: {len(old_records)}")
```

이 테스트 스크립트는 AWS SDK for Python (Boto3)을 사용하여 DynamoDB 테이블에 대한 연결을 생성하고 1년이 지난 항목을 스캔합니다. Lambda 함수가 성공적으로 실행되었는지 확인하기 위해 테스트가 끝나면 테이블에 남아 있는 1년이 지난 레코드 수를 인쇄합니다. Lambda 함수가 성공적이었다면 테이블의 이전 레코드 수는 0이어야 합니다.

## 예제 DynamoDB 테이블 생성 및 채우기
<a name="scheduled-task-app-create-table"></a>

예약된 유지 관리 앱을 테스트하려면 먼저 DynamoDB 테이블을 생성하고 이를 몇 가지 샘플 데이터로 채웁니다. AWS Management Console을 사용하여 수동으로 테이블을 생성하거나 AWS SAM을 사용하여 테이블을 생성할 수 있습니다. 몇 가지 AWS CLI 명령을 사용하여 테이블을 빠르게 생성하고 구성하려면 AWS SAM을 사용하는 것이 좋습니다.

------
#### [ Console ]

**DynamoDB 테이블을 생성하려면**

1. [DynamoDB 콘솔의 테이블 페이지](https://console.aws.amazon.com/dynamodbv2/home#tables)를 엽니다.

1. **테이블 생성**을 선택합니다.

1. 다음을 수행하여 테이블을 생성합니다.

   1. **테이블 세부 정보**에서 **테이블 이름**에 **MyOrderTable**을 입력합니다.

   1. **파티션 키**에서 **Order\$1number**를 입력하고 유형을 **문자열**로 둡니다.

   1. **정렬 키**에서 **Date**를 입력하고 유형을 **문자열**로 둡니다.

   1. **테이블 설정**을 **기본 설정**으로 설정된 상태로 두고 **테이블 생성**을 선택합니다.

1. 테이블 생성이 완료되고 **상태**가 **활성**으로 표시되면 다음을 수행하여 글로벌 보조 인덱스(GSI)를 생성합니다. 앱은 GSI를 사용하여 날짜별로 항목을 직접 검색하고 삭제할 항목을 결정합니다.

   1. 테이블 목록에서 **MyOrderTable**을 선택합니다.

   1. **인덱스** 탭을 선택합니다.

   1. **글로벌 보조 인덱스**에서 **인덱스 생성**을 선택합니다.

   1. **인덱스 세부 정보**에서 **파티션 키**에 **Date**를 입력하고 **데이터 유형**을 **문자열**로 설정한 상태로 둡니다.

   1. **인덱스 이름**에 **Date-index**를 입력합니다.

   1. 다른 모든 파라미터를 기본값으로 설정한 상태로 두고 페이지 하단으로 스크롤한 다음 **인덱스 생성**을 선택합니다.

------
#### [ AWS SAM ]

**DynamoDB 테이블을 생성하려면**

1. DynamoDB 테이블의 `template.yaml` 파일을 저장한 폴더로 이동합니다. 이 예제에서는 두 개의 `template.yaml` 파일을 사용합니다. 템플릿이 별도의 하위 폴더에 저장되어 있는지 확인하고 템플릿이 포함된 올바른 폴더에 DynamoDB 테이블을 생성할 수 있는지 확인하세요.

1. 다음 명령을 실행합니다.

   ```
   sam build
   ```

   이 명령은 배포하려는 리소스에 대한 빌드 아티팩트를 수집하여 배포하려는 적절한 형식과 위치에 배치합니다.

1. `template.yaml` 파일에 지정된 DynamoDB 리소스를 생성하려면 다음 명령을 실행합니다.

   ```
   sam deploy --guided
   ```

   `--guided` 플래그를 사용하면 배포 AWS SAM 프로세스를 안내하는 메시지가 표시됩니다. 이러한 배포의 경우 **cron-app-test-db**의 `Stack name`을 입력하고 Enter를 사용하여 다른 모든 옵션의 기본값을 수락합니다.

   AWS SAM이 DynamoDB 리소스 생성을 완료하면 다음 메시지가 표시됩니다.

   ```
   Successfully created/updated stack - cron-app-test-db in us-west-2
   ```

1. DynamoDB 콘솔의 [테이블](https://console.aws.amazon.com/dynamodbv2/home#tables) 페이지를 열어 DynamoDB 테이블이 생성되었는지 확인할 수도 있습니다. 이름이 `MyOrderTable`인 테이블이 표시됩니다.

------

테이블을 생성한 후 다음에는 앱을 테스트하기 위해 몇 가지 샘플 데이터를 추가합니다. 앞서 다운로드한 CSV 파일(`sample_data.csv`)에는 주문 번호, 날짜, 고객 및 주문 정보로 구성된 여러 예제 항목이 포함되어 있습니다. 제공된 Python 스크립트(`load_sample_data.py`)를 사용하여 해당 데이터를 테이블에 추가합니다.

**테이블에 샘플 데이터를 추가하려면**

1. `sample_data.csv` 및 `load_sample_data.py` 파일이 포함된 디렉터리로 이동합니다. 이러한 파일이 다른 디렉터리에 있는 경우 동일한 위치에 저장되도록 파일을 이동합니다.

1. 다음 명령을 실행하여 스크립트를 실행할 Python 가상 환경을 생성합니다. 다음 단계에서는 AWS SDK for Python (Boto3)을 설치해야 하므로 가상 환경을 사용하는 것이 좋습니다.

   ```
   python -m venv venv
   ```

1. 다음 명령을 실행하여 가상 환경을 활성화합니다.

   ```
   source venv/bin/activate
   ```

1. 다음 명령을 실행하여 가상 환경에 SDK for Python(Boto3)을 설치합니다. 스크립트는 이 라이브러리를 사용하여 DynamoDB 테이블에 연결하고 항목을 추가합니다.

   ```
   pip install boto3
   ```

1. 다음 명령을 실행하여 스크립트를 실행하고 테이블을 채웁니다.

   ```
   python load_sample_data.py
   ```

   스크립트가 성공적으로 실행되면 각 항목을 로드할 때 이를 콘솔에 인쇄하고 `Data loading completed`를 보고해야 합니다.

1. 다음 명령을 실행하여 가상 환경을 비활성화합니다.

   ```
   deactivate
   ```

1. 다음을 수행하여 데이터가 DynamoDB 테이블에 로드되었는지 확인할 수 있습니다.

   1. DynamoDB 콘솔의 [항목 탐색](https://console.aws.amazon.com/dynamodbv2/home#item-explorer) 페이지를 열고 테이블(`MyOrderTable`)을 선택합니다.

   1. **반환된 항목** 창에 스크립트가 테이블에 추가한 CSV 파일의 25개 항목이 표시되어야 합니다.

## 예약된 유지 관리 앱 생성
<a name="scheduled-task-app-create-app"></a>

AWS Management Console 또는 AWS SAM을 사용하여 이 예제 앱의 리소스를 단계별로 생성하고 배포할 수 있습니다. 프로덕션 환경에서는 수동 프로세스를 사용하지 않고 서버리스 애플리케이션을 반복적으로 배포할 수 있는 AWS SAM과 같은 코드형 인프라(IaC) 도구를 사용하는 것이 좋습니다.

이 예제에서는 콘솔 지침에 따라 각 AWS 리소스를 개별적으로 구성하는 방법을 학습하거나 AWS SAM 지침에 따라 AWS CLI 명령을 사용하여 앱을 빠르게 배포할 수 있습니다.

------
#### [ Console ]

**AWS Management Console을 사용하여 함수를 생성하려면**

먼저 기본 스타터 코드가 포함된 함수를 생성합니다. 그런 다음 Lambda 코드 편집기에서 직접 코드를 복사하여 붙여넣거나 `.zip` 패키지로 코드를 업로드하여 해당 코드를 고유한 함수 코드로 대체합니다. 이 태스크에서는 코드를 복사하여 붙여 넣는 것이 좋습니다.

1. Lambda 콘솔의 [함수 페이지](https://console.aws.amazon.com/lambda/home#/functions)를 엽니다.

1. **함수 생성**을 선택합니다.

1. **새로 작성**을 선택합니다.

1. **기본 정보**에서 다음과 같이 합니다.

   1. [**함수 이름(Function name)**]에 `ScheduledDBMaintenance`을 입력합니다.

   1. **런타임**의 경우 최신 Python 버전을 선택합니다.

   1. **아키텍처**에서는 **x86\$164**를 선택합니다.

1. **함수 생성**을 선택합니다.

1. 함수가 생성된 후에는 제공된 함수 코드로 함수를 구성할 수 있습니다.

   1. **코드 소스** 창에서 Lambda가 생성한 Hello world 코드를 이전에 저장한 `lambda_function.py` 파일의 Python 함수 코드로 바꿉니다.

   1. **배포** 섹션에서 **배포**를 선택하여 함수의 코드를 업데이트하세요.  
![\[\]](http://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images/getting-started-tutorial/deploy-console.png)

**함수 메모리 및 타임아웃 구성(콘솔)**

1. 함수의 **구성** 탭을 선택합니다.

1. **일반 구성** 창에서 **편집**을 선택합니다.

1. **메모리**를 256MB로 설정하고 **타임아웃**을 15초로 설정합니다. 프로덕션 환경과 같이 레코드가 많은 큰 테이블을 처리하는 경우에는 **제한 시간**을 더 큰 숫자로 설정하는 것이 좋습니다. 이렇게 하면 함수에서 데이터베이스를 스캔하고 정리하는 데 더 많은 시간을 사용할 수 있습니다.

1. **저장**을 선택합니다.

**로그 형식 구성하기(콘솔)**

Lambda 함수를 구성하여 구조화되지 않은 텍스트나 JSON 형식으로 로그를 출력할 수 있습니다. 로그 데이터를 더 간편하게 검색하고 필터링할 수 있도록 로그에 JSON 형식을 사용하는 것이 좋습니다. Lambda 로그 구성 옵션에 대한 자세한 내용은 [Lambda 함수에 대한 고급 로깅 제어 구성](monitoring-logs.md#monitoring-cloudwatchlogs-advanced) 섹션을 참조하세요.

1. 함수의 **구성** 탭을 선택합니다.

1. **모니터링 및 운영 도구**를 선택합니다.

1. **로깅 구성** 창에서 **편집**을 선택합니다.

1. **로깅 구성**의 경우 **JSON**을 선택합니다.

1. **저장**을 선택합니다.

**IAM 권한을 설정하려면**

함수에 DynamoDB 항목을 읽고 삭제하는 데 필요한 권한을 부여하려면 함수의 [실행 역할](lambda-intro-execution-role.md)에 필요한 권한을 정의하는 정책을 추가해야 합니다.

1. **구성** 탭을 연 다음 왼쪽 탐색 모음에서 **권한**을 선택합니다.

1. **실행 역할**에서 역할 이름을 선택합니다.

1. IAM 콘솔에서 **권한 추가**를 선택한 다음 **인라인 정책 추가**를 선택합니다.

1. JSON 편집기를 사용하여 다음 정책을 입력합니다.  
****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "dynamodb:Scan",
                   "dynamodb:DeleteItem",
                   "dynamodb:BatchWriteItem"
               ],
               "Resource": "arn:aws:dynamodb:*:*:table/MyOrderTable"
           }
       ]
   }
   ```

1. 정책의 이름을 **DynamoDBCleanupPolicy**로 지정한 다음 정책을 생성합니다.

**EventBridge 스케줄러를 트리거로 설정하려면(콘솔)**

1. [EventBridge 콘솔](https://console.aws.amazon.com/events/home)을 엽니다.

1. 왼쪽 탐색 창의 **스케줄러** 섹션에서 **스케줄러**를 선택합니다.

1. **일정 생성**을 선택합니다.

1. 다음을 수행하여 일정을 구성합니다.

   1. **일정 이름**에 일정의 이름을 입력합니다(예: **DynamoDBCleanupSchedule**).

   1. **일정 패턴**에서 **반복 일정**을 선택합니다.

   1. **일정 유형**의 경우 기본값을 **Cron 기반 일정**으로 두고 다음 일정 세부 정보를 입력합니다.
      + **분**: **0**
      + **시간**: **3**
      + **일**: **1**
      + **월**: **\$1**
      + **요일**: **?**
      + **년**: **\$1**

      이 cron 표현식을 평가하면 매월 1일 오전 3시에 실행됩니다.

   1. **유연한 기간**에서 **끄기**를 선택합니다.

1. **다음**을 선택합니다.

1. 다음을 수행하여 Lambda 함수에 대한 트리거를 구성합니다.

   1. **대상 세부 정보** 창에서 **대상 API**를 **템플릿 대상**으로 설정한 상태로 두고 **AWS Lambda 간접 호출**을 선택합니다.

   1. **간접 호출**의 드롭다운 목록에서 Lambda 함수(`ScheduledDBMaintenance`)를 선택합니다.

   1. **페이로드**를 비워 두고 **다음**을 선택합니다.

   1. **권한**으로 스크롤하고 **이 일정에 대한 새 역할 생성**을 선택합니다. 콘솔을 사용하여 새 EventBridge 스케줄러 일정을 생성하면 EventBridge 스케줄러는 일정이 해당 함수를 간접 호출하는 데 필요한 권한으로 새 정책을 생성합니다. 일정 권한 관리에 대한 자세한 내용은 *EventBridge 스케줄러 사용 설명서*의 [Cron 기반 일정](https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html#cron-based) 섹션을 참조하세요.

   1. **다음**을 선택합니다.

1. 설정을 검토하고 **일정 생성**을 선택하여 일정 및 Lambda 트리거의 생성을 완료합니다.

------
#### [ AWS SAM ]

**AWS SAM을 사용하여 앱을 배포하려면**

1. 앱의 `template.yaml` 파일을 저장한 폴더로 이동합니다. 이 예제에서는 두 개의 `template.yaml` 파일을 사용합니다. 파일이 별도의 하위 폴더에 저장되어 있고 앱을 생성하기 위한 템플릿이 포함된 올바른 폴더에 있는지 확인합니다.

1. 이전에 다운로드한 `lambda_function.py` 및 `requirements.txt` 파일을 동일한 폴더에 복사합니다. AWS SAM 템플릿에 지정된 코드 위치는 현재 위치를 의미하는 `./`입니다. AWS SAM은 앱을 배포하려고 할 때 해당 폴더에서 Lambda 함수 코드를 검색합니다.

1. 다음 명령을 실행합니다.

   ```
   sam build --use-container
   ```

   이 명령은 배포하려는 리소스에 대한 빌드 아티팩트를 수집하여 배포하려는 적절한 형식과 위치에 배치합니다. `--use-container` 옵션을 지정하면 Lambda와 유사한 Docker 컨테이너 내에 함수를 빌드합니다. 여기서는 이 옵션을 사용하므로, 빌드가 작동하기 위해 로컬 컴퓨터에 Python 3.12가 설치되어 있지 않아도 됩니다.

1. `template.yaml` 파일에 지정된 Lambda 및 EventBridge 스케줄러 리소스를 생성하려면 다음 명령을 실행합니다.

   ```
   sam deploy --guided
   ```

   `--guided` 플래그를 사용하면 배포 AWS SAM 프로세스를 안내하는 메시지가 표시됩니다. 이러한 배포의 경우 **cron-maintenance-app**의 `Stack name`을 입력하고 Enter를 사용하여 다른 모든 옵션의 기본값을 수락합니다.

   AWS SAM이 Lambda 및 EventBridge 스케줄러 리소스 생성을 완료하면 다음 메시지가 표시됩니다.

   ```
   Successfully created/updated stack - cron-maintenance-app in us-west-2
   ```

1. Lambda 콘솔의 [함수](https://console.aws.amazon.com/lambda/home#/functions) 페이지를 열어 Lambda 함수가 생성되었는지 확인할 수도 있습니다. `ScheduledDBMaintenance`라는 함수가 표시됩니다.

------

## 앱 테스트
<a name="scheduled-task-app-test-app"></a>

 일정이 함수를 올바르게 트리거하고 함수가 데이터베이스의 레코드를 올바르게 정리하는지 테스트하려면 일정을 일시적으로 수정하여 특정 시간에 한 번만 실행되도록 할 수 있습니다. 그런 다음 `sam deploy`를 다시 실행하여 한 달에 한 번 실행되도록 반복 일정을 다시 설정할 수 있습니다.

**AWS Management Console을 사용하여 애플리케이션을 실행하려면**

1. EventBridge 스케줄러 콘솔 페이지로 돌아갑니다.

1. 일정을 선택한 다음 **편집**을 선택합니다.

1. **일정 패턴** 섹션의 **반복**에서 **일회성 일정**을 선택합니다.

1.  호출 시간을 지금부터 몇 분 후로 설정하고 설정을 검토한 다음 **저장**을 선택합니다.

 일정이 실행되고 대상을 간접 호출한 후 `test_app.py` 스크립트를 실행하여 함수가 DynamoDB 테이블에서 모든 이전 레코드를 성공적으로 제거했는지 확인합니다.

**Python 스크립트를 사용하여 이전 레코드가 삭제되었는지 확인하려면**

1.  명령줄에서 `test_app.py`를 저장한 폴더로 이동합니다.

1. 스크립트 실행.

   ```
   python test_app.py
   ```

    성공하면 다음과 같은 결과가 출력됩니다.

   ```
   Total number of old records: 0
   ```

## 다음 단계
<a name="scheduled-task-app-next-steps"></a>

 이제 EventBridge 스케줄러 일정을 수정하여 특정 애플리케이션의 요구 사항을 충족할 수 있습니다. EventBridge 스케줄러는 cron, 속도, 일회성 일정이라는 세 가지 유형의 일정을 지원합니다.

 EventBridge 스케줄러 일정 표현식에 대한 자세한 내용은 *EventBridge 스케줄러 사용자 가이드*의 [일정 유형](https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html)을 참조하세요. *IAM 사용 설명서*의 [액세스 관리](https://docs.aws.amazon.com/IAM/latest/UserGuide/access.html) 

# Lambda 지속성 함수를 사용하여 주문 처리 시스템 생성
<a name="order-processing-app"></a>

**참고**  
필요: API Gateway, 지속성 함수 워크플로 및 지원 서비스(DynamoDB, EventBridge)를 보여주는 아키텍처 다이어그램 추가

## 사전 조건
<a name="order-processing-prerequisites"></a>
+ AWS CLI 설치 및 구성
+ 필요: 특정 지속성 함수 요구 사항

## 소스 코드 파일 생성
<a name="order-processing-source"></a>

프로젝트 디렉터리에 다음 파일을 생성합니다.
+ `lambda_function.py` - 함수 코드
+ `requirements.txt` - 종속성 매니페스트

### 함수 코드
<a name="order-processing-function-code"></a>

```
# NEED: Verify correct imports
import boto3
import json

def lambda_handler(event, context):
    # NEED: Verify DurableContext syntax
    durable = context.durable
    
    try:
        # Validate and store order
        order = await durable.step('validate', async () => {
            return validate_order(event['order'])
        })
        
        # Process payment
        # NEED: Verify wait syntax
        await durable.wait(/* wait configuration */)
        
        # Additional steps
        # NEED: Complete implementation
        
    except Exception as e:
        # NEED: Error handling patterns
        raise e

def validate_order(order_data):
    # NEED: Implementation
    pass
```

### 요구 사항 파일
<a name="order-processing-requirements"></a>

```
# NEED: List of required packages
```

## 앱 배포
<a name="order-processing-deploy"></a>

### 주문에 대한 DynamoDB 테이블 생성
<a name="order-processing-dynamodb"></a>

1. [https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)에서 DynamoDB 콘솔을 엽니다.

1. **테이블 생성** 선택

1. **테이블 이름**에 **Orders** 입력

1. **파티션 키**에 **orderId** 입력

1. 다른 설정은 기본값 유지

1. **테이블 생성** 선택

### Lambda 함수 생성
<a name="order-processing-lambda"></a>

1. [https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)에서 Lambda 콘솔 열기

1. **함수 생성** 선택

1. **새로 작성** 선택

1. **함수 이름**에 **ProcessOrder**을 입력합니다.

1. **런타임**에서 원하는 런타임 선택

1. 필요: 지속성 함수별 구성 추가

1. **함수 생성** 선택

### API Gateway 엔드포인트 생성
<a name="order-processing-apigateway"></a>

1. [https://console.aws.amazon.com/apigateway/](https://console.aws.amazon.com/apigateway/)에서 API Gateway 콘솔 열기

1. **API 생성** 선택

1. **HTTP API** 선택

1. **빌드** 선택

1. Lambda 함수와의 통합 추가

1. 주문 처리 경로 구성

1. API 배포

## 앱 테스트
<a name="order-processing-test"></a>

테스트 주문을 제출합니다.

```
{
    "orderId": "12345",
    "items": [
        {
            "productId": "ABC123",
            "quantity": 1
        }
    ]
}
```

필요: 지속성 함수에 대한 특정 모니터링 지침 추가

## 다음 단계
<a name="order-processing-next-steps"></a>

### 비즈니스 로직 추가
<a name="order-processing-business-logic"></a>

재고 관리를 구현합니다.

```
async def check_inventory(order):
    # Add inventory check logic
    pass
```

가격 계산을 추가합니다.

```
async def calculate_total(order):
    # Add pricing logic
    pass
```

### 오류 처리 개선
<a name="order-processing-error-handling"></a>

보정 로직을 추가합니다.

```
async def reverse_payment(order):
    # Add payment reversal logic
    pass
```

주문 취소를 처리합니다.

```
async def cancel_order(order):
    # Add cancellation logic
    pass
```

### 외부 시스템 통합
<a name="order-processing-integrations"></a>

```
async def notify_shipping_provider(order):
    # Add shipping integration
    pass

async def send_customer_notification(order):
    # Add notification logic
    pass
```

### 모니터링 개선
<a name="order-processing-monitoring"></a>
+ CloudWatch 대시보드 생성
+ 주문 처리 시간 관련 지표 설정
+ 지연된 주문에 대한 알림 구성