自定义资源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
相关产品推荐
相关产品推荐

