如何仅在AWS CloudFormation栈创建时调用AWS Lambda函数?
Got it, let's break this down. You need a standalone CloudFormation stack that runs a Lambda function only once (on initial stack creation) to connect to your existing RDS instance and create a new database—without re-running on stack updates. Here's a step-by-step solution tailored to your Node.js 8.10 Lambda:
Core Idea: Use CloudFormation Custom Resources
CloudFormation Custom Resources send three types of events to your Lambda: Create, Update, and Delete. We'll configure the Lambda to only execute the database creation logic when it receives a Create event. For Update/Delete, it'll just send a success response back to CloudFormation without doing anything.
Step 1: Write the Lambda Function (Node.js 8.10)
This function will handle CloudFormation events, connect to your RDS instance, and create the database only on initial stack creation. It also includes error handling and proper CloudFormation response formatting.
const mysql = require('mysql'); const response = require('cfn-response'); exports.handler = (event, context) => { // Only run logic on CREATE event if (event.RequestType !== 'Create') { console.log(`Skipping ${event.RequestType} event`); response.send(event, context, response.SUCCESS); return; } // RDS connection details (pull from event properties) const dbConfig = { host: event.ResourceProperties.RdsEndpoint, user: event.ResourceProperties.RdsUsername, password: event.ResourceProperties.RdsPassword, port: 3306, connectTimeout: 10000 }; const dbName = event.ResourceProperties.DatabaseName; const connection = mysql.createConnection(dbConfig); connection.connect((err) => { if (err) { console.error('Failed to connect to RDS:', err); response.send(event, context, response.FAILED, { Error: err.message }); connection.end(); return; } // Safety net: only create if database doesn't exist const createDbQuery = `CREATE DATABASE IF NOT EXISTS ${mysql.escapeId(dbName)}`; connection.query(createDbQuery, (queryErr) => { connection.end(); if (queryErr) { console.error('Failed to create database:', queryErr); response.send(event, context, response.FAILED, { Error: queryErr.message }); return; } console.log(`Successfully created database: ${dbName}`); response.send(event, context, response.SUCCESS, { DatabaseName: dbName }); }); }); };
Key details here:
- We explicitly check for the
Createrequest type to skip unnecessary runs. - Added
IF NOT EXISTSto the SQL query as a safety net, even though we're only triggering this once. - Uses the
cfn-responsemodule to send proper status updates back to CloudFormation—this is critical for the stack to complete successfully.
Step 2: CloudFormation Template Configuration
This template creates the Lambda function (with necessary permissions) and a Custom Resource that triggers it only on stack creation.
AWSTemplateFormatVersion: '2010-09-09' Parameters: ExistingRdsEndpoint: Type: String Description: Endpoint of your existing RDS instance ExistingRdsUsername: Type: String Description: Master username for your RDS instance ExistingRdsPassword: Type: String Description: Master password for your RDS instance NoEcho: true NewDatabaseName: Type: String Description: Name of the database to create VpcId: Type: String Description: VPC ID where your RDS instance resides SubnetIds: Type: List<String> Description: Subnets in your VPC to place the Lambda (must have access to RDS) RdsSecurityGroupId: Type: String Description: Security group ID of your RDS instance (allow inbound 3306 from Lambda) Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole Policies: - PolicyName: RdsAccessPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ec2:CreateNetworkInterface - ec2:DeleteNetworkInterface - ec2:DescribeNetworkInterfaces Resource: '*' - Effect: Allow Action: logs:CreateLogGroup Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:log-group:/aws/lambda/*:* CreateDbLambda: Type: AWS::Lambda::Function Properties: Handler: index.handler Runtime: nodejs8.10 Role: !GetAtt LambdaExecutionRole.Arn Code: ZipFile: !Sub | const mysql = require('mysql'); const response = require('cfn-response'); exports.handler = (event, context) => { if (event.RequestType !== 'Create') { console.log(`Skipping ${event.RequestType} event`); response.send(event, context, response.SUCCESS); return; } const dbConfig = { host: '${ExistingRdsEndpoint}', user: '${ExistingRdsUsername}', password: '${ExistingRdsPassword}', port: 3306, connectTimeout: 10000 }; const dbName = '${NewDatabaseName}'; const connection = mysql.createConnection(dbConfig); connection.connect((err) => { if (err) { console.error('Failed to connect to RDS:', err); response.send(event, context, response.FAILED, { Error: err.message }); connection.end(); return; } const createDbQuery = `CREATE DATABASE IF NOT EXISTS ${mysql.escapeId(dbName)}`; connection.query(createDbQuery, (queryErr) => { connection.end(); if (queryErr) { console.error('Failed to create database:', queryErr); response.send(event, context, response.FAILED, { Error: queryErr.message }); return; } console.log(`Successfully created database: ${dbName}`); response.send(event, context, response.SUCCESS, { DatabaseName: dbName }); }); }); }; VpcConfig: SecurityGroupIds: - !Ref RdsSecurityGroupId SubnetIds: !Ref SubnetIds Timeout: 30 CreateDbCustomResource: Type: Custom::CreateDatabase Properties: ServiceToken: !GetAtt CreateDbLambda.Arn RdsEndpoint: !Ref ExistingRdsEndpoint RdsUsername: !Ref ExistingRdsUsername RdsPassword: !Ref ExistingRdsPassword DatabaseName: !Ref NewDatabaseName
Critical Setup Checks
VPC & Security Group Access:
- The Lambda must be deployed in the same VPC as your RDS instance, using subnets that have network access to the RDS.
- Update your RDS security group to allow inbound traffic on port 3306 from the Lambda's security group (or the subnet range used by the Lambda).
Preventing Re-Runs:
- The Custom Resource will only trigger the
Createevent when the stack is first created. If you update the stack later (e.g., change a non-Custom Resource parameter), the Lambda will receive anUpdateevent and skip execution. - If you need to re-run the database creation, you'll have to delete the Custom Resource (or the entire stack) and recreate it.
- The Custom Resource will only trigger the
Security Best Practices:
- The example uses a parameter for the RDS password with
NoEcho: trueto hide it in CloudFormation outputs. For better security, consider using AWS Secrets Manager to store RDS credentials and have the Lambda retrieve them instead of passing them as a parameter.
- The example uses a parameter for the RDS password with
内容的提问来源于stack exchange,提问作者Sean Clarke

