튜토리얼: Amazon S3 트리거를 사용해 Lambda 함수 호출
튜토리얼: Amazon S3 트리거를 사용해 Lambda 함수 호출
이 튜토리얼에서는 콘솔로 Lambda 함수를 만들고 Amazon Simple Storage Service(Amazon S3) 버킷의 트리거를 구성합니다. Amazon S3 버킷에 객체를 추가할 때마다 함수가 실행되고 그 객체 유형을 Amazon CloudWatch Logs에 출력합니다.
본문
이 튜토리얼은 다음을 보여줍니다.
- Amazon S3 버킷을 만듭니다.
- Amazon S3 버킷의 객체 유형을 반환하는 Lambda 함수를 만듭니다.
- 객체가 버킷에 업로드될 때 함수를 호출하는 Lambda 트리거를 구성합니다.
- 먼저 더미 이벤트로, 그다음 트리거를 사용해 함수를 테스트합니다.
이 단계를 완료하면 객체가 Amazon S3 버킷에 추가되거나 삭제될 때마다 실행되도록 Lambda 함수를 구성하는 방법을 배웁니다. AWS Management Console만으로 이 튜토리얼을 완료할 수 있습니다.
Amazon S3 버킷 만들기
- Amazon S3 콘솔을 열고 General purpose buckets 페이지를 선택합니다.
- 지리적 위치에 가장 가까운 AWS 리전을 선택합니다. 화면 상단의 드롭다운 목록으로 리전을 바꿀 수 있습니다. 튜토리얼 뒷부분에서 같은 리전에 Lambda 함수를 만들어야 합니다.
- Create bucket을 선택합니다.
- General configuration 아래에서 다음을 수행합니다.
- Bucket type에서 General purpose가 선택되었는지 확인합니다.
- Bucket name에 Amazon S3 버킷 명명 규칙을 충족하는 전역 고유 이름을 입력합니다. 버킷 이름은 소문자, 숫자, 점(.), 하이픈(-)만 포함할 수 있습니다.
- 다른 모든 옵션은 기본값으로 두고 Create bucket을 선택합니다.
버킷에 테스트 객체 업로드
- Amazon S3 콘솔의 Buckets 페이지를 열고 이전 단계에서 만든 버킷을 선택합니다.
- Upload를 선택합니다.
- Add files를 선택하고 업로드할 객체를 선택합니다. 아무 파일이나 선택할 수 있습니다(예:
HappyFace.jpg). - Open, Upload를 선택합니다.
튜토리얼 뒷부분에서 이 객체로 Lambda 함수를 테스트합니다.
권한 정책 만들기
Lambda가 Amazon S3 버킷에서 객체를 가져오고 Amazon CloudWatch Logs에 쓸 수 있게 하는 권한 정책을 만드세요.
- IAM 콘솔의 Policies 페이지를 엽니다.
- Create Policy를 선택합니다.
- JSON 탭을 선택하고 JSON 편집기에 다음 커스텀 정책을 붙여넣습니다.
{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:PutLogEvents",
"logs:CreateLogGroup",
"logs:CreateLogStream"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::*/*"
}
]
}
- Next: Tags를 선택합니다.
- Next: Review를 선택합니다.
- Review policy 아래의 policy Name에
s3-trigger-tutorial을 입력합니다. - Create policy를 선택합니다.
실행 역할 만들기
실행 역할(execution role)은 Lambda 함수에 AWS 서비스와 리소스에 접근할 권한을 부여하는 AWS Identity and Access Management(IAM) 역할입니다. 이 단계에서는 이전 단계에서 만든 권한 정책을 사용해 실행 역할을 만듭니다.
- IAM 콘솔의 Roles 페이지를 엽니다.
- Create role을 선택합니다.
- 신뢰할 수 있는 엔터티 유형으로 AWS service, 사용 사례로 Lambda를 선택합니다.
- Next를 선택합니다.
- 정책 검색 상자에
s3-trigger-tutorial을 입력합니다. - 검색 결과에서 만든 정책(
s3-trigger-tutorial)을 선택한 뒤 Next를 선택합니다. - Role details 아래의 Role name에
lambda-s3-trigger-role을 입력한 뒤 Create role을 선택합니다.
Lambda 함수 만들기
콘솔에서 Python 3.14 런타임으로 Lambda 함수를 만드세요.
- Lambda 콘솔의 Functions 페이지를 엽니다.
- Amazon S3 버킷을 만든 것과 같은 AWS 리전에서 작업하고 있는지 확인하세요. 화면 상단의 드롭다운 목록으로 리전을 바꿀 수 있습니다.
- Create function을 선택합니다.
- Author from scratch를 선택합니다.
- Basic information 아래에서 다음을 수행합니다.
- Function name에
s3-trigger-tutorial을 입력합니다. - Runtime에서 Python 3.14를 선택합니다.
- Architecture에서 x86_64를 선택합니다.
- Function name에
- Change default execution role 탭에서 다음을 수행합니다.
- 탭을 펼친 뒤 Use an existing role을 선택합니다.
- 앞서 만든
lambda-s3-trigger-role을 선택합니다.
- Create function을 선택합니다.
함수 코드 배포
이 튜토리얼은 Python 3.14 런타임을 사용하지만 다른 런타임용 예제 코드 파일도 제공합니다. 아래 상자의 탭을 선택해 관심 있는 런타임의 코드를 볼 수 있습니다.
Lambda 함수는 Amazon S3에서 받는 event 파라미터에서 업로드된 객체의 키 이름과 버킷 이름을 검색합니다. 그런 다음 AWS SDK for Python(Boto3)의 get_object 메서드로 객체의 메타데이터(업로드된 객체의 콘텐츠 유형(MIME type) 포함)를 검색합니다.
아래 상자에서 Python 탭을 선택하고 코드를 복사하세요.
GitHub에 더 많은 내용이 있습니다. 완전한 예제와 설정·실행 방법은 Serverless examples 저장소에서 확인하세요.
.NET으로 Lambda에서 S3 이벤트 소비 :
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.S3;
using System;
using Amazon.Lambda.S3Events;
using System.Web;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace S3Integration
{
public class Function
{
private static AmazonS3Client _s3Client;
public Function() : this(null)
{
}
internal Function(AmazonS3Client s3Client)
{
_s3Client = s3Client ?? new AmazonS3Client();
}
public async Task<string> Handler(S3Event evt, ILambdaContext context)
{
try
{
if (evt.Records.Count <= 0)
{
context.Logger.LogLine("Empty S3 Event received");
return string.Empty;
}
var bucket = evt.Records[0].S3.Bucket.Name;
var key = HttpUtility.UrlDecode(evt.Records[0].S3.Object.Key);
context.Logger.LogLine($"Request is for {bucket} and {key}");
var objectResult = await _s3Client.GetObjectAsync(bucket, key);
context.Logger.LogLine($"Returning {objectResult.Key}");
return objectResult.Key;
}
catch (Exception e)
{
context.Logger.LogLine($"Error processing request - {e.Message}");
return string.Empty;
}
}
}
}
Go로 Lambda에서 S3 이벤트 소비 :
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"log"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func handler(ctx context.Context, s3Event events.S3Event) error {
sdkConfig, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Printf("failed to load default config: %s", err)
return err
}
s3Client := s3.NewFromConfig(sdkConfig)
for _, record := range s3Event.Records {
bucket := record.S3.Bucket.Name
key := record.S3.Object.URLDecodedKey
headOutput, err := s3Client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &key,
})
if err != nil {
log.Printf("error getting head of object %s/%s: %s", bucket, key, err)
return err
}
log.Printf("successfully retrieved %s/%s of type %s", bucket, key, *headOutput.ContentType)
}
return nil
}
func main() {
lambda.Start(handler)
}
Java로 Lambda에서 S3 이벤트 소비 :
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package example;
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
import software.amazon.awssdk.services.s3.S3Client;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Handler implements RequestHandler<S3Event, String> {
private static final Logger logger = LoggerFactory.getLogger(Handler.class);
@Override
public String handleRequest(S3Event s3event, Context context) {
try {
S3EventNotificationRecord record = s3event.getRecords().get(0);
String srcBucket = record.getS3().getBucket().getName();
String srcKey = record.getS3().getObject().getUrlDecodedKey();
S3Client s3Client = S3Client.builder().build();
HeadObjectResponse headObject = getHeadObject(s3Client, srcBucket, srcKey);
logger.info("Successfully retrieved " + srcBucket + "/" + srcKey + " of type " + headObject.contentType());
return "Ok";
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private HeadObjectResponse getHeadObject(S3Client s3Client, String bucket, String key) {
HeadObjectRequest headObjectRequest = HeadObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
return s3Client.headObject(headObjectRequest);
}
}
JavaScript로 Lambda에서 S3 이벤트 소비 :
import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
const client = new S3Client();
export const handler = async (event, context) => {
// Get the object from the event and show its content type
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
try {
const { ContentType } = await client.send(new HeadObjectCommand({
Bucket: bucket,
Key: key,
}));
console.log('CONTENT TYPE:', ContentType);
return ContentType;
} catch (err) {
console.log(err);
const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
}
};
TypeScript로 Lambda에서 S3 이벤트 소비 :
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { S3Event } from 'aws-lambda';
import { S3Client, HeadObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: process.env.AWS_REGION });
export const handler = async (event: S3Event): Promise<string | undefined> => {
// Get the object from the event and show its content type
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const params = {
Bucket: bucket,
Key: key,
};
try {
const { ContentType } = await s3.send(new HeadObjectCommand(params));
console.log('CONTENT TYPE:', ContentType);
return ContentType;
} catch (err) {
console.log(err);
const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
}
};
PHP로 Lambda에서 S3 이벤트 소비 :
<?php
use Bref\Context\Context;
use Bref\Event\S3\S3Event;
use Bref\Event\S3\S3Handler;
use Bref\Logger\StderrLogger;
require __DIR__ . '/vendor/autoload.php';
class Handler extends S3Handler
{
private StderrLogger $logger;
public function __construct(StderrLogger $logger)
{
$this->logger = $logger;
}
public function handleS3(S3Event $event, Context $context) : void
{
$this->logger->info("Processing S3 records");
// Get the object from the event and show its content type
$records = $event->getRecords();
foreach ($records as $record)
{
$bucket = $record->getBucket()->getName();
$key = urldecode($record->getObject()->getKey());
try {
$fileSize = urldecode($record->getObject()->getSize());
echo "File Size: " . $fileSize . "\n";
// TODO: Implement your custom processing logic here
} catch (Exception $e) {
echo $e->getMessage() . "\n";
echo 'Error getting object ' . $key . ' from bucket ' . $bucket . '. Make sure they exist and your bucket is in the same region as this function.' . "\n";
throw $e;
}
}
}
}
$logger = new StderrLogger();
return new Handler($logger);
Python으로 Lambda에서 S3 이벤트 소비 :
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
import json
import urllib.parse
import boto3
print('Loading function')
s3 = boto3.client('s3')
def lambda_handler(event, context):
#print("Received event: " + json.dumps(event, indent=2))
# Get the object from the event and show its content type
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
try:
response = s3.get_object(Bucket=bucket, Key=key)
print("CONTENT TYPE: " + response['ContentType'])
return response['ContentType']
except Exception as e:
print(e)
print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
raise e
Ruby로 Lambda에서 S3 이벤트 소비 :
require 'json'
require 'uri'
require 'aws-sdk'
puts 'Loading function'
def lambda_handler(event:, context:)
s3 = Aws::S3::Client.new(region: 'region') # Your AWS region
# puts "Received event: #{JSON.dump(event)}"
# Get the object from the event and show its content type
bucket = event['Records'][0]['s3']['bucket']['name']
key = URI.decode_www_form_component(event['Records'][0]['s3']['object']['key'], Encoding::UTF_8)
begin
response = s3.get_object(bucket: bucket, key: key)
puts "CONTENT TYPE: #{response.content_type}"
return response.content_type
rescue StandardError => e
puts e.message
puts "Error getting object #{key} from bucket #{bucket}. Make sure they exist and your bucket is in the same region as this function."
raise e
end
end
Rust로 Lambda에서 S3 이벤트 소비 :
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use aws_lambda_events::event::s3::S3Event;
use aws_sdk_s3::{Client};
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
/// Main function
#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.without_time()
.init();
// Initialize the AWS SDK for Rust
let config = aws_config::load_from_env().await;
let s3_client = Client::new(&config);
let res = run(service_fn(|request: LambdaEvent<S3Event>| {
function_handler(&s3_client, request)
})).await;
res
}
async fn function_handler(
s3_client: &Client,
evt: LambdaEvent<S3Event>
) -> Result<(), Error> {
tracing::info!(records = ?evt.payload.records.len(), "Received request from SQS");
if evt.payload.records.len() == 0 {
tracing::info!("Empty S3 event received");
}
let bucket = evt.payload.records[0].s3.bucket.name.as_ref().expect("Bucket name to exist");
let key = evt.payload.records[0].s3.object.key.as_ref().expect("Object key to exist");
tracing::info!("Request is for {} and object {}", bucket, key);
let s3_get_object_result = s3_client
.get_object()
.bucket(bucket)
.key(key)
.send()
.await;
match s3_get_object_result {
Ok(_) => tracing::info!("S3 Get Object success, the s3GetObjectResult contains a 'body' property of type ByteStream"),
Err(_) => tracing::info!("Failure with S3 Get Object request")
}
Ok(())
}
Lambda 콘솔의 Code source 창에서 Lambda가 만든 코드를 대체해 코드 편집기에 코드를 붙여넣습니다.
DEPLOY 섹션에서 Deploy를 선택해 함수의 코드를 갱신합니다.
Amazon S3 트리거 만들기
- Function overview 창에서 Add trigger를 선택합니다.
- S3를 선택합니다.
- Bucket 아래에서 튜토리얼 앞부분에서 만든 버킷을 선택합니다.
- Event types 아래에서 All object create events가 선택되었는지 확인합니다.
- Recursive invocation 아래에서 같은 Amazon S3 버킷을 입력·출력에 사용하는 것은 권장하지 않는다는 점을 인정하는 체크박스를 선택합니다.
- Add를 선택합니다.
Lambda 콘솔로 Lambda 함수의 Amazon S3 트리거를 만들 때 Amazon S3는 지정한 버킷에 이벤트 알림을 구성합니다. 이 이벤트 알림을 구성하기 전에 Amazon S3는 이벤트 대상이 존재하고 필요한 IAM 정책이 있는지 확인하는 일련의 검사를 수행합니다. Amazon S3는 그 버킷에 구성된 다른 이벤트 알림에 대해서도 이 테스트를 수행합니다.
이 검사 때문에, 버킷에 더 이상 존재하지 않는 리소스나 필요한 권한 정책이 없는 리소스에 대해 이전에 구성된 이벤트 대상이 있다면 Amazon S3는 새 이벤트 알림을 만들 수 없습니다. 트리거를 만들 수 없음을 나타내는 다음 오류 메시지가 표시됩니다.
An error occurred when creating the trigger: Unable to validate the following destination configurations.
같은 버킷을 사용해 다른 Lambda 함수의 트리거를 이전에 구성했는데 그 함수를 삭제했거나 권한 정책을 수정했다면 이 오류가 나타날 수 있습니다.
더미 이벤트로 Lambda 함수 테스트
- Lambda 콘솔의 함수 페이지에서 Test 탭을 선택합니다.
- Event name에
MyTestEvent를 입력합니다. - Event JSON에 다음 테스트 이벤트를 붙여넣습니다. 다음 값을 반드시 바꾸세요.
us-east-1을 Amazon S3 버킷을 만든 리전으로 바꿉니다.amzn-s3-demo-bucket두 곳을 자체 Amazon S3 버킷 이름으로 바꿉니다.test%2FKey를 앞서 버킷에 업로드한 테스트 객체 이름(예:HappyFace.jpg)으로 바꿉니다.
{
"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": "amzn-s3-demo-bucket",
"ownerIdentity": {
"principalId": "EXAMPLE"
},
"arn": "arn:aws:s3:::amzn-s3-demo-bucket"
},
"object": {
"key": "test%2Fkey",
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901"
}
}
}
]
}
- Save를 선택합니다.
- Test를 선택합니다.
함수가 성공적으로 실행되면 Execution results 탭에 다음과 비슷한 출력이 표시됩니다.
Response
"image/jpeg"
Function Logs
START RequestId: 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 Version: $LATEST
2021-02-18T21:40:59.280Z 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 INFO INPUT BUCKET AND KEY: { Bucket: 'amzn-s3-demo-bucket', Key: 'HappyFace.jpg' }
2021-02-18T21:41:00.215Z 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 INFO CONTENT TYPE: image/jpeg
END RequestId: 12b3cae7-5f4e-415e-93e6-416b8f8b66e6
REPORT RequestId: 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 Duration: 976.25 ms Billed Duration: 977 ms Memory Size: 128 MB Max Memory Used: 90 MB Init Duration: 430.47 ms
Request ID
12b3cae7-5f4e-415e-93e6-416b8f8b66e6
Amazon S3 트리거로 Lambda 함수 테스트
구성된 트리거로 함수를 테스트하려면 콘솔을 사용해 Amazon S3 버킷에 객체를 업로드하세요. Lambda 함수가 예상대로 실행되었는지 확인하려면 CloudWatch Logs로 함수의 출력을 보세요.
- Amazon S3 콘솔의 Buckets 페이지를 열고 앞서 만든 버킷을 선택합니다.
- Upload를 선택합니다.
- Add files를 선택하고 파일 선택기를 사용해 업로드할 객체를 선택합니다. 이 객체는 어떤 파일이든 선택할 수 있습니다.
- Open, Upload를 선택합니다.
- CloudWatch 콘솔을 엽니다.
- Lambda 함수를 만든 것과 같은 AWS 리전에서 작업하고 있는지 확인하세요. 화면 상단의 드롭다운 목록으로 리전을 바꿀 수 있습니다.
- Logs, Log groups를 선택합니다.
- 함수의 로그 그룹(
/aws/lambda/s3-trigger-tutorial)을 선택합니다. - Log streams 아래에서 가장 최근 로그 스트림을 선택합니다.
함수가 Amazon S3 트리거에 응답해 올바르게 호출되었다면 다음과 비슷한 출력이 표시됩니다. 보이는 CONTENT TYPE은 버킷에 업로드한 파일 유형에 따라 다릅니다.
2022-05-09T23:17:28.702Z 0cae7f5a-b0af-4c73-8563-a3430333cc10 INFO CONTENT TYPE: image/jpeg
리소스 정리
이 튜토리얼을 위해 만든 리소스를 유지하려 하지 않는다면 이제 삭제할 수 있습니다. 더 이상 사용하지 않는 AWS 리소스를 삭제하면 AWS 계정에 불필요한 비용이 청구되는 것을 막을 수 있습니다.
- Lambda 콘솔의 Functions 페이지를 엽니다.
- 만든 함수를 선택합니다.
- Actions, Delete를 선택합니다.
- 텍스트 입력 필드에
confirm을 입력하고 Delete를 선택합니다. - IAM 콘솔의 Roles 페이지를 엽니다.
- 만든 실행 역할을 선택합니다.
- Delete를 선택합니다.
- 텍스트 입력 필드에 역할 이름을 입력하고 Delete를 선택합니다.
- Amazon S3 콘솔을 엽니다.
- 만든 버킷을 선택합니다.
- Delete를 선택합니다.
- 텍스트 입력 필드에 버킷 이름을 입력합니다.
- Delete bucket을 선택합니다.
다음 단계
'튜토리얼: Amazon S3 트리거를 사용해 썸네일 이미지 만들기'에서 Amazon S3 트리거는 버킷에 업로드된 각 이미지 파일에 대한 썸네일 이미지를 만드는 함수를 호출합니다. 이 튜토리얼은 중간 수준의 AWS 및 Lambda 도메인 지식을 요구합니다. AWS Command Line Interface(AWS CLI)로 리소스를 만드는 방법과 함수 및 그 의존성에 대한 .zip 파일 아카이브 배포 패키지를 만드는 방법을 보여줍니다.