AWS CDK问题:Fargate服务无法连接Aurora RDS数据库
我是AWS及AWS CDK新手,正在部署包含Aurora Postgres、ElastiCache Redis和Fargate的栈,已成功部署数据库和缓存,但Fargate服务因无法连接数据库而无法启动。我认为在配置Fargate访问数据库的权限上有所遗漏,但无法定位问题。执行代码后控制台持续显示:
Stack CdkStack has an ongoing operation in progress and is not stable (CREATE_IN_PROGRESS)
查看服务日志发现存在数据库连接问题。目前我希望数据库可从任意地址访问,以便本地初始化数据,之后仅允许Fargate服务访问,请问该如何配置?
附部署代码:
import * as cdk from 'aws-cdk-lib'; import * as elc from 'aws-cdk-lib/aws-elasticache'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as rds from 'aws-cdk-lib/aws-rds'; import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets'; import {Construct} from 'constructs'; import * as sm from 'aws-cdk-lib/aws-secretsmanager'; import * as logs from "aws-cdk-lib/aws-logs"; import * as path from 'path'; enum Stage { LIVE = "live", TEST = "test" } export class CdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const stage: Stage = Stage.TEST const vpc = new ec2.Vpc(this, `${stage}-vpc`, { vpcName: `${stage}-vpc`, maxAzs: 3, // Default is all AZs in region natGateways: 0, subnetConfiguration: [ { subnetType: ec2.SubnetType.PUBLIC, name: 'public-subnet' }, { subnetType: ec2.SubnetType.PRIVATE_ISOLATED, name: 'private-subnet' } ] }); // const fargateSecurityGroup = new ec2.SecurityGroup(this, `fargate-${stage}-security-group`, { // vpc: vpc, // allowAllOutbound: true // }) // create a security group for aurora db const dbSecurityGroup = new ec2.SecurityGroup(this, `db-security-group-${stage}`, { vpc: vpc, // use the vpc created above allowAllOutbound: true, // allow outbound traffic to anywhere }) const dbPort = 5432 // allow inbound traffic from anywhere to the db dbSecurityGroup.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(dbPort), // allow inbound traffic on port 5432 (postgres) 'allow inbound traffic from anywhere to the db on port 5432' ) const secret = sm.Secret.fromSecretNameV2(this, "prod", "prod") const dbName = "project" const databaseCluster = new rds.DatabaseCluster(this, `database-${stage}-cluster`, { clusterIdentifier: `${stage}-db`, engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_15_2 }), credentials: rds.Credentials.fromSecret(secret), instances: 1, defaultDatabaseName: dbName, port: dbPort, instanceProps: { vpc: vpc, instanceType: new ec2.InstanceType('serverless'), autoMinorVersionUpgrade: true, publiclyAccessible: true, securityGroups: [dbSecurityGroup], vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC, // use the public subnet created above for the db }), enablePerformanceInsights: true, }, deletionProtection: false, cloudwatchLogsRetention: logs.RetentionDays.ONE_WEEK, }); cdk.Aspects.of(databaseCluster).add({ visit(node) { if (node instanceof rds.CfnDBCluster) { node.serverlessV2ScalingConfiguration = { minCapacity: 1, maxCapacity: 1, } } }, }) const subnetsForRedis = vpc.publicSubnets.map((subnet) => subnet.subnetId) const redisSubnetGroup = new elc.CfnSubnetGroup( this, `redis-cluster-private-subnet-group-${stage}`, { cacheSubnetGroupName: `redis-private-subnet-group-${stage}`, subnetIds: subnetsForRedis, description: `redis subnet group for ${stage}` } ) const redis = new elc.CfnCacheCluster(this, `${stage}-cache-cluster`, { cacheNodeType: "cache.t2.micro", engine: "redis", numCacheNodes: 1, clusterName: `${stage}-redis-cache`, cacheSubnetGroupName: redisSubnetGroup.ref, vpcSecurityGroupIds: [vpc.vpcDefaultSecurityGroup], }); redis.addDependency(redisSubnetGroup) const taskIamRole = new cdk.aws_iam.Role(this, `app-${stage}-role`, { roleName: `app-${stage}-role`, assumedBy: new cdk.aws_iam.ServicePrincipal('ecs-tasks.amazonaws.com'), }); const taskDefinition = new ecs.FargateTaskDefinition(this, `${stage}-task`, { taskRole: taskIamRole, }); secret.grantRead(taskIamRole) const root = path.join(__dirname, '..', '..'); const imageAsset = new ecr_assets.DockerImageAsset(this, "image", { directory: root, file: "Dockerfile", }) const image = ecs.ContainerImage.fromDockerImageAsset(imageAsset); taskDefinition.addContainer(`${stage}-container`, { image: image, portMappings: [{ containerPort: 7373, protocol: ecs.Protocol.TCP }], logging: ecs.LogDrivers.awsLogs({ streamPrefix: `${stage}-task`, logRetention: logs.RetentionDays.ONE_WEEK, }), secrets: { "DB_PASSWORD": ecs.Secret.fromSecretsManager(secret, "password"), "DB_USER": ecs.Secret.fromSecretsManager(secret, "username"), }, environment: { "DB_HOST": databaseCluster.clusterEndpoint.hostname, "DB_PORT": databaseCluster.clusterEndpoint.port.toString(), "DB_NAME": dbName, "REDIS_ADDRESS": `${redis.attrRedisEndpointAddress}:${redis.attrRedisEndpointPort}`, }, }); const cluster = new ecs.Cluster(this, `${stage}-cluster`, { clusterName: `${stage}-cluster`, vpc: vpc, containerInsights: true, }) // Create a load-balanced Fargate service and make it public new ecs_patterns.ApplicationLoadBalancedFargateService(this, `fargate-service-${stage}`, { cluster: cluster, cpu: 1024, // Default is 256 memoryLimitMiB: 2048, // Default is 512 taskDefinition: taskDefinition, desiredCount: 1, serviceName: `${stage}-app`, assignPublicIp: true, publicLoadBalancer: true, circuitBreaker: { rollback: true }, //securityGroups: [fargateSecurityGroup], maxHealthyPercent: 200, minHealthyPercent: stage === Stage.TEST ? 0 : 100, // speed up deployment in dev testing openListener: true, }) //https://stackoverflow.com/questions/68314584/allow-connections-from-ecs-to-an-existing-rds-database databaseCluster.connections.allowFrom(cluster, ec2.Port.tcp(dbPort), "Allow from fargate cluster") } }
一、先解决Fargate连接数据库的问题(保留本地访问权限)
你已经配置了允许任意IP访问数据库,但Fargate无法连接的核心原因是:你通过databaseCluster.connections.allowFrom(cluster, ...)授权,但ECS Cluster本身没有安全组,Fargate任务的安全组是由ApplicationLoadBalancedFargateService自动创建的,需要针对这个服务的安全组授权。
修改步骤:
- 将创建Fargate服务的代码赋值给变量,以便获取其安全组:
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, `fargate-service-${stage}`, { cluster: cluster, cpu: 1024, memoryLimitMiB: 2048, taskDefinition: taskDefinition, desiredCount: 1, serviceName: `${stage}-app`, assignPublicIp: true, publicLoadBalancer: true, circuitBreaker: { rollback: true }, maxHealthyPercent: 200, minHealthyPercent: stage === Stage.TEST ? 0 : 100, openListener: true, });
- 替换原来的授权代码,改为针对Fargate服务的连接授权:
// 删除原来的databaseCluster.connections.allowFrom(cluster, ...) databaseCluster.connections.allowFrom(fargateService.service.connections, ec2.Port.tcp(dbPort), "Allow Fargate service access to database");
修改后,Fargate任务的安全组会被允许访问数据库,同时保留任意IP的访问权限,你可以本地连接数据库初始化数据。
二、完成数据初始化后,限制仅Fargate访问数据库
当本地数据初始化完成后,移除允许任意IP访问的规则,只保留Fargate服务的访问权限:
- 注释或删除数据库安全组中允许任意IP的 ingress 规则:
// dbSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(dbPort), 'allow inbound traffic from anywhere to the db on port 5432');
- 保留Fargate服务的授权规则,此时只有Fargate任务能访问数据库。
三、解决栈CREATE_IN_PROGRESS的问题
栈一直处于CREATE_IN_PROGRESS状态是因为Fargate服务启动失败,不断重试导致栈无法完成创建。修复上述数据库连接问题后,Fargate服务能正常启动,栈的创建流程会自动完成。
额外检查点:
- 确认Secrets Manager中的
prod秘钥包含正确的username和password字段,与数据库配置匹配。 - 确保Fargate任务的环境变量
DB_HOST、DB_PORT、DB_NAME正确传递到容器中,当前代码配置无误。 - 你的VPC配置了
natGateways: 0,但Fargate任务开启了assignPublicIp: true,能直接访问公网,这部分配置没问题。
内容的提问来源于stack exchange,提问作者Kateile

