첫 번째 서버리스 함수 생성하기
첫 번째 서버리스 함수 생성하기
이 코드 예제에서는 Lambda 함수를 만들고 호출하며 CloudWatch 로그를 확인하는 방법을 보여드려요.
출처: 문서
본문
- Lambda용 IAM 역할 생성
- 함수 코드 생성
- Lambda 함수 생성
- Lambda 함수 테스트
- 리소스 정리
Bash (AWS CLI with Bash script)
Note GitHub에 더 많은 내용이 있어요. 전체 예제와 설정·실행 방법은 Sample developer tutorials 저장소에서 확인할 수 있어요.
이 스크립트는 Lambda 함수를 만들고, 테스트 이벤트로 호출하며, CloudWatch 로그를 조회하고, 모든 리소스를 정리해요. 기본 런타임은 Python 3.13이며 Node.js 22.x도 선택할 수 있어요.
#!/bin/bash
# AWS Lambda - Create Your First Function
# This script creates a Lambda function, invokes it with a test event,
# views CloudWatch logs, and cleans up all resources.
#
# Source: https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html
#
# Resources created:
# - IAM role (Lambda execution role with basic logging permissions)
# - Lambda function (Python 3.13 or Node.js 22.x runtime)
# - CloudWatch log group (created automatically by Lambda on invocation)
set -eE -o pipefail
###############################################################################
# Setup
###############################################################################
UNIQUE_ID=$(head -c 8 /dev/urandom | od -An -tx1 | tr -d ' ')
FUNCTION_NAME="my-lambda-function-${UNIQUE_ID}"
ROLE_NAME="lambda-execution-role-${UNIQUE_ID}"
LOG_GROUP_NAME="/aws/lambda/${FUNCTION_NAME}"
TEMP_DIR=$(mktemp -d)
readonly TEMP_DIR
LOG_FILE="${TEMP_DIR}/lambda-gettingstarted.log"
exec > >(tee -a "$LOG_FILE") 2>&1
declare -a CREATED_RESOURCES
###############################################################################
# Helper functions
###############################################################################
cleanup_resources() {
# Disable error trap to prevent recursion during cleanup
trap - ERR
set +eE
echo ""
echo "Cleaning up resources..."
echo ""
for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)); do
local RESOURCE="${CREATED_RESOURCES[$i]}"
local TYPE="${RESOURCE%%:*}"
local NAME="${RESOURCE#*:}"
case "$TYPE" in
log-group)
echo "Deleting CloudWatch log group: ${NAME}"
aws logs delete-log-group \
--log-group-name "$NAME" 2>&1 || echo " WARNING: Could not delete log group ${NAME}."
;;
lambda-function)
echo "Deleting Lambda function: ${NAME}"
aws lambda delete-function \
--function-name "$NAME" 2>&1 || echo " WARNING: Could not delete Lambda function ${NAME}."
echo " Waiting for function deletion to complete..."
local DELETE_WAIT=0
while aws lambda get-function --function-name "$NAME" > /dev/null 2>&1; do
sleep 2
DELETE_WAIT=$((DELETE_WAIT + 2))
if [ "$DELETE_WAIT" -ge 60 ]; then
echo " WARNING: Timed out waiting for function deletion."
break
fi
done
;;
iam-role-policy)
local ROLE_PART="${NAME%%|*}"
local POLICY_PART="${NAME#*|}"
echo "Detaching policy from role: ${ROLE_PART}"
aws iam detach-role-policy \
--role-name "$ROLE_PART" \
--policy-arn "$POLICY_PART" 2>&1 || echo " WARNING: Could not detach policy from role ${ROLE_PART}."
;;
iam-role)
echo "Deleting IAM role: ${NAME}"
aws iam delete-role \
--role-name "$NAME" 2>&1 || echo " WARNING: Could not delete IAM role ${NAME}."
;;
esac
done
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
fi
echo ""
echo "Cleanup complete."
}
handle_error() {
echo ""
echo "==========================================="
echo "ERROR: Script failed at $1"
echo "==========================================="
echo ""
if [ ${#CREATED_RESOURCES[@]} -gt 0 ]; then
echo "Attempting to clean up ${#CREATED_RESOURCES[@]} resource(s)..."
cleanup_resources
fi
exit 1
}
trap 'handle_error "line $LINENO"' ERR
wait_for_resource() {
local DESCRIPTION="$1"
local COMMAND="$2"
local TARGET_VALUE="$3"
local TIMEOUT=300
local ELAPSED=0
local INTERVAL=5
echo "Waiting for ${DESCRIPTION}..."
while true; do
local RESULT
RESULT=$(eval "$COMMAND" 2>&1) || true
if echo "$RESULT" | grep -q "$TARGET_VALUE"; then
echo " ${DESCRIPTION} is ready."
return 0
fi
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
echo "ERROR: Timed out waiting for ${DESCRIPTION} after ${TIMEOUT} seconds."
return 1
fi
sleep "$INTERVAL"
ELAPSED=$((ELAPSED + INTERVAL))
done
}
validate_input() {
local input="$1"
local pattern="$2"
if ! [[ "$input" =~ $pattern ]]; then
echo "ERROR: Invalid input: $input"
return 1
fi
return 0
}
###############################################################################
# Region pre-check
###############################################################################
CONFIGURED_REGION=$(aws configure get region 2>/dev/null || true)
if [ -z "$CONFIGURED_REGION" ] && [ -z "$AWS_DEFAULT_REGION" ] && [ -z "$AWS_REGION" ]; then
echo "ERROR: No AWS region configured."
echo "Run 'aws configure set region <region>' or export AWS_DEFAULT_REGION."
exit 1
fi
###############################################################################
# Runtime selection
###############################################################################
echo ""
echo "==========================================="
echo "AWS Lambda - Create Your First Function"
echo "==========================================="
echo ""
echo "Select a runtime for your Lambda function:"
echo " 1) Python 3.13"
echo " 2) Node.js 22.x"
echo ""
echo "Using default: Python 3.13"
RUNTIME_CHOICE="1"
case "$RUNTIME_CHOICE" in
1)
RUNTIME="python3.13"
HANDLER="lambda_function.lambda_handler"
CODE_FILE="lambda_function.py"
cat > "${TEMP_DIR}/${CODE_FILE}" << 'PYTHON_EOF'
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
if not isinstance(event, dict) or 'length' not in event or 'width' not in event:
raise ValueError('Event must contain length and width')
try:
length = float(event['length'])
width = float(event['width'])
if length < 0 or width < 0:
raise ValueError('Length and width must be non-negative')
area = calculate_area(length, width)
print(f'The area is {area}')
logger.info(f'CloudWatch logs group: {context.log_group_name}')
return json.dumps({'area': area})
except (TypeError, ValueError) as e:
logger.error(f'Error processing input: {str(e)}')
raise
def calculate_area(length, width):
return length * width
PYTHON_EOF
echo "Selected runtime: Python 3.13"
;;
2)
RUNTIME="nodejs22.x"
HANDLER="index.handler"
CODE_FILE="index.mjs"
cat > "${TEMP_DIR}/${CODE_FILE}" << 'NODEJS_EOF'
export const handler = async (event, context) => {
if (!event || typeof event.length !== 'number' || typeof event.width !== 'number') {
throw new Error('Event must contain numeric length and width');
}
if (event.length < 0 || event.width < 0) {
throw new Error('Length and width must be non-negative');
}
const area = event.length * event.width;
console.log(`The area is ${area}`);
console.log('CloudWatch log group: ', context.logGroupName);
return JSON.stringify({area});
};
NODEJS_EOF
echo "Selected runtime: Node.js 22.x"
;;
*)
echo "ERROR: Invalid choice. Please enter 1 or 2."
exit 1
;;
esac
###############################################################################
# Step 1: Create IAM execution role
###############################################################################
echo ""
echo "==========================================="
echo "Step 1: Create IAM execution role"
echo "==========================================="
echo ""
TRUST_POLICY='{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
echo "Creating IAM role: ${ROLE_NAME}"
ROLE_OUTPUT=$(aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document "$TRUST_POLICY" \
--query 'Role.Arn' \
--output text 2>&1)
if ! validate_input "$ROLE_OUTPUT" "^arn:aws:iam::[0-9]+:role/"; then
echo "ERROR: Failed to create IAM role"
exit 1
fi
echo "$ROLE_OUTPUT"
ROLE_ARN="$ROLE_OUTPUT"
CREATED_RESOURCES+=("iam-role:${ROLE_NAME}")
echo "Role ARN: ${ROLE_ARN}"
aws iam tag-role \
--role-name "$ROLE_NAME" \
--tags Key=project,Value=doc-smith Key=tutorial,Value=lambda-gettingstarted
echo ""
echo "Attaching AWSLambdaBasicExecutionRole policy..."
aws iam attach-role-policy \
--role-name "$ROLE_NAME" \
--policy-arn "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 2>&1
CREATED_RESOURCES+=("iam-role-policy:${ROLE_NAME}|arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole")
echo "Policy attached."
# IAM roles can take a few seconds to propagate
echo "Waiting for IAM role to propagate..."
sleep 10
###############################################################################
# Step 2: Create Lambda function
###############################################################################
echo ""
echo "==========================================="
echo "Step 2: Create Lambda function"
echo "==========================================="
echo ""
echo "Creating deployment package..."
ORIGINAL_DIR=$(pwd)
cd "$TEMP_DIR" || exit 1
zip -j function.zip "$CODE_FILE" > /dev/null 2>&1 || {
echo "ERROR: Failed to create deployment package"
exit 1
}
cd "$ORIGINAL_DIR" || exit 1
if [ ! -f "${TEMP_DIR}/function.zip" ]; then
echo "ERROR: Deployment package creation failed"
exit 1
fi
echo "Creating Lambda function: ${FUNCTION_NAME}"
echo " Runtime: ${RUNTIME}"
echo " Handler: ${HANDLER}"
echo ""
CREATE_OUTPUT=$(aws lambda create-function \
--function-name "$FUNCTION_NAME" \
--runtime "$RUNTIME" \
--role "$ROLE_ARN" \
--handler "$HANDLER" \
--architectures x86_64 \
--zip-file "fileb://${TEMP_DIR}/function.zip" \
--tags project=doc-smith,tutorial=lambda-gettingstarted \
--query '[FunctionName, FunctionArn, Runtime, State]' \
--output text 2>&1)
if [ -z "$CREATE_OUTPUT" ]; then
echo "ERROR: Failed to create Lambda function"
exit 1
fi
echo "$CREATE_OUTPUT"
CREATED_RESOURCES+=("lambda-function:${FUNCTION_NAME}")
wait_for_resource "Lambda function to become Active" \
"aws lambda get-function-configuration --function-name ${FUNCTION_NAME} --query State --output text" \
"Active"
###############################################################################
# Step 3: Invoke the function
###############################################################################
echo ""
echo "==========================================="
echo "Step 3: Invoke the function"
echo "==========================================="
echo ""
TEST_EVENT='{"length": 6, "width": 7}'
echo "Invoking function with test event: ${TEST_EVENT}"
echo ""
echo "$TEST_EVENT" > "${TEMP_DIR}/test-event.json"
if ! validate_input "$TEST_EVENT" '"length": [0-9]+, "width": [0-9]+'; then
echo "ERROR: Invalid test event format"
exit 1
fi
INVOKE_OUTPUT=$(aws lambda invoke \
--function-name "$FUNCTION_NAME" \
--payload "fileb://${TEMP_DIR}/test-event.json" \
--cli-read-timeout 30 \
"${TEMP_DIR}/response.json" 2>&1)
echo "$INVOKE_OUTPUT"
if [ ! -f "${TEMP_DIR}/response.json" ]; then
echo "ERROR: No response file generated"
exit 1
fi
RESPONSE=$(cat "${TEMP_DIR}/response.json")
echo ""
echo "Function response: ${RESPONSE}"
echo ""
if echo "$INVOKE_OUTPUT" | grep -qi "functionerror"; then
echo "WARNING: Function returned an error."
fi
###############################################################################
# Step 4: View CloudWatch logs
###############################################################################
echo ""
echo "==========================================="
echo "Step 4: View CloudWatch Logs"
echo "==========================================="
echo ""
echo "Log group: ${LOG_GROUP_NAME}"
echo ""
echo "Waiting for CloudWatch logs to be available..."
LOG_STREAMS=""
for i in $(seq 1 6); do
LOG_STREAMS=$(aws logs describe-log-streams \
--log-group-name "$LOG_GROUP_NAME" \
--order-by LastEventTime \
--descending \
--query 'logStreams[0].logStreamName' \
--output text 2>/dev/null) || true
if [ -n "$LOG_STREAMS" ] && [ "$LOG_STREAMS" != "None" ]; then
break
fi
LOG_STREAMS=""
sleep 5
done
if [ -n "$LOG_STREAMS" ] && [ "$LOG_STREAMS" != "None" ]; then
echo "Latest log stream: ${LOG_STREAMS}"
echo ""
echo "--- Log events ---"
LOG_EVENTS=$(aws logs get-log-events \
--log-group-name "$LOG_GROUP_NAME" \
--log-stream-name "$LOG_STREAMS" \
--query 'events[].message' \
--output text 2>&1) || true
echo "$LOG_EVENTS"
echo "--- End of log events ---"
else
echo "No log streams found yet. Logs may take a moment to appear."
echo "You can view them in the CloudWatch console:"
echo " Log group: ${LOG_GROUP_NAME}"
fi
CREATED_RESOURCES+=("log-group:${LOG_GROUP_NAME}")
aws logs tag-log-group \
--log-group-name "$LOG_GROUP_NAME" \
--tags project=doc-smith,tutorial=lambda-gettingstarted
###############################################################################
# Summary and cleanup
###############################################################################
echo ""
echo "==========================================="
echo "SUMMARY"
echo "==========================================="
echo ""
echo "Resources created:"
echo " IAM role: ${ROLE_NAME}"
echo " Lambda function: ${FUNCTION_NAME}"
echo " CloudWatch logs: ${LOG_GROUP_NAME}"
echo ""
echo "==========================================="
echo "CLEANUP"
echo "==========================================="
echo ""
echo "Cleaning up all created resources..."
cleanup_resources
echo ""
echo "Done."
API 상세는 AWS CLI Command Reference의 다음 항목을 참고해요.
AttachRolePolicy,CreateFunction,CreateRole,DeleteFunction,DeleteLogGroup,DeleteRole,DescribeLogStreams,DetachRolePolicy,GetFunction,GetFunctionConfiguration,GetLogEvents,Invoke