基础设施成本优化实战:从资源闲置率40%到5%的FinOps落地策略与自动化工具链
基础设施成本优化实战:从资源闲置率40%到5%的FinOps落地策略与自动化工具链
一、背景与问题
某互联网公司在2025年Q1的月度云计算账单达到285万元——其中阿里云120万、AWS 95万、腾讯云70万(多云混合架构)。财务团队发来质疑:业务QPS同比增长30%,为什么云成本同比增长了85%?
运维团队紧急开展了3周的云资源审计,结果触目惊心:
- ECS/CVM资源闲置率高达40%:1200台云主机中,480台CPU平均利用率低于15%。大量上线初期申请的"预留资源"一直未被释放
- 按量付费占比仅8%:92%的实例是包年包月,大量测试环境和预发布环境的机器在周末和夜间空闲,无法弹性缩容
- 存储资源浪费:云盘快照保留了3年历史快照(累计12TB),数据库备份保留了180天(远超业务要求的30天),对象存储中存在大量超过90天未访问的冷数据仍按标准存储收费
- K8s资源碎片化严重:300+节点中,约25%的节点处于"资源碎片"状态——剩余CPU足够但内存不足或反之,导致新Pod无法调度但节点仍有大量剩余资源
- 负载均衡器和公网IP闲置:128个CLB/SLB中,36个后端流量为零,但这些负载均衡器仍在按月收费
二、FinOps架构与优化策略
2.1 四阶段优化路线图
2.2 成本优化策略矩阵
| 优化策略 | 适用场景 | 预期节省 | 实施难度 | 风险 |
|---|---|---|---|---|
| 闲置资源清理 | 全量 | 15-20% | 低 | 误删风险(需人工确认) |
| 包年包月→按量付费 | 测试/预发布环境 | 5-10% | 低 | 无 |
| 竞价实例替代 | 无状态Web服务/Batch任务 | 20-40% | 中 | 可能被回收 |
| 混合部署+资源超售 | K8s集群 | 15-25% | 中 | 资源争抢 |
| 存储生命周期管理 | 对象存储/快照 | 10-15% | 低 | 需与业务确认 |
| 预留实例优化 | 稳定负载 | 10-20% | 低 | 承诺消费合同 |
| 网络架构优化 | 跨Region/CND | 5-10% | 高 | 架构变更周期长 |
三、核心自动化工具链
3.1 闲置资源识别与清理脚本
#!/usr/bin/env python3 """多云环境闲置资源识别与自动清理调度器""" import logging from dataclasses import dataclass, field from typing import Optional from datetime import datetime, timedelta import json import requests logger = logging.getLogger("finops_cleaner") @dataclass class IdleResource: resource_id: str resource_type: str # ECS/DISK/EIP/SLB/Snapshot/OSS region: str owner: str create_time: datetime last_active_time: Optional[datetime] monthly_cost: float # 月度成本(元) suggested_action: str # 建议操作 confidence: float # 清理安全性评分(0-1) class MultiCloudIdleResourceScanner: """多云闲置资源扫描器 支持的闲置资源类型: 1. ECS/CVM:CPU 7天平均利用率 < 5% 且无网络流量 2. 云盘:未挂载超过7天或挂载但无IO超过30天 3. EIP:未绑定超过24小时 4. SLB/CLB:后端无实例或QPS为0超过7天 5. 快照:创建时间超过90天且关联磁盘已释放 6. 对象存储:超过90天无访问的冷数据 """ # 闲置判定阈值 THRESHOLDS = { "ecs_cpu_util_max": 5.0, # 7天平均CPU利用率阈值(%) "ecs_network_idle_days": 7, # 无网络流量天数 "disk_unattached_days": 7, # 未挂载天数 "disk_no_io_days": 30, # 挂载但无IO天数 "eip_unbound_hours": 24, # 未绑定小时数 "slb_zero_qps_days": 7, # QPS为零天数 "snapshot_max_age_days": 90, # 快照最大保留天数 "oss_cold_data_days": 90, # 对象存储冷数据天数 } def __init__(self, cloud_providers: dict[str, dict]): """ cloud_providers: { "aliyun": {"access_key": "...", "secret": "..."}, "aws": {"access_key": "...", "secret": "..."}, "tencent": {"secret_id": "...", "secret_key": "..."}, } """ self.providers = cloud_providers self.all_idle_resources: list[IdleResource] = [] def scan_all(self, send_notification: bool = True) -> list[IdleResource]: """ 扫描所有云平台的所有闲置资源类型 返回: 闲置资源列表(按成本降序排列) """ self.all_idle_resources = [] for provider_name, credentials in self.providers.items(): logger.info(f"开始扫描: {provider_name}") try: # 1. 扫描闲置ECS/CVM ecs_idle = self._scan_idle_instances(provider_name, credentials) self.all_idle_resources.extend(ecs_idle) # 2. 扫描闲置云盘 disk_idle = self._scan_idle_disks(provider_name, credentials) self.all_idle_resources.extend(disk_idle) # 3. 扫描闲置EIP eip_idle = self._scan_idle_eips(provider_name, credentials) self.all_idle_resources.extend(eip_idle) # 4. 扫描闲置SLB/CLB slb_idle = self._scan_idle_loadbalancers(provider_name, credentials) self.all_idle_resources.extend(slb_idle) # 5. 扫描过期快照 snapshot_idle = self._scan_expired_snapshots(provider_name, credentials) self.all_idle_resources.extend(snapshot_idle) except Exception as e: logger.error(f"扫描云平台失败: {provider_name}, {e}") continue # 按月度成本降序排列 self.all_idle_resources.sort(key=lambda r: r.monthly_cost, reverse=True) # 汇总报告 total_monthly_saving = sum(r.monthly_cost for r in self.all_idle_resources) logger.info( f"扫描完成: 发现 {len(self.all_idle_resources)} 个闲置资源, " f"预计月度节省 {total_monthly_saving:.0f} 元" ) # 发送通知 if send_notification and self.all_idle_resources: self._send_scan_report() return self.all_idle_resources def _scan_idle_instances(self, provider: str, creds: dict) -> list[IdleResource]: """扫描闲置云主机实例""" # 此处为简化实现,生产环境需对接各云厂商API idle_instances = [] try: # 调用云API获取ECS列表 instances = self._call_cloud_api(provider, "DescribeInstances", creds) for instance in instances: # 7天CPU平均利用率 < 5% cpu_avg = self._get_cpu_utilization( provider, creds, instance["InstanceId"], days=7 ) if cpu_avg is not None and cpu_avg < self.THRESHOLDS["ecs_cpu_util_max"]: idle_instances.append(IdleResource( resource_id=instance["InstanceId"], resource_type="ECS", region=instance.get("RegionId", "unknown"), owner=instance.get("Tags", {}).get("Owner", "unknown"), create_time=datetime.fromisoformat(instance["CreationTime"]), last_active_time=None, monthly_cost=self._estimate_monthly_cost(instance), suggested_action=( "CPU 7天平均利用率 {:.1f}%,建议停机或降配" ).format(cpu_avg), confidence=0.85 if cpu_avg < 2.0 else 0.65, )) except Exception as e: logger.error(f"扫描闲置ECS失败: {provider}, {e}") return idle_instances def auto_cleanup(self, resources: list[IdleResource], dry_run: bool = True) -> dict: """ 自动清理闲置资源 dry_run=True: 仅模拟执行,输出操作预览 dry_run=False: 真实执行清理 清理策略: - confidence > 0.8: 自动清理(停机/释放) - confidence 0.5-0.8: 通知所有者,7天无响应后自动清理 - confidence < 0.5: 仅通知,不自动清理 """ actions_taken = [] for resource in resources: if resource.confidence > 0.8: action = { "resource_id": resource.resource_id, "type": resource.resource_type, "action": "自动停机/释放", "reason": resource.suggested_action, } if not dry_run: self._execute_cleanup(resource) actions_taken.append(action) elif resource.confidence >= 0.5: action = { "resource_id": resource.resource_id, "type": resource.resource_type, "action": "通知所有者待确认", "reason": resource.suggested_action, } if not dry_run: self._notify_owner(resource) actions_taken.append(action) return { "mode": "dry_run" if dry_run else "real_execution", "actions_count": len(actions_taken), "actions": actions_taken, } def _send_scan_report(self): """发送闲置资源扫描汇总报告(企微/飞书/钉钉机器人)""" total_cost = sum(r.monthly_cost for r in self.all_idle_resources) by_type = {} for r in self.all_idle_resources: by_type.setdefault(r.resource_type, []).append(r) report_lines = [ f"## 🔍 闲置资源扫描报告 ({datetime.now().strftime('%Y-%m-%d')})", f"**发现闲置资源**: {len(self.all_idle_resources)} 个", f"**预计月度节省**: {total_cost:,.0f} 元", "", "### 按资源类型分布", ] for rtype, resources in by_type.items(): type_cost = sum(r.monthly_cost for r in resources) report_lines.append( f"- **{rtype}**: {len(resources)}个, " f"月度节省 {type_cost:,.0f}元" ) report_text = "\n".join(report_lines) logger.info(report_text) # 生产环境中此处应调用企业IM的Webhook发送消息 # 以下方法在生产环境中需对接具体云厂商的SDK def _call_cloud_api(self, provider: str, action: str, creds: dict) -> list: """统一云API调用封装(简化实现)""" return [] def _get_cpu_utilization(self, provider: str, creds: dict, instance_id: str, days: int) -> Optional[float]: """获取ECS实例的CPU平均利用率""" return None def _estimate_monthly_cost(self, instance: dict) -> float: """估算ECS实例的月度成本""" return 0.0 def _scan_idle_disks(self, provider: str, creds: dict) -> list[IdleResource]: """扫描闲置云盘""" return [] def _scan_idle_eips(self, provider: str, creds: dict) -> list[IdleResource]: """扫描闲置弹性IP""" return [] def _scan_idle_loadbalancers(self, provider: str, creds: dict) -> list[IdleResource]: """扫描闲置负载均衡器""" return [] def _scan_expired_snapshots(self, provider: str, creds: dict) -> list[IdleResource]: """扫描过期快照""" return [] def _execute_cleanup(self, resource: IdleResource): """执行实际的清理操作""" pass def _notify_owner(self, resource: IdleResource): """通知资源所有者""" pass四、优化效果数据
4.1 月度成本分解
| 成本类别 | 优化前(月均) | 优化后(月均) | 节省金额 | 节省比例 |
|---|---|---|---|---|
| 云主机(ECS/CVM) | 132万元 | 78万元 | 54万元 | 41% |
| 负载均衡器 | 12万元 | 7万元 | 5万元 | 42% |
| 公网IP/EIP | 8万元 | 3万元 | 5万元 | 63% |
| 云盘存储 | 18万元 | 11万元 | 7万元 | 39% |
| 对象存储 | 15万元 | 10万元 | 5万元 | 33% |
| 数据库 | 52万元 | 40万元 | 12万元 | 23% |
| 网络带宽 | 25万元 | 20万元 | 5万元 | 20% |
| 其他 | 23万元 | 16万元 | 7万元 | 30% |
| 合计 | 285万元 | 185万元 | 100万元 | 35% |
4.2 闲置率与弹性指标
| 指标 | 优化前 | 优化后 | 目标值 |
|---|---|---|---|
| 平均CPU利用率 | 22% | 58% | 60% |
| 内存利用率 | 35% | 62% | 65% |
| 资源闲置率 | 40% | 5% | <10% |
| 按量付费占比 | 8% | 42% | 50% |
| Spot/竞价实例占比 | 0% | 25% | 35% |
| 月均成本波动 | 285±5万元 | 185±25万元 | - |
五、总结
基础设施成本优化的本质不是"省钱"而是"消除浪费"。从资源闲置率40%降到5%的过程中,总结三条经验:
- 可视化比优化更优先:在知道钱花在哪里之前,任何优化都可能是盲目的。建立完善的资源标签体系和成本分摊模型,是FinOps落地的第一步——这也是最容易被跳过的一步
- 闲置清理的收益远超弹性伸缩:很多团队一上来就搞K8s弹性伸缩、Spot实例混合部署,但这些高级优化在资源闲置率40%的基础上都是"杯水车薪"。先清理僵尸资源、过期快照、闲置ELB——这些操作零风险、零成本、快速见效,能直接节省15-20%
- FinOps是文化不是工具:单靠自动化脚本无法持续保持低成本。建立月度成本复盘机制、预算阈值自动告警、新资源申请的审批流程——这些制度性保障比任何自动化脚本都更重要
从285万降到185万,节省的100万不是一次性事件。如果没有持续的FinOps文化和自动化工具的保障,3个月后成本将再次反弹。成本优化没有终点,只有持续的对抗。
