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

CDK中AwsCustomResource调用DescribeVpcEndpoints获取网卡ID失败问题

问题

我需要获取VPC端点的私有IP,将其映射到Application Load Balancer的目标组中,于是通过两步实现:

  1. 创建AwsCustomResource调用EC2::describeVpcEndpoints获取网络接口ID列表;
  2. 创建第二个CustomResource调用EC2::describeNetworkInterfaces传入上一步的ID,获取私有IP。

我的CDK代码如下:

// 1st get the VPC endpoint network interface IDs
const vpcEndpointsCall: AwsSdkCall = {
    service: "EC2",
    action: "describeVpcEndpoints",
    outputPaths: [`VpcEndpoints.0.NetworkInterfaceIds`],
    parameters: {
        VpcEndpointIds: [myVpcEndpointId],
    },
    physicalResourceId: PhysicalResourceId.of('describeVpcEndpoints'),
}

const vpces = new AwsCustomResource(
    this.stack,
    `DescribeVpcEndpoints-${this.stackProps.deploy.environmentName}`,
    {
        onCreate: vpcEndpointsCall,
        onUpdate: vpcEndpointsCall,
        policy: {
            statements: [
                new PolicyStatement({
                    actions: ["ec2:DescribeVpcEndpoints"],
                    resources: ["*"],
                }),
            ],
        },
    }
);
        
const networkInterfaceIds = vpces.getResponseField(`VpcEndpoints.0.NetworkInterfaceIds`)

for (let index = 0; index < this.stackProps.core!.vpc.availabilityZones.length; index++) {
    
    // 2nd get the private IP addresses of the VPC endpoint network interface IDs
    const sdkCall: AwsSdkCall = {
        service: "EC2",
        action: "describeNetworkInterfaces",
        outputPaths: [`NetworkInterfaces.${index}.PrivateIpAddress`],
        parameters: {
            NetworkInterfaceIds: Token.asString(networkInterfaceIds),
            Filters: [
                { Name: "interface-type", Values: ["vpc_endpoint"] }
            ],
        },
        physicalResourceId: PhysicalResourceId.of('describeNetworkInterfaces'),
    }

    const eni = new AwsCustomResource(
        this.stack,
        `DescribeNetworkInterfaces-${this.stackProps.deploy.environmentName}${index}`,
        {
            onCreate: sdkCall,
            onUpdate: sdkCall,
            policy: {
                statements: [
                    new PolicyStatement({
                        actions: ["ec2:DescribeNetworkInterfaces"],
                        resources: ["*"],
                    }),
                ],
            },
        }
    );

    targetGroup.addTarget(new IpTarget(Token.asString(eni.getResponseField(`NetworkInterfaces.${index}.PrivateIpAddress`)), targetPort))
}

执行cdk deploy时遇到错误:

CustomResource attribute error: Vendor response doesn't contain VpcEndpoints.0.NetworkInterfaceIds key in object arn:aws:cloudformation:eu-west-3:XXXXXXXXXX:stack/CDK-Core-EC2-XXXXXXXXXX/XXXXXXXXXX|DescribeVpcEndpointsXXXXXXXXXX|XXXXXXXXXX in S3 bucket cloudformation-custom-resource-storage-euwest3

describeVpcEndpoints的实际返回结果:

