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

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的声明式编程模型,导致资源跟踪失效:

  1. 用AWS SDK直接调用API检查资源存在性、更新代码,CDK无法识别这些操作产生的资源状态,二次部署时会认为这些资源不属于当前栈,标记为删除。
  2. 仅在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 11:33:10