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

自定义资源Docker镜像连接AWS SecretsManager超时失败求助

问题:VPC公有子网中Docker自定义资源无法连接AWS SecretsManager超时

问题背景

使用TypeScript CDK构建基础设施,添加了基于Docker镜像的自定义资源步骤,镜像内的JavaScript代码在尝试从AWS SecretsManager获取PostgreSQL数据库连接字符串时出现超时错误。自定义资源已部署在公有子网,且已配置SecretsManager的读取权限,但仍无法正常连接。

Stack代码

this.urlSecret = new Secret(this, 'db-url-secret', {
  secretStringValue: new SecretValue(
    `postgres://${databaseUsername}:${password}@${
      this.dbCluster.clusterEndpoint.hostname
    }:${this.dbCluster.clusterEndpoint.port.toString()}/${props.databaseName}?schema=public`,
  ),
});  

const initializer = new CdkResourceInitializer(this, 'MyRdsInit', {
  config: this.urlSecret.secretName, // Docker代码中用于获取Secret的名称
  fnLogRetention: RetentionDays.FIVE_MONTHS,
  fnCode: DockerImageCode.fromImageAsset(`${__dirname}/rds-init-fn-code`, {}),
  fnTimeout: Duration.minutes(2),
  fnSecurityGroups: [],
  vpc: props.vpc,
  subnetsSelection: props.vpc.selectSubnets({
    subnetType: cdk.aws_ec2.SubnetType.PUBLIC
  }),
});     

initializer.customResource.node.addDependency(this.dbCluster);
this.dbCluster.connections.allowFrom(initializer.function, Port.tcp(5432));
this.dbCluster.connections.allowDefaultPortFromAnyIpv4();
this.urlSecret.grantRead(initializer.function);

Docker容器内JavaScript代码

const pg = require('pg');
const fs = require('fs');
const path = require('path')
const AWS = require('aws-sdk')

const secrets = new AWS.SecretsManager({})

exports.handler = async (e) => {
    const { config } = e.params
    var conString = "<connection  string>";
    // config是Secret的名称

    console.log('#############DOCKER');
    console.log(config);    

    const { conn } = await getSecretValue(config)
    console.log(conn);

    var client = new pg.Client(conString);
    await client.connect();

    const sql = fs.readFileSync(path.join(__dirname, 'script.sql')).toString()

    var res = await client.query(sql);

    await client.end();  

    return {
      status: 'OK',
      results: ''
    }
}

function getSecretValue (secretId) {
  return new Promise((resolve, reject) => { 
    secrets.getSecretValue({ SecretId: secretId }, (err, data) => {
      if (err) return reject(err)
      return resolve(JSON.parse(data.SecretString))
    })
  })
}

错误日志

2024-09-13T09:48:49.341Z    7d8d9575-d4c9-4510-992d-a997677a188f    ERROR   Invoke Error    
{
    "errorType": "TimeoutError",
    "errorMessage": "connect ETIMEDOUT 18.179.209.155:443",
    "code": "TimeoutError",
    "message": "connect ETIMEDOUT 18.179.209.155:443",
    "errno": -110,
    "syscall": "connect",
    "address": "18.179.209.155",
    "port": 443,
    "time": "2024-09-13T09:48:49.340Z",
    "region": "ap-northeast-1",
    "hostname": "secretsmanager.ap-northeast-1.amazonaws.com",
    "retryable": true,
    "stack": [
        "Error: connect ETIMEDOUT 18.179.209.155:443",
        "    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1159:16)"
    ]
}

排查思路与解决方案

1. 检查公有子网路由配置

公有子网必须关联包含0.0.0.0/0指向Internet Gateway(IGW)的路由表,否则即使处于公有子网也无法访问公网。确认子网的路由表中存在IGW的路由条目,若缺失需手动添加。

2. 配置安全组出站规则

自定义资源的Lambda函数使用的安全组(当前fnSecurityGroups为空,将使用VPC默认安全组)需允许HTTPS(443端口)出站访问公网。修改CDK代码添加安全组:

const initFnSG = new ec2.SecurityGroup(this, 'InitFnSecurityGroup', { vpc: props.vpc });
// 允许HTTPS出站访问SecretsManager
initFnSG.addEgressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443));

// 在initializer中指定安全组
const initializer = new CdkResourceInitializer(this, 'MyRdsInit', {
  // ... 其他配置
  fnSecurityGroups: [initFnSG],
});

3. 确认Lambda VPC权限与资源

部署在VPC内的Lambda需要创建弹性网络接口(ENI),需确保:

  • 子网有可用的IP地址,未被耗尽
  • Lambda执行角色包含ec2:CreateNetworkInterface、ec2:DescribeNetworkInterfaces、ec2:DeleteNetworkInterface权限(CDK通常会自动添加,可在IAM控制台检查角色权限)

4. 优化SecretsManager访问方式(可选)

若不想通过公网访问SecretsManager,可给VPC添加SecretsManager的接口端点(Interface Endpoint),让流量在VPC内部流转:

new ec2.InterfaceVpcEndpoint(this, 'SecretsManagerVpcEndpoint', {
  vpc: props.vpc,
  service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
  subnets: props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
  securityGroups: [initFnSG] // 允许Lambda安全组访问端点
});

5. 修正代码逻辑错误

创建Secret时存储的是纯连接串字符串,而非JSON对象,因此JavaScript代码中getSecretValue的解析逻辑有误,同时硬编码的连接串未替换为从SecretsManager获取的值,修正如下:

// 修正getSecretValue函数
async function getSecretValue(secretId) {
  const data = await secrets.getSecretValue({ SecretId: secretId }).promise();
  return data.SecretString; // 直接返回纯字符串
}

// 修正handler逻辑
exports.handler = async (e) => {
  const { config } = e.params;
  console.log('#############DOCKER');
  console.log(config);    

  const conString = await getSecretValue(config);
  console.log(conString);

  var client = new pg.Client(conString);
  await client.connect();

  const sql = fs.readFileSync(path.join(__dirname, 'script.sql')).toString()
  var res = await client.query(sql);

  await client.end();  

  return {
    status: 'OK',
    results: ''
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:27:06