Steve
Steve

Reputation: 8809

Lambda can't connect to RDS in the same VPC and subnet

I'm using a Lambda via the Script and Function constructs to run database migrations against an RDS instance. However, the Lambda function can't connect to the RDS cluster despite being in the same VPC and subnet according to SST configuration. Here are the relevant parts of the configuration:

function VpcStack({stack, app}: StackContext) {
    const vpc = new Vpc(stack, `myapp-${app.stage}-vpc`, {
        subnetConfiguration: [
            {
                name: 'private',
                subnetType: SubnetType.PRIVATE_ISOLATED
            }
        ]
    })

    return {vpc}
}

function DatabaseStack({stack, app}: StackContext) {
    dependsOn(VpcStack)

    const database = new RDS(stack, 'database', {
        engine: 'postgresql13.9',
        scaling: {
            autoPause: false,
            minCapacity: 'ACU_2',
            maxCapacity: app.stage === 'production' ? undefined : 'ACU_2'
        },
        defaultDatabaseName: 'myapp',
        cdk: {
            cluster: {
                clusterIdentifier: `myapp-cluster-${app.stage}`,
                vpc: use(VpcStack).vpc,
                vpcSubnets: {
                    subnetType: SubnetType.PRIVATE_ISOLATED
                }
            }
        }
    })

    return {database}
}

function DatabaseMigrationStack({stack}: StackContext) {
    dependsOn(VpcStack)
    dependsOn(DatabaseStack)

    // Perform database migrations
    new Script(stack, 'migrations', {
        onUpdate: new Function(stack, 'migrate', {
            handler: 'sst/migrations/src/index.migrate',
            nodejs: {
                format: 'esm',
                install: ['pg', 'postgres-migrations'],
                minify: false
            },
            copyFiles: [{from: 'packages/schema/migrations'}],
            runtime: 'nodejs18.x',
            environment: getDatabaseEnvironment(),
            timeout: '5 minutes',
            vpc: use(VpcStack).vpc,
            vpcSubnets: {
                subnetType: SubnetType.PRIVATE_ISOLATED
            },
            enableLiveDev: false
        })
    })
}

function getDatabaseEnvironment() {
    const {database} = use(DatabaseStack)

    return {
        DB_HOST: database.clusterEndpoint.hostname,
        DB_PORT: database.clusterEndpoint.port.toString(),
        DB_NAME: database.defaultDatabaseName,
        DB_USER:
            database.cdk.cluster.secret
                ?.secretValueFromJson('username')
                .toString() ?? '',
        DB_PASS:
            database.cdk.cluster.secret
                ?.secretValueFromJson('password')
                .toString() ?? ''
    }
}

Here's the migration code before being transpiled to JS:

import path from 'path'
import {migrate as pgmigrate, type MigrateDBConfig} from 'postgres-migrations'
import url from 'url'

declare const process: {
    env: {
        DB_HOST: string
        DB_PORT: string
        DB_NAME: string
        DB_USER: string
        DB_PASS: string
    }
}

export async function migrate() {
    const dbConfig = {
        host: process.env.DB_HOST,
        port: parseInt(process.env.DB_PORT),
        database: process.env.DB_NAME,
        user: process.env.DB_USER,
        password: process.env.DB_PASS,
        ensureDatabaseExists: true,
        defaultDatabase: 'postgres'
    } satisfies MigrateDBConfig

    await pgmigrate(
        dbConfig,
        path.resolve(
            path.dirname(url.fileURLToPath(import.meta.url)),
            '../../../packages/schema/migrations'
        )
    )
}

After about 130s I get this error:

{
  "errorType": "Error",
  "errorMessage": "connect ETIMEDOUT 10.0.142.195:5432",
  "trace": [
    "Error: connect ETIMEDOUT 10.0.142.195:5432",
    "    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1495:16)"
  ]
}

What would be the reason for this?

Upvotes: 2

Views: 338

Answers (1)

Steve
Steve

Reputation: 8809

I fixed this as follows:

  1. In DatabaseStack, I added the following:
    database.cdk.cluster.connections.allowFromAnyIpv4(
        Port.tcp(database.clusterEndpoint.port)
    )
    
  2. In DatabaseMigrationStack, I separated out the Function (called scriptFunction) and added the following:
    const {database} = use(DatabaseStack)
    
    scriptFunction.connections.allowTo(
        database.cdk.cluster,
        Port.tcp(database.clusterEndpoint.port)
    )
    

Upvotes: 1

Related Questions