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

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自动创建的,需要针对这个服务的安全组授权。

修改步骤:

  1. 将创建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,
});
  1. 替换原来的授权代码,改为针对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服务的访问权限:

  1. 注释或删除数据库安全组中允许任意IP的 ingress 规则:
// dbSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(dbPort), 'allow inbound traffic from anywhere to the db on port 5432');
  1. 保留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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 05:02:17