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

跨AWS CDK Stack与ECS服务共享VPC等资源的最优方案

解决方案:ECS资源共享与跨CDK Stack资源复用

一、ECS服务间共享VPC、Security Group、RDS并配置Global Accelerator

核心思路

通过统一ECS集群复用EC2实例,将所有关联资源(VPC、安全组、RDS)纳入同一网络环境,再借助Global Accelerator提供稳定静态公网IP。

具体实现步骤

  1. 创建共享VPC
    构建包含公有/私有子网的VPC,所有ECS服务、RDS均部署在此VPC内,避免重复创建网络资源。

  2. 复用ECS集群的EC2实例
    创建单个ECS集群(EC2启动类型),将多个ECS服务部署至该集群。集群会自动调度、管理EC2实例,实现资源复用,无需重复创建实例。

  3. 共享Security Group

    • 创建基础安全组:允许ECS服务间内部通信,以及ECS服务访问RDS的指定端口(如MySQL的3306)。
    • 各ECS服务可直接复用此基础安全组,或基于它扩展额外规则(如允许负载均衡访问ECS服务端口)。
  4. 共享RDS实例
    将RDS部署在VPC私有子网中,仅授权ECS服务的安全组访问RDS,既保障数据库安全,又能供所有ECS服务共享使用。

  5. 配置Global Accelerator获取静态IP

    • 为ECS服务绑定的负载均衡(ALB/NLB)创建Global Accelerator终端节点组,关联负载均衡的ARN。
    • Global Accelerator会分配两个静态公网IP,用户可通过这两个IP稳定访问ECS服务,不受区域或IP变动影响。

CDK代码示例(TypeScript)

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as globalaccelerator from 'aws-cdk-lib/aws-globalaccelerator';
import * as gaTargets from 'aws-cdk-lib/aws-globalaccelerator-endpoints';

const app = new cdk.App();

// 1. 共享VPC Stack
const vpcStack = new cdk.Stack(app, 'SharedVpcStack');
const vpc = new ec2.Vpc(vpcStack, 'SharedVpc', {
  maxAzs: 2,
  subnetConfiguration: [
    { name: 'Public', subnetType: ec2.SubnetType.PUBLIC },
    { name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
  ],
});

// 2. 共享ECS集群与RDS Stack
const resourceStack = new cdk.Stack(app, 'SharedResourceStack', {
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});

// 共享安全组
const ecsSg = new ec2.SecurityGroup(resourceStack, 'EcsSharedSg', { vpc });
const rdsSg = new ec2.SecurityGroup(resourceStack, 'RdsSg', { vpc });
rdsSg.addIngressRule(ecsSg, ec2.Port.tcp(3306), '允许ECS访问RDS');

// 共享RDS实例
const rdsInstance = new rds.DatabaseInstance(resourceStack, 'SharedRds', {
  engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_33 }),
  vpc,
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
  securityGroups: [rdsSg],
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
});

// ECS集群(复用EC2实例)
const ecsCluster = new ecs.Cluster(resourceStack, 'SharedEcsCluster', { vpc });
ecsCluster.addCapacity('DefaultAutoScalingGroup', {
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),
  desiredCapacity: 2,
  securityGroups: [ecsSg],
});

// 3. 部署第一个ECS服务
const ecsService1Stack = new cdk.Stack(app, 'EcsService1Stack', {
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});

const alb1 = new elbv2.ApplicationLoadBalancer(ecsService1Stack, 'Alb1', {
  vpc,
  internetFacing: true,
});
const listener1 = alb1.addListener('Listener1', { port: 80 });

const task1 = new ecs.Ec2TaskDefinition(ecsService1Stack, 'Task1');
task1.addContainer('AppContainer1', {
  image: ecs.ContainerImage.fromRegistry('nginx'),
  memoryLimitMiB: 256,
});

const service1 = new ecs.Ec2Service(ecsService1Stack, 'Service1', {
  cluster: ecsCluster,
  taskDefinition: task1,
  desiredCount: 1,
});
listener1.addTargets('Target1', {
  port: 80,
  targets: [service1],
});

// 配置Global Accelerator
const accelerator = new globalaccelerator.Accelerator(ecsService1Stack, 'GlobalAccelerator');
const gaListener = accelerator.addListener('GaListener', {
  portRanges: [{ fromPort: 80 }],
});
gaListener.addEndpointGroup('EndpointGroup', {
  endpoints: [new gaTargets.ApplicationLoadBalancerEndpoint(alb1)],
});

