속성 변수를 사용한 모니터링 대시보드로 여러 서버리스 함수 모니터링하기
속성 변수를 사용한 모니터링 대시보드로 여러 서버리스 함수 모니터링하기
이 코드 예제에서는 CloudWatch 대시보드에 속성 변수(property variable)를 추가해 여러 Lambda 함수를 모니터링하는 방법을 보여드려요.
출처: 문서
본문
다음 작업을 다루어요.
- 모니터링용 Lambda 함수 생성
- CloudWatch 대시보드 생성
- 대시보드에 속성 변수 추가
- 리소스 정리
Bash (AWS CLI with Bash script)
Note GitHub에 더 많은 내용이 있어요. 전체 예제와 설정·실행 방법은 Sample developer tutorials 저장소에서 확인할 수 있어요.
이 스크립트는 두 개의 Lambda 함수와 IAM 실행 역할을 만들고, CloudWatch 대시보드를 생성한 뒤 콘솔에서 FunctionName 속성 변수를 추가하는 방법을 안내해요.
#!/bin/bash
# CloudWatch Dashboard with Lambda Function Variable Script
# This script creates a CloudWatch dashboard with a property variable for Lambda function names
set -euo pipefail
# Security: Set restrictive umask
umask 0077
# Set up logging with secure permissions
LOG_FILE="cloudwatch-dashboard-script-v4.log"
touch "$LOG_FILE"
chmod 600 "$LOG_FILE"
echo "Starting script execution at $(date)" >> "$LOG_FILE"
# Function to log commands and their output (with sensitive data sanitization)
log_cmd() {
local cmd="$1"
local sanitized_cmd="${cmd//--password*/--password [REDACTED]}"
sanitized_cmd="${sanitized_cmd//--secret*/--secret [REDACTED]}"
echo "$(date): Running command: $sanitized_cmd" >> "$LOG_FILE"
eval "$cmd" 2>&1 | tee -a "$LOG_FILE"
return ${PIPESTATUS[0]}
}
# Function to check for errors in command output
check_error() {
local cmd_output="$1"
local cmd_status="$2"
local error_msg="$3"
if [ $cmd_status -ne 0 ] || echo "$cmd_output" | grep -qi "error"; then
echo "ERROR: $error_msg" | tee -a "$LOG_FILE"
# Sanitize output before logging
local sanitized_output="${cmd_output//arn:aws:iam::[0-9]*/arn:aws:iam::ACCOUNT_ID}"
echo "Command output: $sanitized_output" | tee -a "$LOG_FILE"
cleanup_resources
exit 1
fi
}
# Trap errors and cleanup
trap 'cleanup_resources' EXIT ERR INT TERM
# Function to clean up resources
cleanup_resources() {
local exit_code=$?
echo "" | tee -a "$LOG_FILE"
echo "==========================================" | tee -a "$LOG_FILE"
echo "CLEANUP PROCESS" | tee -a "$LOG_FILE"
echo "==========================================" | tee -a "$LOG_FILE"
if [ -n "${DASHBOARD_NAME:-}" ]; then
echo "Deleting CloudWatch dashboard: $DASHBOARD_NAME" | tee -a "$LOG_FILE"
aws cloudwatch delete-dashboards --dashboard-names "$DASHBOARD_NAME" 2>&1 >> "$LOG_FILE" || true
fi
if [ -n "${LAMBDA_FUNCTION1:-}" ]; then
echo "Deleting Lambda function: $LAMBDA_FUNCTION1" | tee -a "$LOG_FILE"
aws lambda delete-function --function-name "$LAMBDA_FUNCTION1" 2>&1 >> "$LOG_FILE" || true
fi
if [ -n "${LAMBDA_FUNCTION2:-}" ]; then
echo "Deleting Lambda function: $LAMBDA_FUNCTION2" | tee -a "$LOG_FILE"
aws lambda delete-function --function-name "$LAMBDA_FUNCTION2" 2>&1 >> "$LOG_FILE" || true
fi
if [ -n "${ROLE_NAME:-}" ]; then
echo "Detaching policy from role: $ROLE_NAME" | tee -a "$LOG_FILE"
aws iam detach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>&1 >> "$LOG_FILE" || true
echo "Deleting IAM role: $ROLE_NAME" | tee -a "$LOG_FILE"
aws iam delete-role --role-name "$ROLE_NAME" 2>&1 >> "$LOG_FILE" || true
fi
# Clean up temporary files securely
shred -vfz -n 3 trust-policy.json lambda_function.py lambda_function.zip 2>/dev/null || rm -f trust-policy.json lambda_function.py lambda_function.zip
echo "Cleanup completed." | tee -a "$LOG_FILE"
return $exit_code
}
# Validate AWS CLI is installed and authenticated
if ! command -v aws &> /dev/null; then
echo "ERROR: AWS CLI is not installed" | tee -a "$LOG_FILE"
exit 1
fi
if ! aws sts get-caller-identity &> /dev/null; then
echo "ERROR: AWS CLI is not properly authenticated" | tee -a "$LOG_FILE"
exit 1
fi
# Get AWS region with validation
AWS_REGION=$(aws configure get region 2>/dev/null || echo "")
if [ -z "$AWS_REGION" ]; then
AWS_REGION="us-east-1"
echo "No region found in AWS config, defaulting to $AWS_REGION" | tee -a "$LOG_FILE"
else
echo "Using AWS region: $AWS_REGION" | tee -a "$LOG_FILE"
fi
# Validate region format
if ! [[ "$AWS_REGION" =~ ^[a-z]{2}-[a-z]+-[0-9]$ ]]; then
echo "ERROR: Invalid AWS region format: $AWS_REGION" | tee -a "$LOG_FILE"
exit 1
fi
# Generate unique identifiers using secure random with validation
RANDOM_ID=$(openssl rand -hex 6)
if [ -z "$RANDOM_ID" ] || [ ${#RANDOM_ID} -ne 12 ]; then
echo "ERROR: Failed to generate valid random identifier" | tee -a "$LOG_FILE"
exit 1
fi
DASHBOARD_NAME="LambdaMetricsDashboard-${RANDOM_ID}"
LAMBDA_FUNCTION1="TestFunction1-${RANDOM_ID}"
LAMBDA_FUNCTION2="TestFunction2-${RANDOM_ID}"
ROLE_NAME="LambdaExecutionRole-${RANDOM_ID}"
# Validate resource names don't exceed AWS limits
if [ ${#DASHBOARD_NAME} -gt 128 ] || [ ${#LAMBDA_FUNCTION1} -gt 64 ] || [ ${#ROLE_NAME} -gt 64 ]; then
echo "ERROR: Generated resource names exceed AWS limits" | tee -a "$LOG_FILE"
exit 1
fi
echo "Using random identifier: $RANDOM_ID" | tee -a "$LOG_FILE"
echo "Dashboard name: $DASHBOARD_NAME" | tee -a "$LOG_FILE"
echo "Lambda function names: $LAMBDA_FUNCTION1, $LAMBDA_FUNCTION2" | tee -a "$LOG_FILE"
echo "IAM role name: $ROLE_NAME" | tee -a "$LOG_FILE"
# Create IAM role for Lambda functions
echo "Creating IAM role for Lambda..." | tee -a "$LOG_FILE"
TRUST_POLICY='{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
echo "$TRUST_POLICY" > trust-policy.json
chmod 600 trust-policy.json
# Validate JSON before use
if ! python3 -m json.tool trust-policy.json > /dev/null 2>&1; then
echo "ERROR: Invalid trust policy JSON" | tee -a "$LOG_FILE"
exit 1
fi
ROLE_OUTPUT=$(log_cmd "aws iam create-role --role-name '$ROLE_NAME' --assume-role-policy-document file://trust-policy.json --tags Key=project,Value=doc-smith Key=tutorial,Value=cloudwatch-streams --output json")
check_error "$ROLE_OUTPUT" $? "Failed to create IAM role"
ROLE_ARN=$(echo "$ROLE_OUTPUT" | python3 -c "import sys, json; print(json.load(sys.stdin)['Role']['Arn'])" 2>/dev/null)
if [ -z "$ROLE_ARN" ]; then
echo "ERROR: Failed to extract Role ARN" | tee -a "$LOG_FILE"
exit 1
fi
echo "Role ARN: $ROLE_ARN" | tee -a "$LOG_FILE"
# Attach Lambda basic execution policy to the role
echo "Attaching Lambda execution policy to role..." | tee -a "$LOG_FILE"
POLICY_OUTPUT=$(log_cmd "aws iam attach-role-policy --role-name '$ROLE_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole")
check_error "$POLICY_OUTPUT" $? "Failed to attach policy to role"
# Wait for role to propagate
echo "Waiting for IAM role to propagate..." | tee -a "$LOG_FILE"
sleep 10
# Create simple Python Lambda function code with security validation
echo "Creating Lambda function code..." | tee -a "$LOG_FILE"
cat > lambda_function.py << 'LAMBDA_EOF'
def handler(event, context):
print("Lambda function executed successfully")
return {
'statusCode': 200,
'body': 'Success'
}
LAMBDA_EOF
chmod 600 lambda_function.py
# Validate Python syntax
if ! python3 -m py_compile lambda_function.py 2>/dev/null; then
echo "ERROR: Invalid Python syntax in Lambda function" | tee -a "$LOG_FILE"
exit 1
fi
# Zip the Lambda function code
zip -j -q lambda_function.zip lambda_function.py
if [ ! -f lambda_function.zip ]; then
echo "ERROR: Failed to create lambda_function.zip" | tee -a "$LOG_FILE"
exit 1
fi
chmod 600 lambda_function.zip
# Validate zip file integrity
if ! unzip -t lambda_function.zip > /dev/null 2>&1; then
echo "ERROR: Created zip file is corrupted" | tee -a "$LOG_FILE"
exit 1
fi
# Create first Lambda function
echo "Creating first Lambda function: $LAMBDA_FUNCTION1..." | tee -a "$LOG_FILE"
LAMBDA1_OUTPUT=$(log_cmd "aws lambda create-function --function-name '$LAMBDA_FUNCTION1' --runtime python3.11 --role '$ROLE_ARN' --handler lambda_function.handler --zip-file fileb://lambda_function.zip --timeout 30 --memory-size 128 --tags project=doc-smith,tutorial=cloudwatch-streams")
check_error "$LAMBDA1_OUTPUT" $? "Failed to create first Lambda function"
# Create second Lambda function
echo "Creating second Lambda function: $LAMBDA_FUNCTION2..." | tee -a "$LOG_FILE"
LAMBDA2_OUTPUT=$(log_cmd "aws lambda create-function --function-name '$LAMBDA_FUNCTION2' --runtime python3.11 --role '$ROLE_ARN' --handler lambda_function.handler --zip-file fileb://lambda_function.zip --timeout 30 --memory-size 128 --tags project=doc-smith,tutorial=cloudwatch-streams")
check_error "$LAMBDA2_OUTPUT" $? "Failed to create second Lambda function"
# Invoke Lambda functions to generate some metrics
echo "Invoking Lambda functions to generate metrics..." | tee -a "$LOG_FILE"
log_cmd "aws lambda invoke --function-name '$LAMBDA_FUNCTION1' --payload '{}' /dev/null" || true
log_cmd "aws lambda invoke --function-name '$LAMBDA_FUNCTION2' --payload '{}' /dev/null" || true
# Create CloudWatch dashboard with property variable
echo "Creating CloudWatch dashboard with property variable..." | tee -a "$LOG_FILE"
# Create dashboard body with proper escaping and validation
DASHBOARD_BODY=$(cat <<EOF
{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"metrics": [
[ "AWS/Lambda", "Invocations", "FunctionName", "$LAMBDA_FUNCTION1" ]
],
"view": "timeSeries",
"stacked": false,
"region": "$AWS_REGION",
"title": "Lambda Invocations",
"period": 300,
"stat": "Sum"
}
}
]
}
EOF
)
# Validate JSON before sending
if ! echo "$DASHBOARD_BODY" | python3 -m json.tool > /dev/null 2>&1; then
echo "ERROR: Dashboard body is not valid JSON" | tee -a "$LOG_FILE"
exit 1
fi
# First create a basic dashboard without variables
echo "Creating initial dashboard without variables..." | tee -a "$LOG_FILE"
DASHBOARD_OUTPUT=$(aws cloudwatch put-dashboard --dashboard-name "$DASHBOARD_NAME" --dashboard-body "$DASHBOARD_BODY" --output json 2>&1)
check_error "$DASHBOARD_OUTPUT" $? "Failed to create initial CloudWatch dashboard"
# Now let's try to add a property variable using the console instructions
echo "To complete the tutorial, please follow these steps in the CloudWatch console:" >> "$LOG_FILE"
# Verify dashboard creation
echo "Verifying dashboard creation..." | tee -a "$LOG_FILE"
VERIFY_OUTPUT=$(aws cloudwatch get-dashboard --dashboard-name "$DASHBOARD_NAME" --output json 2>&1)
check_error "$VERIFY_OUTPUT" $? "Failed to verify dashboard creation"
echo "" | tee -a "$LOG_FILE"
echo "==========================================" | tee -a "$LOG_FILE"
echo "DASHBOARD CREATED SUCCESSFULLY" | tee -a "$LOG_FILE"
echo "==========================================" | tee -a "$LOG_FILE"
echo "Dashboard Name: $DASHBOARD_NAME" | tee -a "$LOG_FILE"
echo "Lambda Functions: $LAMBDA_FUNCTION1, $LAMBDA_FUNCTION2" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
echo "You can view your dashboard in the CloudWatch console:" | tee -a "$LOG_FILE"
echo "https://console.aws.amazon.com/cloudwatch/home#dashboards:name=$DASHBOARD_NAME" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
echo "Script completed successfully." | tee -a "$LOG_FILE"
exit 0
참고: 대시보드에서 속성 변수를 추가하는 단계는 CloudWatch 콘솔에서 수행해요. Actions > Variables > Create a variable, Property variable 선택, Property로 FunctionName 선택, Select menu(dropdown) 입력 방식, Use the results of a metric search + Pre-built queries > Lambda > Errors를 선택하고 Add variable을 눌러 완성해요.
API 상세는 AWS CLI Command Reference의 다음 항목을 참고해요.
AttachRolePolicy,CreateFunction,CreateRole,DeleteDashboards,DeleteFunction,DeleteRole,DetachRolePolicy,GetDashboard,Invoke,PutDashboard