AWS CDK Deploy执行时已创建资源被删除问题求助
问题描述
基于config.json创建包含API Gateway和多个Lambda的CDK Stack,首次执行cdk deploy能成功创建所有资源,但修改Lambda代码后再次执行cdk deploy,栈内已创建的资源会被删除。
相关代码文件
infra-config.json
{ "stackname":"hellostack", "apigateway":"hello_apigateway", "lambda":[ { "lambdaName": "getresource", "lambdaPath": "../../lambda/get_resources", "endpoint": "getresources", "handler":"handler", "memorySize": 128, "method": "GET", "module":"ResourceManagement" }, { "lambdaName": "updateclientmanaged", "lambdaPath": "../../lambda/update_client_managed", "endpoint": "updateclimngd", "handler":"handler", "memorySize": 256, "method": "PATCH", "module":"DealReview" } ] }
bin/infra.ts
const account = '123456789'; const region = 'us-east-2'; const config: Config = getInfraConfig() const app = new cdk.App(); const stackName = config.stackname; // Main function to run cleanup and then deploy the stack async function main() { try { const stack = new LambdaApiStack(app, stackName, { lambdaConfig: config, env: { account, region }, }); console.log(`Bundling process completed successfully for stack: ${stackName}`); } catch (error: any) { console.error(`Error during bundling process for stack: ${stackName}`, error); process.exit(1); // Terminate the process with an error code } } main();
lib/infra-stack.ts
interface LambdaApiStackProps extends cdk.StackProps { lambdaConfig: Config; } export class LambdaApiStack extends cdk.Stack { private apiEnv: string = 'dev' private apiEnvRegion: string = 'us-east-2' private apiSecretVersion: string = 'TESTVERSION' private readonly environmentVariables: any = { WHUB_AWS_REGION: this.apiEnvRegion, WHUB_ENV_INSTANCE: this.apiEnv, WHUB_AWS_SECRET_VERSION: this.apiSecretVersion }; constructor(scope: Construct, id: string, props: LambdaApiStackProps) { super(scope, id, props); const config: Config = props?.lambdaConfig; console.log(`config ${JSON.stringify(config)}`) const apiGatewayClient = new APIGatewayClient({}); this.checkAndCreateApiGateway(apiGatewayClient, props?.lambdaConfig); } async checkAndCreateApiGateway(apiGatewayClient: APIGatewayClient, config: Config) { // Check if the API Gateway exists const apis = await apiGatewayClient.send(new GetRestApisCommand({})); const existingApi = apis.items?.find((api: any) => api.name === config.apigateway); let api; if (existingApi) { console.log(`existingApi ${JSON.stringify(existingApi)}`) api = apigateway.RestApi.fromRestApiAttributes(this, config.apigateway, { restApiId: existingApi.id!, rootResourceId: existingApi.rootResourceId! }); } else { console.log(`inside Create the API Gateway`) // Create the API Gateway api = new apigateway.RestApi(this, config.apigateway, { restApiName: config.apigateway, deploy: false }); console.log(`New APIGateway ${api.restApiId}`) const existingStage = api.deploymentStage; if (!existingStage || existingStage.stageName.toLowerCase() !== 'prod') { console.log(`existingStage ${existingStage}`) const deployment = new apigateway.Deployment(this, `${config.apigateway}-${this.apiEnv}-deploy`, { api, }); new apigateway.Stage(this, `${config.apigateway}-${this.apiEnv}`, { deployment }); } const cfnApi = api.node.defaultChild as apigateway.CfnRestApi; cfnApi.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); } for (const lambdaConfig of config.lambda) { await this.processLambdaConfig(api, lambdaConfig); } } async processLambdaConfig(api: apigateway.IRestApi, lambdaConfig: LambdaConfig) { console.log(`inside processLambdaConfig`) const lambdaAssetPath = path.join(__dirname, lambdaConfig.lambdaPath + '\\index.ts'); const packageLockPath = path.join(__dirname, '../../', '\\package-lock.json'); const lambdaClient = new LambdaClient({ region: this.apiEnvRegion }); const lambdaName = `${this.apiEnv}_${lambdaConfig.lambdaName}`; console.log(`packageLockPath ${packageLockPath}`) console.log(`lambdaAssetPath ${lambdaAssetPath}`) if (!fs.existsSync(lambdaAssetPath)) { throw new Error(`Lambda asset path does not exist: ${lambdaAssetPath}`); } let lambdaRole = this.node.tryFindChild('dev-testexecrole-wa-hubb') as iam.IRole; if (!lambdaRole) { lambdaRole = iam.Role.fromRoleArn(this, `dev-testexecrole-wa-hubb`, `arn:aws:iam::1234567889:role/dev-testexecrole-wa-hubb`); } console.log(`Before lambdaExist`) try { await lambdaClient.send(new GetFunctionCommand({ FunctionName: lambdaName })); await lambdaClient.send(new UpdateFunctionConfigurationCommand({ FunctionName: lambdaName, MemorySize: lambdaConfig.memorySize, Handler: lambdaConfig.handler, Environment: { Variables: this.environmentVariables } })); await lambdaClient.send(new UpdateFunctionCodeCommand({ FunctionName: lambdaName, ZipFile: fs.readFileSync(lambdaAssetPath) })); } catch (err: any) { if (err.name === 'ResourceNotFoundException') { console.log(`After lambdaExist`) const lambdaFunction = new NodejsFunction(this, `${this.apiEnv}_${lambdaConfig.lambdaName}`, { functionName: `${this.apiEnv}_${lambdaConfig.lambdaName}`, memorySize: lambdaConfig.memorySize, timeout: cdk.Duration.minutes(2), runtime: lambda.Runtime.NODEJS_20_X, handler: lambdaConfig.handler, role: lambdaRole, entry: lambdaAssetPath, bundling: { externalModules: ['aws-lambda'], nodeModules: ['pg'] }, depsLockFilePath: packageLockPath, environment: this.environmentVariables }); const integration = new apigateway.LambdaIntegration(lambdaFunction, { requestTemplates: { 'application/json': '{ "statusCode": "200" }' }, }); let rootResource = api.root.getResource(this.apiEnv); if (!rootResource) { rootResource = api.root.addResource(this.apiEnv); console.log("Root Res Created", rootResource) } let moduleResource = rootResource.getResource(`${lambdaConfig.module}`); if (!moduleResource) { moduleResource = rootResource.addResource(`${lambdaConfig.module}`); console.log("Module Res Created", moduleResource) } let resource = moduleResource.getResource(lambdaConfig.endpoint); if (!resource) { resource = moduleResource.addResource(lambdaConfig.endpoint); resource.addMethod(lambdaConfig.method, integration); console.log("Resource Created", resource) } else { resource.addMethod(lambdaConfig.method, integration); console.log("Resource Already Exist") } lambdaFunction.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com')); const cfnLambdaFunction = lambdaFunction.node.defaultChild as CfnResource; cfnLambdaFunction.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); } } } }
问题原因与修复方案
核心问题
代码违反了CDK的声明式编程模型,导致资源跟踪失效:
- 用AWS SDK直接调用API检查资源存在性、更新代码,CDK无法识别这些操作产生的资源状态,二次部署时会认为这些资源不属于当前栈,标记为删除。
- 仅在Lambda不存在时才通过CDK定义资源,二次部署时Lambda已存在,跳过CDK资源定义步骤,进一步加剧了状态不匹配。
修复步骤
1. 移除AWS SDK直接调用,完全用CDK声明式定义资源
CDK会自动对比资源期望状态与实际状态,处理创建/更新/删除,无需手动检查资源存在性。
2. 修改processLambdaConfig方法,始终通过CDK定义Lambda
processLambdaConfig(api: apigateway.IRestApi, lambdaConfig: LambdaConfig) { const lambdaAssetPath = path.join(__dirname, lambdaConfig.lambdaPath, 'index.ts'); const packageLockPath = path.join(__dirname, '../../package-lock.json'); const lambdaName = `${this.apiEnv}_${lambdaConfig.lambdaName}`; if (!fs.existsSync(lambdaAssetPath)) { throw new Error(`Lambda asset path does not exist: ${lambdaAssetPath}`); } let lambdaRole = this.node.tryFindChild('dev-testexecrole-wa-hubb') as iam.IRole; if (!lambdaRole) { lambdaRole = iam.Role.fromRoleArn(this, `dev-testexecrole-wa-hubb`, `arn:aws:iam::1234567889:role/dev-testexecrole-wa-hubb`); } // 始终通过CDK定义Lambda,自动处理更新 const lambdaFunction = new NodejsFunction(this, lambdaName, { functionName: lambdaName, memorySize: lambdaConfig.memorySize, timeout: cdk.Duration.minutes(2), runtime: lambda.Runtime.NODEJS_20_X, handler: lambdaConfig.handler, role: lambdaRole, entry: lambdaAssetPath, bundling: { externalModules: ['aws-lambda'], nodeModules: ['pg'] }, depsLockFilePath: packageLockPath, environment: this.environmentVariables }); const integration = new apigateway.LambdaIntegration(lambdaFunction, { requestTemplates: { 'application/json': '{ "statusCode": "200" }' }, }); // 统一管理API Gateway资源 let rootResource = api.root.getResource(this.apiEnv); if (!rootResource) { rootResource = api.root.addResource(this.apiEnv); } let moduleResource = rootResource.getResource(lambdaConfig.module); if (!moduleResource) { moduleResource = rootResource.addResource(lambdaConfig.module); } let resource = moduleResource.getResource(lambdaConfig.endpoint); if (!resource) { resource = moduleResource.addResource(lambdaConfig.endpoint); } // 避免重复创建API方法 const existingMethod = resource.methods.find(m => m.httpMethod === lambdaConfig.method); if (!existingMethod) { resource.addMethod(lambdaConfig.method, integration); } lambdaFunction.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com')); const cfnLambdaFunction = lambdaFunction.node.defaultChild as CfnResource; cfnLambdaFunction.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); }
3. 修改API Gateway创建逻辑,移除SDK调用
checkAndCreateApiGateway(config: Config) { // 直接通过CDK定义API Gateway,自动识别现有资源 const api = new apigateway.RestApi(this, config.apigateway, { restApiName: config.apigateway, deploy: false }); const existingStage = api.deploymentStage; if (!existingStage || existingStage.stageName.toLowerCase() !== 'prod') { const deployment = new apigateway.Deployment(this, `${config.apigateway}-${this.apiEnv}-deploy`, { api, }); new apigateway.Stage(this, `${config.apigateway}-${this.apiEnv}`, { deployment }); } const cfnApi = api.node.defaultChild as apigateway.CfnRestApi; cfnApi.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); // 直接遍历处理Lambda配置 config.lambda.forEach(lambdaConfig => { this.processLambdaConfig(api, lambdaConfig); }); }
4. 调整构造函数,移除异步操作
CDK构造函数不支持异步逻辑,需要同步执行资源定义:
constructor(scope: Construct, id: string, props: LambdaApiStackProps) { super(scope, id, props); const config: Config = props.lambdaConfig; this.checkAndCreateApiGateway(config); }
关键说明
CDK的核心是声明资源期望状态,而非命令式操作。每次部署时,CDK会自动对比当前栈定义与AWS实际资源状态,执行必要的变更。混合使用AWS SDK和CDK会破坏状态跟踪,引发资源误删或更新异常。
内容的提问来源于stack exchange,提问作者balajivaishnav
相关产品推荐
相关产品推荐