// 4. 部署第二个ECS服务(复用集群、VPC、安全组)
const ecsService2Stack = new cdk.Stack(app, 'EcsService2Stack', {
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});

const task2 = new ecs.Ec2TaskDefinition(ecsService2Stack, 'Task2');
task2.addContainer('AppContainer2', {
  image: ecs.ContainerImage.fromRegistry('httpd'),
  memoryLimitMiB: 256,
});

const service2 = new ecs.Ec2Service(ecsService2Stack, 'Service2', {
  cluster: ecsCluster,
  taskDefinition: task2,
  desiredCount: 1,
  securityGroups: [ecsSg],
});

二、跨AWS CDK Stack共享资源的最佳实践

1. 同App内Stack:构造函数传递资源引用

这是最直接、类型安全的方式,适用于同一CDK App下的多个Stack。

  • 在资源所属Stack中创建资源后,将其作为参数传入依赖Stack的构造函数。
  • 优点:编译时类型检查,无额外导出导入操作,维护简单。
  • 示例:
// 资源Stack
class ResourceStack extends cdk.Stack {
  public readonly vpc: ec2.Vpc;
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    this.vpc = new ec2.Vpc(this, 'SharedVpc');
  }
}

// 依赖Stack
class EcsStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: { vpc: ec2.Vpc }) {
    super(scope, id);
    new ecs.Cluster(this, 'EcsCluster', { vpc: props.vpc });
  }
}

// 使用
const app = new cdk.App();
const resourceStack = new ResourceStack(app, 'ResourceStack');
new EcsStack(app, 'EcsStack', { vpc: resourceStack.vpc });

2. 跨App/同区域:Stack输出与Fn.importValue

适用于不同CDK App部署的Stack,或需要跨Stack共享资源属性的场景。

  • 在资源Stack中通过cdk.CfnOutput导出资源的关键属性(如VPC ID、安全组ARN)。
  • 在依赖Stack中使用cdk.Fn.importValue导入这些属性。
  • 优点:无需代码耦合,支持跨App部署;缺点:缺少类型检查,需确保输出名称完全一致。
  • 示例:
// 资源Stack
const resourceStack = new cdk.Stack(app, 'ResourceStack');
const vpc = new ec2.Vpc(resourceStack, 'SharedVpc');
new cdk.CfnOutput(resourceStack, 'VpcIdOutput', {
  value: vpc.vpcId,
  exportName: 'SharedVpcId',
});

// 依赖Stack
const ecsStack = new cdk.Stack(app, 'EcsStack');
const vpcId = cdk.Fn.importValue('SharedVpcId');
const vpc = ec2.Vpc.fromLookup(ecsStack, 'ImportedVpc', { vpcId });

3. 跨部署/灵活场景:SSM Parameter Store存储资源属性

将资源的关键属性(如VPC ID、安全组ID)存储在SSM Parameter Store中,其他Stack读取这些参数。

  • 优点:支持跨账户、跨区域(需配置对应权限),适合长期共享或频繁变更的资源;缺点:需额外管理SSM参数,增加运维成本。
  • 示例:
import * as ssm from 'aws-cdk-lib/aws-ssm';

// 资源Stack
const resourceStack = new cdk.Stack(app, 'ResourceStack');
const vpc = new ec2.Vpc(resourceStack, 'SharedVpc');
new ssm.StringParameter(resourceStack, 'VpcIdParam', {
  parameterName: '/shared-resources/vpc-id',
  stringValue: vpc.vpcId,
});

// 依赖Stack
const ecsStack = new cdk.Stack(app, 'EcsStack');
const vpcId = ssm.StringParameter.valueFromLookup(ecsStack, '/shared-resources/vpc-id');
const vpc = ec2.Vpc.fromLookup(ecsStack, 'ImportedVpc', { vpcId });

4. 封装为可复用Construct

将共享资源(如VPC、安全组)封装成自定义Construct,在多个Stack中实例化该Construct,确保资源配置一致且避免重复创建。

  • 优点:代码复用性高,配置统一;缺点:需确保Construct在同一环境中仅实例化一次,避免重复创建资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:35:52