停止在 AWS 上烧钱:使用 TypeScript 查找僵尸资源的技术指南
I’m happy to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source link and all formatting exactly as you requested.
AWS中的“幽灵成本”
如果您在 AWS 上管理基础设施,您可能已经在月末打开账单时感到一阵寒意。
我不是在说生产环境 EC2 实例的显而易见的费用。我说的是 “幽灵成本”:
- 那个在三个月前已删除的 staging 环境中仍然开启的 NAT 网关。
- 那个已从实例上卸载但仍然存在的 500 GB EBS 磁盘,每月仍然收取 US$ 0.08/GB。
单个闲置的 NAT 网关大约每月花费 US$ 32.00 —— 相当于 R$ 2,400.00,而它根本没有传输 任何字节。
AWS 并不容易让您看到这些浪费。Cost Explorer 能显示您花了多少钱,但很少会指向正在消耗预算的具体 ID。
一个使用 TypeScript 和 AWS SDK v3 的 DIY 解决方案
作为软件工程师,我的第一反应是:“我不会为此付费。我会用代码解决。”
接下来,我将展示如何创建一个简单的成本审计器,它:
- 检测孤立的 EBS 卷(状态 available)。
- 检测闲置的 NAT 网关(最近 7 天没有连接)。
依赖
npm install @aws-sdk/client-ec2 @aws-sdk/client-cloudwatch
npm install -D typescript ts-node @types/node
脚本 (TypeScript)
import {
EC2Client,
DescribeVolumesCommand,
DescribeNatGatewaysCommand,
} from '@aws-sdk/client-ec2';
import {
CloudWatchClient,
GetMetricStatisticsCommand,
} from '@aws-sdk/client-cloudwatch';
// -------------------------------------------------
// 配置
// -------------------------------------------------
const REGION = 'us-east-1'; // 目标区域
const ec2 = new EC2Client({ region: REGION });
const cw = new CloudWatchClient({ region: REGION });
async function findZombies() {
console.log(`🔍 开始在以下区域扫描: ${REGION}...`);
// -------------------------------------------------
// 1️⃣ 检测孤立的 EBS 卷
// -------------------------------------------------
const volumesData = await ec2.send(
new DescribeVolumesCommand({
Filters: [{ Name: 'status', Values: ['available'] }],
})
);
if (volumesData.Volumes?.length) {
console.log(
`\n🚨 警报: ${volumesData.Volumes.length} 个孤立的 EBS 卷已找到:`
);
volumesData.Volumes.forEach(vol => {
const cost = (vol.Size ?? 0) * 0.08; // gp3 估算 (US$ 0.08/GB)
console.log(
` - ID: ${vol.VolumeId} | 大小: ${vol.Size} GB | 浪费: ~$${cost.toFixed(2)}/月`
);
});
} else {
console.log('\n✅ 未发现孤立的 EBS 卷。');
}
// -------------------------------------------------
// 2️⃣ 检测闲置的 NAT 网关
// -------------------------------------------------
const natData = await ec2.send(
new DescribeNatGatewaysCommand({
Filters: [{ Name: 'state', Values: ['available'] }],
})
);
if (natData.NatGateways?.length) {
console.log(`\n📡 正在分析 ${natData.NatGateways.length} 个 NAT 网关...`);
for (const nat of natData.NatGateways) {
// CloudWatch 指标: ActiveConnectionCount
const metrics = await cw.send(
new GetMetricStatisticsCommand({
Namespace: 'AWS/NATGateway',
MetricName: 'ActiveConnectionCount',
Dimensions: [{ Name: 'NatGatewayId', Value: nat.NatGatewayId! }],
StartTime: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 最近 7 天
EndTime: new Date(),
Period: 86400, // 1 天
Statistics: ['Sum'],
})
);
const totalConnections = metrics.Datapoints?.reduce(
(acc, dp) => acc + (dp.Sum ?? 0),
0
) ?? 0;
if (totalConnections === 0) {
console.log(`⚠️ 检测到 NAT 僵尸: ${nat.NatGatewayId}`);
console.log(` 估计成本: ~$32.00/月 | VpcId: ${nat.VpcId}`);
}
}
} else {
console.log('\n✅ 未发现活跃的 NAT 网关。');
}
}
// -------------------------------------------------
findZombies().catch(err => {
console.error('❌ 执行审计器时出错:', err);
});
技术说明
为了保持示例的可读性,我没有实现分页。在拥有数百个资源的生产账户中,AWS API 只会返回一个子集并提供 NextToken。您需要将调用包装在 while (nextToken) 循环中,以确保没有资源被遗漏。
为什么不要在日常使用自制脚本?
| 问题 | 影响 |
|---|---|
| 分页复杂性 | 手动循环容易出错(无限循环或数据不完整)。 |
| 多区域地狱 | AWS 有超过 30 个区域;需要为每个区域编写循环会增加复杂性。 |
| 本地安全 | 需要在机器上存储具有管理员权限的访问密钥。如果密钥泄露,风险是全盘的。 |
| 维护 | API 会变化,指标会演进。维护脚本的更新会消耗本可以用于产品功能的时间。 |
工程团队应该自动化枯燥工作,而不是增加更多维护工作。
InfraLens – 自动审计,无代码
为消除这些摩擦点,我开发了 InfraLens。它:
- 封装上述逻辑(包括自动分页和多区域扫描)。
- 打包成一个免费 Web 工具 用于审计。
- 100 % 只读且无代理的架构——不会在你的账户中写入任何内容。
设计即安全
| 控制项 | 工作原理 |
|---|---|
| 仅限元数据 | 临时 IAM 角色仅拥有 Describe* 和 List* 的严格权限。 |
| 零写入 | 没有 Create*、Delete* 或 Modify* 权限。 |
| 不访问数据 | 我们无法读取数据库或 S3 对象的内容。 |
疑问: “我要把我的 AWS 连接到第三方工具吗?”
回答: 连接是通过 CloudFormation 创建的临时角色 完成的。没有永久密钥会被本地存储。
摘要
- 识别闲置资源 使用快速脚本(如上例)。
- 考虑维护、 安全性和多区域 的复杂性。
- 采用现成的解决方案 如 InfraLens 进行持续、安全、无代码的审计。
如果想尝试脚本或了解更多关于 InfraLens,只需在下方评论! 🚀
可以继续运行手动脚本(并在周末调试分页),也可以在5分钟内解决此问题。
如果您想看到 现在正在浪费多少金钱,我已开放 演示模式(无需登录)和 真实扫描仪 的访问。
👉 在 InfraLens 上进行免费审计
不要等到财务敲门时才发现浪费。