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

ECS访问RDS出现Circular Dependency错误的排查与解决请求

循环依赖错误原因及ECS访问RDS的解决方案

问题场景

我创建了连接RDS PostgreSQL的ECS Fargate服务,负载均衡器为公网类型,RDS部署在ISOLATED子网中,期望ECS容器、负载均衡器及EC2能够访问该RDS数据库。在ECSStack中添加允许ECS安全组访问RDS的入站规则后,出现了循环依赖错误。

错误信息

❌ CdkStack failed: Error [ValidationError]: Circular dependency between resources: [RDSNestedStackRDSNestedStackResource4BEA771C, ECSNestedStackECSNestedStackResourceFF50F200]

当前架构代码

RDSStack代码

export class RDSStack extends NestedStack {
  public readonly dbInstance: rds.DatabaseInstance;

  constructor(scope: Construct, id: string, props: RDSStackProps) {
    super(scope, id);

    // RDS security group
    const rdsSecurityGroup = new ec2.SecurityGroup(this, `RDSSecurityGroup`, {
      vpc: props.vpc,
      description: 'Security group for RDS instance',
    });

    // New private subnet group for RDS instance
    const rdsSubnetGroup = new rds.SubnetGroup(this, `RDSInstanceSubnetGroup`, {
      vpc: props.vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      description: 'Subnet group for RDS instance',
    });

    // RDS instance in the private subnet with no public access
    this.dbInstance = new rds.DatabaseInstance(this, `RDSInstance`, {
      publiclyAccessible: false,
      subnetGroup: rdsSubnetGroup,
      vpc: props.vpc,
      port: 5432,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      securityGroups: [rdsSecurityGroup],
      // more
    });

    // EC2 security group 
    const ec2SecurityGroup = new ec2.SecurityGroup(this, `EC2SecurityGroup`, {
      vpc: props.vpc,
      description: 'Security group for EC2 instance',
    });

    // Allow SSM traffic (HTTPS)
    ec2SecurityGroup.addEgressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic for SSM');

    // Allow all outbound connections for EC2 instance
    ec2SecurityGroup.addEgressRule(ec2.Peer.anyIpv4(), ec2.Port.allTraffic());

    // ingress rules to RDS security group to allow traffic from the EC2
    rdsSecurityGroup.addIngressRule(
      ec2.Peer.securityGroupId(ec2SecurityGroup.securityGroupId),
      ec2.Port.tcp(5432),
      'Allow DB access from EC2 instance'
    );

}

ECSStack代码

export class ECSStack extends NestedStack {
  constructor(scope: Construct, id: string, props: ECSStackProps) {
    super(scope, id, props);

    const ecsSecurityGroup = new ec2.SecurityGroup(this, `ECSSecurityGroup`, {
      vpc: props.vpc,
      description: 'Security group for ECS',
    });

    const cluster = new ecs.Cluster(this, `ECSCluster`, { vpc });
    const taskDefinition = //...
    const container = //...

    const albfs = new ecs_patterns.ApplicationLoadBalancedFargateService(this, `FargateService`,
      {
        publicLoadBalancer: true,
        securityGroups: [ecsSecurityGroup],
        assignPublicIp: true,
        // more
      }
    );

    const lbSecurityGroup = albfs.loadBalancer.connections.securityGroups[0];
    lbSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80), // 443 for https
      'Allow HTTP from anywhere'
    );

    // ----> Problem line of codes 
    // Grant permissions to ECS task to access RDS
    props.dbInstance.connections.securityGroups[0].addIngressRule(
      ecsSecurityGroup,
      ec2.Port.tcp(5432),
      'Allow ECS access to RDS Postgres'
    );
}

循环依赖错误原因

当前架构中,RDSStack和ECSStack是两个嵌套栈,形成了双向依赖:

  1. ECSStack通过props依赖RDSStack创建的dbInstance,才能配置RDS安全组的入站规则
  2. 同时,修改RDS安全组的操作会让RDSStack反过来依赖ECSStack创建的ecsSecurityGroup

CloudFormation需要明确的资源创建顺序,这种双向依赖导致它无法确定先创建哪个栈,因此抛出循环依赖错误。

解决方案:通过父栈协调安全组规则

