Terraform lambda 调用可能超时

Terraform lambda invocation probable timeout

在为此花费了 7 个小时之后,我决定与您联系。 我需要在 terraform 流中更新凭据。由于机密不应该在状态文件中,我使用 AWS lambda 函数来更新 RDS 实例的机密。密码通过 CLI 传递。

   locals {
  db_password = tostring(var.db_user_password)
}

data "aws_lambda_invocation" "credentials_manager" {
  function_name = "credentials-manager"
  input = <<JSON
{
  "secretString": "{\"username\":\"${module.db_instance.db_user}\",\"password\":\"${local.db_password}\",\"dbInstanceIdentifier\":\"${module.db_instance.db_identifier}\"}",
  "secretId": "${module.db_instance.db_secret_id}",
   "storageId": "${module.db_instance.db_identifier}",
   "forcedMod": "${var.forced_mod}"
}
JSON

    depends_on = [
    module.db_instance.db_secret_id,
  ]
}


output "result" {
  description = "String result of Lambda execution"
  value       = jsondecode(data.aws_lambda_invocation.credentials_manager.result)
}

为了确保 RDS 实例状态为 'available',lambda 函数还包含一个等待程序。 当我手动执行该功能时,一切都很顺利。 但在 terraform 中,它不会从这里开始:

data.aws_lambda_invocation.credentials_manager: Refreshing state...

但是,当我查看 AWS Cloud Watch 时,我可以看到 Terraform 一遍又一遍地调用 lambda 函数。