{
    "VpcEndpoints": [
        {
            "VpcEndpointId": "XXX",
            "VpcEndpointType": "Interface",
            "VpcId": "XXX",
            "ServiceName": "com.amazonaws.eu-west-3.execute-api",
            "State": "available",
            "PolicyDocument": "{
  \"Statement\": [
    {
      \"Action\": \"*\", 
      \"Effect\": \"Allow\", 
      \"Principal\": \"*\", 
      \"Resource\": \"*\"
    }
  ]
}",       
            "RouteTableIds": [],
            "SubnetIds": [
                "subnet-XXX",
                "subnet-XXX",
                "subnet-XXX"
            ],
            "Groups": [
                {
                    "GroupId": "XXX",
                    "GroupName": "XXX"
                },
                {
                    "GroupId": "XXX",
                    "GroupName": "XXX"
                }
            ],
            "IpAddressType": "ipv4",
            "DnsOptions": {
                "DnsRecordIpType": "ipv4"
            },
            "PrivateDnsEnabled": true,
            "RequesterManaged": false,
            "NetworkInterfaceIds": [
                "eni-XXX",
                "eni-XXX",
                "eni-XXX"
            ],
            "DnsEntries": [
                {
                    "DnsName": "vpce-XXX.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "vpce-XXX-eu-west-3c.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "vpce-XXX-eu-west-3b.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "vpce-XXX-eu-west-3a.execute-api.eu-west-3.vpce.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "execute-api.eu-west-3.amazonaws.com",
                    "HostedZoneId": "XXX"
                },
                {
                    "DnsName": "*.execute-api.eu-west-3.amazonaws.com",
                    "HostedZoneId": "XXX"
                }
            ],
            "CreationTimestamp": "XXX",
            "Tags": [],
            "OwnerId": "XXX"
        }
    ]
}

明明返回结果里存在VpcEndpoints.0.NetworkInterfaceIds,但getResponseField就是取不到,求解决思路。

解决思路
  • 修正outputPaths与getResponseField的匹配逻辑
    当设置outputPaths: ['VpcEndpoints.0.NetworkInterfaceIds']时,Custom Resource会提取该路径对应的值作为输出属性,属性键为路径的最后一个节点NetworkInterfaceIds,而非完整路径。因此需要修改getResponseField的参数:

    const networkInterfaceIds = vpces.getResponseField('NetworkInterfaceIds');
    

    或者直接移除outputPaths配置,让Custom Resource保留完整API响应,再通过完整路径获取值:

    const vpcEndpointsCall: AwsSdkCall = {
        service: "EC2",
        action: "describeVpcEndpoints",
        // 移除outputPaths,保留完整响应
        parameters: {
            VpcEndpointIds: [myVpcEndpointId],
        },
        physicalResourceId: PhysicalResourceId.of('describeVpcEndpoints'),
    }
    const networkInterfaceIds = vpces.getResponseField('VpcEndpoints.0.NetworkInterfaceIds');
    
  • 修复Token传递错误
    networkInterfaceIds是CDK Token类型,本身代表数组,无需用Token.asString转换,直接传递给NetworkInterfaceIds参数即可:

    parameters: {
        NetworkInterfaceIds: networkInterfaceIds,
        Filters: [
            { Name: "interface-type", Values: ["vpc_endpoint"] }
        ],
    },
    
  • 保证PhysicalResourceId唯一性
    循环中创建的Custom Resource如果使用相同的PhysicalResourceId,会被CloudFormation判定为同一资源引发冲突,需要给每个实例设置唯一ID:

    physicalResourceId: PhysicalResourceId.of(`describeNetworkInterfaces-${index}`),
    
  • 优化CustomResource创建逻辑
    无需循环创建多个Custom Resource,可一次性获取所有ENI的IP,再循环添加到目标组:

    const sdkCall: AwsSdkCall = {
        service: "EC2",
        action: "describeNetworkInterfaces",
        parameters: {
            NetworkInterfaceIds: networkInterfaceIds,
            Filters: [
                { Name: "interface-type", Values: ["vpc_endpoint"] }
            ],
        },
        physicalResourceId: PhysicalResourceId.of('describeNetworkInterfaces-all'),
    }
    
    const enis = new AwsCustomResource(
        this.stack,
        `DescribeNetworkInterfaces-${this.stackProps.deploy.environmentName}`,
        {
            onCreate: sdkCall,
            onUpdate: sdkCall,
            policy: {
                statements: [
                    new PolicyStatement({
                        actions: ["ec2:DescribeNetworkInterfaces"],
                        resources: ["*"],
                    }),
                ],
            },
        }
    );
    
    for (let index = 0; index < this.stackProps.core!.vpc.availabilityZones.length; index++) {
        const privateIp = enis.getResponseField(`NetworkInterfaces.${index}.PrivateIpAddress`);
        targetGroup.addTarget(new IpTarget(Token.asString(privateIp), targetPort));
    }
    

内容的提问来源于stack exchange,提问作者Romain Spinelli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:45:54