속성 변수를 사용한 모니터링 대시보드로 여러 서버리스 함수 모니터링하기

속성 변수를 사용한 모니터링 대시보드로 여러 서버리스 함수 모니터링하기

이 코드 예제에서는 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

더 알아보기 (Learn more)