这是 lambda 策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1589715377799",
      "Action": [
        "rds:ModifyDBInstance",
        "rds:DescribeDBInstances"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

lambda 函数如下所示:

const secretsManager = require('aws-sdk/clients/secretsmanager')
const rds = require('aws-sdk/clients/rds')
const elastiCache = require('aws-sdk/clients/elasticache')
const log = require('loglevel')


/////////////////////////////////////////
// ENVIRONMENT VARIABLES
/////////////////////////////////////////
const logLevel = process.env["LOG_LEVEL"];
const region = process.env["REGION"]


/////////////////////////////////////////
// CONFIGURE LOGGER
log.setLevel(logLevel);
let protocol = []

/////////////////////////////////////////


/////////////////////////////////////////
// DEFINE THE CLIENTS
const SM = new secretsManager({ region })
const RDS = new rds({ region })
const ELC = new elastiCache({region})
/////////////////////////////////////////


/////////////////////////////////////////
// FUNCTION DEFINITIONS
/////////////////////////////////////////


// HELPERS

/**
 * @function waitForSeconds
 * Set a custom waiter.
 * 
 * @param {int} milseconds      - the milliseconds to set as timeout.
 * 
 */

const waitForSeconds = (ms) => {
   return new Promise(resolve => setTimeout(resolve, ms))
}




// AWS SECRETS MANAGER FUNCTIONS

/**
 * @function UpdateSecretInSM
 * The function updates the secrect value in the corresponding secret.
 * 
 * @param {string} secretId      - The id of the secret located in AWS SecretsManager 
 * @param {string} secretString  - The value of the new secret
 * 
 */
const UpdateSecretInSM = async (secretId,secretString) => {

    const params = {SecretId: secretId, SecretString: secretString}



    try {
        const data = await SM.updateSecret(params).promise()
        log.info(`[INFO]: Password for ${secretId} successfully changed in Scecrets Manager!`)
        let success = {Timestamp: new Date().toISOString(),Func: 'UpdateSecretInSM', Message: `Secret for ${secretId} successfully changed!`}
        protocol.push(success)
        return
    } catch (err) {
        log.debug("[DEBUG]: Error: ", err.stack);
        let error = {Timestamp: new Date().toISOString(),Func: 'UpdateSecretInSM', Error: err.stack}
        protocol.push(error)
        return
    }
}





/**
 * @function GetSecretFromSM
 * The function retrieves the specified secret from AWS SecretsManager.
 * Returns the password.
 * 
 * @param {string} secretId   - secretId that is available in AWS SecretsManager
 * 
 */
const GetSecretFromSM = async (secretId) => {


    try {
        const data = await SM.getSecretValue({SecretId: secretId}).promise()
        log.debug("[DEBUG]: Secret: ", data);
        let success = {Timestamp: new Date().toISOString(),Func: 'GetSecretFromSM', Message: 'Secret from SecretsManager successfully received!'}
        protocol.push(success)
        const { SecretString } = data
        const password = JSON.parse(SecretString)
        return password.password

    } catch (err) {

        log.debug("[DEBUG]: Error: ", err.stack);
        let error = {Timestamp: new Date().toISOString(),Func: 'GetSecretFromSM', Error: err.stack}
        protocol.push(error)
        return
    }

}


// AWS RDS FUNCTIONS

/**
 * @function ChangeRDSSecret
 * Change the secret of the specified RDS instance.
 * 
 * @param {string} rdsId     - id of the RDS instance
 * @param {string} password  - new password
 * 
 */
const ChangeRDSSecret = async (rdsId,password) => {

    const params = {
        DBInstanceIdentifier: rdsId,
        MasterUserPassword: password
    }



    try {
        await RDS.modifyDBInstance(params).promise() 

        log.info(`[INFO]: Password for ${rdsId} successfully changed!`)
        let success = {Timestamp: new Date().toISOString(), Func: 'ChangeRDSSecret', Message: `Secret for ${rdsId} successfully changed!`}
        protocol.push(success)
        return
    } catch (err) {

        log.debug("[DEBUG]: Error: ", err.stack);
        let error = {Timestamp: new Date().toISOString(), Func: 'ChangeRDSSecret', Error: err.stack}
        protocol.push(error)
        return 

    }

}


const DescribeRDSInstance = async(id) => {


    const params = { DBInstanceIdentifier : id }

    const secondsToWait = 10000

    try {
        let pendingModifications = true

        while (pendingModifications == true) {
            log.info(`[INFO]: Checking modified values for ${id}`)

            let data = await RDS.describeDBInstances(params).promise()
            console.log(data)

            // Extract the 'PendingModifiedValues' object
            let myInstance = data['DBInstances']
            myInstance = myInstance[0]

            if (myInstance.DBInstanceStatus === "resetting-master-credentials") {
                log.info(`[INFO]:Password change is being processed!`)
                 pendingModifications = false

            }

            log.info(`[INFO]: Waiting for ${secondsToWait/1000} seconds!`)
            await waitForSeconds(secondsToWait)
        }


        let success = {Timestamp: new Date().toISOString(), Func: 'DescribeRDSInstance', Message: `${id} available again!`}
        protocol.push(success)
        return 

    } catch (err) {
         log.debug("[DEBUG]: Error: ", err.stack);
        let error = {Timestamp: new Date().toISOString(), Func: 'DescribeRDSInstance', Error: err.stack}
        protocol.push(error)
        return 

    }

}


const WaitRDSForAvailableState = async(id) => {


/**
 * @function WaitRDSForAvailableState
 * Wait for the instance to be available again.
 * 
 * @param {string} id           - id of the RDS instance
 *
 */
    const params = { DBInstanceIdentifier: id}


    try {
        log.info(`[INFO]: Waiting for ${id} to be available again!`)
        const data = await RDS.waitFor('dBInstanceAvailable', params).promise()

        log.info(`[INFO]: ${id} available again!`)
        let success = {Timestamp: new Date().toISOString(), Func: 'WaitRDSForAvailableState', Message: `${id} available again!`}
        protocol.push(success)
        return
    } catch (err) {
        log.debug("[DEBUG]: Error: ", err.stack);
        let error = {Timestamp: new Date().toISOString(), Func: 'WaitRDSForAvailableState', Error: err.stack}
        protocol.push(error)
        return 

    }


}


// AWS ELASTICACHE FUNCTIONS

// ... removed since they follow the same principle like RDS





/////////////////////////////////////////
// Lambda Handler
/////////////////////////////////////////


exports.handler = async (event,context,callback) => {

    protocol = []
    log.debug("[DEBUG]: Event:", event)
    log.debug("[DEBUG]: Context:", context)

    // Variable for the final message the lambda function will return
    let finalValue



    // Get the password and rds from terraform output
    const secretString = event.secretString // manual input
    const secretId = event.secretId // coming from secretesmanager
    const storageId = event.storageId // coming from db identifier
    const forcedMod = event.forcedMod // manual input


    // Extract the password from the passed secretString to for comparison
    const passedSecretStringJSON = JSON.parse(secretString)
    const passedSecretString = passedSecretStringJSON.password


    const currentSecret = await GetSecretFromSM(secretId)

    // Case if the password has already been updated
    if (currentSecret !== "ChangeMeViaScriptOrConsole" && passedSecretString === "ChangeMeViaScriptOrConsole") {
        log.debug("[DEBUG]: No change necessary.")
        finalValue = {timestamp: new Date().toISOString(), 
            message: 'Lambda function execution finished!', 
            summary: 'Password already updated. It is not "ChangeMeViaScriptOrConsole."'}

        return finalValue
    }   

    // Case if the a new password has not been set yet
    if (currentSecret === "ChangeMeViaScriptOrConsole" && passedSecretString === "ChangeMeViaScriptOrConsole") {
        finalValue = {timestamp: new Date().toISOString(), 
            message: 'Lambda function execution finished!', 
            summary: 'Password still "ChangeMeViaScriptOrConsole". Please change me!'}

        return finalValue
    }

//   Case if the passed password is equal to the stored password and if pw modification is enforced
    if (currentSecret === passedSecretString && forcedMod === "no") {
        finalValue = {timestamp: new Date().toISOString(), 
            message: 'Lambda function execution finished!', 
            summary: 'Stored password is the same as the passed one. No changes made!'}

        return finalValue
    }


    // Case for changing the password
    if (passedSecretString !== "ChangeMeViaScriptOrConsole") {

        // Update the secret in SM for the specified RDS Instances
        await UpdateSecretInSM(secretId,secretString)

        log.debug("[DEBUG]: Secret updated for: ", secretId)

        // Change the new secret vom SM
        const updatedSecret = await GetSecretFromSM(secretId)

        log.debug("[DEBUG]: Updated secret: ", updatedSecret)


        if (secretId.includes("rds")) {

            // Update RDS instance with new secret and wait for it to be available again
            await ChangeRDSSecret(storageId, updatedSecret)
            await DescribeRDSInstance(storageId)
            await WaitRDSForAvailableState(storageId)

        } else if (secretId.includes("elasticache")) {

          // ... removed since it is analogeous to RDS


        } else {

            protocol.push(`No corresponding Storage Id exists for ${secretId}. Please check the Secret Id/Name in the terraform configuration.`)
        }


        finalValue ={timestamp: new Date().toISOString(), 
            message: 'Lambda function execution finished!', 
            summary: protocol}

        return finalValue

    } else {

        finalValue = {timestamp: new Date().toISOString(), 
            message: 'Lambda function execution finished!', 
            summary: 'Nothing changed'}

        return finalValue
    }




}

有人知道如何解决或减轻这种行为吗?

能否展示一下您的 lambda 函数的 iam 策略?据我了解,您的 lambda 函数可能缺少此资源 aws_lambda_permissionhttps://www.terraform.io/docs/providers/aws/r/lambda_permission.html