You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅在AWS CloudFormation栈创建时调用AWS Lambda函数?

Solution: Trigger Lambda Only on First CloudFormation Stack Creation to Create RDS Database

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 Create request type to skip unnecessary runs.
  • Added IF NOT EXISTS to the SQL query as a safety net, even though we're only triggering this once.
  • Uses the cfn-response module 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

  1. 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).
  2. Preventing Re-Runs:

    • The Custom Resource will only trigger the Create event when the stack is first created. If you update the stack later (e.g., change a non-Custom Resource parameter), the Lambda will receive an Update event 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.
  3. Security Best Practices:

    • The example uses a parameter for the RDS password with NoEcho: true to 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.

内容的提问来源于stack exchange,提问作者Sean Clarke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:32:40