核心思路是让两个嵌套栈之间不再直接互相依赖,而是通过父栈来管理安全组的入站规则配置。

步骤1:修改RDSStack,暴露RDS安全组

在RDSStack中新增暴露rdsSecurityGroup的属性,让父栈能直接访问:

export class RDSStack extends NestedStack {
  public readonly dbInstance: rds.DatabaseInstance;
  public readonly rdsSecurityGroup: ec2.SecurityGroup; // 新增暴露安全组

  constructor(scope: Construct, id: string, props: RDSStackProps) {
    super(scope, id);

    // 初始化安全组时直接赋值给实例属性
    this.rdsSecurityGroup = new ec2.SecurityGroup(this, `RDSSecurityGroup`, {
      vpc: props.vpc,
      description: 'Security group for RDS instance',
    });

    // 其余代码不变,创建dbInstance时使用this.rdsSecurityGroup
    const rdsSubnetGroup = new rds.SubnetGroup(this, `RDSInstanceSubnetGroup`, {
      vpc: props.vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      description: 'Subnet group for RDS instance',
    });

    this.dbInstance = new rds.DatabaseInstance(this, `RDSInstance`, {
      publiclyAccessible: false,
      subnetGroup: rdsSubnetGroup,
      vpc: props.vpc,
      port: 5432,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      securityGroups: [this.rdsSecurityGroup],
      // 其他配置...
    });

    // EC2相关代码不变...
  }
}

步骤2:修改ECSStack,暴露ECS安全组

在ECSStack中新增暴露ecsSecurityGroup的属性,并移除原来的问题代码:

export class ECSStack extends NestedStack {
  public readonly ecsSecurityGroup: ec2.SecurityGroup; // 新增暴露安全组

  constructor(scope: Construct, id: string, props: ECSStackProps) {
    super(scope, id, props);

    // 初始化安全组时赋值给实例属性
    this.ecsSecurityGroup = new ec2.SecurityGroup(this, `ECSSecurityGroup`, {
      vpc: props.vpc,
      description: 'Security group for ECS',
    });

    const cluster = new ecs.Cluster(this, `ECSCluster`, { vpc: props.vpc });
    const taskDefinition = //... 任务定义代码不变
    const container = //... 容器配置代码不变

    const albfs = new ecs_patterns.ApplicationLoadBalancedFargateService(this, `FargateService`,
      {
        publicLoadBalancer: true,
        securityGroups: [this.ecsSecurityGroup],
        assignPublicIp: true,
        // 其他配置...
      }
    );

    const lbSecurityGroup = albfs.loadBalancer.connections.securityGroups[0];
    lbSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80), // 443 for https
      'Allow HTTP from anywhere'
    );

    // 移除原来的问题代码
}

步骤3:在父栈中配置安全组规则

在根栈(比如CdkStack)中,分别获取两个嵌套栈的安全组,添加RDS的入站规则:

export class CdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // 假设已通过lookup或创建方式获取VPC
    const vpc = ec2.Vpc.fromLookup(this, 'ExistingVpc', {
      vpcId: 'vpc-xxxxxx' // 替换为你的VPC ID
    });

    // 创建两个嵌套栈
    const rdsStack = new RDSStack(this, 'RDSNestedStack', { vpc });
    const ecsStack = new ECSStack(this, 'ECSNestedStack', { 
      vpc, 
      dbInstance: rdsStack.dbInstance // ECS仍需要dbInstance用于连接配置
    });

    // 在父栈中添加ECS安全组访问RDS的入站规则
    rdsStack.rdsSecurityGroup.addIngressRule(
      ecsStack.ecsSecurityGroup,
      ec2.Port.tcp(5432),
      'Allow ECS access to RDS Postgres'
    );
  }
}

额外配置检查

  1. 确认ECS安全组的出站规则:默认安全组允许所有出站流量,若自定义了规则,需确保允许到RDS端口(5432)的流量
  2. 确认VPC路由表:ISOLATED子网的路由表无需配置公网网关,但要确保ECS所在子网(因为assignPublicIp=true,通常是PUBLIC子网)到ISOLATED子网的流量是允许的(默认VPC内子网之间互通,除非路由表有限制)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:54:55